Nonlinear Curve Fit — Control Systems/Optimization
Control_Systems/Optimization/Nonlinear_Curve_Fit · 1 input / 3 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text
Description#
The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.
Nonlinear Curve Fit
Control Systems / Optimization
Fits a model you type to a window of measurements that arrive on a
port, and answers the fitted parameters – MATLAB's
lsqcurvefit, solved again on every sample:
[x, resnorm, exitflag] = lsqcurvefit(F, x₀, xdata, ydata), minimizing Σi ( F(x, xdatai) − ydatai )² over the parameters x.
The abscissa is configuration and the ordinates are the signal: the model is sampled at fixed sites and the measurements at those sites arrive on the wire, which is the shape a streaming fit has – a spectrum at fixed wavelengths, a sensor bank at fixed positions, a decay sampled at fixed delays.
Ports
- ydata – the measured ordinates, one per abscissa site, oldest site first. A COLUMN [W,1], where W is the number of entries in Abscissa. A size that disagrees stops the run and says both numbers.
- x – the fitted parameters, a column [n,1] in the order they are named in Parameters. n and its size follow from the configuration alone, never from the input.
- resnorm – the sum of squared residuals at the answer, [1,1]; MATLAB's second output.
- exitflag – MATLAB's third output, [1,1]. See Notes for what each value means.
Parameters
- Options – empty (default), or the path of a Solver Options block (
Home/Solver Options), MATLAB’s options argument: for the run, every option it sets replaces this block’s parameter of the same name; one it leaves atdefault, or one this block does not have, changes nothing. - Model – the expression fitted, over the parameter names and
the independent variable:
a*exp(b*t),a/(1 + exp(-b*(t - c))),a*sin(b*t + c). It is read by the console's own symbolic engine and accepts that engine's function set – sin cos tan sec csc cot asin acos atan acot sinh cosh tanh asinh acosh atanh exp log log2 log10 sqrt, the operators + − × / ^, andpi.absandsignare refused by name, because the engine will not differentiate them without an assumption no block may depend on, and this block needs the derivative. - Parameters – the names fitted, separated by spaces or commas:
a b. One to 4 of them, and the output column carries them in this order. - Independent Variable – the name the abscissa takes in the
model,
tby default. It must not be one of the parameter names. - Abscissa – the xdata, a row or a column of 2 to 40 numbers.
Its length is W, the height the ydata port must have, and it need not be
evenly spaced – that is the whole difference from the presets in
Curve Fitting. It must have at least as many entries as there are
parameters, because
lsqcurvefitrefuses a problem with fewer measurements than unknowns. - Start Point – x₀, one entry per parameter. The search is LOCAL, so this chooses which minimum is found.
- Optimality Tolerance – MATLAB's
OptimalityTolerance, default 1e-6. The search stops with flag 1 when the largest gradient entry falls below it. - Function Tolerance – MATLAB's
FunctionTolerance, default 1e-6; flag 3. - Step Tolerance – MATLAB's
StepTolerance, default 1e-6; flag 2. - Maximum Iterations – default 400, MATLAB's. The loop is emitted with a FIXED trip count, so a large value here costs every target the same run time whether the search needs it or not.
- Maximum Function Evaluations – default 400. Reaching either limit is flag 0.
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Code export
All ten targets. The model and its derivatives are printed into each one in
that target's own syntax, with the abscissa folded in as literals, so a
generated core carries no expression reader and no table of sites. The three
HDL targets – VHDL, Verilog and SystemVerilog – run the search in
real arithmetic and are simulation-only, quantized at the
port boundary: a trust region with a secular equation and a Brent root is not a
Q16.16 datapath. Every setting above is baked in at export time; none of them
is tunable on a generated core.
Simulink bridge
None. The Optimization Toolbox ships no Simulink library at all, so there is
no block to map onto and no library path a diagram could name;
lsqcurvefit is a MATLAB function. The block is reported rather
than dropped when a model crosses. Sampling Time (s) → SampleTime, as on
every block, does not apply here for the same reason.
Notes
- Algebraic. No state: the whole fit is re-run from the start point on every sample, so the answer depends on the window and never on what came before it.
- The Jacobian is exact, not a finite difference. The symbolic engine
differentiates the model with respect to each parameter when the configuration
loads. MATLAB's own default is the opposite –
Jacobianis'off', solsqcurvefitestimates it by forward differences unless told otherwise – and the two take different steps on the same problem. Compare againstJacobian='on'. - Exit flags, MATLAB's: 1 the gradient is small enough, 2 the step is small enough, 3 the change in the sum of squares is small enough, 0 an iteration or evaluation limit was reached.
- ⚠ LOCAL, and a small gradient is not a certificate. A different Start Point can reach a different fit, and a model that cannot describe the data walks the parameters outwards until a limit stops it – exit flag 0 with a large x is that case, not a defect.
- ⚠ A model that can overflow is a software-only export. An
expcan overflow at a parameter the search passes through, andlsqcurvefithandles that: the trial point is rejected and the trust region is cut by twenty instead of by four. The seven software targets do the same. The three HDL targets do not get that far – arealthat overflows stops the simulation rather than carrying an infinity – so keep the abscissa and the model inside the range arealcan hold if the generated core is going to hardware. - ⚠ The whole fit runs once per sample, at a fixed trip count. This is a real solver on the wire, not a recursion – budget for it.
- The nine preset fits in Control Systems / Curve Fitting are cheaper where they apply: they fit a FIXED model on an EVENLY SPACED abscissa, and most of them collapse into a bank of constants at configuration load. Reach for this block when the model is not one of theirs, or the sites are not evenly spaced.
Code facts#
| Fact | Value |
|---|---|
| registered type | Control_Systems/Optimization/Nonlinear_Curve_Fit |
| family | Control_Systems/Optimization |
| solver environment class | ICoreBlock_0_Control_Systems_1_Optimization_2_Nonlinear_Curve_Fit |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Optimization/Nonlinear_Curve_Fit/ICoreBlock_0_Control_Systems_1_Optimization_2_Nonlinear_Curve_Fit.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/Optimization/Nonlinear_Curve_Fit/ICoreBlock_0_Control_Systems_1_Optimization_2_Nonlinear_Curve_Fit.h |
| default size on canvas | 170 × 90 px |
| ports at insert | 1 in, 3 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | ydata |
| 2 | out | ICoreDouble | x |
| 3 | out | ICoreDouble | resnorm |
| 4 | out | ICoreDouble | exitflag |
Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.
Configuration variables#
| Config variable | Default | Simulink parameter |
|---|---|---|
Model | a*exp(b*t) | — |
Parameters | a b | — |
Independent Variable | t | — |
Abscissa | [0.9 1.5 13.8 19.8 24.1 37.9 88.3 94.8 128.3 137.2 145.2 … | — |
Start Point | [100; -1] | — |
Optimality Tolerance | 1e-6 | — |
Function Tolerance | 1e-6 | — |
Step Tolerance | 1e-6 | — |
Maximum Iterations | 400 | — |
Maximum Function Evaluations | 400 | — |
Options | — | — |
Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.
Simulink bridge#
| support | Support::None |
| Simulink path | — |
| port-count rule | PortsParam::None |
SampleTime parameter | yes |
Caveat (shown to the user): the Optimization Toolbox ships no Simulink library at all, so there is no block to map onto and no library path a diagram could name; lsqcurvefit is a MATLAB function. The block is reported rather than dropped when a model crosses
Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The checker has a blind spot here — it could not resolve something (a grouped port bullet, a computed config name), which is reported and never counted as a pass. A reader has to settle it:
B0no sample under docs/generated/samples/ — nothing to cross-check (P8.1)
The verdict above is
tools/docs/check_block_descriptions.py(P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.
File banner (developer view)#
The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).
Nonlinear Curve Fit -- lsqcurvefit over a model you type, fitted to a window on a wire [x, resnorm, exitflag] = lsqcurvefit(F, x0, xdata, ydata) resnorm = sum over i of ( F(x, xdata_i) - ydata_i )^2
⚠ lsqcurvefit AND lsqnonlin ARE ONE SOLVER, AND MATLAB SAYS SO IN CODE RATHER THAN IN PROSE.
lsqcurvefit.m(toolbox/shared/optimlib) wraps the user's F in a nested function calledobjectivethat evaluates it at the captured xdata and subtracts the captured ydata, and hands THAT to the samelsqncommonlsqnonlin uses. Nothing else differs: same default algorithm (trust-region-reflective, measured), same options, same exit flags. So the search here is the shared one --../ICoreTrustRegionSupport.h, where snls.m, trdog.m and trust.m are transcribed and where every claim about the ITERATION belongs -- and this file is the residual: the model, its Jacobian, and the block.⚠ THE MODEL IS AN EXPRESSION THE USER TYPES, WHICH IS WHAT MAKES THIS A DIFFERENT BLOCK FROM ITS NINE NEIGHBOURS IN Curve_Fitting. Those fit a FIXED model on a REGULAR abscissa:
exp1,power1,gauss1,fourier1,sin1,rat,weibull, a polynomial, a spline, with the sample sites at x0 + k*h. This one takes any expression the console's symbolic engine can read, over any abscissa at all, and reports lsqcurvefit's own three answers.THE PIPELINE, all of it at configuration load and none of it per sample:
- "Model" is read by
ICoreSymbolicProgramover the declared names -- the parametersfirst, the independent variable last.
ICoreSymbolicProgram::jacobiandifferentiates it with respect to every declaredname, through the console's own symbolic engine over exact rational arithmetic. The first n entries are the parameter derivatives, and they are the Jacobian the search gets. ⚠ SO THE JACOBIAN IS ANALYTIC, NOT A FINITE DIFFERENCE -- which is exactly the comparison below is made against (
Jacobian= 'on'), because MATLAB's DEFAULT is 'off' and a finite-difference Jacobian is a different algorithm on the same problem.
- The abscissa is CONFIGURATION, so every one of the W model evaluations the emitted
code carries has its own xdata folded in as a literal, and the derivative of the model with respect to the abscissa -- which the engine computes and nothing needs -- is never printed.
⚠ THE ORDINATES ARE THE SIGNAL AND THE ABSCISSA IS NOT, WHICH IS A DECISION AND NOT AN OVERSIGHT. A streaming fit re-measures y at fixed sites -- a spectrum at fixed wavelengths, a sensor bank at fixed positions, a decay sampled at fixed delays -- and that is the shape every other block in this family has too: the thing that varies is on the wire, the thing that describes the problem is config. It also buys the fold in step 3: an xdata that arrived on a port could not be a literal in ten generated programs, and every model
Sample results#
No sample run is committed for this block. Samples come from the headless harness (DOCS_PLAN.md P8.1) into docs/generated/samples/; until one exists this block's behaviour is witnessed by the parity and export-verification suites, not by a plot here.