Numerics — what the solver will and will not do#
Everything ICoreBlocks computes — a console expression, a block's state update, a discretized model — is done in IEEE double precision, and every matrix operation goes through one linear-algebra layer whose conventions this page states as you meet them: what a singular matrix gives you, which decomposition orderings and signs you get, which discretization spellings are understood, and what precision the HDL exports carry. The equations themselves are on Solver mathematics — the forms, the discretizations and the stepping scheme; the pacing of a run is on Sample time and loops — how the simulator paces a diagram. Every claim below is either read from the named source file or observed in the console runs pasted at the end (binary built 2026-08-16 from approximately commit 2e126fbf).
The rules you meet#
- Every value is a double. Matrices store
double(ICoreMatrix.cpp,std::vector<double> data); a scalar is a 1×1 matrix. The console's# Integer/# Doubleannotation is a display verdict, not a type: an anonymous result is labelledIntegerwhen it is finite and has no fractional part (ICoreCommandWindow.cpp,formatResultRepr); a named variable is labelled from its text —x = 1e6saysDoublebecause1e6does not parse as an integer literal (ICoreVariable.cpp,assignType). - A failed matrix operation logs and returns a placeholder — it never throws.
inv,chol,det,solve,eig,eigvecand every otherICoreMatrixfunction report through the run diagnosis ([ICoreRunDiagnosis] - Error - …on the console) and return the default matrix, which is a 1×1 zero — printed as0 # Integer. Exit code stays 0. Check the log line, not the value. inv(A)refuses a singular matrix;solve(A,b),pinv(A),det(A),cond(A),rank(A)do not.inverse()runs a full-pivot LU and returns the zero placeholder withUnable to compute matrix inverse. Matrix is singular.whenisInvertible()is false (ICoreMatrix.cpp,inverse).solve()is a column-pivoted QR and returns some vector for a singular or inconsistent system with no message;pinv()is an SVD pseudo-inverse with automatic toleranceeps · max(rows, cols) · σ_max;det()returns whatever the LU produces (a signed zero for a singular matrix);cond()returnsinfwhen the smallest singular value is exactly 0;rank()counts singular values above1e-12.- An ill-conditioned matrix inverts without a warning.
inv([1 1; 1 1.0000000001])returns entries of1e+10and says nothing; onlycond()(4e+10) tells you. There is no automatic conditioning check anywhere on the inverse path. /between matrices is multiplication byinv()of the right operand, soA / Binheritsinv's singular refusal and its silence on ill-conditioning (ICoreExpressionEvaluator.cpp,matrixDiv). There is no\(left-division) operator; usesolve(A,b).- Division by an exact scalar zero is an error, not
inf.1/0,0/0and[1 2] ./ 0all stop witherror: division by zero(exit code 1). Element-wise division by a zero inside a matrix is not checked:[1 2] ./ [0 0]gives[[inf, inf]]. - NaN and Inf otherwise propagate as IEEE says.
sqrt(-1)isnan,log(0)is-inf,1e308*10isinf— all# Double, all exit code 0, and a variable assigned one prints back asx = nan # Double. ^is not an operator.2^62is not an expression at all (unknown command: 2^62); usepow(A,n)for element-wise power andmpower(A,n)for a matrix power (integern; a negativengoes throughinv()).
Decomposition conventions — what you get, and in what order#
All decompositions delegate to Eigen; the conventions below are what the delegation makes observable (ICoreMatrix.cpp, functions named).
| Function | Convention observed |
|---|---|
eig(A) | Eigenvalues in the order Eigen's general solver produces them — not sorted. Real spectrum → N×1 column; any complex value → N×2 [real, imag] matrix (ICoreExpressionEvaluator.cpp, complexListToMatrix). Run: eig([2 0; 0 1]) = [[2], [1]]; eig([2 1; 1 2]) = [[3], [1]]. |
eigvec(A) | Columns are eigenvectors, real part only. A symmetric matrix takes the self-adjoint solver, whose eigenvalues are ascending — so for a symmetric A the column order of eigvec(A) is the reverse of eig(A)'s row order (run: eigvec([2 1; 1 2]) = [[-0.707107, 0.707107], [0.707107, 0.707107]], the first column belonging to eigenvalue 1). A non-symmetric matrix keeps eig's order. A complex eigenpair logs eigenvectors(): matrix has complex eigenpairs; returning real part only. and hands back the real parts. |
qrq(A), qrr(A) | Thin Householder QR, A = Q·R. Signs are Householder's: the diagonal of R may be negative (qrr([1 2; 3 4]) = [[-3.16228, -4.42719], [0, -0.632456]]). |
lul(A), luu(A), lup(A) | Partial-pivot LU, P·A = L·U, unit-diagonal L. lup([1 2; 3 4]) = [[0, 1], [1, 0]]. Square only. |
svdu(A), svds(A), svdv(A) | Thin Jacobi SVD, A = U·diag(S)·Vᵀ. svds is a column of non-negative singular values in descending order; the sign of a singular triple is not fixed (svdu([3 0; 0 -2]) = [[1, -0], [0, -1]], svdv = identity). |
chol(A) | Lower factor L, A = L·Lᵀ, positive diagonal (chol([4 2; 2 3]) = [[2, 0], [1, 1.41421]]). A non-SPD input logs Cholesky failed: matrix must be symmetric positive-definite. and returns the zero placeholder. |
ldltl/ldltd/ldltp(A) | Pivoted LDLᵀ, PᵀLDLᵀP = A; ldltd is the diagonal as a column. |
null(A) | Kernel basis as columns, from a full-pivot LU. |
Because these are conventions and not unique answers, the console regression corpus pins them as golden cases; a different sign in your own derivation is not a defect in either.
Discretization — which spellings are understood, and what a typo does#
- In the app, the method is a fixed choice, not free text. The seven options are exactly
Zero-order Hold,First-order Hold,Impulse,Tustin,Matched,Backward Euler,Forward Euler(ICoreModelConfigurator.cpp,DISCRETE_METHOD_*); the model-configuration panel offers them in a combo box, and the console'ssetModelConfig discretizationMethod <value>accepts an option case-insensitively, by any|-separated segment, or by a unique case-insensitive substring — and rejects anything else with the list (ICoreModelConfigCommands.cpp,resolveChoice). Run:setModelConfig discretizationMethod tustn→no option matches 'tustn' — available: …;tustin→discretizationMethod = Tustin. Notezohis not a substring ofZero-order Holdand is rejected; typeZero-order Hold(spaces are fine) or the unique substringzero;holdis reported ambiguous. - Below the option list, the string is matched loosely and an unknown string silently becomes ZOH. The conversion routine lower-cases the method and recognises
foh/first-order hold/firstorderhold/first order hold,impulse/imp,tustin,matched/match,backwardeuler/backward-euler/backward euler,forwardeuler/forward-euler/forward euler; everything else, includingzohand any misspelling, takes the Zero-order Hold branch with no diagnostic (ICoreStateSpaceDiscretization.cpp,discretizeStateSpace). The de-discretization routine recognises onlytustin,matched/match,forwardeulervariants, and defaults to ZOH inversion. - Where the loose match is reachable by a user: the project file.
solver.ini'sdiscretizationMethodkey is read back through a setter that does not validate against the option list (ICoreStudioSerialization.cppreads it;ICoreModelConfigurator::setDiscretizationMethodassigns unconditionally), and the per-block dispatch then finds no matchingisDiscretizationMethod_*and takes the// Default ZOHbranch (ICoreBlockSolverEnvironment.cpp,discretize). A hand-edited or foreign-version project withdiscretizationMethod=Tustntherefore simulates as Zero-order Hold with no message. Blocks with their own per-block method (Discrete Nonlinear State Space, Discrete Nonlinear State Space — Control Systems/Discrete) use the same seven option strings and the same silent ZOH default. Which model each method produces is on Solver mathematics — the forms, the discretizations and the stepping scheme.
Fixed point on the HDL path — what VHDL / Verilog / SystemVerilog give you#
- One format, Q16.16, for every signal. VHDL:
subtype Fx is sfixed(ICORE_INT_BITS-1 downto -ICORE_FRAC_BITS)with both constants16in the generatedicore_pkg.vhd(ICoreVHDLParser.cpp,buildSupportPackage). Verilog / SystemVerilog: `ICORE_INT_BITS 16 `,ICORE_FRAC_BITS 16 `,ICORE_WIDTH `= 32 inicore_defs.vh(ICoreVerilogParser.cpp,buildDefs`). That is 16 integer bits including sign — a range of −32768 to +32767.99998 — with a quantum of 2⁻¹⁶ ≈ 1.53e-5. Every signal port is a fixed-size bus of these; there is no per-block or per-signal word length. - Rounding at the boundary. Constants and testbench stimuli enter through
to_fx: VHDL'sto_sfixedrounds; the two Verilogs add ±0.5 before$rtoi, i.e. round-to-nearest, ties away from zero (the comment infixedPointFunctionsrecords why bare truncation was replaced). Values leave throughto_real=value / 2^16. Fixed-point → count conversions (fx_to_int, used for indices, delays, periods) floor by arithmetic shift on all three targets, to agree with the software targets'floor(). - Inside a block, products are formed at double width and shifted back once. The generated core declares
reg signed [2*ICORE_WIDTH-1:0] acc(ICoreVerilogParser.cpp, core builder); a synthesizable block body multiplies intoacc(Q32.32) and assignsacc >>> ICORE_FRAC_BITSto the 32-bit signal (read in, e.g.,Gear_Train'sgenerateBodyCode_Verilog). The>>>floors (toward −∞), while VHDL bodies useresize(a*b, acc), which rounds — so the two HDL families can differ by one quantum on a value that sits exactly off the lattice, and an antisymmetric operation likeq(-v)vs-q(v)shows it. Overflow is not saturated in the Verilog bodies read: the shifted product is assigned to a narrower bus and the high bits are dropped (wrap-around). Whether VHDL'sresizesaturates was not verified from the generated text and is not claimed here. - "Synthesizable" versus "simulation-only". A block whose HDL body is written in Q16.16 integer arithmetic is synthesizable. A block that needs
sin/cos, a random generator or other real-valued math has HDL bodies that carry the arithmetic in the simulator'srealtype (VHDLieee.math_real, Verilogreal), quantizing only at the port boundary — the generated file saysSIMULATION-ONLY real arithmeticin its banner, and the block's page says so (e.g. Planar Arm Forward Kinematics — Robotics/Planar Kinematics; Constant — Control Systems/Sources is the synthesizable case). Such output runs in an HDL simulator and passes verification, but a synthesis tool will not build it. 81 block sources carry the simulation-only marker on 2026-08-17. Every software target (C, C++, Python, Java, Rust, MATLAB, PLC ST) usesdouble/LREALthroughout, so the HDL family is the only place quantization exists. How to export is on Exporting code — the ten targets, what each produces, and what verification proves.
Things that surprise people#
- "
invprinted 0 and my model kept going." The zero placeholder is a value, so anything downstream computes with it. Read the console for the[ICoreRunDiagnosis] - Errorline; in a script checkdet/cond/rankfirst, or usepinv. - "
solvegave me an answer for a singular system." It always does — column-pivoted QR returns a vector even when none or infinitely many exist (solve([1 2; 2 4], [1; 1])=[[0], [0.3]]). Check the residualA*x - byourself. - "
eigandeigvecdisagree on the order." Only for symmetric matrices, and always: the symmetric path sorts ascending,eigdoes not sort. Pair them by re-checkingA*v = λ*v, or readeigoff the diagonal of the same solver by asking foreigvecand multiplying. - "
1/0is an error but[1 2]./[0 0]isinf." The evaluator checks for a scalar zero divisor explicitly; element-wise arithmetic is bare IEEE. - "My project silently simulates as ZOH." Its
discretizationMethodtext is not one of the seven exact spellings — pick the method again in the panel or withsetModelConfig. - "VHDL and Verilog verify to different residuals on the same diagram." Round versus floor on the product shift; a one-quantum (1.5e-5) disagreement on values off the Q16.16 lattice is the datapath, not the block.
- "My HDL export runs in the simulator but will not synthesize." The block is simulation-only in HDL; its page says so.
Real runs (2026-08-17)#
All runs: HOME=<scratch> QT_QPA_PLATFORM=offscreen build-mac/ICoreBlocks.app/Contents/MacOS/ICoreBlocks --console "<line>", binary built 2026-08-16 (≈ commit 2e126fbf), Qt platform noise stripped, everything else verbatim.
--console "inv([1 2; 2 4])"
[ICoreRunDiagnosis] - Error - Unable to compute matrix inverse. Matrix is singular.
0 # Integer (exit 0)
--console "x = inv([1 2;2 4])"
[ICoreRunDiagnosis] - Error - Unable to compute matrix inverse. Matrix is singular.
x = 0 # Integer
--console "det([1 2; 2 4])" → -0 # Integer
--console "solve([1 2; 2 4], [1; 2])" → [[0], [0.5]] # Matrix of Double
--console "solve([1 2; 2 4], [1; 1])" → [[0], [0.3]] # Matrix of Double
--console "pinv([1 2; 2 4])" → [[0.04, 0.08], [0.08, 0.16]] # Matrix of Double
--console "cond([1 2; 2 4])" → inf # Double
--console "rank([1 2; 2 4])" → 1 # Integer
--console "inv([1 1; 1 1.0000000001])" → [[1e+10, -1e+10], [-1e+10, 1e+10]] # Matrix of Double
--console "cond([1 1; 1 1.0000000001])" → 4e+10 # Double
--console "[1 2] / [1 2; 2 4]"
[ICoreRunDiagnosis] - Error - Unable to compute matrix inverse. Matrix is singular.
error: '/' inner dimensions disagree: 1x2 / 2x2 (exit 1)
--console "1/0" → error: division by zero (exit 1)
--console "0/0" → error: division by zero (exit 1)
--console "[1 2] ./ 0" → error: division by zero (exit 1)
--console "[1 2] ./ [0 0]" → [[inf, inf]] # Matrix of Double
--console "sqrt(-1)" → nan # Double
--console "log(0)" → -inf # Double
--console "1e308*10" → inf # Double
--console "x = sqrt(-1)" → x = nan # Double
--console "2/1" → 2 # Integer
--console "5/2" → 2.5 # Double
--console "0.1+0.2" → 0.3 # Double
--console "1e6" → 1e+06 # Integer
--console "x = 1e6" → x = 1e6 # Double
--console "2^62" → unknown command: 2^62 (exit 1)
--console "eig([2 0; 0 1])" → [[2], [1]] # Matrix of Double
--console "eigvec([2 0; 0 1])" → [[0, 1], [1, 0]] # Matrix of Double
--console "eig([2 1; 1 2])" → [[3], [1]] # Matrix of Double
--console "eigvec([2 1; 1 2])" → [[-0.707107, 0.707107], [0.707107, 0.707107]] # Matrix of Double
--console "eig([1 2; 0 3])" → [[1], [3]] # Matrix of Double
--console "eigvec([1 2; 0 3])" → [[1, 0.707107], [0, 0.707107]] # Matrix of Double
--console "eig([0 -1; 1 0])" → [[0, 1], [0, -1]] # Matrix of Double
--console "eigvec([0 -1; 1 0])"
[ICoreRunDiagnosis] - Warning - eigenvectors(): matrix has complex eigenpairs; returning real part only.
[[-0.707107, -0.707107], [0, 0]] # Matrix of Double
--console "eig([1 2 3; 4 5 6; 7 8 10])" → [[16.7075], [-0.90574], [0.198247]] # Matrix of Double
--console "qrq([1 2; 3 4])" → [[-0.316228, -0.948683], [-0.948683, 0.316228]] # Matrix of Double
--console "qrr([1 2; 3 4])" → [[-3.16228, -4.42719], [0, -0.632456]] # Matrix of Double
--console "lul([1 2; 3 4])" → [[1, 0], [0.333333, 1]] # Matrix of Double
--console "luu([1 2; 3 4])" → [[3, 4], [0, 0.666667]] # Matrix of Double
--console "lup([1 2; 3 4])" → [[0, 1], [1, 0]] # Matrix of Double
--console "svds([3 0; 0 -2])" → [[3], [2]] # Matrix of Double
--console "svdu([3 0; 0 -2])" → [[1, -0], [0, -1]] # Matrix of Double
--console "svdv([3 0; 0 -2])" → [[1, 0], [0, 1]] # Matrix of Double
--console "chol([4 2; 2 3])" → [[2, 0], [1, 1.41421]] # Matrix of Double
--console "chol([1 2; 2 1])"
[ICoreRunDiagnosis] - Error - Cholesky failed: matrix must be symmetric positive-definite.
0 # Integer
--console "ldltd([4 2; 2 3])" → [[4], [2]] # Matrix of Double
--console "setModelConfig discretizationMethod tustn"
setModelConfig discretizationMethod: no option matches 'tustn' — available:
Zero-order Hold
First-order Hold
Impulse
Tustin
Matched
Backward Euler
Forward Euler
--console "setModelConfig discretizationMethod tustin" → discretizationMethod = Tustin
--console "setModelConfig discretizationMethod zoh" → (rejected, same list)
--console "setModelConfig discretizationMethod Zero-order Hold" → discretizationMethod = Zero-order Hold
--console "setModelConfig discretizationMethod zero" → discretizationMethod = Zero-order Hold
--console "setModelConfig discretizationMethod hold"
setModelConfig discretizationMethod: 'hold' is ambiguous — matches:
Zero-order Hold
First-order Hold
For contributors#
The module page for the numerics zone holds the invariants behind this page (the Eigen wrapper rule, the recognised-method list, the golden decomposition corpus, the ICoreTimeResponse discrete-only trap); the simulator page holds the discretize-on-build behaviour that reads the method; the code-generation page holds the HDL parser map. All three are internal pages listed in this page's related: front-matter.