Releases: jamestjsp/controlsys
v0.8.0 — Reduce/equalize bug fixes
Bug-fix release addressing three issues in Reduce / MinimalRealization / balancing.
Fixed
-
#26 —
Reducenow short-circuits to a cleanly emptySystemwhen the controllability or observability staircase collapses to zero states. Previously it fell through toextractSubmatrix(A, 0, 0, 0, 0)and got back a phantom 1×1 zeroA(via #28), producing a 1-state "reduced" system thatIsStablereported as marginally unstable. -
#27 —
equalize()(the Parlett-Reinsch state-space balancing used whenReduceis called withEqualize: true) has been rewritten to match LAPACKDGEBALwith the SLICOTTB01IDstate-space extension that includesBrows andCcolumns in the row/column 1-norms. The previous implementation diverged toInfon the issue #26 closed loop (A[0,1] = -Inf,B[0,0] = 1e+186after one pass). Fixed bugs: per-step update wasc *= sclfac²instead of linear;rwas never updated in the loops; convergence check divided byf; scaling application direction was inverted. Added overflow guards matching the LAPACKsfmin2/sfmax2bounds. -
#28 —
extractBlocknow returns a true 0×0&mat.Dense{}for requests with both dimensions zero, instead of themax(rows,1)/max(cols,1)phantom fallback. This eliminates the underlying trigger of #26. Mixed-zero requests (0×N, N×0) still use the old fallback pending a caller audit.
Tests added
TestReduceIssue26PIPlantLoop— exact reproducer from #26 (PI + first-order plant closed loop).TestReduceZeroControllableReturnsEmpty/TestReduceZeroObservableReturnsEmpty— B=0 / C=0 edge cases.TestEqualizeImbalanced2x2— eigenvalue preservation and norm-ratio improvement on a skewed 2×2.TestEqualizeIssue26SystemFinite—Reduce(Equalize: true)on the issue #26 system produces finite, stable output.TestExtractBlockZeroDim— 0×0 request returns a 0×0 dense.
Full diff: v0.7.1...v0.8.0
v0.7.1 — HinfSyn fix & numerical hardening
Bug Fixes
- HinfSyn: negate Bk in controller construction (was returning wrong sign)
- Canon: modal form falls back to Schur for ill-conditioned eigenvectors
- Input validation: reject improper transfer functions, harden descriptor Riccati and Q PSD checks
Tests
- 105 new hardening tests from python-control audit
v0.7.0 — FRD performance, CST fixes, and README sync
Highlights
- optimize the FRD stack with contiguous FRD storage, reusable SVD workspace, and sharply lower allocation counts in FRD feedback/series/parallel paths
- fix canonical-form conjugate-pair handling and include the recent FRDMargin/Lsim correctness fixes that landed after v0.6.0
- sync the README with the current public API surface, including PID/PID2/Pidtune, FRD workflows, and corrected exported names
v0.6.0 — New synthesis, estimation, and analysis APIs
New APIs
System synthesis
| Symbol | Description |
|---|---|
H2Syn(P, nmeas, ncont) → *H2SynResult |
H₂ optimal controller synthesis |
HinfSyn(P, nmeas, ncont) → *HinfSynResult |
H∞ controller synthesis |
Lqg(sys, Q, R, Qn, Rn, opts) → *LqgResult |
LQG controller (LQR + Kalman filter) |
SmithPredictor(controller, model, delay, padeOrder) → *System |
Smith predictor for time-delay systems |
State estimation
| Symbol | Description |
|---|---|
NewEKF(model, x0, P0) → *EKF |
Extended Kalman filter |
type EKFModel |
Nonlinear model definition (F, H, FJac, HJac, Q, R) |
System identification
| Symbol | Description |
|---|---|
ERA(markov, order, dt) → *ERAResult |
Eigensystem Realization Algorithm |
Analysis
| Symbol | Description |
|---|---|
RootLocus(sys, gains) → *RootLocusResult |
Root locus computation |
IsStabilizable(A, B, continuous) |
Stabilizability test |
IsDetectable(A, C, continuous) |
Detectability test |
Linearize(model, x0, u0) → *System |
Jacobian linearization of nonlinear models |
Lyapunov equations (signature change)
Lyap and DLyap now accept an optional *LyapunovOpts argument (pass nil for default behavior — backward compatible at the call site if opts was not used).
| Symbol | Description |
|---|---|
Lyap(A, Q, opts) |
Continuous Lyapunov equation |
DLyap(A, Q, opts) |
Discrete Lyapunov equation |
NewLyapunovWorkspace(n) → *LyapunovWorkspace |
Pre-allocate solver workspace for repeated calls |
type LyapunovOpts |
Solver options (workspace reuse) |
New error sentinels
ErrNotStabilizable, ErrNotDetectable, ErrInvalidPartition, ErrNoFiniteH2Norm, ErrH2DirectFeedthrough, ErrGammaNotAchievable, ErrDescriptorSingular
Bug fixes
- ERA odd-length panic —
r := (len(markov)+1)/2caused out-of-bounds on minimum-valid odd-length input; fixed tolen(markov)/2 - H2Syn nonzero D22 — old path silently produced an incorrect controller; now returns
ErrH2DirectFeedthroughbefore any computation EvalFrignoringsys.Delay— per-channel delay matrix was not applied; fixed via sharedapplyIODelayAtShelper- LU singularity detection — all 8
lu.Det()==0guards replaced withlu.Cond()*ε ≥ 1(LAPACKdgecon), which correctly catches ill-conditioned transforms
Performance
FreqResponse/EvalFr— single-point evaluation computesC(sI-A)⁻¹B+Ddirectly; noTransferFunctionconstruction- EKF — all scratch matrices pre-allocated in
NewEKF, reused across everyPredict/Updatecall - Discrete Lyapunov solver —
blocks,blockStart,bufslices reused via workspace threading
v0.5.0
Breaking Changes
Lqe,Kalman,Kalmdnow accept*RiccatiOptsas final parameter for workspace reuse
Performance Optimizations
Riccati Solvers (Care/Dare)
RiccatiWorkspacestruct pools ~25 arrays, reducing GC churn by ~96% (1.95 MB/call eliminated)- Care_N10: 54,640 → 994 B/op, Dare_N10: 63,784 → 906 B/op
HinfNorm
- Replace 50x per-frequency EvalFr+SVD loop with single vectorized
Sigma()call - 43% faster, 95% less memory, 83% fewer allocs
Transfer Functions
- Pool
vPolyRecurrenceallocations: 25% fewer allocs per matrix transformation
FeedbackLFT
- Scoped BLAS workspace eliminates ~48 intermediate allocations (321 → 273 allocs/op)
Balreal / Balred
- Single unified buffer replaces 17
make()calls (104 → 88 allocs/op)
ControllabilityStaircase
- Static
blockBufeliminates per-iterationmat.NewDense(126 → 109 allocs/op)
Raw Stride Indexing
- Replace
At()/Set()with directRawMatrix().Dataindexing in transfer.go, frequency.go, feedback_lft.go hot paths
Lqr/Kalman Workspace Propagation
Lqe/Kalman/KalmdproxyRiccatiOptsto inner Care/Dare calls- Kalman_N100: 2,213 Ki → 309 Ki B/op (86% reduction)
v0.4.0
Breaking Changes
System.InternalDelay,System.B2,System.C2,System.D12,System.D21,System.D22fields replaced bySystem.LFT *LFTDelaynested structsys.InternalDelay→sys.LFT.Tausys.B2→sys.LFT.B2(same for C2, D12, D21, D22)nilcheck:sys.LFT == nilreplaces individual field checks
Improvements
- Add
copyStrided/copyBlockmatrix copy helpers, eliminating ~100 duplicate inline copy loops - Unify duplicate
extractBlock/extractSubmatrixfunctions - -3.5% benchmark geomean improvement across 101 benchmarks (up to -17% on delay operations)
- Net -51 lines of code
Tests & Coverage
- Add MATLAB delay system tests (Pade, Thiran, c2d, feedback, margins)
- Add exact MATLAB/python-control expected values to all tests
- Tighten test tolerances
- Coverage 83.9% → 85.7%
Other
- Update README with full API coverage
v0.3.0
Performance & Robustness
Simulation hot-path optimizations
- Replace gonum
MulVec/AddVecwrappers with directblas64.Gemvcalls insimulateNoDelay(-22%) andsimulateWithInternalDelay(-74%) - Fuse
A*x + B*uintoGemv(beta=1)accumulation, eliminating intermediate buffers
Frequency response allocation reduction
- Add
TransferFunc.evalIntofor allocation-free transfer function evaluation FreqResponseallocs -71% (560→160),Bodeallocs -81% (992→192)
Numerical robustness (#347510c)
- Harden numerical robustness and close test coverage gaps
Simulation performance (#4429669)
- Batch delayed MIMO simulation to remove per-input replay overhead (#2)
New benchmarks
- 7 MATLAB-inspired benchmarks: DC motor, Boeing 747 lateral/longitudinal, mass-spring-damper chain, large system (50 states), feedback+simulate pipeline, large MIMO Bode
v0.2.0
Bug Fixes
-
Fix DiscretizeZOH silently dropping InternalDelay (#1)
DiscretizeZOHandDiscretizenow properly handle systems with internal delays (LFT structure produced byFeedbackwith delays in the feedback path).Two underlying bugs fixed:
DiscretizeZOH/Discretizesilently discarded the LFT delay structure (B2,C2,D12,D21,D22,InternalDelay), reducing the system to its delay-free rational part — causingS ≈ 1andT ≈ 0instead of correct sensitivity functions.discretizeWithInternalDelaycopied the continuous-timeB2unchanged instead of computingBd2 = Γ·B2via the augmented matrix exponential, making the delayed feedback gain ~20× too large.