Skip to content

Classical control theory analysis stack #198

@rororowyourboat

Description

@rororowyourboat

Summary

Add classical control theory analysis capabilities across four existing packages, bridging the gap between GDS's structural specification strengths and the numerical/frequency-domain analysis that control engineers expect. Zero new third-party dependencies — everything fits within existing extras.

Motivation

GDS can specify, compose, verify, and simulate control systems structurally. The symbolic subpackage already computes LinearizedSystem (A, B, C, D) via Jacobian linearization. But the framework stops there — it cannot answer "is this system stable?", "what are the gain/phase margins?", "what does the Bode plot look like?", or "what LQR gain matrix should I use?". These are table-stakes questions for any control practitioner.

The key insight is that LinearizedSystem.A/B/C/D (already list[list[float]]) is the exact gateway to the entire classical control stack. Every capability below consumes those matrices.

Architecture

Four layers, matching existing package responsibilities:

                     ┌─────────────────────────────────────┐
                     │         gds-domains/symbolic         │
                     │  (SymPy — mathematical objects)      │
                     │                                      │
                     │  linearize.py  [exists]              │
                     │  transfer.py   [new — #199]          │
                     │  delay.py      [new — #200]          │
                     └──────────┬───────────┬──────────────┘
                                │           │
               list[list[float]]│           │ SymPy exprs
                                │           │
          ┌─────────────────────▼──┐   ┌────▼──────────────────┐
          │     gds-analysis       │   │      gds-proof        │
          │  (scipy — numerical)   │   │  (SymPy — formal)     │
          │                        │   │                        │
          │  linear.py   [#201]    │   │  lyapunov.py  [#203]  │
          │  response.py [#202]    │   │                        │
          └──────────┬─────────────┘   └────────────────────────┘
                     │
          plain arrays (freqs, mag, phase)
                     │
          ┌──────────▼─────────────┐
          │       gds-viz          │
          │  (matplotlib — plots)  │
          │                        │
          │  frequency.py  [#204]  │
          │  response.py   [#204]  │
          └────────────────────────┘

Interface contract: No numpy arrays cross package boundaries. All inter-package data is list[float] / list[list[float]]. Numpy/scipy are internal implementation details.

Child Issues

# Package New File Scope Deps Tier
#199 gds-domains symbolic/transfer.py Transfer functions, poles/zeros, controllability/observability, sensitivity functions [symbolic] (existing) 1
#200 gds-domains symbolic/delay.py Padé approximation for time delays [symbolic] (existing) 2
#201 gds-analysis linear.py Eigenvalue stability, frequency response, margins, discretization, LQR, Kalman [continuous] (existing) 1
#202 gds-analysis response.py Step/impulse response computation + time-domain metrics [continuous] (existing) 1
#203 gds-proof analysis/lyapunov.py Lyapunov stability proofs, quadratic Lyapunov, passivity certificates sympy (existing hard dep) 2
#204 gds-viz frequency.py + response.py Bode, Nyquist, Nichols, root locus, step/impulse response plots [control] new extra 1

Dependency Graph Between Issues

#199 (transfer functions)
  ↑
  ├── #200 (delay — uses TransferFunction)
  ├── #201 (linear analysis — uses TF for margins)
  │     ↑
  │     └── #204 (viz — plots data from #201)
  │
  └── #204 (viz — root locus uses TF coefficients)

#202 (response metrics) — independent, can start immediately
  ↑
  └── #204 (viz — annotates plots with metrics from #202)

#203 (Lyapunov proofs) — independent, can start immediately

Suggested Implementation Order

  1. gds-domains/symbolic: transfer functions, poles/zeros, and sensitivity functions #199 Transfer functions — foundational; gds-domains/symbolic: Padé approximation for time delay modeling #200, gds-analysis: numerical linear systems analysis, discretization, and controller synthesis #201, gds-viz: frequency response and time-domain response plots #204 depend on this
  2. gds-analysis: step and impulse response computation with time-domain metrics #202 Response metrics — independent, trivial, immediately useful for existing examples
  3. gds-analysis: numerical linear systems analysis, discretization, and controller synthesis #201 Numerical linear analysis — eigenvalues + frequency response + discretization + LQR
  4. gds-viz: frequency response and time-domain response plots #204 Control plots — visualize the numbers from gds-analysis: numerical linear systems analysis, discretization, and controller synthesis #201 and gds-analysis: step and impulse response computation with time-domain metrics #202
  5. gds-proof: Lyapunov stability proofs and passivity certificates #203 Lyapunov proofs — independent, extends gds-proof
  6. gds-domains/symbolic: Padé approximation for time delay modeling #200 Time delay — standalone, lower priority

Note: #202 and #203 have no upstream dependencies and can be implemented in parallel with #199.

End-to-End Workflow (Target State)

from gds_domains.symbolic import SymbolicControlModel
from gds_domains.symbolic.transfer import ss_to_tf, poles
from gds_analysis.linear import frequency_response, lqr, discretize, is_stable
from gds_analysis.response import step_response, step_response_metrics
from gds_viz.frequency import bode_plot, root_locus_plot

# Existing: define + linearize
model = SymbolicControlModel(...)
ls = model.linearize(x0=[0.0], u0=[0.0], param_values={...})

# New Layer 1: symbolic analysis
tf = ss_to_tf(ls)
print(poles(tf))                    # → [(-1+2j), (-1-2j)]

# New Layer 2: numerical analysis
assert is_stable(ls.A)
omega, mag, phase = frequency_response(ls.A, ls.B, ls.C, ls.D)
K, S, E = lqr(ls.A, ls.B, Q=..., R=...)
Ad, Bd, Cd, Dd = discretize(ls.A, ls.B, ls.C, ls.D, dt=0.01)
t, y = step_response(ls.A, ls.B, ls.C, ls.D, t_span=(0, 10))
metrics = step_response_metrics(t, y, setpoint=1.0)

# New Layer 3: visualization
bode_plot(omega, mag, phase)
root_locus_plot(tf.num, tf.den)

MATLAB Tech Talk Concepts Addressed

Video Concepts Covered Issues
1 — Everything About Control Theory Stability analysis, LQR, Kalman filter #201, #203
2 — Transfer Functions S-domain, poles/zeros, root locus #199, #204
3 — Step Response Rise time, overshoot, settling time, steady-state error #202, #204
4 — Bode/Nyquist/Nichols Frequency response, gain/phase margin #201, #204
5 — Transfer Function in Code Discretization (Tustin, ZOH, Euler) #201
7 — Gang of Six Sensitivity functions, loop shaping #199
8 — Passivity Lyapunov stability, energy-based proofs #203
9 — Gain Scheduling Multi-point linearization + LQR #201
10 — Notch Filters Transfer function composition #199, #204
11 — Time Delay Padé approximation, phase lag #200
12 — Padé Approximations Rational polynomial delay model #200
13 — Non-Minimum Phase RHP zero detection #199, #204
14 — Z-Transform Discretization bridge #201

Out of Scope

  • MPC — needs QP/NLP solver (cvxpy, casadi); real new dependency
  • HIL testing — runtime/hardware concern
  • Auto-code generation — potential future for gds-interchange
  • Requirements DB / ReqIF — document management

Metadata

Metadata

Assignees

No one assigned

    Labels

    control-theoryClassical control theory capabilitiesenhancementNew feature or requestroadmapImprovement roadmap item

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions