matlab-fit-simbiology-model

$npx mdskill add matlab/matlab-agentic-toolkit/matlab-fit-simbiology-model

Fit SimBiology model parameters to experimental data using various methods.

  • Estimates model parameters from observed data using fitproblem, NLME, or NCA.
  • Depends on SimBiology toolbox and fitproblem, sbiofit, sbiofitmixed functions.
  • Selects method based on task: fitproblem for single, NLME for population, NCA for PK metrics.
  • Returns fitted model, parameter estimates, confidence intervals, and goodness-of-fit statistics.

SKILL.md

.github/skills/matlab-fit-simbiology-modelView on GitHub ↗
---
name: matlab-fit-simbiology-model
description: "Fit SimBiology model parameters to data — fitproblem, population NLME, virtual patients, and NCA. Use when asked to fit, estimate, calibrate, or compute PK metrics."
license: MathWorks BSD-3-Clause
metadata:
  author: MathWorks
  version: "1.1"
---

# Fit SimBiology Models

Estimate parameters from data using `fitproblem`, fit population models
with NLME, generate virtual patients, and compute NCA metrics.

## When to Use

- "fit", "estimate", "calibrate" model parameters
- Parameter estimation from experimental/observed data
- Population PK/PD, NLME, mixed effects, inter-individual variability
- Virtual patients, virtual cohorts
- NCA, AUC, Cmax, Tmax, half-life, clearance
- Keywords: "fit", "estimate", "calibrate", "population", "NCA", "AUC"

## When NOT to Use

- Model construction or diagram (use `matlab-build-simbiology-model`)
- Simulation without fitting (use `matlab-simulate-simbiology-model`)
- Sensitivity analysis (use `matlab-simulate-simbiology-model`)

## Must-Follow Rules

### 1. Use `fitproblem` for parameter estimation

Always use `fitproblem` instead of calling `sbiofit` or `sbiofitmixed`
directly. `fitproblem` provides a unified, declarative interface:
```matlab
prob = fitproblem;
prob.Model = model;
prob.Data = data;
prob.ResponseMap = "Species = DataColumn";
prob.Estimated = estimatedInfo({'param'}, 'Bounds', [lo hi]);
results = fit(prob);
```
Do NOT call `sbiofit(model, data, ...)` or `sbiofitmixed(model, data, ...)`
directly — their positional argument signatures are error-prone.

### 2. Fitting requires `groupedData`, NOT a plain table

Always wrap data:
```matlab
data = groupedData(table(...));
data.Properties.IndependentVariableName = 'Time';
```

### 3. `ResponseMap` maps model outputs to data columns

Format is always `"ModelOutput = DataColumnName"`:

```matlab
% Single compartment — use species name on the left
prob.ResponseMap = "Drug = DrugConc";

% Multi-compartment — use qualified name to disambiguate
prob.ResponseMap = "Central.Drug = DrugConc";

% When species name matches data column name, still use the = format
prob.ResponseMap = "Drug = Drug";
```

Use the unqualified species name unless the same species name exists
in multiple compartments (then qualify with `Compartment.Species`).

### 4. Always set bounds

Prevent non-physical values (negative rates, etc.):
```matlab
estimParams = estimatedInfo({'ke','ka'}, ...
    'InitialValue', [0.2, 1.0], ...
    'Bounds', [0.01 1; 0.1 5]);
```

### 5. Use log transform for rate constants

Parameters spanning orders of magnitude (clearances, rate constants)
benefit from log-transform estimation. Set `.Transform` after creation:
```matlab
ei = estimatedInfo({'ke','ka'}, 'InitialValue', [0.1, 0.5], 'Bounds', [0.01 1; 0.1 5]);
ei(1).Transform = 'log';
ei(2).Transform = 'log';
```

Alternative: use `'log(param)'` name syntax (equivalent result):
```matlab
ei = estimatedInfo({'log(ke)','log(ka)'}, 'InitialValue', [0.1, 0.5], 'Bounds', [0.01 1; 0.1 5]);
```

**Important:** `InitialValue` and `Bounds` are always in the
**untransformed** (natural) domain. Do NOT pass `log(value)`.

Available transforms: `'log'`, `'logit'`, `'probit'`

Do NOT pass `'Transform'` as a name-value pair to the `estimatedInfo`
constructor — it errors. Always set the `.Transform` property after.

### 6. Error models for population fitting

Choose the error model that matches the noise structure:
- `'constant'` — absolute noise uniform
- `'proportional'` — noise scales with magnitude (most PK data)
- `'combined'` — both constant and proportional
- `'exponential'` — log-normal residual

### 7. NCA requires `sbioncaoptions` object

Do not use name-value pairs. Column names are camelCase.
EVDose column uses `NaN` for non-dose rows.

## Decision Table

| Scenario | Approach |
|----------|----------|
| Single subject or pooled fit | `fitproblem` with `FitFunction="sbiofit"` |
| Individual fits per subject | `fitproblem` with `Pooled=false` |
| Population NLME (IIV, random effects) | `fitproblem` with `FitFunction="sbiofitmixed"` |
| Model-independent PK metrics | `sbionca` |

## `fitproblem` Workflow (Preferred)

Use `fitproblem` for all parameter estimation. It provides a unified,
declarative interface that replaces direct calls to `sbiofit`/`sbiofitmixed`:

```matlab
% 1. Prepare data
data = groupedData(table(tSample, yData, 'VariableNames', {'Time','Drug'}));
data.Properties.IndependentVariableName = 'Time';

% 2. Define parameters with bounds
estimParams = estimatedInfo({'ke','ka'}, ...
    'InitialValue', [0.2, 1.0], ...
    'Bounds', [0.01 1; 0.1 5]);

% 3. Build the fit problem
prob = fitproblem;
prob.Model = model;
prob.Data = data;
prob.ResponseMap = "Drug = Drug";
prob.Estimated = estimParams;
prob.Doses = dose;                  % optional
prob.FunctionName = 'scattersearch';
prob.ProgressPlot = true;           % show live progress

% 4. Fit
results = fit(prob);

% 5. Inspect
disp(results.ParameterEstimates);
plot(results);
```

### Key `fitproblem` properties

| Property | Purpose |
|----------|---------|
| `Model` | The SimBiology model object |
| `Data` | `groupedData` table |
| `Estimated` | `estimatedInfo` object (**not** `EstimatedParameters`) |
| `ResponseMap` | Maps model species to data columns |
| `Doses` | Dose object(s) (**not** `Dose`) |
| `FitFunction` | `"sbiofit"` (default) or `"sbiofitmixed"` |
| `FunctionName` | Algorithm: `'scattersearch'`, `'nlinfit'`, `'fminsearch'`, `'lsqnonlin'`, `'particleswarm'` |
| `ProgressPlot` | `true` to show live fitting progress |
| `UseParallel` | `true` for parallel evaluation |
| `Pooled` | `true`/`false`/`"auto"` (sbiofit only) |
| `ErrorModel` | `"constant"`, `"proportional"`, `"combined"`, `"exponential"` |
| `Variants` | Variants to apply during fitting |

**Common property name mistakes:** `prob.Estimated` (not `EstimatedParameters`),
`prob.Doses` (not `Dose`), `prob.FunctionName` (not `Algorithm` or `Method`).

### Estimation algorithms

| Method | Use Case |
|--------|----------|
| `'scattersearch'` | Built-in global search, no extra toolbox — **start here** |
| `'nlinfit'` | Default local; smooth problems |
| `'lsqnonlin'` | Bounded least squares (Optimization Toolbox) |
| `'fminsearch'` | Derivative-free, simple problems |
| `'particleswarm'` | Global search (Global Optimization Toolbox) |

### Dosing from multi-subject data

When subjects receive different doses, use `createDoses` to extract
per-subject dose objects from the data. The dose column must have `NaN`
on non-dosing rows:

```matlab
% Data format: dose amount only at administration time, NaN elsewhere
%   ID  Time  Dose  DrugConc  Group
%   1   0     50    0         LowDose
%   1   1     NaN   2.05      LowDose
%   ...
%   3   0     200   0         HighDose

% Create template dose targeting the depot species
tempDose = sbiodose('StudyDose');
tempDose.TargetName = 'Depot.Drug';   % match your model's dose target

% Extract per-subject doses from groupedData
doseArray = createDoses(gData, 'Dose', '', tempDose);

% Pass to fitproblem
prob.Doses = doseArray;
```

**Critical:** If all rows have the dose value (not just dosing times),
`createDoses` will treat every row as a dose event. Use `NaN` on
non-dosing rows.

### Population fitting (pooled vs individual)

```matlab
data.Properties.GroupVariableName = 'SubjectID';

% Pooled — one parameter set for all
prob.Pooled = true;

% Individual — separate per subject
prob.Pooled = false;
```

### Category-based pooling (per-group estimates)

To estimate parameters separately per category (e.g., dose group), use
`CategoryVariableName` on the `estimatedInfo` object — **not** on
`fitproblem` or `sbiofit`:

```matlab
estimParams = estimatedInfo({'ke'}, 'InitialValue', 0.1, 'Bounds', [0.01 1]);
estimParams.CategoryVariableName = 'DoseGroup';  % column in data table
% Do NOT set prob.Pooled — leave it at the default
```

**Warning:** Do NOT set `prob.Pooled` when using `CategoryVariableName`.
Setting `Pooled=false` triggers per-subject individual fitting that
**ignores** `CategoryVariableName` (MATLAB issues a warning). Leave
`Pooled` unset to let the category-based pooling work correctly.

## NLME Population Fitting

For inter-individual variability and random effects estimation,
set `FitFunction` to `"sbiofitmixed"`:

```matlab
% 1. Load & tag grouped data
data = groupedData(readtable('pop_pk_data.csv'));
data.Properties.IndependentVariableName = 'Time';
data.Properties.GroupVariableName = 'SubjectID';

% 2. Define parameters (Bounds ignored by sbiofitmixed — use InitialValue only)
estimParams = estimatedInfo({'CL','Vd','ka'}, ...
    'InitialValue', [5, 50, 1.2]);

% 3. Build the fit problem
prob = fitproblem;
prob.Model = model;
prob.Data = data;
prob.ResponseMap = "DrugConc = Concentration";
prob.Estimated = estimParams;
prob.FitFunction = "sbiofitmixed";
prob.ErrorModel = "proportional";
prob.ProgressPlot = true;

% 4. Fit
results = fit(prob);

% 5. Inspect
results.FixedEffects
results.RandomEffectCovarianceMatrix
results.IndividualParameterEstimates
```

### When to use NLME vs sbiofit

| Criterion | `FitFunction="sbiofit"` | `FitFunction="sbiofitmixed"` |
|-----------|-----------|----------------|
| Single subject | Yes | |
| Multiple subjects, no IIV | Yes (pooled) | |
| Inter-individual variability | | Yes |
| Random effects estimation | | Yes |
| Covariate modeling | | Yes |
| Small datasets (< 5 subjects) | Yes | May not converge |
| Bounds on parameters | Yes (enforced) | **Ignored** — use good InitialValue instead |

### NLME with covariates (CovariateModel)

When covariates (e.g., weight, age) influence parameters, use a
`CovariateModel` instead of `estimatedInfo`:

```matlab
covModel = CovariateModel;
covModel.Expression = {
    'CL = theta1 + theta2*WT + eta1'
    'Vd = theta3 + theta4*WT + eta2'
    'ka = theta5 + eta3'
};
initVals = covModel.constructDefaultFixedEffectValues;
initVals.theta1 = 5; initVals.theta2 = 0.1;
initVals.theta3 = 50; initVals.theta4 = 0.5;
initVals.theta5 = 1.2;
covModel.FixedEffectValues = initVals;

prob = fitproblem;
prob.Model = model;
prob.Data = data;  % groupedData with WT column
prob.ResponseMap = "DrugConc = Concentration";
prob.FitFunction = "sbiofitmixed";
prob.Estimated = covModel;
prob.ErrorModel = "proportional";
results = fit(prob);
```

**When to use which:**
- `estimatedInfo` — NLME without covariates (simpler, fewer parameters)
- `CovariateModel` — NLME with covariates (parameter-covariate relationships)

**Expression rules:** `theta` prefix for fixed effects, `eta` for random
effects. One random effect max per expression. Use `verify(covModel)` to
validate syntax before fitting.

## Virtual Patient Generation

### From assumed distributions (Scenarios)

Use `SimBiology.Scenarios` with `makedist` — avoids manual matrix construction:

```matlab
sc = SimBiology.Scenarios;
add(sc, 'elementwise', 'ke', makedist('Lognormal', 'mu', log(0.1), 'sigma', 0.3), 'Number', 100);
add(sc, 'elementwise', 'ka', makedist('Lognormal', 'mu', log(0.5), 'sigma', 0.25), 'Number', 100);

simfun = createSimFunction(model, sc, {'Drug'}, []);
results = simfun(sc, 24);
```

### From NLME results (sbiosampleparameters)

Use `sbiosampleparameters` to sample from fitted population parameters —
it respects the covariate model parameterization automatically:

```matlab
% Extract from NLME results
covModel = covariateModel(nlmeResults);
thetas = nlmeResults.FixedEffects;
omega = nlmeResults.RandomEffectCovarianceMatrix;

% Sample 200 virtual patients
nVP = 200;
vpParams = sbiosampleparameters(covModel.Expression, thetas, omega, nVP);

% Simulate
simfun = createSimFunction(model, {'CL','Vd','ka'}, {'Cp'}, []);
vpSim = simfun(vpParams, 48);
```

## Non-Compartmental Analysis (NCA)

### From simulation output

Use explicit `OutputTimes` to ensure sufficient time-resolution for NCA
(the default solver output may have too few points near Cmax):

```matlab
cs = getconfigset(m, 'active');
cs.SolverOptions.OutputTimes = linspace(0, 24, 200);

[t, x, names] = sbiosimulate(m);
drugIdx = find(strcmp(names, 'Drug'));
Vd = sbioselect(m, 'Type', 'parameter', 'Name', 'Vd');
conc = x(:, drugIdx) ./ Vd.Value;
evDose = NaN(size(t)); evDose(1) = 100;
data = table(t, conc, evDose, 'VariableNames', {'Time','Concentration','EVDose'});

opt = sbioncaoptions;
opt.concentrationColumnName = 'Concentration';
opt.timeColumnName = 'Time';
opt.EVDoseColumnName = 'EVDose';
opt.AdministrationRoute = 'ExtraVascular';
ncaResults = sbionca(data, opt);
```

### Administration routes

| Route | Dose column | Extra config |
|-------|-------------|--------------|
| `'ExtraVascular'` | `opt.EVDoseColumnName` | — |
| `'IVBolus'` | `opt.IVDoseColumnName` | — |
| `'IVInfusion'` | `opt.IVDoseColumnName` | `opt.infusionRateColumnName` |

### Key NCA metrics

**All metric names use underscores** (e.g., `C_max` not `Cmax`):

| Metric | Description |
|--------|-------------|
| `AUC_0_last` | Area under curve (0 to last time) |
| `AUC_infinity` | AUC extrapolated to infinity |
| `C_max` | Maximum observed concentration |
| `T_max` | Time of Cmax |
| `T_half` | Terminal elimination half-life |
| `CL` | Clearance (dose / AUC) |
| `V_z` | Volume of distribution (terminal) |
| `MRT` | Mean residence time |

### Multi-subject NCA

```matlab
data.Properties.GroupVariableName = 'SubjectID';
opt.groupColumnName = 'SubjectID';
ncaResults = sbionca(data, opt);
```

## Confidence Intervals and Profile Likelihood

**Restriction:** `sbioparameterci` only works with results from nonlinear
regression (`sbiofit`). It does NOT support NLME results (`sbiofitmixed`).

After fitting with `sbiofit`, compute confidence intervals:

### Gaussian (asymptotic) CI — fast, default

```matlab
ciResults = sbioparameterci(fitResults);
disp(ciResults.Results);  % table: Name (cell), Estimate, Bounds, ConfidenceInterval (Nx2 double), Status (categorical)
plot(ciResults);
```

**Column types in `.Results` table:**
- `Name` — cell array of char (`Results.Name{i}`)
- `ConfidenceInterval` — Nx2 double matrix (`Results.ConfidenceInterval(i,:)`)
- `Status` — **categorical** (`Results.Status(i)`, NOT `{i}`)

### Profile likelihood CI — more accurate for nonlinear models

```matlab
ciPL = sbioparameterci(fitResults, 'Type', 'ProfileLikelihood');
plot(ciPL);  % shows profile likelihood curves with CI bounds
% Custom confidence level: 'Alpha', 0.10 for 90% CI
```

| Type | Speed | Use when |
|------|-------|----------|
| `'Gaussian'` (default) | Fast | Quick check, well-behaved problems |
| `'ProfileLikelihood'` | Slower | Final results, parameter identifiability |

## Conventions

- **Species names must differ from compartment names.** Use distinct names: compartment `Depot` with species `DrugDepot` (not species `Depot` inside compartment `Depot`).
- **Units on compartment volumes:** Always specify units on compartment volumes (e.g., `'liter'`). Set `DimensionalAnalysis = true` and `VariableUnits` on `groupedData`. For pure amount-based models, set `Value = 1` and omit units.
- **Loading `.sbproj`:** `proj = sbioloadproject('f.sbproj'); model = proj.(fieldnames(proj){1});`
- **Extracting data:** `selectbyname(sbiosimulate(m), 'Drug')` for specific variables; `resample(sd, tSample, 'linear')` for specific times
- Start with `'scattersearch'` if unsure about parameter landscape
- Use `'log'` transform for parameters spanning orders of magnitude (see Rule 5)
- Do NOT call `sbioaccelerate(model)` before fitting — no effect, wastes time
- Set `cs.MaximumWallClock = 60` — stops hung simulations from bad guesses
- Set `prob.ProgressPlot = true` for long-running fits

## Evaluating Fit Quality

```matlab
plot(results);                      % observed vs predicted overlay
plotResiduals(results);             % residuals vs time
plotResidualDistribution(results);  % histogram — should be ~normal
plotActualVersusPredicted(results); % identity line check

results.LogLikelihood  % higher = better
results.AIC            % lower = better (penalizes complexity)
results.BIC            % lower = better (stronger penalty)
results.MSE            % mean squared error
```

**Model comparison:** Compare by BIC — `deltaBIC < -10` = strong evidence
for complex model; `deltaBIC > 0` = simpler model preferred.

**When to escalate to NLME:** Multiple subjects with different parameter
values, systematic subject-specific residual patterns, need to quantify
inter-individual variability, or covariates may explain differences.
- Pass **model objects** (not UUID strings) to fitting functions
- Do NOT call `sbiofit`/`sbiofitmixed`/`sbionlmefit` directly — use `fitproblem`

## References
Load on demand for detailed guidance:
- `references/nca-analysis-guidance.md` — full NCA patterns, IV infusion, metrics interpretation

----

Copyright 2026 The MathWorks, Inc.

----

More from matlab/matlab-agentic-toolkit

SkillDescription
matlab-access-datafeed>
matlab-add-awgnRead BEFORE writing any code that adds Additive White Gaussian Noise (AWGN) to signals and converts between SNR, Eb/No, Es/No, and per-subcarrier SNR for communications simulations, using awgn(), convertSNR(), berawgn(). The default MATLAB patterns for AWGN (e.g., 'measured' option, manual SNR formulas) produce subtly incorrect results. This skill specifies the correct calling conventions, required function usage, and critical anti-patterns that must be avoided.
matlab-analyze-ams-waveformAnalyze AMS waveform data using Mixed-Signal Blockset utilities: phase noise measurement, clock jitter, anti-aliased resampling, timing measurements, lock time, INL/DNL, ADC/DAC calibration, HSpice import. Use when analyzing time-domain voltage from PLL/VCO/clock simulations, measuring phase noise from variable-step solver output, computing jitter, or resampling non-uniform data.
matlab-analyze-dataAnalyze data using MATLAB. Use when the task involves tables, timetables, time-series data, numeric arrays, sensor matrices, or gridded data — including but not limited to exploring, filtering, sorting, cleaning, transforming, aggregating, smoothing, padding, trimming, and answering questions about data. MATLAB provides extensive, easy-to-use built-in functions for these workflows with no additional products required.
matlab-analyze-dependenciesAnalyze the effective toolbox file set to produce a Dependency Manifest — classify all transitive dependencies as included, product, add-on, or external-unresolved, then present resolution options with tradeoffs. Use after matlab-define-toolbox-api when the spec is approved.
matlab-analyze-emS-parameters, insertion loss, fields, currents, mesh control, and solver selection for RF PCB performance validation. TRIGGER: user asks to compute S-parameters, analyze insertion/return loss, extract fields or currents, compare MoM vs FEM, or control mesh for any RF PCB component. Invoke BEFORE writing sparameters() or solver code — API is non-obvious. SKIP: designing or creating components (use the specific matlab-design-pcb-* skill), material/stackup setup only (use matlab-manage-pcb-material), optimization sweeps (use matlab-optimize-pcb-design), PDN/IR-drop analysis (use matlab-analyze-pcb-pdn).
matlab-analyze-installed-antennaAnalyze antennas installed on electrically large conducting platforms using MATLAB Antenna Toolbox. Loads platform geometry from STL/STEP/IGES, installs antenna elements, selects electromagnetic solvers (MoM-PO, FMM, MoM), and computes patterns, impedance, coupling, and efficiency. Use when the user wants to model an antenna on a vehicle, aircraft, ship, satellite, or other large structure.
matlab-analyze-pcb-pdnPDN DC voltage/current analysis, IR drop, design rule checking, and multi-net batch analysis on imported PCB layouts. TRIGGER: user asks about power integrity, PDN analysis, IR drop, voltage distribution, current density, power nets, or design rule checking on a PCB. Invoke BEFORE writing code — the PDN API chain is specialized and non-obvious. SKIP: importing a PCB file (use matlab-read-pcb-layout), EM field/S-parameter extraction (use matlab-analyze-em), material/stackup setup only (use matlab-manage-pcb-material), transmission line design (use matlab-design-pcb-txline).
matlab-analyze-rcsCalculate and visualize monostatic and bistatic radar cross section (RCS) using MATLAB Antenna Toolbox. Computes RCS of platforms, antennas, and arrays with PO, MoM, and FMM solvers, supporting HH/VV/HV/VH polarization, GPU acceleration, and near-field observation. Use when the user wants to compute, plot, or analyze radar cross section.
matlab-analyze-rf-propagationAnalyze RF propagation and plan wireless sites using MATLAB Antenna Toolbox. Creates transmitter/receiver sites, computes signal strength, coverage maps, SINR, line-of-sight, and ray tracing in geographic or indoor environments. Supports multiple propagation models (free-space, close-in, Longley-Rice, ray tracing, rain/gas/fog), custom terrain, building data, and directional antennas. Use when the user wants to compute coverage, signal strength, path loss, SINR, ray tracing, or plan a wireless network.