Matlab Identify Linear System
MathWorksOfficialCreated by MathWorks
- Skill
- Tool integration
- Science and research
- BSD-2-Clause
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured, expert-level workflow for estimating linear dynamic models from input-output or time-series data. It guides users through the entire identification process, including data preparation, model structure selection, parameter estimation, and rigorous validation. By leveraging the MATLAB System Identification Toolbox, it helps engineers and researchers derive accurate models for simulation, prediction, and control design while avoiding common pitfalls like overfitting or incorrect delay estimation.
What it does
- Estimates transfer function, state-space, and process models from measurement data.
- Determines optimal model order and time delays using systematic scanning methods.
- Performs automated validation on held-out data to ensure model generalization.
- Provides diagnostic tools for residual analysis to verify model adequacy.
- Supports advanced identification tasks like MIMO system modeling and frequency-domain estimation.
- Offers robust options for handling outliers and enforcing model stability.
When to use it
- When you need a data-driven linear model for simulation or control design.
- When you have measured I/O data and need to determine the underlying system dynamics.
- When you need to compare different model structures like ARX, state-space, or transfer functions.
- When you need to extract modal parameters from frequency response data.
- When you need to validate a plant model for PID tuning.
Advantages
- Follows a proven, expert-defined methodology for reliable model identification.
- Prevents common errors like validating on training data or ignoring system delays.
- Automates the comparison of multiple model structures to find the best fit.
- Provides clear, actionable diagnostic metrics for model quality.
- Scales from simple SISO systems to complex MIMO identification tasks.
Usage examples
- “Identify a state-space model for my MIMO system using the data in 'experiment_data.mat'.”
- The agent will load the data, perform delay estimation, scan an order range, estimate the state-space model, and report the validation fit and bandwidth per channel.
- “I have step response data. Find a process model.”
- The agent will apply the process model estimation path, fitting P1D or P2D models and reporting the gain, time constant, and dead time.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/matlab/matlab-identify-linear-system/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip matlab-identify-linear-system.zip -d .agents/skills
Documentation by MathWorks
Linear Model Identification
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.
When to Use
Identify a transfer function, state-space, or process model from I/O data
Determine model order from measurement data
Fit a parametric model for simulation, prediction, or control design
Convert frequency response data (FRD) to a parametric model
Compare model structures (ARX vs state-space vs transfer function)
Determine frequency response from time-domain data
Determine a plant model for PID tuning or control design
Obtain a data-driven linear model when linearization of a Simulink model is not possible or practical
Tune parameters of a physics-based model (grey-box) using data
Compare multiple models to determine which best fits the data
Simulate or predict system response using the identified model
Perform subspace identification for high-order systems or MIMO systems, or use Eigenvalue Realization Algorithm (ERA)
Extract modal parameters (natural frequencies, damping ratios, mode shapes) from frequency response
Compare model structures (ARX vs state-space vs transfer function)
Study the possibility of feedback in data by analyzing the correlation between input and output signals
Study persistence of excitation in the input signals to ensure that the data is informative enough for model identification
When NOT to Use
When the system is inherently nonlinear and a linear model is not appropriate
When the available data is insufficient or of poor quality for reliable model identification
When the primary goal is to identify a nonlinear model (e.g., neural state-space, NLARX, Hammerstein-Wiener)
When estimating the parameters of a Simulink model using experimental data; use Simulink Design Optimization Toolbox instead
When designing a controller; use Control System Toolbox skills after identifying the plant
Signal processing (filtering, spectral analysis without model fitting) — use signal processing skills
Execution Strategy
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:
Create/load data + split into estimation/validation
Estimate delay, select structure, estimate model(s)
Validate on held-out data by simulation
Print results
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=falsewhen 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=falseduring ANY search loop (order scan, delay scan) — covariance for discarded models wastes timeFocus='simulation'for simulation/control use onssestOptions,n4sidOptions,procestOptions(NOT available ontfestOptions— tfest has no Focus)Multi-model compare returns CELL:
[~, 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 analogFor multi-input InterSample: use column cell
{'zoh'; 'foh'}(NOT row cell)Delay-first: ALWAYS call
delayestor inspect impulse response BEFORE any model estimation (even for MIMO, even when delay seems small)Hedge delays: NEVER trust a single delay estimate — always try nk AND nk±1, compare fits, pick best
Order range: When selecting order, use a RANGE (vector) not a single integer —
ssest(ze, 2:8, opt)notssest(ze, 4, opt)
Arguments
The user provides: $ARGUMENTS
Parse:
project_name (optional): name of a project under
projects/that has aSPEC.mddata_source (optional): path to a
.matfile, variable name in workspace, or inline description of the data
If neither is provided, ask the user to specify a data source or describe the identification problem.
Design Principles
Start simple, add complexity only when data justifies it. Try order 2-4 before 10-15.
Delay first. A wrong delay cannot be fixed by higher order — it's catastrophic.
Set Focus correctly. The #1 missed option. Default 'prediction' is sometimes wrong for simulation use.
Always hold out validation data. Never report training fit as performance.
Regularization > high order. A regularized ARX(30) often outperforms unregularized ARX(5). Use
arxRegul, orssregest.State-space is the default. When unsure,
ssesthandles MIMO, CT/DT, needs only order n.Compare 2-3 structures. The first model is rarely the best.
Check residuals. A high fit with correlated residuals means the model is missing dynamics.
Know when to stop. >90% fit with white residuals on validation data is success.
Fast Path — Use When Problem Is Clear
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, endFRD / 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.
Deep Path — For Ambiguous or Failed First Attempts
Use this structured investigation when the fast path fails (fit < 70%), the problem is ambiguous, or the user asks for deeper analysis.
Problem Characterization
Determine: SISO/MIMO, time/frequency domain, intended use (simulation/prediction/control), known constraints. See references/data-preparation.md for preprocessing details.
Nonlinearity Check (STOP/GO Gate)
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 |
Decision
If nonlinearity is mild (gain varies <20%), proceed with linear ID but note limitations
If strong nonlinearity detected, recommend: Hammerstein-Wiener, NLARX, or neural state-space
If non-stationary (time-varying), consider segmented estimation or recursive methods
Data Preparation
See references/data-preparation.md for the full preprocessing workflow including:
Loading and inspection (
advice,plot)Preprocessing checklist (offsets, missing data, outliers, non-uniform sampling)
Prefiltering (band-pass, frequency weighting)
InterSample behavior for CT models
Train/validation split
Frequency-domain conversion
Data quality red flags
Probability of output feedback in the data (
checkFeedback)Persistence of excitation check (
pexcit)
Model Type Selection
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-spaceSee references/model-structures.md for detailed guidance on process models, polynomial models, and when to use each.
Order Determination
See references/order-determination.md for methods:
Delay estimation (
delayest, impulse response)ARX structure search (
arxstruc,selstruc)Subspace order selection (
n4sidwith order range)Iterative complexity (transfer function ladder)
Process model ladder
Frequency-domain path (AAA initialization)
Rules of thumb:
Max useful order:
n_max ~ min(N/20, 30)MIMO state-space: start with
n = max(ny, nu) * 2up to5If ARX(10) and ssest(4) give similar fits, prefer ssest(4)
Stop increasing order when improvement < 2% per additional parameter
Estimation
See references/estimation.md for the full estimation workflow including:
Critical options (Focus, InitialState, Stability, Regularization, OutputWeight, WeightingFilter, ErrorThreshold)
Initialization methods for ssest and tfest
Why ssest outperforms n4sid
Frequency-domain estimation path (AAA, lsrf)
Estimation commands for all model types
Regularized ARX to state-space (ssregest)
Structured estimation with parameter bounds
Multi-model strategy
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'));Validation (MANDATORY)
Compare on Validation Data (MANDATORY)
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 liftingResidual Analysis
% 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 Interpretation
| 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.
Results and Artifacts
Generate Outputs
Model summary — type, order, fit percentage, key parameters
Comparison plot — measured vs. simulated on validation data
Bode plot — frequency response with confidence bounds
Residual plot — autocorrelation and cross-correlation
% 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);Results Summary
================================================================
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>
================================================================Save Artifacts
MATLAB script —
identify_model.mcontaining the full reproducible workflowModel MAT file —
identified_model.matwith the final model objectFigures — PNG files for comparison, Bode, residuals
Results summary — printed to console
Special Topics
For advanced scenarios, see:
references/special-topics.md — closed-loop ID, control-oriented ID, MIMO, continuous-time, physics-informed constraints, frequency-domain limitations, recursive estimation, tfest vs. tfestimate
Common Gotchas — Quick Reference
| # | 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.
Files
- ATTRIBUTION.mdAdded by AutoGineer1.0 KiB
- LICENSELicence file added by AutoGineer1.4 KiB
- SKILL.md20.4 KiB
- manifest.yaml570 B
- references/data-preparation.md12.2 KiB
- references/estimation.md8.3 KiB
- references/model-structures.md4.5 KiB
- references/order-determination.md3.3 KiB
- references/special-topics.md12.4 KiB
- references/validation-and-diagnostics.md7.1 KiB
references/special-topics.md
Special Topics
9.1 Closed-Loop Identification
When data is collected under feedback control, special methods are required.
When is Closed-Loop ID Necessary?
Unstable plants — cannot operate without feedback
Safety/economic constraints — cannot open loop
Systems already in operation — only routine data available
Methods
| Method | Requirements | Consistent? | When to use |
|---|---|---|---|
| Direct | Known controller, high SNR | Only with correct noise model | Simple cases, high SNR |
| Indirect (two-stage) | Known controller, reference signal | Yes | Industry default — most robust |
| IV methods | Instruments uncorrelated with noise | Yes | Colored noise in closed loop |
| Coprime factorization | Known controller | Yes | Unstable plants |
Do's and Don'ts
DO:
Ensure reference/setpoint has sufficient excitation
Use indirect method as default — robust, no noise model needed
If using direct method, specify a noise model (ARMAX, BJ) — ARX is biased in closed loop
Use
iv4when ARX/OE give biased resultsValidate by simulation (prediction fit is misleadingly good in closed loop)
DON'T:
Don't use
arxdirectly on closed-loop data expecting unbiased plant estimatesDon't ignore the controller
Don't open the loop on an unstable plant
Practical Workflow
% Indirect method: identify closed-loop TF from r->y, then back-calculate G
T_ry = tfest(iddata(y, r, Ts), np_cl, nz_cl);
% Direct method with noise model (biased without it!)
model = bj(iddata(y, u, Ts), [nb nc nd nf nk]);
% IV method (consistent in closed loop without noise model)
model = iv4(iddata(y, u, Ts), [na nb nk]);
% For unstable plants
opt = ssestOptions('Focus', 'simulation', 'EnforceStability', false, ...
InteractiveOrderSelection=false);
model = ssest(iddata(y, u, Ts), n, opt);Bias Reference
| Estimator | Open loop | Closed loop (no noise model) | Closed loop (correct noise model) |
|---|---|---|---|
| ARX/LS | Consistent | BIASED | N/A (fixed noise model) |
| IV4 | Consistent | Consistent | Consistent |
| OE | Consistent | BIASED | N/A (no noise model) |
| ARMAX | Consistent | Consistent (if C correct) | Consistent |
| BJ | Consistent | Consistent (if C,D correct) | Consistent |
| ssest/n4sid | Consistent | Depends on noise handling | Consistent |
9.2 Control-Oriented Identification
When the model will be used for controller design, identification choices must be guided by the control objective.
Core Philosophy
| Concern | Simulation-oriented ID | Control-oriented ID |
|---|---|---|
| Frequency focus | Full bandwidth | Crossover region (+/-1 decade around wc) |
| Error metric | Time-domain NRMSE | Frequency-weighted H-inf or nu-gap |
| Bias tolerance | Low everywhere | Low near crossover; bias OK at extremes |
| Noise model | Important for prediction | Only matters for observer design |
| Uncertainty | Nice-to-have | Essential for robust design |
| Model order | Minimize for parsimony | Must be enough for gain/phase near wc |
Frequency Weighting for Control
% Emphasize crossover region (e.g., wc = 10 rad/s)
wc = 10;
[bw, aw] = butter(2, [wc/10, wc*5]/(pi/Ts), 'bandpass');
opt = ssestOptions('Focus', 'simulation', 'WeightingFilter', {bw, aw}, ...
InteractiveOrderSelection=false);
model = ssest(data, n, opt);Uncertainty for Robust Control
% Extract pointwise uncertainty
w = logspace(-2, 3, 200);
[mag, phase, wout, sdmag, sdphase] = bode(model, w);
% Convert to multiplicative uncertainty
Delta_mult = squeeze(sdmag) ./ squeeze(mag);
% Fit bounding filter (Robust Control Toolbox)
W_I = fitmagfrd(frd(Delta_mult, w, model.Ts), 2);nu-gap Metric
[~, nugap_val] = gapmetric(model_new, model_old);
[~, gam] = ncfmargin(model_old, controller);
% SAFE if nugap_val < gam (controller remains stabilizing)9.3 MIMO Identification
Practical Tips
For large number of outputs, set
OutputWeighttoeye(ny)Consider separating outputs into sub-groups, identifying separately, then vertically concatenating models
If input is periodic, use
etfe/spafor empirical FRD, thenssestwithInitializeMethod='AAA'
% Check input directionality
G_frd = spa(data);
for k = 1:numel(G_frd.Frequency)
cond_G(k) = cond(squeeze(G_frd.ResponseData(:,:,k)));
end
% High condition number -> some input directions poorly identified
% Ensure inputs excite ALL directions (uncorrelated PRBS)
u1 = idinput(N, 'prbs', [0 0.5], [-1 1]);
u2 = idinput(N, 'prbs', [0 0.3], [-1 1]); % different clock rate
data_mimo = iddata(y, [u1 u2], Ts);
% OutputWeight for balanced MIMO fitting with statistically optimal weighting
opt = ssestOptions('OutputWeight', 'noise', InteractiveOrderSelection=false);
model = ssest(data_mimo, n, opt);MIMO Validation
Check singular value plots:
sigma(model)Validate each I/O channel separately AND together
For control: check sensitivity and complementary sensitivity, not just open-loop fit
9.4 Continuous-Time Identification from Sampled Data
Direct CT estimation avoids discretization artifacts:
% Direct CT state-space
model_ct = ssest(data, n, 'Ts', 0, ssestOptions('Focus', 'simulation', ...
InteractiveOrderSelection=false));
% Direct CT transfer function (default is CT when data.Ts > 0)
model_ct = tfest(data, np, nz);
% Critical: set InterSample correctly
data.InterSample = 'foh'; % for smooth analog inputsWhen to prefer CT identification:
Physical parameters needed (mass, damping, stiffness)
Multiple datasets with different sampling rates
Very fast sampling (DT poles cluster near z=1, ill-conditioned)
Need to combine with physics (ODE models)
9.5 Physics-Informed Constraints
| Prior knowledge | How to encode | MATLAB implementation |
|---|---|---|
| Stability | Poles inside stable region | opt.EnforceStability = true |
| Stability margin | Poles inside radius rho < 1 | opt.Advanced.StabilityThreshold.z = 0.98 |
| DC gain known | G(0) or G(1) fixed | Structure constraints on num/den |
| Positive gain | K > 0 | Structure.Numerator.Minimum |
| Zero coupling | Off-diagonal = 0 | Structure.D.Free(i,j) = false |
| Known integrator | Pole at s=0 / z=1 | Use process model with 'I' |
% Grey-box regularization (stay close to physics-based prior)
opt = ssestOptions(InteractiveOrderSelection=false);
opt.Regularization.Lambda = 100;
opt.Regularization.Nominal = 'model';
sys0 = idss(A_prior, B_prior, C_prior, D_prior);
model = ssest(data, sys0, opt);9.6 Frequency-Domain Data: Limitations
| Can estimate | Cannot estimate |
|---|---|
| Transfer function (tfest) | Noise model K |
| State-space (ssest with AAA/lsrf) | BJ/ARMAX (require noise model) |
| Output-Error (oe) | DT models from CT frequency data (Ts=0) |
| ARX (arx) | Meaningful residual analysis |
| Process models (procest) |
9.7 Troubleshooting Frequency-Domain Fits
When fitting a transfer function to FRD data (idfrd), the cost function is:
J = Σ |W(ωk) · (G(ωk) - f(ωk))|²where W is the weighting, G is the model, and f is the measured data. Uneven SNR across frequency causes the optimizer to waste parameters on noisy regions while ignoring dynamics of interest.
Diagnostic: Identify Where the Fit is Poor
Plot the squared error magnitude vs. frequency to see which data points dominate the loss:
[resp_model] = squeeze(freqresp(model, Gfrd.Frequency));
resp_data = squeeze(Gfrd.ResponseData);
err = abs(resp_model - resp_data).^2;
semilogx(Gfrd.Frequency, err);
xlabel('Frequency (rad/s)'); ylabel('|G(w) - f(w)|^2');If errors concentrate in a frequency band with high magnitude but poor SNR, the optimizer is chasing noise there and ignoring low-magnitude dynamics elsewhere.
Preprocessing FRD Data
| Technique | Purpose | Command |
|---|---|---|
| Truncate frequency range | Remove out-of-band noise | Gfrd = fselect(Gfrd, wlow, whigh) |
| Smooth noisy regions | Reduce variance while preserving shape | movmean(resp(idx), 3) on low-SNR band |
| Preserve resonant peaks | Don't smooth dynamics of interest | Apply smoothing selectively by frequency band |
% Truncate to frequency range of interest
Gfrd = fselect(Gfrd, 1, 2e4);
% Selective smoothing: only below 40 rad/s where SNR is poor
f = squeeze(Gfrd.ResponseData);
w = Gfrd.Frequency;
idx_low = w < 40;
f(idx_low) = movmean(f(idx_low), 3);
Gfrd.ResponseData = reshape(f, [1 1 numel(f)]);Frequency Weighting for tfest
tfestOptions('WeightingFilter', W) accepts three forms:
| Form | Syntax | Effect |
|---|---|---|
| String shortcut | 'inv' | Weight = `1/ |
| String shortcut | 'invsqrt' | Weight = `1/√ |
| Custom vector | Weight (Nf×1) | Full per-frequency control |
% Start with 'invsqrt' — typically a good initial choice
opt = tfestOptions('WeightingFilter', 'invsqrt');
model = tfest(Gfrd, np, nz, opt);
% If that's insufficient, build a custom weight vector
w = Gfrd.Frequency;
Weight = ones(size(w));
Weight(w < 10) = Weight(w < 10) / 10; % down-weight noisy low-freq
Weight(w > 40 & w < 6000) = Weight(w > 40 & w < 6000) * 30; % up-weight dynamics band
opt = tfestOptions('WeightingFilter', Weight);
model = tfest(Gfrd, np, nz, opt);Iterative Troubleshooting Loop
Estimate with default weighting
Plot
|W(w)·(G(w)-f(w))|²— identify which frequencies dominate the errorAdjust: preprocess data (truncate/smooth) OR adjust weights (reduce noisy bands, boost dynamics bands)
Re-estimate and compare
Repeat until the model captures all dynamics of interest
Common Pitfalls
| Symptom | Cause | Fix |
|---|---|---|
| Model fits high-magnitude noise, misses resonances | Unweighted cost dominated by high- | f |
| Spurious poles/zeros at low frequency | Optimizer chasing noisy low-freq data | Truncate via fselect or down-weight below noise floor |
| Model misses valley/antiresonance | Low magnitude = low contribution to J | Up-weight that frequency band |
| Good fit at most frequencies but wrong at extremes | Insufficient data or extrapolation | Truncate to reliable range before fitting |
9.8 tfest vs. tfestimate — Clarification
tfest (System Identification Toolbox) | tfestimate (Signal Processing Toolbox) | |
|---|---|---|
| What it does | Parametric: fits a transfer function model (idtf) | Non-parametric: computes empirical frequency response |
| Output | idtf object | Complex frequency response vector |
| Model? | Yes — simulate, convert, design controllers | No — frequency response plot only |
| Extract coefficients | [num, den] = tfdata(model, 'v') | N/A |
| Use when | You need a parametric model | Quick look at frequency response |
Workflow combining both: Use tfestimate (or spa/etfe) first to see the empirical frequency response, then use tfest to fit a parametric model to that shape.
9.9 Model Conversion and Reduction
d2c, c2d, d2d
model_ct = d2c(model_dt); % default ZOH
model_ct = d2c(model_dt, 'tustin'); % Tustin (bilinear)
model_dt = c2d(model_ct, Ts_new); % default ZOH
model_new = d2d(model_dt, Ts_new); % change sample timePitfalls:
ZOH discretization of systems with relative degree >= 2 introduces unstable sampling zeros
Tustin avoids sampling zeros but distorts near Nyquist
Very fast sampling causes ill-conditioning in d2c
Model Reduction
% Balanced reduction
R = reducespec(model, "balanced");
view(R)
R.Order = n_reduced;
model_red = getrom(R);
% Modal reduction (preserves dominant modes)
R = reducespec(model, "modal");
view(R)
R.Order = n_reduced;
model_red = getrom(R);Model Export
% Extract numerator/denominator (transfer function)
[num, den] = tfdata(model, 'v');
% Extract state-space matrices
[A, B, C, D] = ssdata(model);
% To Control System Toolbox format (strip identification metadata)
sys_css = ss(model); % or tf(model), zpk(model)Copyright 2026 The MathWorks, Inc.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Created by
- MathWorks
- Publisher
- MathWorks (Official)
- Source
- matlab/matlab-agentic-toolkit · skills-catalog/control-systems/matlab-identify-linear-system · commit 2a7f215
- Licence
- BSD 2-Clause License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE.md)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by MathWorks or MathWorks.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download matlab-identify-linear-system.zip