Skip to content

feat: gds-analysis package + audit fixes + examples#146

Merged
rororowyourboat merged 22 commits intomainfrom
dev
Mar 28, 2026
Merged

feat: gds-analysis package + audit fixes + examples#146
rororowyourboat merged 22 commits intomainfrom
dev

Conversation

@rororowyourboat
Copy link
Copy Markdown
Collaborator

Summary

  • gds-analysis package: New package bridging gds-framework to gds-sim
    • spec_to_model(): GDSSpec → gds_sim.Model adapter
    • guarded_policy(): runtime AdmissibleInputConstraint enforcement with depends_on projection
    • trajectory_distances(): StateMetric computation on trajectories
    • reachable_set(): R(x) via trajectory sampling with ReachabilityResult metadata
    • reachable_graph(): BFS reachability with float_tolerance
    • configuration_space(): iterative Tarjan SCC for X_C
  • StateMetric (Paper Assumption 3.2): structural annotation in gds-framework
  • Integration examples: SIR epidemic + crosswalk analysis scripts
  • Two independent audits (software + data science) with all Phase 1-2 fixes applied
  • Research journal entries 006-007

Test plan

  • 52 gds-analysis tests pass (94% coverage)
  • 499 gds-framework tests pass
  • 184 gds-owl tests pass
  • Both example scripts produce expected output
  • Ruff lint + format clean

rohan and others added 22 commits March 28, 2026 17:34
New package connecting structural annotations from gds-framework to
gds-sim execution, completing the spec-to-simulation pipeline.

adapter.py: spec_to_model() maps GDSSpec blocks to gds_sim.Model
  - BoundaryAction + Policy → policies
  - Mechanism.updates → state update functions (keyed by state var)
  - Default initial state from entities
  - Optional constraint enforcement via AdmissibleInputConstraint

constraints.py: guarded_policy() wraps policies with admissibility
  checks at runtime (warn/raise/zero on violation)

metrics.py: trajectory_distances() computes StateMetric distances
  between successive trajectory states

21 tests covering adapter, constraints, metrics, and end-to-end
simulation with constraint enforcement and distance computation.
…s 4-5)

reachability.py implements Paper Definitions 4.1 and 4.2:

- reachable_set(): compute R(x) by running one timestep per input sample,
  deduplicating reached states by fingerprint
- reachable_graph(): BFS expansion from initial states, building an
  adjacency dict of state transitions
- configuration_space(): Tarjan's SCC algorithm on the reachability graph,
  returning mutually reachable state sets sorted by size

11 new tests: single/multiple/duplicate inputs, empty inputs, depth-1
and depth-2 graph expansion, SCC cases (self-loop, cycle, DAG,
disconnected), and end-to-end thermostat integration.
Add gds-continuous package — continuous-time ODE integration engine
mirroring gds-sim's architecture but wrapping scipy.integrate.solve_ivp.
Standalone (pydantic-only), scipy is an optional dependency.

Restructure root metapackage from hard dependencies to optional extras
so users pick adapters: `uv add gds-core[control,continuous]`.

43 tests, 90% coverage. Covers exponential decay (exact solution),
harmonic oscillator (energy conservation), parameter sweeps, 6 solver
methods, and terminal event detection.
Extends ControlModel with symbolic ODEs (StateEquation, OutputEquation)
that compile to plain Python callables via sympy.lambdify. Includes
Jacobian linearization at arbitrary operating points.

Pipeline: SymbolicControlModel → to_ode_function() → ODEFunction
callable that feeds directly into gds-continuous. SymPy is optional.

29 tests, 95% coverage. Covers decay, oscillator, Van der Pol models
with exact RHS verification, linearization Jacobians, and end-to-end
integration through gds-continuous.
Full pipeline demonstration: structural spec → adapter → simulate →
metrics + reachability. Tests population conservation, epidemic
progression, trajectory distances, reachable set, reachability graph,
configuration space (SCCs), and constraint enforcement.

8 tests using float-valued SIR dynamics (beta=0.3, gamma=0.1, N=1000).
Recreates key numerical results from mzargham/hc-marimo using
gds-continuous, proving the ODE engine handles a real differential
game (Isaacs 1951).

5 tests verify: Hamiltonian conservation (H* ~ 0), costate norm
conservation (||p||^2 invariant), forward/backward capture round-trip,
stationary evader straight-line capture, and parameter sweep over
evader speed ratios.
Full differential game example demonstrating the gds-symbolic +
gds-continuous pipeline against mzargham/hc-marimo reference:

- Symbolic derivation of Hamiltonian and optimal controls (SymPy)
- Hand-coded numpy RHS as independent cross-check
- Backward reachable set (isochrone) computation
- Conservation law verification (H*, ||p||^2)
- Forward/backward capture round-trip
- Usable part boundary conditions

9 tests tracing to hc-marimo verification IDs (T1-T7, ISO).
…operties

End-to-end test of the crosswalk mechanism design problem using
gds-analysis. Behavioral functions implement discrete Markov transitions
from the Zargham & Shorish crosswalk lectures.

Tests grounded in NotebookLM analysis of 6 source documents:
- Crosswalk safety guarantee: p=k → Stopped, never Accident
- Accident reachable via jaywalking with bad luck
- Flowing unreachable from Accident in one step
- Not crossing preserves Flowing
- All three states reachable from Flowing
- Configuration space via SCC analysis
Interactive notebook with parameter sliders (v_E, omega_max, ell),
backward trajectory computation via gds-continuous, isochrone
visualization, and conservation law verification table.

16 cells: symbolic derivation (SymPy), lambdification, ODEModel
integration, matplotlib trajectory/isochrone plots.
Crosswalk analysis: reachable set from each traffic state, reachability
graph, configuration space (SCCs), trajectory metrics, and verification
that Flowing is unreachable from Accident — grounded in Zargham & Shorish
crosswalk lectures.

SIR analysis: epidemic simulation with population conservation check,
Euclidean state distance metrics, and reachable set from initial (S=999,
I=1, R=0) under varied infection rates.

Both scripts use the full gds-analysis pipeline: structural spec →
adapter → simulate → metrics + reachability.
Critical:
1. _step_once strips metadata keys (timestep, substep, run, subset)
   from returned state dicts. BFS at depth > 1 no longer passes
   corrupted state to nested calls.
2. Adapter now warns when constraints are registered for non-
   BoundaryAction blocks (Policy/ControlAction).

Important:
3. guarded_policy projects state to depends_on fields before calling
   constraint — enforces R1 structural skeleton at runtime.
4. ControlAction blocks now handled by adapter (raises ValueError if
   missing policy function, same as BoundaryAction/Policy).
5. Docstring corrected: "All blocks packed into a single
   StateUpdateBlock" (was falsely claiming per-wiring groups).
6. Tarjan SCC rewritten as iterative (no recursion limit for large
   reachability graphs).
7. assert replaced with ValueError in trajectory_distances (safe
   under python -O).

Minor:
8. TYPE_CHECKING guards used for type-only imports.
9. Documentation claims left for separate update.
C-1: Wire output_fn into engine — now called per-timepoint with test
C-2: Replace sympify with parse_expr (no eval, restricted local_dict)
I-4: Move gds-continuous from hard dep to optional extra in gds-symbolic
I-5: Remove runs field from ODESimulation (no purpose for deterministic ODEs)
M-2: SymbolicError now inherits from CSError (exception hierarchy)
M-3: Remove dead require_numpy() from _compat.py
M-4: Replace relative imports with absolute in HC example tests
I-2/I-3: Add CLAUDE.md documenting bridge gap and time-varying input limitation
Also: add gds-analysis to root modular extras
feat: continuous-time ODE engine, SymPy bridge, and modular package extras
New gds_viz.phase module with vector fields, trajectories, nullclines,
and full phase_portrait() combining all three. Supports >2D systems
via fixed_states projection.

Behind [phase] optional extra (matplotlib + numpy + gds-continuous) —
existing Mermaid functionality has no new dependencies.

10 tests: vector field computation, trajectory integration, nullclines,
Lorenz 3D projection. 97% package coverage.

Closes #126.
Add `ogs.equilibrium` module with Nash equilibrium solving for 2-player
normal-form games extracted from PatternIR terminal conditions.

- `payoffs: dict[str, float]` field on TerminalCondition for numeric payoffs
- `extract_payoff_matrices(ir)` builds numpy payoff matrices from action
  spaces and terminal conditions
- `compute_nash(ir)` delegates to nashpy (support/vertex/Lemke-Howson)
- `NashResult` with support() and expected_payoffs() methods

nashpy is an optional dep: `uv add gds-games[nash]`

11 tests: Prisoner's Dilemma (unique pure NE), Matching Pennies (unique
mixed NE at 0.5/0.5), Battle of Sexes (3 NE), expected payoffs, error
cases.

Closes #77.
Software audit fixes:
- Remove phantom spec parameter from reachable_set/reachable_graph
  (was accepted but never read)
- Document state key convention ("Entity.Variable") in adapter module
  docstring
- Warn on multi-update Mechanisms (same SUF registered per variable)
- Add exhaustive/sampled distinction to reachability docstrings

Data science audit fixes:
- Document coverage semantics: discrete systems should use exhaustive
  input enumeration, continuous results are approximate
- Add SCC completeness caveat to configuration_space docstring
…e 2)

reachable_set() now returns ReachabilityResult dataclass with:
- states: list of distinct reached states
- n_samples: number of input samples tried
- n_distinct: number of distinct states found
- is_exhaustive: caller-declared exhaustive enumeration flag

New float_tolerance parameter rounds float values before fingerprinting,
preventing distinct states from float rounding noise. Passed through
to reachable_graph and _state_fingerprint.

3 new tests: metadata fields, exhaustive flag, float tolerance.
Total: 52 tests, 94% coverage.
- Crosswalk reachability tests now set exhaustive=True (the 3 input
  samples are exhaustive for the discrete {cross, safe_crossing} space)
- Float tolerance test uses assertEqual(n_distinct, 1) instead of <=
@rororowyourboat rororowyourboat merged commit e8bacbd into main Mar 28, 2026
4 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant