/** * Control-theory matrix equations — `dlyap`/`care`/`dare`. * * These solve the algebraic matrix equations that underlie LQR (linear- * quadratic regulator) and Kalman-filter design: * * - {@link dlyap} — discrete Lyapunov equation `A X Aᵀ − X + Q = 0`, solved * directly via the Kronecker-product linear system * `(I − A⊗A) vec(X) = vec(Q)` (exact for the small `n` this targets; * the codebase's `linsolve` does the O(n²)³ = O(n⁶) dense solve). * - {@link care} — continuous algebraic Riccati equation * `AᵀX + XA − X B R⁻¹ Bᵀ X + Q = 0`, solved via the **matrix sign * function** applied to the Hamiltonian `H = [[A, −BR⁻¹Bᵀ], [−Q, −Aᵀ]]`. * This needs no stabilizing initial gain that a Kleinman/Newton iteration * would require (the natural starting point `X₀ = 0` is not stabilizing * for e.g. the classic double-integrator `A = [[0,1],[0,0]]`, which is only * marginally stable). The sign-function Newton iteration converges * unconditionally (given the standard stabilizability/detectability * assumptions) directly from `H` itself. * * The classical alternative is the **Hamiltonian-eigenvector** construction * (eigendecompose `H`, take the stable invariant subspace `U=[U₁;U₂]`, * `X = U₂U₁⁻¹`). Since `matrix`'s `eig` gained complex eigenvectors * (`vectorsIm`, 2026-07-16) that path IS now implementable — but it was * prototyped and **measured against the sign function** (2026-07-18) and * found strictly **less** accurate on every corpus case, with the gap * widening on ill-conditioned Hamiltonians (near-uncontrollable / * lightly-damped: Riccati residual ~1e-10..1e-13 for the eigenvector path * vs ~1e-13..1e-14 for the sign function). Eigenvector-basis subspace * extraction is the numerically inferior classical method (ill-conditioned * eigenvectors near defectiveness), which is why LAPACK/scipy use ordered * Schur, not eigenvectors. The self-contained sign-function method is * therefore retained. (`funm`/`cosm`/`sinm` likewise use eigen*values*, * not eigenvectors — see `matrix-functions.ts`.) * - {@link dare} — discrete algebraic Riccati equation * `AᵀXA − X − AᵀXB(R + BᵀXB)⁻¹BᵀXA + Q = 0`, solved via the * **structure-preserving doubling algorithm** (SDA), the discrete-time * analogue of the sign-function method above: it iterates a triple * `(Aₖ, Gₖ, Hₖ)` with quadratic convergence and, likewise, needs no * stabilizing initial gain. * * All three return the (numerically symmetrized) stabilizing solution `X`. * Both `care`/`dare` require `R` invertible and `(A, B)` stabilizable — the * standard LQR well-posedness assumptions. * * @packageDocumentation */ /** * Solve the discrete-time Lyapunov (Stein) equation `A X Aᵀ − X + Q = 0` for * `X`, given square `A` and `Q` of the same size. * * Builds the Kronecker-product linear system `(I − A⊗A) vec(X) = vec(Q)` * explicitly (practical for the `n ≲ 20` this targets — the system has * `n²` unknowns) and solves it with `linsolve`. * * @example * dlyap([[0.5, 0], [0, 0.5]], [[1, 0], [0, 1]]) // => (4/3) * I (since 0.25x - x + 1 = 0) */ export declare function dlyap(A: number[][], Q: number[][]): number[][]; /** * Solve the continuous-time algebraic Riccati equation (CARE) * `AᵀX + XA − X B R⁻¹ Bᵀ X + Q = 0` for the stabilizing symmetric `X`, * given `A` (n×n), `B` (n×m), `Q` (n×n, typically PSD), `R` (m×m, invertible). * * Method: Newton iteration for the **matrix sign function** of the * Hamiltonian `H = [[A, −BR⁻¹Bᵀ], [−Q, −Aᵀ]]` (2n×2n): * * `Zₖ₊₁ = ½(cₖ Zₖ + cₖ⁻¹ Zₖ⁻¹)`, `Z₀ = H`, `cₖ = √(‖Zₖ⁻¹‖₁ / ‖Zₖ‖₁)` * * converging quadratically to `S = sign(H)`. The projector `P = ½(I − S)` * has range equal to `H`'s stable (Re λ < 0) invariant subspace; taking its * first `n` columns `U = [U₁; U₂]` (split at the `n`-th row) gives * `X = U₂ U₁⁻¹` (real arithmetic throughout — no complex eigenvectors * needed, unlike the classical Hamiltonian-eigenvector construction). * * @example * care([[0, 1], [0, 0]], [[0], [1]], [[1, 0], [0, 1]], [[1]]) * // => [[sqrt(3), 1], [1, sqrt(3)]] (pinned vs scipy.linalg.solve_continuous_are) */ export declare function care(A: number[][], B: number[][], Q: number[][], R: number[][]): number[][]; /** * Solve the discrete-time algebraic Riccati equation (DARE) * `AᵀXA − X − AᵀXB(R + BᵀXB)⁻¹BᵀXA + Q = 0` for the stabilizing symmetric * `X`, given `A` (n×n), `B` (n×m), `Q` (n×n, typically PSD), `R` (m×m, * invertible). * * Method: the **structure-preserving doubling algorithm** (SDA) — the * discrete-time analogue of the sign-function iteration used by {@link care}. * Starting from `A₀ = A`, `G₀ = B R⁻¹ Bᵀ`, `H₀ = Q`, it iterates * * `Aₖ₊₁ = Aₖ(I + GₖHₖ)⁻¹Aₖ` * `Gₖ₊₁ = Gₖ + Aₖ(I + GₖHₖ)⁻¹Gₖ Aₖᵀ` * `Hₖ₊₁ = Hₖ + Aₖᵀ Hₖ(I + GₖHₖ)⁻¹Aₖ` * * with `Hₖ → X` quadratically. Like {@link care}'s sign-function method, this * needs no stabilizing initial gain (unlike a Kleinman/Newton iteration on * the DARE directly) — only the standard stabilizability/detectability * assumptions. * * @example * dare([[1, 1], [0, 1]], [[0], [1]], [[1, 0], [0, 1]], [[1]]) * // => [[2.94712297, 2.36920541], [2.36920541, 4.61313426]] * // (pinned vs scipy.linalg.solve_discrete_are) */ export declare function dare(A: number[][], B: number[][], Q: number[][], R: number[][]): number[][]; //# sourceMappingURL=control-equations.d.ts.map