---
title: "Type System Architecture"
subtitle: "Foundational Types and Structured Results"
author: "Gil Benezer"
date: today
format:
html:
toc: true
toc-depth: 5
code-fold: show
code-tools: true
theme: cosmo
execute:
eval: true
cache: true
warning: false
---
## Overview {#sec-overview}
The **Type System** is the **foundational layer (Layer 0)** that provides semantic types, structured results, and type-safe interfaces for the entire framework. It consists of **8 focused modules** defining over **200 type aliases** and **structured dictionaries**.
::: {.callout-important}
## Internal Framework - Not for Direct Use
**This documentation describes internal framework types.** Users should NOT directly reference or manipulate these types except through system methods. The type system is an internal implementation detail that provides:
- Type safety through static checking
- IDE autocomplete support
- Clear documentation of expected types
- Backend-agnostic interfaces
```python
# ✓ CORRECT: Types work transparently through system methods
system = Pendulum()
result = system.integrate(x0, u=None, t_span=(0, 10))
# result is IntegrationResult (TypedDict) - IDE knows fields available
A, B = system.linearize(x_eq, u_eq)
# A is StateMatrix, B is InputMatrix - types guide usage
# ✗ INCORRECT: No need to import or reference types directly
from cdesym.types.core import StateVector, ControlVector
x: StateVector = np.array([1.0, 0.0]) # Unnecessary annotation
```
The type system enables type checking and IDE support while remaining invisible during normal use. This documentation is provided for framework developers and advanced users implementing custom components.
:::
```{python}
#| label: setup-imports
#| echo: false
#| output: false
# Global setup for all code examples
import numpy as np
import torch
import jax.numpy as jnp
import sympy as sp
from typing import Union, Tuple, Dict, List, Optional, Callable, Any, Literal, TypedDict, Protocol
from enum import Enum
import warnings
warnings.filterwarnings('ignore', category=UserWarning)
# Import for examples
from cdesym import Pendulum, ContinuousSymbolicSystem
```
## Architecture Philosophy {#sec-architecture-philosophy}
**Type-Driven Design** - Types are not just annotations—they are architecture.
The type system enables:
1. **Semantic Clarity** - Names convey mathematical meaning (`StateVector`, not `ArrayLike`)
2. **Type Safety** - Static checking via mypy/pyright catches errors before runtime
3. **IDE Support** - Autocomplete knows `result['t']` exists and is `TimePoints`
4. **Backend Agnosticism** - Same types work with NumPy/PyTorch/JAX
5. **Structured Results** - TypedDict for dictionaries (not plain `dict`)
6. **Self-Documentation** - Type signatures encode mathematical constraints
**Example comparison:**
```{python}
#| label: ex-type-comparison
#| eval: false
# ✗ Bad: Unclear types and semantics
def bad(x, u): # What are these? What dimensions? What backend?
return x + u
# ✓ Good: Clear semantics and constraints
def good(x: StateVector, u: ControlVector) -> StateVector:
"""Clear: state in, control in, state out. Works with any backend."""
return x + u
```
## Framework Layers {#sec-framework-layers}
```
┌────────────────────────────────────────────────────────────┐
│ APPLICATION LAYER │
│ (UI Framework, Delegation Layer, Integration Framework) │
└─────────────────────┬──────────────────────────────────────┘
│ uses types from
↓
┌────────────────────────────────────────────────────────────┐
│ TYPE SYSTEM (Layer 0) │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ FOUNDATIONAL TYPES │ │
│ │ • core.py - Vectors, matrices │ │
│ │ • backends.py - Backend enums, configs │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ DOMAIN TYPES │ │
│ │ • trajectories.py - Time series results │ │
│ │ • linearization.py - Jacobian tuples │ │
│ │ • symbolic.py - SymPy types │ │
│ │ • control_classical.py - Control design results │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ STRUCTURAL TYPES │ │
│ │ • protocols.py - Abstract interfaces │ │
│ │ • utilities.py - Helper types, guards │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
```
## Foundational Types {#sec-foundational-types}
### core.py: Fundamental Building Blocks {#sec-corepy}
**File:** `core.py`
The `core` module provides fundamental building blocks for all other types in the framework. These types establish semantic clarity and backend agnosticism throughout the library.
**Key categories:**
#### Multi-Backend Arrays (20+ types)
Backend-agnostic array types that work transparently with NumPy, PyTorch, and JAX:
```python
ArrayLike = Union[np.ndarray, torch.Tensor, jnp.ndarray]
NumpyArray = np.ndarray
TorchTensor = torch.Tensor
JaxArray = jnp.ndarray
ScalarLike = Union[float, int, np.number, torch.Tensor, jnp.ndarray]
IntegerLike = Union[int, np.integer]
```
**Usage in framework:**
```{python}
#| label: ex-multi-backend
system = Pendulum()
# Same function works with all backends
x_np = np.array([1.0, 0.0]) # NumPy
x_torch = torch.tensor([1.0, 0.0]) # PyTorch
x_jax = jnp.array([1.0, 0.0]) # JAX
# Backend detected automatically - no type errors
dx_np = system(x_np, np.zeros(1))
dx_torch = system(x_torch, torch.zeros(1))
dx_jax = system(x_jax, jnp.zeros(1))
print("✓ Backend-agnostic type system")
```
#### Semantic Vector Types (15+ types)
Vector types that convey mathematical meaning:
```python
StateVector # x ∈ ℝⁿˣ - System state
ControlVector # u ∈ ℝⁿᵘ - Control input
OutputVector # y ∈ ℝⁿʸ - Measured output
NoiseVector # w ∈ ℝⁿʷ - Stochastic noise
EquilibriumState # x_eq - Equilibrium state
EquilibriumControl # u_eq - Equilibrium control
TimeDerivative # dx/dt - State derivative
StateIncrement # δx - State deviation
ControlIncrement # δu - Control deviation
```
**Why semantic types matter:**
```{python}
#| label: ex-semantic-types
#| eval: false
# ✗ Unclear what arrays represent
def compute(arr1, arr2, arr3):
return arr1 @ arr2 + arr3
# ✓ Clear mathematical meaning
def compute_control(x: StateVector, K: GainMatrix, u_ff: ControlVector) -> ControlVector:
"""State feedback with feedforward: u = -K*x + u_ff"""
return -K @ x + u_ff
```
#### Matrix Types (30+ types)
Matrix types organized by mathematical purpose:
**Dynamics matrices:**
```python
StateMatrix # A ∈ ℝⁿˣˣⁿˣ - ∂f/∂x
InputMatrix # B ∈ ℝⁿˣˣⁿᵘ - ∂f/∂u
DiffusionMatrix # G ∈ ℝⁿˣˣⁿʷ - Noise intensity
```
**Observation matrices:**
```python
OutputMatrix # C ∈ ℝⁿʸˣⁿˣ - ∂h/∂x
FeedthroughMatrix # D ∈ ℝⁿʸˣⁿᵘ - Direct feedthrough
```
**Control matrices:**
```python
GainMatrix # K ∈ ℝⁿᵘˣⁿˣ - Feedback gain
CostMatrix # Q ∈ ℝⁿˣˣⁿˣ - State cost
ControlCostMatrix # R ∈ ℝⁿᵘˣⁿᵘ - Control cost
```
**Stochastic matrices:**
```python
CovarianceMatrix # P ∈ ℝⁿˣˣⁿˣ - Covariance
ProcessNoiseMatrix # Q ∈ ℝⁿˣˣⁿˣ - Process noise cov
MeasurementNoiseMatrix # R ∈ ℝⁿʸˣⁿʸ - Measurement noise cov
```
**Special matrices:**
```python
IdentityMatrix # I ∈ ℝⁿˣⁿ
ZeroMatrix # 0 ∈ ℝᵐˣⁿ
GramianMatrix # Controllability/observability gramian
ControllabilityMatrix # [B AB A²B ... Aⁿ⁻¹B]
ObservabilityMatrix # [C; CA; CA²; ...; CAⁿ⁻¹]
```
#### Function Signatures (10+ types)
Callable types that define function interfaces:
```python
DynamicsFunction # (x, u) → dx/dt
OutputFunction # (x) → y
ControlPolicy # (t, x) → u
CostFunction # (x, u) → scalar
ObserverFunction # (y, u) → x_hat
```
**Usage example:**
```{python}
#| label: ex-function-types
from cdesym.types import StateVector, ControlVector
# Function signature guides implementation
def simulate_with_controller(
system: Callable[[StateVector, ControlVector], StateVector], # DynamicsFunction
controller: Callable[[float, StateVector], ControlVector], # ControlPolicy
x0: StateVector,
t_span: Tuple[float, float]
) -> Tuple[np.ndarray, np.ndarray]:
"""Type signatures document the contract."""
# Implementation here
pass
print("✓ Function types document interfaces")
```
#### System Properties (15+ types)
Types for system dimensions and properties:
```python
StateDimension # nx - Number of states
ControlDimension # nu - Number of controls
OutputDimension # ny - Number of outputs
NoiseDimension # nw - Number of Wiener processes
SystemOrder # order - Differential order
EquilibriumPoint # (x_eq, u_eq) - Tuple
EquilibriumName # str - Named equilibrium
```
### backends.py: Backend Configuration {#sec-backendspy}
**File:** `backends.py`
The `backends` module defines backend selection, device management, and method specification types.
**Key categories:**
#### Backend Types
```python
Backend = Literal["numpy", "torch", "jax"]
Device = str # 'cpu', 'cuda:0', 'mps', 'tpu'
class BackendConfig(TypedDict, total=False):
backend: Backend
device: Optional[Device]
dtype: Optional[str] # 'float32', 'float64'
```
**Usage in framework:**
```{python}
#| label: ex-backend-config
system = Pendulum()
# Backend configuration handled internally
print(f"Default backend: {system.backend.default_backend}")
print(f"Default device: {system.backend.preferred_device}")
# Users interact through simple methods
system.set_default_backend('numpy')
system.to_device('cpu')
with system.use_backend('torch'):
print(f"Temporary backend: {system.backend.default_backend}")
```
#### Integration Methods
```python
IntegrationMethod = str # 'RK45', 'dopri5', 'tsit5', etc.
# Specific categories
OdeMethod = str # Deterministic methods
SdeMethod = str # Stochastic methods
FixedStepMethod = str # Fixed-step methods
AdaptiveMethod = str # Adaptive methods
```
#### Discretization Methods
```python
DiscretizationMethod = Literal[
"exact", # Matrix exponential
"euler", # Forward Euler
"tustin", # Bilinear transform
"backward", # Backward Euler
"matched", # Zero-order hold
]
```
#### SDE Types
```python
SDEType = Literal["ito", "stratonovich"]
class NoiseType(Enum):
ADDITIVE = "additive"
MULTIPLICATIVE = "multiplicative"
MULTIPLICATIVE_DIAGONAL = "multiplicative_diagonal"
MULTIPLICATIVE_SCALAR = "multiplicative_scalar"
MULTIPLICATIVE_GENERAL = "multiplicative_general"
UNKNOWN = "unknown"
class ConvergenceType(Enum):
STRONG = "strong" # Pathwise convergence
WEAK = "weak" # Distribution convergence
```
#### System Configuration
```python
class SystemConfig(TypedDict, total=False):
"""Complete system configuration."""
nx: int # State dimension
nu: int # Control dimension
ny: int # Output dimension
nw: int # Noise dimension
order: int # System order
dt: Optional[float] # Time step (discrete)
backend: Backend
device: Device
```
## Domain Types {#sec-domain-types}
### trajectories.py: Time Series Results {#sec-trajectoriespy}
**File:** `trajectories.py`
The `trajectories` module defines types for time series data and simulation results.
**Key categories:**
#### Trajectory Types
```python
StateTrajectory = ArrayLike # (T, nx) or (T, batch, nx)
ControlSequence = ArrayLike # (T, nu) or (T, batch, nu)
OutputSequence = ArrayLike # (T, ny)
NoiseSequence = ArrayLike # (T, nw)
```
**Convention: Time-major ordering** - All trajectories use `(T, ...)` format where `T` is the first dimension.
#### Time Types
```python
TimePoints = ArrayLike # (T,) - Time grid
TimeSpan = Tuple[float, float] # (t0, tf) - Interval
TimeStep = float # dt - Step size
```
#### Integration Results (TypedDict)
**Deterministic ODE Integration:**
```python
class IntegrationResult(TypedDict, total=False):
"""ODE integration result."""
t: TimePoints # Time points
x: StateTrajectory # State trajectory (T, nx)
success: bool # Integration succeeded
message: str # Status message
nfev: int # Function evaluations
nsteps: int # Integration steps
integration_time: float # Wall time (seconds)
solver: str # Integrator name
# Optional fields (adaptive methods)
njev: int # Jacobian evaluations
nlu: int # LU decompositions
status: int # Solver status code
sol: Any # Dense output object
dense_output: bool # Dense output available
```
**Stochastic SDE Integration:**
```python
class SDEIntegrationResult(TypedDict, total=False):
"""SDE integration result (extends IntegrationResult)."""
# All IntegrationResult fields, plus:
diffusion_evals: int # Diffusion function calls
noise_samples: NoiseVector # Brownian increments used
n_paths: int # Number of trajectories
convergence_type: str # 'strong' or 'weak'
sde_type: str # 'ito' or 'stratonovich'
noise_type: str # Noise structure
```
**Batch Simulation:**
```python
class BatchSimulationResult(TypedDict):
"""Batched simulation result."""
t: TimePoints # (T,)
x: StateTrajectory # (T, batch, nx)
u: ControlSequence # (T, batch, nu)
batch_size: int
statistics: Dict[str, ArrayLike] # Mean, std, etc.
```
**Why TypedDict:**
```{python}
#| label: ex-typeddict-benefits
system = Pendulum()
x0 = np.array([1.0, 0.0])
# TypedDict result provides IDE support
result = system.integrate(x0, u=None, t_span=(0, 10))
# IDE knows these fields exist and their types
t = result['t'] # TimePoints
x = result['x'] # StateTrajectory
success = result['success'] # bool
solver = result['solver'] # str
print(f"Integration {'succeeded' if success else 'failed'}")
print(f"Solver: {solver}")
```
### linearization.py: Jacobian Types {#sec-linearizationpy}
**File:** `linearization.py`
The `linearization` module defines return types for linearization operations.
**Mathematical forms:**
**Continuous systems:**
$$\delta\dot{x} = A\delta x + B\delta u$$
where:
$$A = \frac{\partial f}{\partial x}\bigg|_{(x_{eq}, u_{eq})} \in \mathbb{R}^{n_x \times n_x}$$
$$B = \frac{\partial f}{\partial u}\bigg|_{(x_{eq}, u_{eq})} \in \mathbb{R}^{n_x \times n_u}$$
**Discrete systems:**
$$\delta x[k+1] = A_d\delta x[k] + B_d\delta u[k]$$
**Type definitions:**
#### Basic Linearization
```python
DeterministicLinearization = Tuple[StateMatrix, InputMatrix]
# Returns: (A, B) where
# A = ∂f/∂x - State Jacobian
# B = ∂f/∂u - Control Jacobian
StochasticLinearization = Tuple[StateMatrix, InputMatrix, DiffusionMatrix]
# Returns: (A, B, G) where
# A = ∂f/∂x
# B = ∂f/∂u
# G = ∂g/∂x or g(x_eq) - Diffusion
LinearizationResult = Union[DeterministicLinearization, StochasticLinearization]
# Polymorphic: works with both
```
#### Output Linearization
```python
ObservationLinearization = Tuple[OutputMatrix, FeedthroughMatrix]
# Returns: (C, D) where
# C = ∂h/∂x - Output Jacobian
# D = ∂h/∂u - Feedthrough (usually 0)
```
#### Full State-Space
```python
FullLinearization = Tuple[StateMatrix, InputMatrix, OutputMatrix, FeedthroughMatrix]
# Returns: (A, B, C, D)
FullStochasticLinearization = Tuple[
StateMatrix, InputMatrix, DiffusionMatrix, OutputMatrix, FeedthroughMatrix
]
# Returns: (A, B, G, C, D)
```
**Usage example:**
```{python}
#| label: ex-linearization-types
system = Pendulum()
# Get equilibrium
x_eq, u_eq = system.get_equilibrium('origin')
# Type annotation guides usage
A, B = system.linearize(x_eq, u_eq) # DeterministicLinearization
# Natural tuple unpacking
print(f"A shape: {A.shape}") # (nx, nx)
print(f"B shape: {B.shape}") # (nx, nu)
```
### symbolic.py: SymPy Integration {#sec-symbolicpy}
**File:** `symbolic.py`
The `symbolic` module provides types for SymPy symbolic expressions.
**Key categories:**
#### Symbolic Variables
```python
SymbolicVariable = sp.Symbol # Single variable
SymbolicVector = sp.Matrix # Vector of symbols
SymbolicMatrix = sp.Matrix # Matrix expression
SymbolicExpression = sp.Expr # General expression
```
#### System Components
```python
DynamicsExpression = sp.Matrix # f(x, u) symbolic
OutputExpression = sp.Matrix # h(x) symbolic
DiffusionExpression = sp.Matrix # g(x, u) symbolic
ParameterDict = Dict[sp.Symbol, float] # Parameter values
```
#### Jacobian Expressions
```python
JacobianExpression = sp.Matrix # ∂f/∂x symbolic
HessianExpression = sp.Matrix # ∂²f/∂x² symbolic
GradientExpression = sp.Matrix # ∇f symbolic
```
**Usage in framework:**
```{python}
#| label: ex-symbolic-types
# Define custom system using symbolic types
class MySystem(ContinuousSymbolicSystem):
def define_system(self, m=1.0, k=10.0):
# SymbolicVariable
x, v = sp.symbols('x v', real=True)
u = sp.symbols('u', real=True)
# SymbolicVector
self.state_vars = [x, v]
self.control_vars = [u]
# DynamicsExpression (SymbolicMatrix)
self._f_sym = sp.Matrix([v, -k*x/m + u/m])
# ParameterDict
m_sym, k_sym = sp.symbols('m k', positive=True)
self.parameters = {m_sym: m, k_sym: k}
self.order = 1
system = MySystem()
print("✓ Symbolic types guide system definition")
```
### control_classical.py: Control Design Results {#sec-controlclassicalpy}
**File:** `control_classical.py`
The `control_classical` module provides TypedDict results for classical control theory operations.
**Key categories:**
#### System Analysis Results
**Stability Analysis:**
```python
class StabilityInfo(TypedDict):
"""Stability analysis result.
Stability Criteria:
- Continuous: All Re(λ) < 0 (left half-plane)
- Discrete: All |λ| < 1 (inside unit circle)
"""
eigenvalues: np.ndarray # Complex eigenvalues
magnitudes: np.ndarray # |λ| values
max_magnitude: float # Spectral radius
spectral_radius: float # Same as max_magnitude
is_stable: bool # Asymptotically stable
is_marginally_stable: bool # On stability boundary
is_unstable: bool # Unstable
```
**Controllability:**
```python
class ControllabilityInfo(TypedDict, total=False):
"""Controllability analysis result.
Test: rank(C) = nx where C = [B AB A²B ... Aⁿ⁻¹B]
"""
controllability_matrix: ControllabilityMatrix # (nx, nx*nu)
rank: int # Rank of C
is_controllable: bool # rank == nx
uncontrollable_modes: Optional[np.ndarray] # Eigenvalues
```
**Observability:**
```python
class ObservabilityInfo(TypedDict, total=False):
"""Observability analysis result.
Test: rank(O) = nx where O = [C; CA; CA²; ...; CAⁿ⁻¹]
"""
observability_matrix: ObservabilityMatrix # (nx*ny, nx)
rank: int # Rank of O
is_observable: bool # rank == nx
unobservable_modes: Optional[np.ndarray] # Eigenvalues
```
#### Control Design Results
**LQR Controller:**
```python
class LQRResult(TypedDict):
"""Linear Quadratic Regulator result.
Minimizes: J = ∫(x'Qx + u'Ru)dt (continuous)
J = Σ(x'Qx + u'Ru) (discrete)
Control law: u = -Kx
"""
gain: GainMatrix # Feedback gain K (nu, nx)
cost_to_go: CovarianceMatrix # Riccati solution P (nx, nx)
closed_loop_eigenvalues: np.ndarray # eig(A - BK)
stability_margin: float # Phase/gain margin
```
**Kalman Filter:**
```python
class KalmanFilterResult(TypedDict):
"""Kalman Filter (optimal estimator) result.
System:
x[k+1] = Ax[k] + Bu[k] + w[k], w ~ N(0,Q)
y[k] = Cx[k] + v[k], v ~ N(0,R)
Estimator: x̂[k+1] = Ax̂[k] + Bu[k] + L(y[k] - Cx̂[k])
"""
gain: GainMatrix # Kalman gain L (nx, ny)
error_covariance: CovarianceMatrix # Error cov P (nx, nx)
innovation_covariance: CovarianceMatrix # Innovation S (ny, ny)
observer_eigenvalues: np.ndarray # eig(A - LC)
```
**LQG Controller:**
```python
class LQGResult(TypedDict):
"""Linear Quadratic Gaussian controller result.
Combines LQR (optimal control) + Kalman (optimal estimation)
via separation principle.
Controller: u = -Kx̂
Estimator: x̂[k+1] = Ax̂[k] + Bu[k] + L(y[k] - Cx̂[k])
"""
control_gain: GainMatrix # LQR gain K (nu, nx)
estimator_gain: GainMatrix # Kalman gain L (nx, ny)
control_cost_to_go: CovarianceMatrix # Controller Riccati P
estimation_error_covariance: CovarianceMatrix # Estimator Riccati P
separation_verified: bool # Separation principle holds
closed_loop_stable: bool # Overall stability
controller_eigenvalues: np.ndarray # eig(A - BK)
estimator_eigenvalues: np.ndarray # eig(A - LC)
```
**Pole Placement:**
```python
class PolePlacementResult(TypedDict):
"""Pole placement (eigenvalue assignment) result.
Design K such that eig(A - BK) = desired poles
"""
gain: GainMatrix # State feedback gain K
desired_poles: np.ndarray # Desired eigenvalues
achieved_poles: np.ndarray # Actual eig(A - BK)
is_controllable: bool # Arbitrary placement possible
```
**Luenberger Observer:**
```python
class LuenbergerObserverResult(TypedDict):
"""Luenberger observer (deterministic estimator) result.
Observer: x̂̇ = Ax̂ + Bu + L(y - Cx̂)
Error dynamics: ė = (A - LC)e
"""
gain: GainMatrix # Observer gain L (nx, ny)
desired_poles: np.ndarray # Desired observer poles
achieved_poles: np.ndarray # Actual eig(A - LC)
is_observable: bool # Arbitrary placement possible
```
**Usage example:**
```{python}
#| label: ex-control-results
#| eval: false
# Stability analysis (TypedDict provides structure)
stability: StabilityInfo = system.control.analyze_stability(A, system_type='continuous')
if stability['is_stable']:
print(f"Stable with spectral radius {stability['spectral_radius']:.3f}")
# LQR design (clear result structure)
lqr: LQRResult = system.control.design_lqr(A, B, Q, R, system_type='continuous')
K = lqr['gain']
closed_loop_eigs = lqr['controller_eigenvalues']
# Kalman filter (all fields documented)
kalman: KalmanFilterResult = system.control.design_kalman(
A, C, Q_process, R_measurement, system_type='discrete'
)
L = kalman['gain']
# LQG controller (separation principle results)
lqg: LQGResult = system.control.design_lqg(
A, B, C, Q, R, Q_process, R_measurement, system_type='discrete'
)
K = lqg['controller_gain']
L = lqg['estimator_gain']
```
## Structural Types {#sec-structural-types}
### protocols.py: Abstract Interfaces {#sec-protocolspy}
**File:** `protocols.py`
The `protocols` module defines abstract interfaces via Protocol (structural typing).
**Key categories:**
#### System Protocols
```python
class DynamicalSystemProtocol(Protocol):
"""Abstract interface for dynamical systems."""
@property
def nx(self) -> int: ...
@property
def nu(self) -> int: ...
def __call__(self, x: StateVector, u: ControlVector) -> StateVector: ...
class ContinuousSystemProtocol(DynamicalSystemProtocol, Protocol):
"""Continuous-time system interface."""
def integrate(
self,
x0: StateVector,
u_func: Callable,
t_span: TimeSpan
) -> IntegrationResult: ...
class DiscreteSystemProtocol(DynamicalSystemProtocol, Protocol):
"""Discrete-time system interface."""
@property
def dt(self) -> float: ...
def step(self, x: StateVector, u: ControlVector) -> StateVector: ...
class StochasticSystemProtocol(Protocol):
"""Stochastic system interface."""
@property
def nw(self) -> int: ...
def drift(self, x: StateVector, u: ControlVector) -> StateVector: ...
def diffusion(self, x: StateVector, u: ControlVector) -> DiffusionMatrix: ...
```
#### Controller Protocols
```python
class ControllerProtocol(Protocol):
"""Controller interface."""
def compute_control(self, x: StateVector) -> ControlVector: ...
class FeedbackControllerProtocol(ControllerProtocol, Protocol):
"""Linear feedback controller."""
@property
def K(self) -> GainMatrix: ...
```
#### Observer Protocols
```python
class ObserverProtocol(Protocol):
"""State observer interface."""
def observe(self, x: StateVector) -> OutputVector: ...
def estimate(self, y: OutputVector, u: ControlVector) -> StateVector: ...
```
**Why Protocol:**
```{python}
#| label: ex-protocol-benefits
#| eval: false
# No inheritance needed - structural typing
class MyCustomSystem:
@property
def nx(self) -> int:
return 2
@property
def nu(self) -> int:
return 1
def __call__(self, x: StateVector, u: ControlVector) -> StateVector:
return x + u
# Satisfies protocol structurally (duck typing with type safety)
system: DynamicalSystemProtocol = MyCustomSystem() # ✓ Type checker approves!
```
### utilities.py: Helper Types {#sec-utilitiespy}
**File:** `utilities.py`
The `utilities` module provides helper types, type guards, and performance tracking.
**Key categories:**
#### Type Guards
```python
def is_numpy(arr: ArrayLike) -> bool:
"""Check if array is NumPy."""
return isinstance(arr, np.ndarray)
def is_torch(arr: ArrayLike) -> bool:
"""Check if array is PyTorch."""
return hasattr(arr, '__module__') and 'torch' in arr.__module__
def is_jax(arr: ArrayLike) -> bool:
"""Check if array is JAX."""
return hasattr(arr, '__module__') and 'jax' in arr.__module__
```
#### Shape Utilities
```python
def is_batched(arr: ArrayLike, expected_dims: int = 1) -> bool:
"""Check if array is batched."""
return arr.ndim > expected_dims
def get_batch_size(arr: ArrayLike) -> Optional[int]:
"""Get batch size if batched."""
return arr.shape[0] if is_batched(arr) else None
def get_state_dim(x: StateVector) -> int:
"""Get state dimension."""
return x.shape[-1] if x.ndim > 0 else 1
```
#### Performance Types
```python
class ExecutionStats(TypedDict):
"""Performance statistics."""
count: int # Number of calls
total_time: float # Total time (seconds)
avg_time: float # Average time
min_time: float # Fastest call
max_time: float # Slowest call
```
#### Validation Types
```python
class ValidationResult(TypedDict):
"""Validation result."""
valid: bool
errors: List[str]
warnings: List[str]
info: Dict[str, Any]
```
## Design Principles {#sec-design-principles}
### 1. Semantic Over Structural {#sec-principle-semantic}
**Principle:** Names convey mathematical meaning, not implementation details.
```{python}
#| label: ex-semantic-principle
#| eval: false
# ✗ Structural (what it is)
def compute(arr1: np.ndarray, arr2: np.ndarray) -> np.ndarray:
return arr1 @ arr2
# ✓ Semantic (what it means)
def compute_control(x: StateVector, K: GainMatrix) -> ControlVector:
"""u = -Kx"""
return -K @ x
```
### 2. Backend Agnosticism {#sec-principle-backend}
**Principle:** Same types work with NumPy/PyTorch/JAX.
```{python}
#| label: ex-backend-principle
# Same function signature works for all backends
def dynamics(x: StateVector, u: ControlVector) -> StateVector:
# Works with NumPy, PyTorch, JAX
return x + u # Backend detected from input
# Works with all backends
dx_np = dynamics(np.array([1.0, 0.0]), np.array([0.0]))
dx_torch = dynamics(torch.tensor([1.0, 0.0]), torch.tensor([0.0]))
dx_jax = dynamics(jnp.array([1.0, 0.0]), jnp.array([0.0]))
print("✓ Backend-agnostic types")
```
### 3. TypedDict for Structured Results {#sec-principle-typeddict}
**Principle:** Never return plain dictionaries—use TypedDict for structure and safety.
```{python}
#| label: ex-typeddict-principle
#| eval: false
# ✗ Plain dict (no IDE support, no type checking)
def integrate() -> dict:
return {'t': t, 'x': x, 'success': True}
# ✓ TypedDict (type-safe, self-documenting)
def integrate() -> IntegrationResult:
return {
't': t, # TimePoints - IDE knows this
'x': x, # StateTrajectory - IDE knows this
'success': True, # bool - IDE knows this
'nfev': 100, # int - Required field
'integration_time': 0.5,
'solver': 'RK45'
}
```
### 4. Optional Fields with total=False {#sec-principle-optional}
**Principle:** Use `total=False` for optional fields in TypedDict.
```python
class IntegrationResult(TypedDict, total=False):
# Required fields (always present)
t: TimePoints
x: StateTrajectory
success: bool
# Optional fields (may not be present)
njev: int # Only adaptive methods
sol: Any # Dense output (optional)
```
### 5. Polymorphic Types via Union {#sec-principle-polymorphic}
**Principle:** Use Union for polymorphic return types.
```python
LinearizationResult = Union[
Tuple[StateMatrix, InputMatrix], # Deterministic
Tuple[StateMatrix, InputMatrix, DiffusionMatrix] # Stochastic
]
# Single function handles both
def analyze(result: LinearizationResult):
A, B = result[0], result[1]
if len(result) == 3:
G = result[2] # Stochastic
```
### 6. Protocol for Interfaces {#sec-principle-protocol}
**Principle:** Define interfaces via Protocol (structural typing) not inheritance.
```{python}
#| label: ex-protocol-principle
#| eval: false
# No inheritance needed - structural typing
class MySystem:
@property
def nx(self) -> int:
return 2
def __call__(self, x: StateVector, u: ControlVector) -> StateVector:
return x + u
# Satisfies DynamicalSystemProtocol structurally
system: DynamicalSystemProtocol = MySystem() # ✓ Type checker approves!
```
## Usage Throughout Framework {#sec-usage-throughout-framework}
### In UI Framework {#sec-usage-ui-framework}
```python
class ContinuousSymbolicSystem(SymbolicSystemBase, ContinuousSystemBase):
def __call__(
self,
x: StateVector,
u: Optional[ControlVector] = None
) -> StateVector:
"""Evaluate dynamics (types guide implementation)."""
return self._dynamics.evaluate(x, u, backend=self.backend.default_backend)
def linearize(
self,
x_eq: EquilibriumState,
u_eq: EquilibriumControl,
backend: Backend = "numpy"
) -> DeterministicLinearization:
"""Compute linearization (return type documents structure)."""
return self._linearization.linearize_continuous(x_eq, u_eq, backend)
```
### In Delegation Layer {#sec-usage-delegation-layer}
```python
class DynamicsEvaluator:
def evaluate(
self,
x: StateVector,
u: Optional[ControlVector],
backend: Backend
) -> StateVector:
"""Evaluate forward dynamics (types ensure correctness)."""
f_func: DynamicsFunction = self.code_gen.generate_dynamics(backend)
return f_func(x, u)
def get_stats(self) -> ExecutionStats:
"""Get performance statistics (TypedDict result)."""
return {
'count': self._call_count,
'total_time': self._total_time,
'avg_time': self._total_time / self._call_count,
'min_time': self._min_time,
'max_time': self._max_time
}
```
### In Integration Framework {#sec-usage-integration-framework}
```python
class ScipyIntegrator(IntegratorBase):
def integrate(
self,
x0: StateVector,
u_func: Callable[[ScalarLike, StateVector], Optional[ControlVector]],
t_span: TimeSpan,
t_eval: Optional[TimePoints] = None
) -> IntegrationResult:
"""Integrate using scipy (TypedDict ensures complete result)."""
# ... implementation ...
result: IntegrationResult = {
't': sol.t,
'x': sol.y.T,
'success': sol.success,
'message': sol.message,
'nfev': sol.nfev,
'nsteps': sol.nfev,
'integration_time': elapsed,
'solver': self.name
}
# Optional fields (type system allows this)
if hasattr(sol, 'njev'):
result['njev'] = sol.njev
return result
```
## Type Statistics {#sec-type-statistics}
### Type Distribution
| Category | Count | Examples |
|----------|-------|----------|
| Vector Types | 15+ | StateVector, ControlVector, OutputVector |
| Matrix Types | 30+ | StateMatrix, GainMatrix, CovarianceMatrix |
| Function Types | 10+ | DynamicsFunction, ControlPolicy |
| Backend Types | 20+ | Backend, Device, NoiseType |
| Trajectory Types | 15+ | StateTrajectory, IntegrationResult |
| Linearization Types | 15+ | DeterministicLinearization |
| Symbolic Types | 10+ | SymbolicExpression, DynamicsExpression |
| Protocol Types | 20+ | DynamicalSystemProtocol |
| Utility Types | 20+ | ExecutionStats, ValidationResult |
| TypedDict Results | 15+ | IntegrationResult, LQRResult, StabilityInfo |
| **TOTAL** | **200+** | **Complete type system** |
## Key Strengths {#sec-key-strengths}
::: {.callout-tip}
## Type System Benefits
1. **Semantic Clarity** - Names convey mathematical meaning
2. **Type Safety** - Static checking prevents errors
3. **IDE Support** - Autocomplete and inline documentation
4. **Backend Agnostic** - Works with NumPy/PyTorch/JAX transparently
5. **Structured Results** - TypedDict not plain dict
6. **Self-Documenting** - Types encode constraints and invariants
7. **Composition** - Types compose naturally
8. **Extensible** - Easy to add new types
9. **Consistent** - Same conventions throughout framework
10. **Testable** - Type-driven testing patterns
:::
## Summary
The type system is the **foundational layer** that enables clean, type-safe architecture throughout ControlDESymulation. By providing semantic types, structured results, and protocol-based interfaces, it supports:
- **Type-driven development** - Types guide implementation
- **Static verification** - Catch errors before runtime
- **Multi-backend support** - Transparent backend switching
- **Clear contracts** - Function signatures document expectations
- **Maintainability** - Types make code self-documenting
**The type system is infrastructure—users benefit from it without needing to understand it.** This documentation is provided for framework developers and advanced users who need to understand the internal type architecture.