---
name: create-sdi-run
description: >
Import data into the Simulation Data Inspector (SDI) from MAT, CSV, or Excel
files, from workspace variables, or from a Simulink simulation. Use when the
user wants to view a logged file in SDI, load a bench-test log, or verify
what a just-completed `Simulink.sdi.createRun` or `sim(model)` produced.
Covers the `'file'`, `'vars'`, and `'namevalue'` sources of `createRun`, the
auto-populate behavior after `sim()`, and post-import verification. Not for
comparing existing runs (use `Simulink.sdi.compareRuns`); not for streaming
data live during a running simulation (use
`Simulink.sdi.createRunOrAddToStreamedRun` — a separate API); and not for
authoring Gherkin or Simulink Test test cases, assertions, or regression
tests (use `testing-simulink-models`). Do not activate on test-authoring or
verification prompts even when no test-authoring skill is available — say
so and stop.
license: MathWorks BSD-3-Clause
metadata:
author: MathWorks
version: "1.0"
---
# Create SDI Run
Get data into the Simulation Data Inspector using the shortest path that works. Most agents reflexively read files into the workspace and rebuild signals by hand; this skill teaches the direct paths and when each applies.
## When to Use
- User has a `.mat`, `.csv`, or `.xlsx` file of logged data and wants to view it in SDI.
- User just ran `sim(model)` and wants to see the logged signals.
- User has `timeseries`, `timetable`, or `Simulink.SimulationData.Dataset` variables in the workspace and wants an SDI run.
- User asks about `Simulink.sdi.createRun`, `Simulink.sdi.getAllRunIDs`, or `Simulink.sdi.getRun`.
## When NOT to Use
- Comparing two existing runs — that is a distinct workflow (`Simulink.sdi.compareRuns`).
- **Streaming data live during a running simulation** — this is a separate API (`Simulink.sdi.createRunOrAddToStreamedRun`), not a mode of `createRun`. Prompts like "stream data into SDI while my sim is running" belong to that API. Do not activate on those.
- Files in MDF (`.mf4`), ULG, ROS bag, CAN log, or HDF5 formats — see [references/format-support.md](references/format-support.md) for what SDI reads natively vs. what needs a different toolbox.
- Customizing plot layout, cursors, or subplot arrangement beyond opening the SDI window.
- **Authoring Gherkin `.feature` files, Simulink Test cases, `matlab.unittest` classes, or any pass/fail assertions or regression tests** for a model or subsystem — use `testing-simulink-models` (Gherkin-based `model_test`, requires Simulink Test). Prompts like "write a Gherkin test for X", "verify abs(-5) equals 5", "create a regression test for this subsystem", or any request whose deliverable is a test artifact belong there. Do **not** activate this skill on such prompts even when `testing-simulink-models` is unavailable — in that case, tell the user which skill is missing and stop.
## Workflow
Answer these four questions **in order**. The first "yes" tells you what to do.
### 1. Is the data already in SDI?
After `sim(model)` with signal logging enabled, SDI **auto-populates** one run. **Do not call `createRun`** — that produces a duplicate.
```matlab
before = Simulink.sdi.getAllRunIDs;
simOut = sim("myModel");
after = Simulink.sdi.getAllRunIDs;
if numel(after) > numel(before)
Simulink.sdi.view; % done — just open the window
end
```
If no new run appeared, logging is off. Enable it via the Simulink Agentic Toolkit `model_edit` tool with a `configure` op setting `DataLogging` on the target port, then re-simulate — or fall back to Step 3 with `simOut`. As a last resort, ask the user to right-click a signal → **Log Selected Signals** in the Simulink editor themselves; that step is for the user to perform, not the agent.
### 2. Is the data in a file?
Use the `'file'` source. One call handles MAT, CSV, and Excel natively — no `load`, no `readtable`, no `sheetnames` loop.
```matlab
runID = Simulink.sdi.createRun("bench_test","file","logs/bench_test.mat");
```
**Excel with N sheets returns a 1×N `int32` vector** — one run ID per sheet. All returned runs inherit the base `name`; if the user needs sheet-named runs, rename each run in place with `sheetnames()` — do **not** delete and re-import per sheet:
```matlab
runIDs = Simulink.sdi.createRun("cases","file","cases.xlsx"); % [id1, id2, ...]
sheets = sheetnames("cases.xlsx");
for k = 1:numel(runIDs)
Simulink.sdi.getRun(runIDs(k)).Name = sheets(k);
end
```
**Time-column header rule (CSV and Excel).** SDI's built-in reader requires the time column to be named literally `time` (case-insensitive). Headers like `time_s`, `t`, `Time (s)`, `Timestamp` cause `SDI:sdi:ImportError`. **Try `'file'` first and catch the error** — do not pre-inspect the header row and guess, because SDI's matching rules can differ from your interpretation. On `SDI:sdi:ImportError`, fall back to the `readmatrix` + `array2timetable` + `'vars'` pattern (Step 3 / CSV pattern below). Do **not** rewrite the source file.
**Other `createRun` failures.** `createRun` can also throw for reasons this skill does not enumerate — corrupted files, unsupported data types inside a MAT/Excel, malformed structs, permission errors, unregistered custom readers. When you catch an error identifier that is not `SDI:sdi:ImportError`, do **not** invent a fix or silently retry. Surface the full error identifier and message to the user, list the file or variables involved, and suggest they inspect the source (e.g., `whos("-file",path)` for a MAT, or `sheetnames(path)` plus a per-sheet read for an Excel). Ask before attempting an alternative import path.
### 3. Is the data in workspace variables?
Two sources, different purposes:
- **`'vars'`** — pass variables whose names or `.Name` properties are already correct. This is the default.
- **`'namevalue'`** — pass explicit signal names when the variables have no `.Name` metadata (e.g., bare arrays or unnamed `timeseries`).
Timeseries whose `.Name` is already what you want:
```matlab
% TEMPLATE — not executable
runID = Simulink.sdi.createRun("baseline","vars", speedTS, torqueTS);
```
Timetable — signals inherit the column names:
```matlab
% TEMPLATE — not executable
runID = Simulink.sdi.createRun("baseline","vars", benchTT);
```
Bare arrays needing explicit names:
```matlab
% TEMPLATE — not executable
runID = Simulink.sdi.createRun("baseline","namevalue", ...
{"velocity","load"}, {velocityData, loadData});
```
**Gotcha (Gap E):** if a `timeseries` passed to `'namevalue'` has its own `.Name` set, `.Name` **wins** over the caller-supplied name. To force the name you want, either set `ts.Name` first and use `'vars'`, or clear it before `'namevalue'`.
### 4. Verify the run
After every `createRun` or auto-populate:
```matlab
ids = Simulink.sdi.getAllRunIDs;
run = Simulink.sdi.getRun(ids(end));
fprintf("Run %d: %s (%d signals)\n", ids(end), run.Name, run.SignalCount);
for k = 1:run.SignalCount
fprintf(" %s\n", run.getSignalByIndex(k).Name);
end
Simulink.sdi.view;
```
Verify against MATLAB via the MATLAB MCP server's `evaluate_matlab_code` tool. If `SignalCount == 0`, the import silently produced nothing — investigate the source, do not report success.
## Key Functions
All available from R2023a with base Simulink; no additional toolbox required.
| Function | Purpose | Available From |
|---|---|---|
| `Simulink.sdi.createRun(name)` | Empty run — used with `Run.add` for a per-signal build; rarely the right first choice. | R2011b |
| `Simulink.sdi.createRun(name,"file",path)` | Import a `.mat`, `.csv`, or `.xlsx` file. Returns scalar `int32` for MAT/CSV, `1×N int32` vector for N-sheet Excel. | R2011b |
| `Simulink.sdi.createRun(name,"vars",v1,v2,...)` | Import workspace variables whose names/`.Name` are correct. | R2011b |
| `Simulink.sdi.createRun(name,"namevalue",names,values)` | Import with explicit names. See Gap E caveat. | R2011b |
| `Simulink.sdi.view` | Open the SDI window. Safe to call repeatedly; opens only if not visible. | R2011b |
| `Simulink.sdi.getAllRunIDs` | Return `int32` vector of every run's ID. Use before/after `sim` to detect auto-populate. | R2017a |
| `Simulink.sdi.getRun(runID)` | Return the `Simulink.sdi.Run` object for a run ID. | R2011b |
| `Simulink.sdi.clear` | Delete all SDI runs. **Ask the user first** — destructive. | R2011b |
Four `createRun` sources exist: **empty**, **`'vars'`**, **`'namevalue'`**, **`'file'`**. When unsure, run `help Simulink.sdi.createRun`.
## Patterns
### Pattern: MAT file → SDI (one line)
```matlab
runID = Simulink.sdi.createRun("sensor_log","file","data/sensor_log.mat");
Simulink.sdi.view;
```
The variables inside the MAT file may be `timeseries`, `timetable`, `Simulink.SimulationData.Dataset`, or legacy struct-with-time — SDI's reader picks them up automatically.
### Pattern: CSV file → SDI (try `'file'`, catch, fall back)
Attempt the one-liner first. On `SDI:sdi:ImportError`, fall back to the timetable path — do not rewrite the file:
```matlab
try
runID = Simulink.sdi.createRun("bench_run","file","logs/bench_run.csv");
catch ME
if strcmp(ME.identifier,"SDI:sdi:ImportError")
raw = readmatrix("logs/bench_run.csv");
headers = string(readcell("logs/bench_run.csv","Range","1:1"));
tt = array2timetable(raw(:,2:end), ...
"RowTimes", seconds(raw(:,1)), ...
"VariableNames", headers(2:end));
runID = Simulink.sdi.createRun("bench_run","vars", tt);
else
rethrow(ME);
end
end
```
Assumes the time column is column 1 and expressed in seconds — the common case for bench logs. If the time column sits elsewhere or uses different units, adjust the index or wrap it with the appropriate `duration` constructor (`milliseconds`, `minutes`, …) before passing to `RowTimes`.
### Pattern: Excel with multiple sheets → one run per sheet
One `createRun` call per file. Every returned run initially inherits the **base name** you supplied — SDI does not auto-apply sheet names. Rename in place with `sheetnames()`; do **not** delete and re-import per sheet. The same time-column-header rule applies as for CSV — if a sheet's time column is not named `time`, `createRun` throws `SDI:sdi:ImportError`; wrap this call in the same try-then-fallback shown in the CSV pattern, using `readmatrix(...,"Sheet",sheets(k))` and `readcell(...,"Sheet",sheets(k),"Range","1:1")` per sheet.
```matlab
runIDs = Simulink.sdi.createRun("test_matrix","file","data/two_runs.xlsx");
% runIDs is a 1xN int32 vector — one ID per sheet, all named "test_matrix".
sheets = sheetnames("data/two_runs.xlsx");
for k = 1:numel(runIDs)
run = Simulink.sdi.getRun(runIDs(k));
run.Name = sheets(k); % rename in place to the sheet name
fprintf("Sheet run: %s (%d signals)\n", run.Name, run.SignalCount);
end
```
To restrict to specific sheets, use the `sheets` name-value — the returned vector is trimmed accordingly:
```matlab
runIDs = Simulink.sdi.createRun("cases","file","cases.xlsx", ...
sheets=["baseline","variant"]);
```
### Pattern: Post-sim — just open SDI
```matlab
before = Simulink.sdi.getAllRunIDs;
simOut = sim("controller_test");
after = Simulink.sdi.getAllRunIDs;
if numel(after) > numel(before)
Simulink.sdi.view; % logged signals already imported
else
% No auto-populate — logging is off. Enable it and re-simulate, or
% pass simOut through createRun as a fallback:
Simulink.sdi.createRun("run_from_simout","vars", simOut);
Simulink.sdi.view;
end
```
### Pattern: Rename workspace signals
Set `.Name` before `'vars'`. This avoids the `'namevalue'` override trap.
```matlab
data = load("data/speed_torque.mat");
speed = data.speed;
torque = data.torque;
speed.Name = "velocity";
torque.Name = "torqueNm";
runID = Simulink.sdi.createRun("baseline","vars", speed, torque);
```
## Conventions
**Always**
- **Check whether SDI already has the data before calling `createRun`.** Use `Simulink.sdi.getAllRunIDs` before and after `sim(model)`. If a new run appeared, `sim` already imported the logged signals — call `Simulink.sdi.view` and stop.
- **Prefer the `'file'` source** for `.mat`, `.csv`, `.xlsx`. It handles the read and the signal-naming for you. Reserve `load`/`readtable`/`sheetnames` for the CSV time-column fallback and for genuinely workspace-first workflows.
- **Verify with `SignalCount`.** After every import, confirm at least one signal exists. Zero signals means the import failed silently.
- **Read `help Simulink.sdi.createRun`** if you are unsure which source applies. The four sources are the entire API surface for import.
**Ask First**
- `Simulink.sdi.clear` — deletes every run. Confirm with the user before running.
- Modifying the source file (e.g., renaming a CSV column to `time`) — prefer the timetable fallback pattern instead of altering user data.
- Attempting an alternative import path after a non-`SDI:sdi:ImportError` failure — surface the error to the user first, then ask which path they want.
**Never**
- Never invent function names. `Simulink.sdi.createRunFromFile` does not exist. Use `Simulink.sdi.createRun(name,"file",path)`.
- Never rebuild a file-side workflow through `load` / `readtable` / manual `timeseries` construction / `Run.add` when the `'file'` source handles the same file natively.
- Never assume `namevalue` names take precedence over `timeseries.Name` — `.Name` wins. See Gap E in Common Mistakes.
- Never claim success without inspecting `SignalCount` and the signal names.
## Common Mistakes
| Mistake | Why It's Wrong | Correct Approach |
|---|---|---|
| Reading MAT/CSV/Excel yourself and calling `createRun(...,"vars",...)` | Duplicates the `'file'` source's work; loses SDI's automatic signal-name handling; scales badly for multi-sheet Excel. | `Simulink.sdi.createRun(name,"file",path)`. |
| Deleting the multi-sheet import and re-calling `createRun` in a per-sheet loop because both runs share the base name | Discards the correct one-call `'file'` import and violates the "single call handles sheet expansion" convention. All runs from one Excel file inherit the base name — they are **not** auto-renamed to sheet names. | Keep the one `createRun(...,"file",xlsx)` call and rename each run in place: `Simulink.sdi.getRun(runIDs(k)).Name = sheets(k)`. |
| Calling `createRun(...,"namevalue",{"SimOut"},{simOut})` after `sim(model)` | With signal logging on, `sim` already imported the run. This adds a duplicate. | `sim(model); Simulink.sdi.view;` (verify with `getAllRunIDs` before/after). |
| Using `Simulink.sdi.createRunFromFile` | Function does not exist. Hallucination. | `Simulink.sdi.createRun(name,"file",path)`. |
| Passing `namevalue` names when the timeseries has its own `.Name` | `.Name` overrides the caller-supplied name silently — Gap E. | Set `ts.Name` first, then use `'vars'`. Or clear `.Name` before `'namevalue'`. |
| Pre-inspecting the header and skipping `'file'` because you *think* the time column is misnamed | Your guess can differ from SDI's actual matching rules; skipping `'file'` costs the automatic signal handling. | Try `'file'` first, catch `SDI:sdi:ImportError`, then use the timetable fallback (Step 3 / CSV pattern). |
## References
- [references/format-support.md](references/format-support.md) — which file formats SDI reads natively, which need a toolbox, which need conversion. Consult when the user has a file extension other than `.mat`, `.csv`, or `.xlsx`.
- [references/createrun-signatures.md](references/createrun-signatures.md) — the four `createRun` sources side-by-side with return-value shapes and name-value options. Consult when picking a source or debugging return values.
----
Copyright 2026 The MathWorks, Inc.
----