Running Multiple Simulations in Parallel
Set up and run parallel simulations with parsim, batchsim, or simulink.multisim.DesignStudy.
When to Use
- Parameter sweeps, Monte Carlo studies, batch simulations (local or cluster)
parsim (local parallel), batchsim (cluster offload), DesignStudy (large-scale combinations)
preSimFcn/postSimFcn callbacks for setup or post-processing
- Live visualization as simulations complete (ValueStore pattern)
When NOT to Use
- Single simulation →
simulating-simulink-models
- Pass/fail tests →
testing-simulink-models
- Model editing →
building-simulink-models
Prerequisites
If model inspection cannot resolve them, ask the user to provide: the exact model filename, the parameter name and type (workspace variable, block parameter, or model parameter), and the logged signal name(s) to use for metrics. Do not guess any of these values.
Key Functions
| Function | Purpose | Toolbox | Available From |
|---|
Simulink.SimulationInput | Specify per-run parameter overrides and callbacks | Simulink | R2017a |
parsim | Run a SimulationInput array / DesignStudy on a parallel pool | Simulink | R2017a |
batchsim | Offload a campaign to a cluster as a background job | Simulink | R2018b |
simulink.multisim.DesignStudy | Generate multi-parameter combinations (with .Variable / .ModelParameter / .BlockParameter / .ExternalInput / .Exhaustive / .Sequential) | Simulink | R2024a |
parpool | Create a parallel pool sized to the campaign | Parallel Computing Toolbox | R2013b |
parcluster | Query the cluster profile and worker capacity | Parallel Computing Toolbox | R2012a |
Match suggestions to the running release. The "Available From" column is authoritative: before recommending a function, confirm it exists in the release in use (version('-release')) and never propose one introduced later.
Workflow
-
Project setup — Before discovery, cd to the project folder if provided and open the Simulink project (openProject) if a .prj exists. This ensures referenced models and supporting files are on the MATLAB path and will be automatically transferred to parallel workers by parsim.
-
Discovery findings — Read-only inspection (no simulations). Report these as facts (no confirmation needed):
- Model: list
.slx files to resolve the actual filename. Never guess a model name — resolve it from the filesystem. Ensure the model is open or loaded before inspecting it.
- Sweep parameter(s) + type: workspace variable / block parameter / model parameter (see Discovering Parameter Type).
- Output metrics + how they are accessed: determine where each metric comes from — this decides the
postSimFcn accessor before any code is written (see "Getting Metrics Out"):
- Logged signals: use
ModelLoggingInfo.createFromModel(model) to find the logged set without simulating; check the logging variable name (query the model's SignalLoggingName parameter via model_query_params) — do not assume logsout. Access by name.
- Root Outports: if the model has no logged signals, top-level Outports are the metric source — captured via
SaveOutput/yout. Their yout Dataset elements are typically name-less, so getElement('name')/get('name') will not match — access them by BlockPath or positional index. Enumerate the top-level Outport blocks with model_read (or model_overview).
- Signal marked loggable but currently off: capture it non-destructively with a
DataLoggingOverride (see "Getting Metrics Out") — no model edit. If the signal is not in the model's loggable set, verifySignalAndModelPaths errors; that signal needs a model change (enable logging or add an Outport) — surface it as a consent-required plan point, do not enable it silently.
Record which mechanism (and therefore which accessor) each metric uses, so the postSimFcn is authored correctly from the start.
- How the model's data is provisioned: determine where the model's data comes from and whether it loads with the model — a
PreLoadFcn, a bound data dictionary, or model-workspace variables all load when the model does, so they reach each worker automatically. Only data from a step that does not run on load must be established explicitly per worker (see "Getting Data and Dependencies to Workers").
- Pool capacity:
parcluster default profile capacity.
- Optimization compatibility: inferred FastRestart tunability (from parameter type) and whether Rapid Accelerator is a candidate.
-
Simulation plan — The decisions the user approves. Present as numbered points; end with: "Shall I proceed? To adjust, reference any point (e.g., 'for point 3, use X instead')."
- Sweep: values + count (
linspace for uniform). For Monte Carlo/uncertainty, ask the sampling method if unspecified and record the seed (client-side — see Reproducibility). For multiple parameters, ask Exhaustive (full grid) vs Sequential (paired).
- How to run: execution (
parsim vs batchsim) and specification (SimulationInput array vs DesignStudy) per the comparison tables — state the choice, don't default silently.
- Output signal(s) for metrics.
- Post-processing:
- If the user described a metric or goal → reduce results by default:
postSimFcn keeps only that metric and discards all other signals and full time-series. State this in the plan so they can adjust it (e.g. name extra signals to return, which postSimFcn appends to the SimulationOutput).
- If no goal was given → estimate the total logged-signal data size (signals × time steps × runs) and ask which signals, if any, they want to keep — full logs from many runs can exhaust memory.
- Live visualization (optional): ValueStore +
KeyUpdatedFcn if wanted, and what to plot. Ask yes/no explicitly.
- Pool size: see "Sizing the pool".
- Options:
UseFastRestart='on' if tunable; Rapid Accelerator if applicable (see those sections). Don't add other options unless asked.
- Worker dependencies:
ManageDependencies='on' (the default) auto-transfers detected dependencies; ask the user to let you know if any other files need to be manually attached via AttachedFiles.
- Script: propose a
.m filename in the model's folder, so the sweep script, its callback files, and the model sit together and are on the path when the script runs. If the script must live elsewhere, addpath the model's folder when loading the model so the model and its callback files stay on the path.
CRITICAL: Do not run simulations until the user explicitly confirms the plan. Always wait for confirmation — even if a reusable script already exists, even if the request seems straightforward. No exceptions.
-
Write the script — Only after the user confirms. Before writing, read the reference file(s) for every topic the task touches (see "References") and follow the patterns they prescribe. Write a .m script file with the agent's Write tool.
-
Test run, then expand — For large campaigns (N ≥ 500), first run the script on the first 2 SimulationInputs (in(1:2)) over only a few time steps (set a short StopTime for the test) to validate postSimFcn correctness and FastRestart/Rapid Accelerator tunability cheaply — the first sim compiles, the second reveals whether the change is tunable. If it succeeds, restore the full StopTime, expand to all simulations (in(1:N)), and run the campaign with run_matlab_file. For small campaigns (N < 500), skip the test and run all N directly (checking ErrorMessage afterward catches a broken postSimFcn). Do not bake a dry-run/validation-mode/sample-size switch into the deliverable script. Do not use Simulation Manager unless the user asks.
-
Analyze and visualize — Use markers + lines for plots.
-
Always ask for next steps — After the sweep, suggest plotting, finding optimal values, flagging outliers, checking requirements, or refining the sweep. Only suggest analysis derivable from the available data — do not offer what was discarded during post-processing.
Discovering Signal Names and Parameter Type
Resolve logged signal names and locate the sweep parameter before writing any code — see references/signal-and-parameter-discovery.md.
Choosing How to Run
Two independent choices — pick one from each axis. Both parsim and batchsim accept either a SimulationInput array or a DesignStudy, and both run on a local pool or a remote cluster (MATLAB Parallel Server), so the axes compose freely.
Execution: parsim vs batchsim
| parsim | batchsim |
|---|
| How it runs | Interactive — runs on the pool and blocks the client session | Offloaded — submits a background job (simjob = batchsim(in)) |
| Results | Returned in-session: out = parsim(in) | Retrieved later: fetchOutputs(simjob) |
| Prefer when | You want results now; short/medium campaigns; iterating on the setup | Long campaigns (hours/days); you want to keep using or close MATLAB; access the job later from another machine |
| Simulation Manager UI | Supported (ShowSimulationManager="on") | Not available |
| Limitation | Ties up the session until all runs finish | Job-management overhead; results are not immediate |
Specification: SimulationInput array vs DesignStudy
| SimulationInput array | simulink.multisim.DesignStudy |
|---|
| How you define it | Build and index the array explicitly (in(k) = ...) | Declare parameters + a combination (Exhaustive/Sequential); it generates the run set |
| Prefer when | Single-parameter sweeps; full per-run control; custom per-run logic | Multi-parameter combinations; large structured sweeps |
| Pros | Simple, transparent; straightforward preSimFcn/postSimFcn wiring | No manual nested loops; scales to large grids; OutputLocation control for large result sets |
| Limitation | Manual nested loops get unwieldy for multi-parameter grids | More API surface; parsim(designStudy) returns a Future — call fetchOutputs; verify syntax with help simulink.multisim.DesignStudy |
Decide both axes during discovery and record them in the Simulation plan — do not default to parsim + SimulationInput without considering the cues above.
SimulationInput array + parsim
model = 'MyModel';
gains = linspace(0.5, 5, 20);
N = numel(gains);
clear in; % Prevent stale array from prior runs
in(1:N) = Simulink.SimulationInput(model); % allocate the array once
for k = 1:N
% Mutate in(k) in place — do not call Simulink.SimulationInput(model) again here.
in(k) = in(k).setVariable('Kp', gains(k)); % only the run-to-run override
end
out = parsim(in);
Setup once, vary per run. Build campaign-wide state once — allocate the SimulationInput array, load base data (see "Getting data and dependencies to workers"), open the project. Inside the loop do only what changes run-to-run: per-run overrides and callbacks. Never re-allocate the array or re-run initialization inside the loop.
Sizing the pool
Open a pool of min(numSims, capacity) — never more workers than sims. parsim opens one automatically, but sizing it explicitly avoids requesting more workers than the profile would otherwise open for a small sweep. Derive the capacity from the profile: PreferredPoolNumWorkers is the pool size a plain parpool opens, and NumWorkers is the profile's maximum — take the smaller, because requesting more than NumWorkers errors. parpool also errors if a pool already exists, so reuse an adequate one rather than restarting it:
c = parcluster(); % default profile
capacity = min(c.PreferredPoolNumWorkers, c.NumWorkers);
target = min(N, capacity); % N = number of sims
pool = gcp('nocreate');
if isempty(pool) || pool.NumWorkers < target
delete(pool); % no-op if empty; clears an undersized pool
parpool(target);
end % else: reuse the existing adequate pool
Setting parameters
Use setVariable for workspace variables, setModelParameter for model-level settings (e.g., StopTime), setBlockParameter for block dialog parameters, and setExternalInput for time-varying input signals. For a variable in the model workspace or a data dictionary (not the base workspace), setVariable must name it: setVariable('Mu', value, 'Workspace', model) — plain setVariable('Mu', value) targets the base workspace and will not reach it. Confirm which workspace the variable lives in via Discovering Parameter Type before writing the call. Use setVariable only for values that change run-to-run — see "Getting data and dependencies to workers" for where base initialization belongs.
Optimization: FastRestart and Rapid Accelerator
UseFastRestart='on' reuses the compiled model across tunable-only runs; Rapid Accelerator (SimulationMode='rapid-accelerator') runs a compiled target for many fast runs after a one-time build. Do not add options beyond these unless the user asks. For the full option table, the FastRestart tunability rules, and the Rapid Accelerator build/staleness caveats, see references/optimization-options.md.
DesignStudy and batchsim (API notes)
DesignStudy specifies values for massive simulations using parameter combinations; batchsim offloads a campaign as a retrievable job — see references/designstudy-and-batchsim.md.
Getting Data and Dependencies to Workers
Workers do not inherit the client base workspace — decide what transfers automatically, what needs AttachedFiles, and when a SetupFcn is (and is not) required. For Monte Carlo, seed and record the RNG on the client. See references/worker-data-transfer.md.
Migrating Existing Scripts
If the user provides an existing script or mentions they have one, identify which anti-patterns it uses, show the mapping and its benefits, and offer to rewrite using the recommended approach.
| Anti-pattern | Recommended |
|---|
parfor + sim | parsim with SimulationInput array |
set_param + sim in a loop | setBlockParameter / setVariable on SimulationInput |
assignin('base', ...) + sim | setVariable on SimulationInput |
for loop + sim + manual result collection | parsim + postSimFcn |
Key conversions: set_param(blk, p, v) → in.setBlockParameter(blk, p, v), assignin('base', n, v) → in.setVariable(n, v), parfor → parsim(in). See Setting parameters and postSimFcn sections for full patterns.
When a set_param call targets a block dialog parameter whose value is a workspace variable (e.g., a Gain block with Gain = 'Kq'), the swept quantity is that variable — migrate to setVariable('Kq', value) rather than setBlockParameter. Run Discovering Parameter Type on the target first to confirm where the value lives.
Getting Metrics Out
Pick the metric accessor during discovery — logged signal, root Outport, or a signal that is loggable but currently off — so the postSimFcn is authored correctly the first time. See references/output-metrics.md.
Pre/Post Simulation Functions and Visualization
Use preSimFcn to derive dependent parameters and postSimFcn to post-process each run — deriving and reducing to the needed metrics — and drive live plots with a ValueStore KeyUpdatedFcn — see references/simulation-callbacks.md.
Failure Handling
After parsim completes, check for failed runs:
failedIdx = find(arrayfun(@(o) ~isempty(o.ErrorMessage), out));
for k = failedIdx
fprintf('Run %d: %s\n', k, out(k).ErrorMessage);
end
If failures occur, inspect the error messages to identify the root cause (e.g., config mismatch, missing variable, invalid block path). Present the errors to the user, explain the likely cause, and propose an updated plan to fix the issue before re-running. Do not blindly retry — parsim failures are typically deterministic and will recur without a fix.
- Non-tunable parameter errors (e.g., dimension changes, sample time conflicts):
UseFastRestart is enabled but the swept parameter is not tunable. Remove UseFastRestart and re-run.
Debugging
Profile runs, read worker-side diaries, size output to avoid out-of-memory, and clear stale build artifacts — see references/debugging.md.
Guardrails
- Never guess model filenames from user descriptions — list
.slx files in the project directory to resolve the actual name.
- Never modify the user's model — not even in memory — without explicit consent. This includes enabling signal logging, changing config parameters, or any
set_param call on the model. If changes are needed, present them in the plan and use setModelParameter on SimulationInput for non-destructive application where possible.
- Never use
set_param to drive simulations — use SimulationInput/DesignStudy.
- Never use
try-catch around signal access in postSimFcn — confirm signals via ModelLoggingInfo first.
- Never guess signal names — verify from model without simulating.
- Never run simulations for discovery purposes without user confirmation — models can take hours to simulate.
- Never change parameters beyond what user requested.
- Never assume
parsim accepts sim options (e.g., OutputFcn, StopFcn) — parsim has its own API. Check parsim documentation for the relevant MATLAB release before using any option.
- Never add parsim options beyond what the tunability rules prescribe unless the user explicitly requests them.
- Always use
in/out as variable names.
- Always set parameters with the documented single-call form —
in = in.setModelParameter(Name=Value), in = in.setVariable(name, value) — one call per statement.
- Always set only the parameters a task needs, and only when the model's current setting doesn't already provide it — e.g. set
SaveOutput='on' only if it is currently off; leave other model parameters at their defaults.
- Always use
setExternalInput with a Dataset.
- Always check
simOut.ErrorMessage in postSimFcn.
- Always use markers + lines for plots (e.g.,
'-o').
- Prefer
DesignStudy for large exhaustive combinations.
- Prefer
postSimFcn to reduce results over full log transfer.
- Keep the generated script lean — only the callbacks the task needs; avoid many small local functions.
References
Each topic's APIs and code patterns live in its reference file. Read the matching reference before writing code for that topic, and follow the patterns it prescribes.
| When the task involves… | Read before writing code |
|---|
| Resolving signal names and sweep-parameter type | references/signal-and-parameter-discovery.md |
| Accessing output metrics (logged / Outport / off) | references/output-metrics.md |
| Getting data and dependencies to workers | references/worker-data-transfer.md |
| preSimFcn / postSimFcn / live visualization | references/simulation-callbacks.md |
| DesignStudy and batchsim API | references/designstudy-and-batchsim.md |
| FastRestart and Rapid Accelerator options | references/optimization-options.md |
| Debugging parallel runs | references/debugging.md |
Copyright 2026 The MathWorks, Inc.