A capability is missing
Code elsewhere needs a factorization, a norm or a fit that ICoreMatrix doesn't
offer yet.
The numerics are Eigen, a mature linear-algebra library. You write against
ICoreMatrix and its neighbours. Exactly one directory in the product is allowed to
name Eigen, and a check that runs before every release proves nothing else does.
x = A.solve(b)→A.colPivHouseholderQr().solve(B)QRexpM = (M * Ts).expm()→A.exp()Matrix fnsU = A.svdU()→JacobiSVD<MatrixXd>SVDL = A.choleskyL()→LLT<MatrixXd>Choleskyv = A.symmetricEigenvalues()→SelfAdjointEigenSolver<MatrixXd>Eigenvaluesr = p.roots()→PolynomialSolver<double, Dynamic>PolynomialsICoreSpline::bSplineFit(x, y, xq, 3)→SplineFitting<Spline<double, 1>>SplinesICoreFFT::forward(signal)→FFT<double>FFTfitTransferFunction(u, y, 1, 2, Ts)→LevenbergMarquardt<NumericalDiff<…>>LMNine calls you can make, and
what each one runs on the other side. The left column is all the rest of the product ever writes,
and all your code needs to. The right column exists in one directory only, so every call comes back
as an ICoreMatrix or another ICore type, and never as an Eigen one.
Root finding, matrix exponentials, spline fitting, spectra and nonlinear least squares are the work of a library that has done them for years. These are the modules the wrapper includes. There is one list of them, kept in one header.
The dense linear algebra behind solve, inverse,
determinant, the LU, QR, Cholesky, LDLᵀ and SVD factors,
pseudoInverse, rank, conditionNumber, and both the general
and the self-adjoint eigensolvers. Quaternions and rotations come from its Geometry part.
ICorePolynomial::roots(), which returns real and complex roots
together.
The matrix exponential that turns a continuous state space into a zero-order-hold discrete one.
ICoreSpline::bSplineFit, which interpolates through the points at the degree you
ask for.
ICoreFFT, with its magnitude and phase built on top of it.
Jacobians of functions that have no analytic derivative.
Fits a transfer function to measured input and output data.
This is the product's own zero-order-hold discretization, verbatim. It builds a block
matrix, scales it by the sample time and takes its exponential, and it never names Eigen.
| joins matrices side by side, & stacks them, and
expm() is the matrix exponential.
Every part of the product outside the numerics module that needs linear algebra is written this way, and your code would be too. Nothing on this side of the wall has to know which library is on the other side.
// Conversion idea: // Construct [ A B ] -> [ Ad Bd ] // [ 0 0 ] [ 0 I ] ICoreMatrix top = ss_cont.A | ss_cont.B; // n x (n+m) ICoreMatrix bottom = Z_mn | Z_mm; // m x (n+m) ICoreMatrix M = top & bottom; // (n+m) x (n+m) // Exponentiate ICoreMatrix expM = (M * Ts).expm();
Keeping a dependency behind a wall usually relies on everyone being careful. Here a check scans every source file for an Eigen include, an Eigen type or the wrapper's own umbrella header, and one hit outside the numerics module fails the rule. The check runs as its own suite in the pre-release run.
Code elsewhere needs a factorization, a norm or a fit that ICoreMatrix doesn't
offer yet.
The new code goes inside the numerics module, next to the Eigen code it uses. Nobody reaches around the wrapper to get it.
The caller gets a method on ICoreMatrix or a neighbouring type, never an Eigen
type.
A single Eigen reference outside the module fails the rule, and the release doesn't ship.
Because every call passes through one place, that place can decide what the answer looks like. A capability is added or fixed once, for every caller. These are cases where the wrapper does more than pass the call through.
x = A.solve(b)square → exact · tall → least squares · wide → refusedIt is a column-pivoted QR, which is what MATLAB's \ means for a tall matrix. For a
wide one it refuses, rather than returning one of infinitely many solutions.
R = A.choleskyUpper(&p)A = R'·R, or the pivot p it stopped atThis one is written by hand instead of calling Eigen's LLT, because a decomposition that only
reports failure can't tell you p, the pivot where it failed. MATLAB's
[R, p] = chol(A) returns it, so this does too.
A.symmetricEigenvalues()
A.symmetricEigenvectors()value k belongs to column k, ascendingThis is the order MATLAB's eig uses for a symmetric matrix. Code that needs the
pairing asks for it here by name, and doesn't have to rely on the order of two separate
solvers.
A.norm_2()the largest singular value, from the SVDIt used to be a power iteration with a 1e-6 tolerance. It was moved onto the SVD so that it
matches MATLAB's exact norm(A, 2), and every caller got the fix at once.
ICoreMatrix.h still
declares two conversion members that take and return an Eigen matrix, so that one header needs
Eigen to parse. It is recorded debt: a separate check allows exactly one such header, a second one
fails outright, and the allowance can only go down. The Blocks SDK's public
icore/ headers do not include it.eigenvalues()
and eigenvectors() run different Eigen solvers, which order their answers by
different rules. When you need value k to belong to column k, use the symmetric pair above.inverse() of a singular
matrix logs an error and returns a 1×1 zero, so check the log rather than the value.
inverseOrInf() gives MATLAB's all-Inf answer instead.
solve() returns a vector without a warning, and nothing checks conditioning
automatically, so ask conditionNumber(). The
full conventions are on Numerics
- what the solver will and will not do.The whole API is in the reference: ICoreMath
· Foundation, which covers ICoreMatrix, ICorePolynomial,
ICoreSpline and the rest.
See also: The maths in one place · Global solvers, per-rate subsystems
Download the application from the customer portal, or read the documentation first - the manual, a page for every block, and the full command reference are public.