Generated banner for Matlab Identify Linear System
Generated logo for Matlab Identify Linear System

Matlab Identify Linear System

MathWorksOfficialCreated by MathWorks

  • Skill
  • Tool integration
  • Science and research
  • BSD-2-Clause
  • No issues found

From the author

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.
— the description by MathWorks

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

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/matlab/matlab-identify-linear-system/download
  2. Unzip 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:

  1. Create/load data + split into estimation/validation

  2. Estimate delay, select structure, estimate model(s)

  3. Validate on held-out data by simulation

  4. 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=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 execution

  • EstimateCovariance=false during ANY search loop (order scan, delay scan) — covariance for discarded models wastes time

  • Focus='simulation' for simulation/control use on ssestOptions, n4sidOptions, procestOptions (NOT available on tfestOptions — 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 model

  • data.InterSample = 'foh' BEFORE CT estimation if input is smooth analog

  • For multi-input InterSample: use column cell {'zoh'; 'foh'} (NOT row cell)

  • Delay-first: ALWAYS call delayest or 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) not ssest(ze, 4, opt)

Arguments

The user provides: $ARGUMENTS

Parse:

  • project_name (optional): name of a project under projects/ that has a SPEC.md

  • data_source (optional): path to a .mat file, 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

  1. Start simple, add complexity only when data justifies it. Try order 2-4 before 10-15.

  2. Delay first. A wrong delay cannot be fixed by higher order — it's catastrophic.

  3. Set Focus correctly. The #1 missed option. Default 'prediction' is sometimes wrong for simulation use.

  4. Always hold out validation data. Never report training fit as performance.

  5. Regularization > high order. A regularized ARX(30) often outperforms unregularized ARX(5). Use arxRegul, or ssregest.

  6. State-space is the default. When unsure, ssest handles MIMO, CT/DT, needs only order n.

  7. Compare 2-3 structures. The first model is rarely the best.

  8. Check residuals. A high fit with correlated residuals means the model is missing dynamics.

  9. 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, 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.


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.

MethodHowInterpretation
Amplitude dependenceEstimate models from datasets at different input amplitudesIf gain/dynamics change with amplitude -> nonlinear
Harmonic analysisApply periodic input, check for even harmonics in output spectrumEven harmonics indicate nonlinearity
Model order escalationFit orders 2, 4, 8, 12, 16 — plot fit vs. orderPlateauing at low fit despite high order -> nonlinearity
Residual structureInspect residuals vs. amplitude of u or ySystematic patterns -> nonlinear
Split-data testEstimate 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
ISNLARXUse 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-space

See 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 (n4sid with 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) * 2 up to 5

  • If 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 lifting
Residual 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)DiagnosisAction
WhiteUncorrelatedModel is adequateDone
Significant at lagsUncorrelatedNoise model insufficient, but plant model may be OK for simulationIncrease noise model order (C/D in ARMAX/BJ); plant G is still usable
WhiteSignificant at lag kMissing input dynamics at lag kAdd regressor u(t-k): increase nb or adjust nk in ARX/ARMAX/BJ
Significant at lagsSignificant at lag kBoth plant and noise model inadequateIncrease both model order and noise order; check delay
Fit Interpretation
Fit %VerdictNext Action
> 95%ExcellentDone — report results
85-95%GoodAcceptable; try one alternative to confirm
70-85%ModerateIncrease order, try different structure, check data
50-70%PoorWrong structure, missing nonlinearity, or bad data
< 50%FailedReassess 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
  1. Model summary — type, order, fit percentage, key parameters

  2. Comparison plot — measured vs. simulated on validation data

  3. Bode plot — frequency response with confidence bounds

  4. 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
  1. MATLAB script — identify_model.m containing the full reproducible workflow

  2. Model MAT file — identified_model.mat with the final model object

  3. Figures — PNG files for comparison, Bode, residuals

  4. 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

#MistakeConsequenceFix
1Validating on training dataOverfitting undetectedAlways hold out validation set
2Using prediction fit to judge simulation qualityFalse confidence in dynamicsValidate by simulation (horizon=Inf)
3Wrong InterSample settingSystematic bias at high freq in CT modelsSet 'foh' for smooth analog inputs
4Ignoring Focus optionModel optimizes wrong criterionSet Focus='simulation' for sim/control use
5Not detrending data with offsetsDC gain wrong, poor overall fitdetrend(data) or use offsets
6Using ARX in closed loopBiased plant estimateUse iv4, BJ, or indirect method
7Wrong delay assumedCatastrophic — no amount of order helpsEstimate delay first, try +/-1
8Over-parameterizingGreat training fit, poor generalizationRegularize or reduce order
9Ignoring uncertaintyFalse precision in modelAlways check showConfidence on Bode
10Assuming ICs from training apply to new dataPoor validation fitRe-estimate ICs for each new dataset
11Not passing 'Ts',Value as name-value pair (Value>0) to ssest, tfest for DT modelGets CT model insteadssest(data, n, 'Ts', data.Ts, opt)
12Using wrong initial conditions for simulationBad fit to validation datause findstates' or data2state' to determine the initial conditions that maximize the fit to the validation data
13Forget to scale dataIll-conditioned identification problem leading to bad resultsEnsure your inputs and outputs, and time units are scaled appropriately, especially when using numerical optimization algorithms.

Copyright 2026 The MathWorks, Inc.


Files

references/data-preparation.md

Data Preparation

3.1 Load and Inspect

advice(data);   % automated data quality assessment
plot(data);     % visual inspection

3.2 Preprocessing Checklist

IssueDetectionFix
Offset/trendmean(data.y) far from zerodata = detrend(data) or set InputOffset/OutputOffset
Operating point offsetsI/O signals not centered at zero (system operates around non-zero equilibrium)Remove operating point: data = detrend(data, 0) or specify InputOffset/OutputOffset in estimation options
Missing samplesNaN in signalsdata = misdata(data)
OutliersVisual or isoutlierRemove, interpolate, or use ErrorThreshold (robust estimation)
Non-uniform samplingdiff(data.SamplingInstants) variesResample or use CT estimation
Multi-experimentMultiple runs availabledata = merge(d1, d2, d3)
InterSample behavior unknownEstimating CT model from DT dataSet data.InterSample = 'zoh' or 'foh' or 'bl'

Operating point rule: Linear identification assumes signals represent deviations from equilibrium. If the system operates around a non-zero steady state (e.g., temperature at 350K, valve at 40% open), you MUST subtract the operating point before identification. This is especially critical for MIMO systems where each channel may have a different offset. Failure to remove offsets corrupts DC gain estimates and biases the entire model.

% Per-channel operating point removal (MIMO)
data = detrend(data, 0);  % removes mean from each I/O channel

% Or specify offsets explicitly (preserves physical meaning)
opt = ssestOptions('Focus', 'simulation', InteractiveOrderSelection=false);
opt.InputOffset = [u1_op; u2_op];    % operating point for each input
opt.OutputOffset = [y1_op; y2_op];   % operating point for each output
model = ssest(data, n, opt);

Outlier rule: If outliers are > 5% of data, use ErrorThreshold (robust estimation) instead of removing them — the data may be telling you the system is nonlinear.

3.3 Prefiltering

Prefiltering both input and output BEFORE identification changes the frequency weighting of the estimation.

% Band-pass to focus on dynamics of interest (e.g., 1-100 rad/s)
[b,a] = butter(4, [1 100]/(pi/Ts), 'bandpass');
ze_filt = idfilt(ze, {b,a});  % filters BOTH u and y with same filter

% High-pass to remove drift (alternative to detrend)
[b,a] = butter(2, 0.5/(pi/Ts), 'high');
ze_filt = idfilt(ze, {b,a});

Critical rule: Always filter BOTH input and output with the SAME filter. Filtering only one signal falsifies the transfer function estimate.

When to prefilter:

  • Remove frequencies outside the range of interest (reduces bias from unmodeled dynamics)

  • Suppress frequency bands with poor SNR

  • Focus estimation on the control-relevant frequency range

When NOT to prefilter:

  • If you want the noise model to be meaningful (prefiltering changes the noise model interpretation)

  • If data is already clean and you're using Focus option instead (Focus achieves similar effect internally)

3.4 InterSample Behavior (Critical for Continuous-Time Models)

When estimating CT models from DT data, you MUST specify how the input behaves between samples:

SettingMeaningWhen to use
'zoh'Zero-order hold (piecewise-constant)DAC output, relay, digital control signals
'foh'First-order hold (piecewise-linear)Smoothly varying analog inputs
'bl'Band-limited (no power above Nyquist)Filtered/anti-aliased analog signals
data.InterSample = 'foh';  % Set before CT estimation
% Access: for SISO single-experiment, InterSample is a char — use char comparison:
%   strcmp(data.InterSample, 'foh')
% For MIMO or multi-experiment, it is a cell array.
% Or per-channel for MIMO:
data.InterSample = {'zoh'; 'foh'};  % COLUMN cell (Nu×1): input 1 = ZOH, input 2 = FOH
% WARNING: row cell {'zoh', 'foh'} FAILS — must be column cell for multi-input data

Rule: Default is 'zoh'. If your physical input is analog and smoothly varying, 'foh' gives more accurate CT models. Wrong InterSample setting causes systematic bias at high frequencies.

3.5 Train/Validation Split

ze = data(1:floor(end*0.7));    % 70% estimation
zv = data(floor(end*0.7)+1:end); % 30% validation

Rule: Never validate on training data. If data is short (N < 200), use k-fold cross-validation instead of a holdout split.

3.6 Frequency-Domain Conversion

FFT of time-domain data is lossless — it opens the door to fast, non-iterative frequency-domain identification. Consider converting when:

  • Data is very large (N > 50000): rational fitting is much faster than iterative PEM on raw time data

  • Input is periodic (multisine, swept sine, repeated PRBS): FFT gives exact frequency response at excited frequencies with no leakage

  • You want fast initialization before iterative time-domain refinement

% Convert time-domain to frequency-domain (lossless via FFT)
ze_frd = fft(ze);        % iddata in frequency domain

% Empirical frequency response estimates:
G_etfe = etfe(ze);       % raw (noisy but unbiased)
G_spa = spa(ze);         % smoothed (biased but low variance)

% For periodic data: use exactly one period (or integer multiple)
ze_periodic = ze(1:period_samples);
G_periodic = etfe(ze_periodic);  % exact at excited frequencies

3.7 Data Quality Red Flags (STOP — do not proceed)

Before starting identification, check for these conditions that will produce misleading results:

Red FlagSymptomAction
Feedback presentu correlates with past yUse closed-loop methods (see Special Topics)
Actuator saturationu clips at fixed valuesTrim saturated segments or use nonlinear ID
Output quantizationy has staircase appearanceIncrease measurement resolution or use longer data
Data too shortN < 3 * settling time / TsCollect more data
No excitationInput is constant or nearly soCannot identify — need active excitation
Poor excitationOnly step/impulse (1–2 levels)Insufficient for parametric ID — use PRBS, multisine, or at least 3–4 distinct input levels
Multi-rate I/OInputs and outputs at different ratesResample to common rate or use CT estimation

Excitation rule: Simple transient experiments (single step, impulse) are rarely informative enough for proper identification. A step response constrains only the DC gain and dominant time constant — it cannot reliably separate poles, zeros, or delay from dynamics. If experiment design is under your control, collect data where the input changes at least 3–4 distinct levels (amplitude and direction). Preferred excitation signals:

SignalStrengthsWhen to use
PRBSBroadband, easy to generate, binaryGeneral purpose, robust
MultisineExact frequency content, periodic, low crest factorWhen frequency range is known
Swept sine (chirp)Continuous frequency coverageSingle-channel frequency sweep
Random steps (multilevel)Practical for manual operationWhen automated excitation is unavailable
% PRBS: broadband, N samples, band [0 0.8]*Nyquist
u = idinput(N, 'prbs', [0 0.8], [-1 1]);

% Multisine: controlled frequency content, N samples
u = idinput(N, 'sine', [0.01 0.9], [], struct('NumSinusoids', 20));

% Random multilevel steps (at least 4 levels)
levels = [-1 -0.5 0.5 1];
step_len = round(N / 20);  % ~20 transitions
u = repelem(levels(randi(4, 20, 1)), step_len);
u = u(1:N)';

3.8 Sampling Rate Assessment (Over/Undersampled Data)

Non-optimal sampling rate is one of the most common reasons for poor identification results. Diagnose it early — before wasting effort on model structure selection.

Diagnostic: Is My Data Well-Sampled?
% Quick sampling assessment
Ts = data.Ts;
N = size(data.y, 1);

% Estimate dominant time constant from impulse response
h = impulseest(data, [], impulseestOptions(RegularizationKernel="TC"));
[y_imp, t_imp] = impulse(h);
y_abs = abs(y_imp(:));
idx_settled = find(y_abs < 0.05 * max(y_abs), 1, 'first');
tau_est = t_imp(idx_settled) / 3;  % rough dominant time constant

% Compute sampling ratio
ratio = tau_est / Ts;
fprintf('Estimated tau: %.2f s, Ts: %.4f s, ratio tau/Ts: %.1f\n', tau_est, Ts, ratio);

if ratio > 100
    fprintf('WARNING: OVERSAMPLED (tau/Ts = %.0f >> 15)\n', ratio);
elseif ratio < 3
    fprintf('WARNING: UNDERSAMPLED (tau/Ts = %.1f < 3)\n', ratio);
else
    fprintf('Sampling rate OK (tau/Ts in 3-100 range)\n');
end
Oversampling (Ts << tau, ratio > 50–100)

Symptoms:

  • DT poles cluster very close to z=1 (ill-conditioned numerics)

  • ARX/ARMAX models need very high orders to capture slow dynamics

  • Noise dominates the useful signal bandwidth

  • d2c conversion becomes numerically unstable

  • High-order FIR impulse response needed (thousands of taps)

  • Poorly-damped modes are especially vulnerable: a lightly-damped pole at wn with fast sampling maps to z = exp((-zeta*wn ± j*wn)*Ts) — both magnitude and angle increments become tiny, making the pole nearly indistinguishable from the unit circle. Noise pushes the estimate outside |z|=1, producing a spuriously unstable model. This is the single most common cause of "my identified model is unstable even though the system is clearly stable."

Fixes (in order of preference):

% Option 1: Decimate with anti-alias filter (preferred for DT estimation)
target_ratio = 10;  % aim for ~10 samples per tau
dec_factor = max(1, round(ratio / target_ratio));
data_dec = idresamp(data, dec_factor, 1);  % decimate by dec_factor
fprintf('Decimated: Ts %.4f -> %.4f (factor %d)\n', Ts, data_dec.Ts, dec_factor);

% Option 2: Estimate in continuous time (bypass DT ill-conditioning entirely)
data.InterSample = 'foh';
opt = ssestOptions('Focus', 'simulation', InteractiveOrderSelection=false);
model_ct = ssest(data, n, 'Ts', 0, opt);

% Option 3: Use regularized ARX if DT model is required
% Regularization handles the ill-conditioning from near-unit-circle poles
[Lambda, R] = arxRegul(data, [na nb nk], arxRegulOptions('RegularizationKernel', 'TC'));
opt = arxOptions;
opt.Regularization.Lambda = Lambda;
opt.Regularization.R = R;
model = arx(data, [na nb nk], opt);

Decision rule: If tau/Ts > 50, either decimate or use CT estimation. Do not attempt unregularized DT polynomial models — they will be numerically ill-conditioned.

Undersampling (Ts >> tau, ratio < 3)

Symptoms:

  • Fast dynamics aliased — model cannot distinguish modes above Nyquist

  • Step response appears instantaneous (settling in 1-2 samples)

  • Identified bandwidth limited to π/Ts regardless of true system

  • delayest unreliable when true delay ≈ Ts or less

  • Bode plot meaningless above Nyquist — model extrapolates

Fixes:

% Option 1: Resample if raw (higher-rate) data is available
% (This requires going back to the data source)

% Option 2: Accept limited bandwidth — identify only what's resolvable
% Constrain model bandwidth to Nyquist
wN = pi / Ts;
fprintf('Nyquist freq: %.2f rad/s — cannot resolve dynamics above this\n', wN);

% Option 3: Use CT estimation with band-limited InterSample
data.InterSample = 'bl';  % signals are band-limited below Nyquist
opt = ssestOptions('Focus', 'simulation', InteractiveOrderSelection=false);
model = ssest(data, n, 'Ts', 0, opt);

% Option 4: For mildly undersampled (ratio 2-3), increase model order
% to approximate the fast modes that leak through aliasing

Decision rule: If tau/Ts < 3, the data cannot resolve the dominant dynamics. Either acquire faster-sampled data or accept that the identified model is a low-bandwidth approximation.

Summary Table
Conditiontau/Ts ratioPrimary symptomBest fix
Severely oversampled> 100Poles at z≈1, high-order FIRDecimate or CT estimation
Moderately oversampled50–100Slow convergence, noise-dominatedDecimate by 5-10x
Well-sampled5–50—No action needed
Marginally undersampled3–5Some fast modes near NyquistCT estimation with 'bl'
Severely undersampled< 3Aliasing, dynamics unresolvableRe-acquire data

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.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download matlab-identify-linear-system.zip