Skip to content

Participant-wise Fitting

Use optimize_entries when you want one independent best parameter set per participant (or condition, subject, item — any discrete ID).

Each entry gets its own Optuna study. Studies run concurrently via max_concurrent_entries.

Example

from datetime import timedelta
from slurptuna import execution_mode, loss, optimize_entries

PARTICIPANTS = {
    "p01": {"alpha": 0.20, "beta": 0.80},
    "p02": {"alpha": 0.35, "beta": 0.55},
    "p03": {"alpha": 0.60, "beta": 0.25},
}

@loss(
    name="my_model",
  description="Participant-wise fit",
    parameter_space={"alpha": (0.0, 1.0), "beta": (0.0, 1.0)},
)
def my_model(params, seed, context):
    pid = context["entry_id"]          # ← entry_id is injected automatically
    truth = PARTICIPANTS[pid]
    return abs(params["alpha"] - truth["alpha"]) + abs(params["beta"] - truth["beta"])

if __name__ == "__main__":
    result = optimize_entries(
        my_model,
        entry_ids=PARTICIPANTS.keys(),
      mode=execution_mode("distributed"),
        n_trials=20,
        n_seeds=400,
        chunk_size=20,
        run_name_prefix="my_model_participants",
        max_concurrent_entries=3,      # all three run at the same time
        max_concurrent_trials=2,
        worker_parallelism=2,
        cpus_per_task=2,
        worker_time_limit=timedelta(hours=2),
        slurm_qos="short",
    )

    for pid, params in result.best_params_by_entry.items():
        print(pid, params)

Output structure

Per-participant runs are stored as subfolders of the prefix directory:

runs/my_model_participants/
  summary.json        ← all participants' best params in one place
  p01/
    summary.json
    meta.json
    optuna.db
    trials/
  p02/
    ...
  p03/
    ...

The top-level summary.json is the convenient final output:

{
  "entries": ["p01", "p02", "p03"],
  "best_params_by_entry": {
    "p01": { "alpha": 0.201, "beta": 0.799 },
    "p02": { "alpha": 0.353, "beta": 0.548 },
    "p03": { "alpha": 0.598, "beta": 0.251 }
  },
  "best_values_by_entry": {
    "p01": 0.003,
    "p02": 0.007,
    "p03": 0.009
  }
}

Loading participants from a CSV

import csv
from pathlib import Path

def load_participants(path):
    with Path(path).open() as f:
        return {row["id"]: {"alpha": float(row["alpha"]), "beta": float(row["beta"])}
                for row in csv.DictReader(f)}

PARTICIPANTS = load_participants("participants.csv")

Then pass entry_ids=PARTICIPANTS.keys() as usual.

Difference from optimize_run with a dict loss

optimize_run with a loss that returns a dict averages across participants to find one shared parameter set that fits all participants simultaneously. optimize_entries finds separate best params per participant. Use whichever matches your modeling assumption.