This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
gds-core — monorepo for the Generalized Dynamical Systems ecosystem. Typed compositional specifications for complex systems, grounded in GDS theory. Eight packages managed as a uv workspace.
| Package | Import | Location | Role |
|---|---|---|---|
| gds-framework | gds |
packages/gds-framework/ |
Core engine |
| gds-domains | gds_domains.{stockflow,control,business,software,games,symbolic} |
packages/gds-domains/ |
All domain DSLs |
| gds-sim | gds_sim |
packages/gds-sim/ |
Discrete-time simulation |
| gds-continuous | gds_continuous |
packages/gds-continuous/ |
Continuous-time ODE engine |
| gds-analysis | gds_analysis, gds_analysis.psuu |
packages/gds-analysis/ |
Spec-to-sim bridge + PSUU |
| gds-interchange | gds_interchange.owl |
packages/gds-interchange/ |
OWL/SHACL/SPARQL + future bridges |
| gds-viz | gds_viz |
packages/gds-viz/ |
Mermaid + phase portraits |
| gds-examples | — | packages/gds-examples/ |
Tutorials + examples |
Deprecated shim packages (v0.99.0, re-export with DeprecationWarning): gds-owl, gds-psuu, gds-stockflow, gds-control, gds-games, gds-software, gds-business, gds-symbolic.
# Install all packages (workspace-linked)
uv sync --all-packages
# Run tests per-package
uv run python -m pytest packages/gds-framework/tests -v
uv run python -m pytest packages/gds-domains/tests -v
uv run python -m pytest packages/gds-sim/tests -v
uv run python -m pytest packages/gds-continuous/tests -v
uv run python -m pytest packages/gds-analysis/tests -v
uv run python -m pytest packages/gds-interchange/tests -v
uv run python -m pytest packages/gds-viz/tests -v
uv run python -m pytest packages/gds-examples -v
# Run a single test
uv run python -m pytest packages/gds-framework/tests/test_blocks.py::TestStackComposition::test_rshift_operator -v
# Lint & format
uv run ruff check packages/
uv run ruff format --check packages/
# Build a specific package
uv build --package gds-framework
# Docs
uv sync --all-packages --group docs
uv run mkdocs build --strict
uv run mkdocs serveThis is a uv workspace monorepo. The root pyproject.toml declares packages/* as workspace members. Each package has its own pyproject.toml with package-specific dependencies and build config. Shared tooling (ruff, docs) is configured at the root.
gds-framework ← core engine (pydantic only, no upstream deps)
↑
├── gds-domains ← all domain DSLs [games: typer,jinja2] [symbolic: sympy] [nashpy: nashpy]
│ stockflow, control, business, software, games, symbolic
├── gds-viz ← Mermaid diagrams + phase portraits [matplotlib]
└── gds-interchange ← OWL/SHACL/SPARQL + future bridges [shacl: pyshacl]
↑
gds-examples ← tutorials (depends on most packages)
gds-sim ← discrete-time simulation (standalone, pydantic only)
↑
gds-analysis ← spec→sim bridge, reachability, PSUU [psuu: optuna] [continuous: gds-continuous]
gds-continuous ← continuous-time ODE engine (standalone, pydantic only) [scipy]
Layer 0 — Composition Algebra (blocks/, compiler/, ir/, verification/generic_checks.py):
Domain-neutral engine. Blocks with bidirectional typed interfaces, composed via four operators (>>, |, .feedback(), .loop()). A 3-stage compiler flattens composition trees into flat IR (blocks + wirings + hierarchy). Six generic verification checks (G-001..G-006) validate structural properties on the IR.
Layer 1 — Specification Framework (spec.py, canonical.py, state.py, spaces.py, types/):
Where GDS theory lives. GDSSpec is the central registry for types, spaces, entities, blocks, wirings, and parameters. project_canonical() derives the formal h = f ∘ g decomposition. Nine semantic checks (SC-001..SC-009) validate domain properties on the spec.
These layers are loosely coupled — you can use the composition algebra without GDSSpec, and GDSSpec does not depend on the compiler.
Six domain DSLs live in gds-domains as subpackages (gds_domains.stockflow, .control, .games, .software, .business, .symbolic). The stockflow, control, software, and business subpackages follow a shared pattern:
- Elements — frozen Pydantic models for user-facing declarations (not GDS blocks)
- Model — mutable container with
@model_validatorconstruction-time validation - Compiler — two public functions:
compile_model(model)→GDSSpec(registers types, spaces, entities, blocks, wirings, parameters)compile_to_system(model)→SystemIR(builds composition tree, delegates togds.compiler.compile.compile_system)
- Verification — domain-specific checks on the model, plus optional delegation to G-001..G-006 via SystemIR
All DSLs map to the same GDS roles: exogenous inputs → BoundaryAction, decision/observation logic → Policy, state updates → Mechanism + Entity. ControlAction is unused by DSL compilers (all non-state-updating blocks resolve to Policy). It is available for hand-built models as the output map y = C(x, d). See docs/framework/design/controller-plant-duality.md. Canonical h = f ∘ g holds cleanly for all three domains.
The composition tree follows a convergent tiered pattern:
(exogenous inputs | observers) >> (decision logic) >> (state dynamics)
.loop(state dynamics → observers)
gds_domains.games is more complex — it subclasses AtomicBlock as OpenGame with its own IR (PatternIR), but projects back to SystemIR via PatternIR.to_system_ir().
These coexist at different levels:
-
Token-based (
types/tokens.py) — structural set matching at composition/wiring time. Port names auto-tokenize by splitting on+(space-plus-space) and,(comma-space), then lowercasing each part. Plain spaces are NOT delimiters:"Heater Command"is one token"heater command". The>>operator and auto-wiring use token overlap for matching."Temperature + Setpoint"auto-wires to"Temperature"because they share the token"temperature". -
TypeDef-based (
types/typedef.py) — runtime validation at the data level. TypeDef wraps a Python type + optional constraint predicate. Used by Spaces and Entities to validate actual data values. Never called during compilation.
Block tree → flatten() → list[AtomicBlock] → block_compiler() → list[BlockIR]
→ _walk_wirings() → list[WiringIR] (explicit + auto-wired)
→ _extract_hierarchy() → HierarchyNodeIR tree
= SystemIR(blocks, wirings, hierarchy)
Domain DSLs use explicit StackComposition(wiring=[...]) between tiers where auto-wiring token overlap doesn't hold, falling back to >> auto-wiring where it does.
Only 5 concrete Block types exist — domain packages subclass AtomicBlock only, never the operators:
AtomicBlock— leaf nodeStackComposition(>>) — sequential, validates token overlapParallelComposition(|) — independent, no validationFeedbackLoop(.feedback()) — backward within evaluation, CONTRAVARIANTTemporalLoop(.loop()) — forward across temporal boundaries, COVARIANT only
Block roles (BoundaryAction, Policy, Mechanism, ControlAction) subclass AtomicBlock and enforce constraints at construction via @model_validator.
Both use the pluggable pattern: Callable[[T], list[Finding]].
- Generic checks (G-001..G-006) operate on
SystemIR— structural topology only - Semantic checks (SC-001..SC-009) operate on
GDSSpec— domain properties (completeness, determinism, reachability, type safety, parameter references, canonical wellformedness, admissibility references, transition read consistency) - Domain checks operate on domain models (e.g.,
StockFlowModel,ControlModel) — pre-compilation structural validation
main— stable release branch. Only receives merges fromdev.dev— integration branch. All feature/fix PRs targetdevfirst.- Feature branches branch from
devand PR back todev. - When
devis stable and ready for release, mergedev→main.
- All data models are Pydantic v2
BaseModel— frozen for value objects, mutable for registries @model_validator(mode="after")returningSelffor construction-time invariant enforcement- Absolute imports only (never relative)
TYPE_CHECKINGguard inblocks/base.pyto break circular dependency withblocks/composition.py- Ruff config at root: line-length 88, rules
E, W, F, I, UP, B, SIM, TCH, RUF - Tags (
Taggedmixin) are inert — stripped at compile time, never affect verification - Parameters (Θ) are structural metadata — GDS never assigns values or binds them
GDSSpec.collect()type-dispatches TypeDef/Space/Entity/Block/ParameterDef; SpecWiring stays explicit viaregister_wiring()- Versioning:
__version__in each package's__init__.pyis the single source of truth —pyproject.tomlreads it viadynamic = ["version"]+[tool.hatch.version]. When bumping a version, only edit__init__.py. When a package starts using a new gds-framework API, bump itsgds-framework>=lower bound in the same change. - Each package published independently to PyPI via tag-based workflow (
gds-framework/v0.3.1) - Per-package CLAUDE.md files contain package-specific architecture details