Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Fit SimBiology model parameters to data — fitproblem, population NLME, virtual patients, and NCA. Use when asked to fit, estimate, calibrate, or compute PK metrics.
.claude/skills/matlab-matlab-fit-simbiology-model/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 89% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 39% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 88% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 185% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 54% | 0% |
Estimate parameters from data using fitproblem, fit population models with NLME, generate virtual patients, and compute NCA metrics.
matlab-build-simbiology-model)matlab-simulate-simbiology-model)matlab-simulate-simbiology-model)fitproblem for parameter estimationAlways use fitproblem instead of calling sbiofit or sbiofitmixed directly. fitproblem provides a unified, declarative interface:
matlabprob = 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.
groupedData, NOT a plain tableAlways wrap data:
matlabdata = groupedData(table(...)); data.Properties.IndependentVariableName = 'Time';
ResponseMap maps model outputs to data columnsFormat 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).
Prevent non-physical values (negative rates, etc.):
matlabestimParams = estimatedInfo({'ke','ka'}, ... 'InitialValue', [0.2, 1.0], ... 'Bounds', [0.01 1; 0.1 5]);
Parameters spanning orders of magnitude (clearances, rate constants) benefit from log-transform estimation. Set .Transform after creation:
matlabei = 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):
matlabei = 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.
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 residualsbioncaoptions objectDo not use name-value pairs. Column names are camelCase. EVDose column uses NaN for non-dose rows.
| 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);
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).
| 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) |
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.
matlabdata.Properties.GroupVariableName = 'SubjectID'; % Pooled — one parameter set for all prob.Pooled = true; % Individual — separate per subject prob.Pooled = false;
To estimate parameters separately per category (e.g., dose group), use CategoryVariableName on the estimatedInfo object — not on fitproblem or sbiofit:
matlabestimParams = 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.
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
| 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 |
When covariates (e.g., weight, age) influence parameters, use a CovariateModel instead of estimatedInfo:
matlabcovModel = 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.
Use SimBiology.Scenarios with makedist — avoids manual matrix construction:
matlabsc = 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);
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);
Use explicit OutputTimes to ensure sufficient time-resolution for NCA (the default solver output may have too few points near Cmax):
matlabcs = 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);
| Route | Dose column | Extra config | |-------|-------------|--------------| | 'ExtraVascular' | opt.EVDoseColumnName | — | | 'IVBolus' | opt.IVDoseColumnName | — | | 'IVInfusion' | opt.IVDoseColumnName | opt.infusionRateColumnName |
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 |
matlabdata.Properties.GroupVariableName = 'SubjectID'; opt.groupColumnName = 'SubjectID'; ncaResults = sbionca(data, opt);
Restriction: sbioparameterci only works with results from nonlinear regression (sbiofit). It does NOT support NLME results (sbiofitmixed).
After fitting with sbiofit, compute confidence intervals:
matlabciResults = 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})matlabciPL = 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 |
Depot with species DrugDepot (not species Depot inside compartment Depot).'liter'). Set DimensionalAnalysis = true and VariableUnits on groupedData. For pure amount-based models, set Value = 1 and omit units..sbproj: proj = sbioloadproject('f.sbproj'); model = proj.(fieldnames(proj){1});selectbyname(sbiosimulate(m), 'Drug') for specific variables; resample(sd, tSample, 'linear') for specific times'scattersearch' if unsure about parameter landscape'log' transform for parameters spanning orders of magnitude (see Rule 5)sbioaccelerate(model) before fitting — no effect, wastes timecs.MaximumWallClock = 60 — stops hung simulations from bad guessesprob.ProgressPlot = true for long-running fitsmatlabplot(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.
sbiofit/sbiofitmixed/sbionlmefit directly — use fitproblemLoad on demand for detailed guidance:
references/nca-analysis-guidance.md — full NCA patterns, IV infusion, metrics interpretationCopyright 2026 The MathWorks, Inc.
Other measured skills in the registry, with their headline benchmark lift.