Analysis¶
Analysis is the public namespace for structural checks and transfer-oriented inspection of state-space models.
The namespace includes:
ctrb()andobsv()for controllability and observability matricespoles()for eigenvalue inspectionevalfr(),freqresp(), anddcgain()for transfer evaluationctrb_gramian()andobsv_gramian()for finite-horizon continuous Gramianslyap()anddlyap()for continuous and discrete Lyapunov equation solverszeros()for transmission zeros of square LTI systems
Minimal Example¶
import jax
jax.config.update("jax_enable_x64", True)
import jax.numpy as jnp
import contrax as cx
sys = cx.ss(
jnp.array([[0.0, 1.0], [-2.0, -0.4]]),
jnp.array([[0.0], [1.0]]),
jnp.array([[1.0, 0.0]]),
jnp.zeros((1, 1)),
)
poles = cx.poles(sys)
dc = cx.dcgain(sys)
Wc = cx.ctrb_gramian(sys, t=2.0)
Conventions¶
poles(sys)returns the system eigenvaluesevalfr(sys, point)evaluates the transfer map at one complex pointfreqresp(sys, omega)evaluates over a frequency griddcgain(sys)evaluates the zero-frequency or steady-state gain
These helpers are intentionally focused on state-space analysis rather than a full transfer-function algebra layer.
Transform Behavior¶
Most analysis helpers are direct array computations on fixed system data, so
they fit naturally into jit and batched workflows when the underlying linear
algebra path does.
The Gramian helpers are continuous-time, matrix-exponential-based utilities rather than hot-path primitives.
Lyapunov Equations¶
lyap(A, Q) solves the continuous Lyapunov equation \(A X + X A^\top + Q = 0\).
dlyap(A, Q) solves the discrete form \(A X A^\top - X + Q = 0\).
Both are design-time utilities implemented via the Kronecker-form linear system. They are not intended for use in hot paths.
Transmission Zeros¶
zeros(sys) computes the transmission zeros of a square LTI system.
For invertible \(D\), zeros are the eigenvalues of \(A - B D^{-1} C\).
For \(D = 0\) (the common case), zeros are computed via controlled-invariant subspace iteration using SVD-based null-space and range-space operations — no generalized Schur decomposition is required, keeping the computation JAX-native without a GPU-unfriendly LAPACK dispatch.
Numerical Notes¶
Finite-horizon Gramians use a Van Loan block-matrix exponential construction
and require jax_enable_x64=True.
When comparing realizations, prefer invariant checks such as rank, conditioning, pole location, and transfer behavior. Exact matrix equality is only meaningful when the realization basis is intentionally identical.
Related Pages¶
- Systems for the models these helpers inspect
- Simulation for time-domain validation alongside frequency- domain or structural checks
- JAX-native workflows for broader end-to-end usage patterns
contrax.analysis
¶
contrax.analysis — structural and transfer analysis helpers.
ctrb(sys: DiscLTI | ContLTI) -> Array
¶
Construct the controllability matrix of a state-space system.
Forms [B, AB, A^2 B, ..., A^{n-1} B] for a continuous or discrete linear
system.
This is a structural analysis primitive rather than a yes-or-no
controllability test by itself. Users typically inspect the downstream rank
or conditioning of the returned matrix. The implementation uses
jax.lax.scan so it composes with transforms, but it is still intended
primarily for design-time analysis rather than tight inner loops.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
DiscLTI | ContLTI
|
Continuous or discrete LTI system. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
Controllability matrix. Shape: |
Source code in contrax/analysis.py
ctrb_gramian(sys: ContLTI, t: float = 10.0) -> Array
¶
Compute the finite-horizon controllability Gramian.
Uses the Van Loan block-matrix exponential construction to evaluate the finite-horizon controllability Gramian in closed form for continuous-time linear systems.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
ContLTI
|
Continuous-time linear system. |
required |
t
|
float
|
Integration horizon. Shape: scalar. |
10.0
|
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
Finite-horizon controllability Gramian. Shape: |
Source code in contrax/analysis.py
dcgain(sys: DiscLTI | ContLTI) -> Array
¶
Return the zero-frequency or steady-state gain of an LTI system.
For continuous systems this evaluates G(0). For discrete systems this
evaluates G(1).
This helper assumes the corresponding resolvent is nonsingular. It is a useful quick check for low-frequency behavior, but it is not by itself a stability certificate.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
DiscLTI | ContLTI
|
Continuous or discrete LTI system. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
DC gain matrix. Shape: |
Source code in contrax/analysis.py
dlyap(A: Array, Q: Array) -> Array
¶
Solve the discrete Lyapunov equation AXA^T - X + Q = 0.
Returns the unique symmetric solution X when all eigenvalues of A satisfy |λ_i λ_j| ≠ 1 (i.e. A is Schur-stable or the product condition holds).
Uses an explicit Kronecker-form linear solve. This is exact for small systems but scales as O(n^6) in the dense case; it is intended for design-time use, not inner-loop computation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
A
|
Array
|
State matrix. Shape: |
required |
Q
|
Array
|
Right-hand-side matrix. Shape: |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
Solution X such that AXA^T - X = -Q. Shape: |
Examples:
>>> import jax.numpy as jnp
>>> import contrax as cx
>>> A = jnp.array([[0.5, 0.0], [0.0, 0.3]])
>>> Q = jnp.eye(2)
>>> X = cx.dlyap(A, Q)
>>> jnp.allclose(A @ X @ A.T - X + Q, 0.0, atol=1e-6)
Array(True, dtype=bool)
Source code in contrax/analysis.py
evalfr(sys: DiscLTI | ContLTI, point: complex | Array) -> Array
¶
Evaluate the transfer matrix at a complex frequency point.
For continuous systems this computes G(s) = C (sI - A)^-1 B + D. For
discrete systems it computes G(z) = C (zI - A)^-1 B + D.
This is the first transfer-evaluation helper in Contrax rather than a full transfer-function algebra layer. It is intended for design-time analysis, spot checks, and building higher-level frequency-response tools.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
DiscLTI | ContLTI
|
Continuous or discrete LTI system. |
required |
point
|
complex | Array
|
Complex evaluation point |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
Transfer matrix evaluated at |
Source code in contrax/analysis.py
freqresp(sys: DiscLTI | ContLTI, omega: Array) -> Array
¶
Evaluate the frequency response over angular frequencies.
Continuous systems are evaluated on the imaginary axis at s = j omega.
Discrete systems are evaluated on the unit circle at
z = exp(j omega dt).
This is a basic state-space frequency-response helper. It returns the complex transfer matrix at each frequency but does not yet format Bode or Nyquist plots for you.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
DiscLTI | ContLTI
|
Continuous or discrete LTI system. |
required |
omega
|
Array
|
Angular frequencies in rad/s. Shape: |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
Complex frequency response. Shape: |
Source code in contrax/analysis.py
lyap(A: Array, Q: Array) -> Array
¶
Solve the continuous Lyapunov equation AX + XA^T + Q = 0.
Returns the unique symmetric solution X when all eigenvalues of A satisfy Re(λ_i + λ_j) ≠ 0 (i.e. A is stable or the sum condition holds).
Uses an explicit Kronecker-form linear solve. This is exact for small systems but scales as O(n^6) in the dense case; it is intended for design-time use, not inner-loop computation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
A
|
Array
|
State matrix. Shape: |
required |
Q
|
Array
|
Right-hand-side matrix. Shape: |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
Solution X such that AX + XA^T = -Q. Shape: |
Examples:
>>> import jax.numpy as jnp
>>> import contrax as cx
>>> A = jnp.array([[-1.0, 0.0], [0.0, -2.0]])
>>> Q = jnp.eye(2)
>>> X = cx.lyap(A, Q)
>>> jnp.allclose(A @ X + X @ A.T + Q, 0.0, atol=1e-6)
Array(True, dtype=bool)
Source code in contrax/analysis.py
obsv(sys: DiscLTI | ContLTI) -> Array
¶
Construct the observability matrix of a state-space system.
Forms [C; CA; CA^2; ...; CA^{n-1}] for a continuous or discrete linear
system.
This is a structural analysis primitive rather than a yes-or-no
observability test by itself. Users typically inspect the downstream rank
or conditioning of the returned matrix. The implementation uses
jax.lax.scan so it composes with transforms, but it is still intended
primarily for design-time analysis rather than tight inner loops.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
DiscLTI | ContLTI
|
Continuous or discrete LTI system. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
Observability matrix. Shape: |
Source code in contrax/analysis.py
obsv_gramian(sys: ContLTI, t: float = 10.0) -> Array
¶
Compute the finite-horizon observability Gramian.
Uses the same Van Loan block-matrix exponential construction as
ctrb_gramian(), applied to the dual system.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
ContLTI
|
Continuous-time linear system. |
required |
t
|
float
|
Integration horizon. Shape: scalar. |
10.0
|
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
Finite-horizon observability Gramian. Shape: |
Source code in contrax/analysis.py
poles(sys: DiscLTI | ContLTI) -> Array
¶
Return the poles of a state-space system.
For the current state-space-only Contrax surface, poles are the eigenvalues
of the state matrix A.
This is a lightweight analysis helper that composes with jit and vmap
for fixed-size systems. The return dtype is generally complex, even for
real-valued systems with real poles.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
DiscLTI | ContLTI
|
Continuous or discrete LTI system. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
System poles. Shape: |
Source code in contrax/analysis.py
zeros(sys: DiscLTI | ContLTI) -> Array
¶
Return the transmission zeros of a state-space system.
Transmission zeros are the values s (continuous) or z (discrete) for
which the Rosenbrock system matrix M(s) = [[sI - A, -B], [C, D]] loses rank.
D = 0 (most common case): uses a controlled-invariant subspace iteration to find V, the largest (A, B)-controlled invariant subspace in ker(C). Returns the eigenvalues of A restricted to V. Works for both SISO and square MIMO (p == m) systems.
D invertible (square, full rank): uses the direct formula
zeros = eig(A - B @ solve(D, C)).
Non-square D and rank-deficient non-zero D are not yet supported.
This is a design-time structural analysis primitive; it is not intended
for use inside jit-compiled loops.
Reference: Basile & Marro (1992), "Controlled and Conditioned Invariants in Linear System Theory", Prentice-Hall.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sys
|
DiscLTI | ContLTI
|
Continuous or discrete LTI system. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
Array |
Array
|
Transmission zeros. Complex dtype. Number of zeros equals |
Array
|
the dimension of V* (D = 0) or |
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
For non-square D or rank-deficient non-zero D. |