matlab/matlab-identify-linear-system
> 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.
----
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.