> Identify a linear dynamic model from input-output or time-series data using MATLAB System Identification Toolbox. Use when estimating transfer function, state-space, ARX, ARMAX, BJ, OE polynomial or process models from measurement data.
npx skills add https://github.com/matlab/matlab-agentic-toolkit --skill matlab-identify-linear-system
Estimate a linear dynamic model from measurement data using MATLAB System Identification Toolbox. This skill selects the right model type, determines model order, estimates parameters, and validates results — following the methodology a System Identification Toolbox expert would use.
Write a single end-to-end MATLAB script and run it. Do NOT step through phases one tool call at a time. The script should:
Only break into multiple steps if the first script fails or produces poor results (fit < 70%).
Critical rules for every script (MANDATORY — violating any of these is a bug):
InteractiveOrderSelection=false when using order vectors — this is a HIDDEN property (not visible in disp() or tab-complete) on BOTH ssestOptions AND n4sidOptions. It MUST be set explicitly or a GUI popup HALTS executionEstimateCovariance=false during ANY search loop (order scan, delay scan) — covariance for discarded models wastes timeFocus='simulation' for simulation/control use on ssestOptions, n4sidOptions, procestOptions (NOT available on tfestOptions — tfest has no Focus)[~, fits] = compare(zv, m1, m2); fits{1}, fits{2} — ALWAYS pass 2+ models to ONE compare() call, NEVER call compare() separately per modeldata.InterSample = 'foh' BEFORE CT estimation if input is smooth analog{'zoh'; 'foh'} (NOT row cell)delayest or inspect impulse response BEFORE any model estimation (even for MIMO, even when delay seems small)ssest(ze, 2:8, opt) not ssest(ze, 4, opt)The user provides: $ARGUMENTS
Parse:
projects/ that has a SPEC.md.mat file, variable name in workspace, or inline description of the dataIf neither is provided, ask the user to specify a data source or describe the identification problem.
arxRegul, or ssregest.ssest handles MIMO, CT/DT, needs only order n.If the problem maps directly to one of these patterns, write a single script immediately:
Step/impulse response → process model (do NOT split single-transient data):
% Step data is one transient — splitting creates IC discontinuity. Use full data.
opt = procestOptions('Focus', 'simulation');
m1 = procest(data, "P1D", opt); m2 = procest(data, "P2D", opt);
[~, fits] = compare(data, m1, m2); fprintf('P1D: %.1f%%, P2D: %.1f%%\n', fits{:});
fprintf('K=%.2f, Tp=%.1f, Td=%.1f\n', m1.Kp, m1.Tp1, m1.Td);
SISO time-domain → transfer function:
ze = data(1:floor(end*0.7)); zv = data(floor(end*0.7)+1:end);
nk = delayest(ze);
% Hedge delay: try nk-1, nk, nk+1
delays = max(1, nk + (-1:1));
opt = ssestOptions('Focus', 'simulation', InteractiveOrderSelection=false, EstimateCovariance=false);
models = cell(1, numel(delays));
for i = 1:numel(delays)
models{i} = tfest(ze, 3, 1, delays(i)*ze.Ts);
end
[~, fits] = compare(zv, models{:}); fprintf('Delay hedge fits: '); fprintf('%.1f%% ', fits{:}); fprintf('\n');
[~, best] = max(cell2mat(fits)); nk_best = delays(best);
% Final estimation with best delay
m1 = tfest(ze, 2, 0, nk_best*ze.Ts); m2 = tfest(ze, 3, 1, nk_best*ze.Ts);
m3 = ssest(ze, 2:6, opt);
[~, fits] = compare(zv, m1, m2, m3); fprintf('Fits: %.1f%%, %.1f%%, %.1f%%\n', fits{:});
MIMO → state-space:
ze = data(1:floor(end*0.7)); zv = data(floor(end*0.7)+1:end);
nk = delayest(ze); % delay-first, even for MIMO
opt = ssestOptions('Focus', 'simulation', InteractiveOrderSelection=false, EstimateCovariance=false);
sys = ssest(ze, 1:10, opt); % order RANGE, not single integer
[~, fit] = compare(zv, sys); fprintf('Fit: %.1f%%\n', fit);
% For MIMO bandwidth, use per-channel: bandwidth(sys(i,j))
for i = 1:size(sys,1), for j = 1:size(sys,2)
fprintf('BW(%d,%d)=%.2f rad/s\n', i, j, bandwidth(sys(i,j)));
end, end
FRD / large periodic data → frequency-domain path:
opt = ssestOptions('InitializeMethod', 'AAA', 'Focus', 'simulation', ...
InteractiveOrderSelection=false, EstimateCovariance=false);
opt.SearchOptions.MaxIterations = 0;
sys = ssest(Gfrd, 1:maxOrder, opt);
If the fast path gives fit > 85%, you're done. Report results and move on.
Use this structured investigation when the fast path fails (fit < 70%), the problem is ambiguous, or the user asks for deeper analysis.
Determine: SISO/MIMO, time/frequency domain, intended use (simulation/prediction/control), known constraints. See references/data-preparation.md for preprocessing details.
Before committing to linear identification, verify that a linear model is appropriate.
| Method | How | Interpretation |
|--------|-----|----------------|
| Amplitude dependence | Estimate models from datasets at different input amplitudes | If gain/dynamics change with amplitude -> nonlinear |
| Harmonic analysis | Apply periodic input, check for even harmonics in output spectrum | Even harmonics indicate nonlinearity |
| Model order escalation | Fit orders 2, 4, 8, 12, 16 — plot fit vs. order | Plateauing at low fit despite high order -> nonlinearity |
| Residual structure | Inspect residuals vs. amplitude of u or y | Systematic patterns -> nonlinear |
| Split-data test | Estimate on first half, validate on second half AND vice versa (use low model order, e.g. 2-4, to avoid false positives from estimation variance) | Asymmetric fits -> non-stationary or nonlinear |
| ISNLARX | Use the isnlarx method on iddata to assess severity of nonlinearity |
See references/data-preparation.md for the full preprocessing workflow including:
advice, plot)checkFeedback)pexcit)Apply this decision tree. The FIRST matching branch is the recommendation:
1. Physical structure known (ODEs with unknown parameters)?
--> idgrey + greyest (outside this skill's scope)
2. Frequency-domain data (idfrd), very large dataset (N > 50k), periodic input, or high modal density?
--> Frequency-domain path:
- ssest with InitializeMethod='AAA' (SISO/SIMO/MISO/MIMO — only option for full MIMO FRD)
- ssest with InitializeMethod='lsrf' (SISO/SIMO/MISO only — vector fitting)
- tfest on idfrd/etfe/spa data (uses lsrf internally; SISO/SIMO/MISO only)
3. Low-order process (1-3 poles, <=1 zero, with gain+delay)?
--> idproc + procest
4. SISO, continuous-time, moderate complexity (np <= 10)?
--> idtf + tfest
5. MIMO, or high-order, or "just need a good model quickly"?
--> idss + ssest (with n4sid for initialization)
6. Need explicit noise model (prediction/filtering application)?
--> Polynomial models: ARX, IV4, ARMAX, OE, BJ
7. Time-series (no input, output only)?
--> ar() for AR, or ssest with nu=0 for state-space
See references/model-structures.md for detailed guidance on process models, polynomial models, and when to use each.
See references/order-determination.md for methods:
delayest, impulse response)arxstruc, selstruc)n4sid with order range)Rules of thumb:
n_max ~ min(N/20, 30)n = max(ny, nu) * 2 up to 5See references/estimation.md for the full estimation workflow including:
Key reminders:
% Focus — available on ssestOptions, n4sidOptions, arxOptions, etc. (NOT tfestOptions)
opt = ssestOptions('Focus', 'simulation'); % for simulation/control
opt = ssestOptions('Focus', 'prediction'); % for forecasting
% NOTE: tfest does NOT have a Focus option. For time-domain data, tfest always
% produces a stable model. Use WeightingFilter for frequency emphasis with tfest.
% Disable interactive order selection when using an order vector
opt = n4sidOptions(InteractiveOrderSelection=false);
% or: opt = ssestOptions(InteractiveOrderSelection=false);
% Continuous-time estimation — set 'Ts',0 for ssest; tfest is CT by default
opt = ssestOptions('Focus', 'simulation', InteractiveOrderSelection=false);
model_ct = ssest(data, n, 'Ts', 0, opt);
model_ct = tfest(data, np, nz); % CT by default from sampled data
data.InterSample = 'foh'; % set BEFORE estimation for smooth analog inputs
% Regularization for high-order ARX
[Lambda, R] = arxRegul(data, orders, arxRegulOptions('RegularizationKernel', 'TC'));
CRITICAL: Always validate by simulation (infinite prediction horizon). A model estimated with Focus='prediction' can show excellent 1-step-ahead fits even when the dynamics are wrong.
% Validate by simulation (default of compare)
[yhat, fit] = compare(zv, model);
fprintf('Validation fit (simulation): %.1f%%\n', fit);
% Multi-model comparison — fit is a CELL ARRAY, not a numeric vector
[yhat, fits] = compare(zv, m1, m2, m3);
fprintf('Fits: %.1f%%, %.1f%%, %.1f%%\n', fits{:});
% Extract as numeric vector: cell2mat(fits)
% 1-step prediction fit (for forecasting models ONLY)
[yhat_pred, fit_pred] = compare(zv, model, 1);
% WARNING: fit_pred >> fit_sim means the noise model is doing the heavy lifting
% Programmatic residual analysis (no plots — suitable for batch/agent mode)
[e, r] = resid(zv, model);
% e = residual iddata object
% r = 3D array [M x nz x nz] where nz = ny + nu, M = number of lags (26 default)
% r(:,1:ny,1:ny) = residual autocovariance (RAW, not normalized)
% r(:,ny+1:end,1:ny) = cross-covariance between input and residual
% Quick whiteness check (SISO: ny=1, nu=1, nz=2):
acf = r(:,1,1) / r(1,1,1); % normalize by lag-0 to get correlation
N = size(zv.y, 1);
conf99 = 2.58 / sqrt(N); % 99% confidence bound
is_white = all(abs(acf(2:end)) < conf99);
fprintf('Residuals white: %s (99%% bound = %.4f)\n', string(is_white), conf99);
% Cross-correlation: input-residual (SISO)
xcf = r(:,2,1) / sqrt(r(1,1,1) * r(1,2,2)); % normalized cross-covariance
is_uncorr = all(abs(xcf) < conf99);
fprintf('Residuals uncorrelated with input: %s\n', string(is_uncorr));
Residual interpretation guide:
| Autocorrelation (acf) | Cross-correlation (xcf) | Diagnosis | Action |
|----------------------|------------------------|-----------|--------|
| White | Uncorrelated | Model is adequate | Done |
| Significant at lags | Uncorrelated | Noise model insufficient, but plant model may be OK for simulation | Increase noise model order (C/D in ARMAX/BJ); plant G is still usable |
| White | Significant at lag k | Missing input dynamics at lag k | Add regressor u(t-k): increase nb or adjust nk in ARX/ARMAX/BJ |
| Significant at lags | Significant at lag k | Both plant and noise model inadequate | Increase both model order and noise order; check delay |
| Fit % | Verdict | Next Action |
|-------|---------|-------------|
| > 95% | Excellent | Done — report results |
| 85-95% | Good | Acceptable; try one alternative to confirm |
| 70-85% | Moderate | Increase order, try different structure, check data |
| 50-70% | Poor | Wrong structure, missing nonlinearity, or bad data |
| < 50% | Failed | Reassess fundamentals (delay? feedback? nonlinear?) |
When model is inadequate — re-estimation recipe:
% Scan an order RANGE (never just guess one number)
opt = ssestOptions('Focus', 'simulation', InteractiveOrderSelection=false, EstimateCovariance=false);
model_new = ssest(ze, 2:8, opt);
% Compare old and new with multi-model compare (MANDATORY pattern)
[~, fits] = compare(zv, model_old, model_new);
fprintf('Old: %.1f%%, New: %.1f%%\n', fits{:});
See references/validation-and-diagnostics.md for uncertainty analysis, stability assessment, diagnostic checklist, and initial conditions guidance.
% Comparison plot
[yhat, fit] = compare(zv, model);
title(sprintf('Validation: %.1f%% fit', fit));
% Bode with confidence
h = bodeplot(model);
showConfidence(h, 3);
% Residuals
resid(zv, model);
================================================================
Linear Model Identification — Results
================================================================
Model type: <idss / idtf / idpoly / idproc>
Order: <n / [na nb nk] / np poles, nz zeros>
Delay: <nk samples (X seconds)>
Focus: <simulation / prediction>
Validation fit: XX.X% (NRMSE on held-out data)
FPE: <value>
AIC: <value>
Key dynamics:
Poles: <dominant pole locations>
Zeros: <zero locations if few>
DC gain: <value>
Bandwidth: <-3dB frequency (SISO only; for MIMO use bandwidth(model(i,j)) per channel)>
Status: FIT ACHIEVED / BELOW TARGET — <recommendation>
================================================================
identify_model.m containing the full reproducible workflowidentified_model.mat with the final model objectFor advanced scenarios, see:
| # | Mistake | Consequence | Fix |
|---|---------|-------------|-----|
| 1 | Validating on training data | Overfitting undetected | Always hold out validation set |
| 2 | Using prediction fit to judge simulation quality | False confidence in dynamics | Validate by simulation (horizon=Inf) |
| 3 | Wrong InterSample setting | Systematic bias at high freq in CT models | Set 'foh' for smooth analog inputs |
| 4 | Ignoring Focus option | Model optimizes wrong criterion | Set Focus='simulation' for sim/control use |
| 5 | Not detrending data with offsets | DC gain wrong, poor overall fit | detrend(data) or use offsets |
| 6 | Using ARX in closed loop | Biased plant estimate | Use iv4, BJ, or indirect method |
| 7 | Wrong delay assumed | Catastrophic — no amount of order helps | Estimate delay first, try +/-1 |
| 8 | Over-parameterizing | Great training fit, poor generalization | Regularize or reduce order |
| 9 | Ignoring uncertainty | False precision in model | Always check showConfidence on Bode |
| 10 | Assuming ICs from training apply to new data | Poor validation fit | Re-estimate ICs for each new dataset |
| 11 | Not passing 'Ts',Value as name-value pair (Value>0) to ssest, tfest for DT model | Gets CT model instead | ssest(data, n, 'Ts', data.Ts, opt) |
| 12 | Using wrong initial conditions for simulation | Bad fit to validation data | use findstates' or data2state' to determine the initial conditions that maximize the fit to the validation data |
|13| Forget to scale data | Ill-conditioned identification problem leading to bad results | Ensure your inputs and outputs, and time units are scaled appropriately, especially when using numerical optimization algorithms.|
----
Copyright 2026 The MathWorks, Inc.
----
Integration with protocols.io API for managing scientific protocols. This skill should be used when working with protocols.io to search, create, update, or publish protocols; manage protocol steps and materials; handle discussions and comments; organize workspaces; upload and manage files; or integrate protocols.io functionality into workflows. Applicable for protocol discovery, collaborative protocol development, experiment tracking, lab protocol management, and scientific documentation.
Analyzes job descriptions and generates tailored resumes that highlight relevant experience, skills, and achievements to maximize interview chances
Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.
Build and distribute Expo development clients locally or via TestFlight
Use when you have a written implementation plan to execute in a separate session with review checkpoints
Data structure for annotated matrices in single-cell analysis. Use when working with .h5ad files or integrating with the scverse ecosystem. This is the data format skill—for analysis workflows use scanpy; for probabilistic models use scvi-tools; for population-scale queries use cellxgene-census.
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
Comprehensive molecular biology toolkit. Use for sequence manipulation, file parsing (FASTA/GenBank/PDB), phylogenetics, and programmatic NCBI/PubMed access (Bio.Entrez). Best for batch processing, custom bioinformatics pipelines, BLAST automation. For quick lookups use gget; for multi-service integration use bioservices.
Take matlab/matlab-identify-linear-system from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.