Choosing a DSL¶
Five domain-specific languages compile to the same GDS core. This guide helps you pick the right one for your problem -- or decide when to use the framework directly.
Starting from the Problem¶
The Decision Matrix below is a technical reference — it assumes you already know your primitives. In practice, most modelers start earlier: with a domain question.
The same system can often be modeled with more than one DSL. An epidemic could be stockflow (if you care about accumulation rates) or raw framework (if you just need a state transition). A supply chain could be stockflow (stocks and flows), CLD (causal influences), or SCN (inventory and topology). The DSL choice depends on what you want to verify, not just what domain you are in.
The natural workflow is: Problem → What do I want to check? → DSL. Once you pick a DSL, roles and block structure follow more naturally because the DSL embeds domain conventions about what matters.
Decision Matrix¶
| If your system has... | Use | Package | Why |
|---|---|---|---|
| Stocks accumulating over time | gds-stockflow | stockflow |
Native stock/flow/auxiliary semantics with accumulation dynamics |
| State-space dynamics (A, B, C, D matrices) | gds-control | gds_control |
Control theory mapping with sensors, controllers, plant states |
| Strategic agents making decisions | gds-games | ogs |
Game-theoretic composition with utility/payoff channels |
| Software architecture to formalize | gds-software | gds_software |
Six diagram types: DFD, SM, Component, C4, ERD, Dependency |
| Business processes or supply chains | gds-business | gds_business |
Causal loop, supply chain network, value stream mapping |
| None of the above | gds-framework | gds |
Build your own vocabulary on the composition algebra |
DSL Profiles¶
gds-stockflow¶
Domain: System dynamics -- stocks, flows, auxiliaries, converters.
Best for: Accumulation dynamics, resource pools, population models, anything modeled with stock-flow diagrams.
Example: SIR epidemic model, Lotka-Volterra predator-prey, inventory management.
from stockflow.dsl.elements import Auxiliary, Converter, Flow, Stock
from stockflow.dsl.model import StockFlowModel
model = StockFlowModel(
name="Population",
stocks=[Stock(name="Population", initial=1000.0, non_negative=True)],
flows=[
Flow(name="births", target="Population"),
Flow(name="deaths", source="Population"),
],
converters=[Converter(name="birth_rate"), Converter(name="death_rate")],
auxiliaries=[
Auxiliary(name="net_growth", inputs=["birth_rate", "death_rate"]),
],
)
Canonical form: h = f . g with |X| = number of stocks, |f| = number of accumulation mechanisms.
Domain checks: SF-001 (orphan stocks), SF-003 (auxiliary cycles), SF-004 (unused converters), plus 2 more.
gds-control¶
Domain: Feedback control systems -- states, inputs, sensors, controllers.
Best for: Thermostat-like control loops, PID controllers, any system with plant state, measurement, and actuation.
Example: Temperature regulation, resource level tracking, robotic control.
from gds_control.dsl.elements import Controller, Input, Sensor, State
from gds_control.dsl.model import ControlModel
model = ControlModel(
name="Thermostat",
states=[State(name="temperature", initial=20.0)],
inputs=[Input(name="heater")],
sensors=[Sensor(name="temp_sensor", observes=["temperature"])],
controllers=[
Controller(name="pid", reads=["temp_sensor", "heater"], drives=["temperature"]),
],
)
Canonical form: h = f . g with |X| = number of states, full dynamical character.
DSL element mapping: State -> Mechanism + Entity, Input -> BoundaryAction, Sensor -> Policy, Controller -> Policy.
gds-games (OGS)¶
Domain: Game theory -- strategic interactions, decision games, payoff computation.
Best for: Multi-agent decision problems, mechanism design, auction theory, commons dilemmas.
Example: Prisoner's dilemma, resource extraction games, insurance contracts.
from ogs.dsl.games import CovariantFunction, DecisionGame
from ogs.dsl.pattern import Pattern, PatternInput
from ogs.dsl.types import InputType, Signature, port
agent = DecisionGame(
name="Player",
signature=Signature(
x=(port("Resource Signal"),),
y=(port("Extraction Decision"),),
r=(port("Player Payoff"),),
),
logic="Choose extraction amount",
)
Canonical form: h = g (stateless -- no mechanisms, no state). All game blocks map to Policy.
Key difference: OGS uses OpenGame (a subclass of AtomicBlock) with its own PatternIR, which projects back to SystemIR via PatternIR.to_system_ir(). It also has backward (contravariant) channels for utility signals.
gds-software¶
Domain: Software architecture -- six diagram types for formalizing system structure.
Best for: Documenting and verifying software architectures, data flows, state machines, component interactions.
Diagram types:
| Type | What it models |
|---|---|
| DFD (Data Flow Diagram) | Processes, data stores, external entities |
| SM (State Machine) | States and transitions |
| Component | Provided/required interfaces |
| C4 | Context, container, component views |
| ERD (Entity-Relationship) | Data model relationships |
| Dependency | Module dependencies |
from gds_software.dsl.elements import DFDProcess, DFDDataStore, DFDExternalEntity
from gds_software.dsl.model import SoftwareModel
model = SoftwareModel(
name="Order System",
diagram_type="dfd",
processes=[DFDProcess(name="Process Order")],
data_stores=[DFDDataStore(name="Order DB")],
external_entities=[DFDExternalEntity(name="Customer")],
)
Canonical form: Varies by diagram type. DFD with data stores has state (|X| > 0). ERD and Dependency are typically stateless.
Domain checks: 27 checks across all six diagram types.
gds-business¶
Domain: Business dynamics -- causal loops, supply chains, value streams.
Best for: Business process modeling, supply chain optimization, value stream analysis.
Diagram types:
| Type | What it models |
|---|---|
| CLD (Causal Loop Diagram) | Reinforcing/balancing feedback loops |
| SCN (Supply Chain Network) | Nodes, links, inventory accumulation |
| VSM (Value Stream Map) | Process steps, buffers, cycle times |
from gds_business.cld.elements import Variable, CausalLink
from gds_business.cld.model import CLDModel
model = CLDModel(
name="Market Dynamics",
variables=[Variable(name="Demand"), Variable(name="Price"), Variable(name="Supply")],
links=[
CausalLink(source="Demand", target="Price", polarity="+"),
CausalLink(source="Price", target="Supply", polarity="+"),
CausalLink(source="Supply", target="Demand", polarity="-"),
],
)
Canonical form: CLD is stateless (h = g). SCN has full dynamics (h = f . g). VSM is stateful only with buffers.
Domain checks: 11 checks across all three diagram types.
Feature Comparison¶
State and Canonical Form¶
| DSL | Has State? | Canonical Form | Character |
|---|---|---|---|
| gds-stockflow | Yes (stocks) | h = f . g |
Dynamical -- state-dominant accumulation |
| gds-control | Yes (plant states) | h = f . g |
Dynamical -- full feedback control |
| gds-games | No | h = g |
Strategic -- pure policy computation |
| gds-software | Varies by diagram | Varies | Diagram-dependent |
| gds-business CLD | No | h = g |
Stateless -- pure signal relay |
| gds-business SCN | Yes (inventory) | h = f . g |
Dynamical -- inventory accumulation |
| gds-business VSM | Optional (buffers) | Varies | Stateful only with buffers |
The canonical spectrum
All DSLs compile to the same h = f . g decomposition with varying dimensionality of the state space X. When |X| = 0, the system is stateless and h = g. When both |f| > 0 and |g| > 0, the system is fully dynamical. GDS is a unified transition calculus -- not just a dynamical systems framework.
GDS Role Mapping¶
Every DSL maps its elements to the same four GDS roles:
| GDS Role | stockflow | control | games | software | business |
|---|---|---|---|---|---|
| BoundaryAction | Converter | Input | PatternInput | External entity | External source |
| Policy | Flow, Auxiliary | Sensor, Controller | All game types | Process, Transform | Variable, Link |
| Mechanism | Accumulation | State Dynamics | (none) | Data store update | Inventory update |
| ControlAction | (unused) | (unused) | (unused) | (unused) | (unused) |
Warning
ControlAction is unused across all five DSLs. Use Policy for all decision, observation, and control logic.
Verification Depth¶
| DSL | Domain Checks | Generic (G-series) | Semantic (SC-series) |
|---|---|---|---|
| gds-stockflow | 5 (SF-001..SF-005) | 6 (via SystemIR) | 7 (via GDSSpec) |
| gds-control | Domain validation | 6 (via SystemIR) | 7 (via GDSSpec) |
| gds-games | OGS-specific | 6 (via SystemIR) | 7 (via GDSSpec) |
| gds-software | 27 across 6 diagrams | 6 (via SystemIR) | 7 (via GDSSpec) |
| gds-business | 11 across 3 diagrams | 6 (via SystemIR) | 7 (via GDSSpec) |
When to Use Raw gds-framework¶
Use the framework directly when:
-
No DSL vocabulary fits. Your domain does not map cleanly to stocks/flows, control loops, games, software diagrams, or business processes.
-
You need custom block roles. The existing roles (BoundaryAction, Policy, Mechanism) work, but you want domain-specific naming or constraints.
-
You are exploring a new domain. Build a prototype with raw blocks and composition, then decide if a DSL would help.
from gds import (
BoundaryAction,
GDSSpec,
Mechanism,
Policy,
compile_system,
interface,
verify,
)
# Build directly with the composition algebra
sensor = BoundaryAction(name="Sensor", interface=interface(forward_out=["Reading"]))
logic = Policy(name="Logic", interface=interface(forward_in=["Reading"], forward_out=["Command"]))
actuator = Mechanism(
name="Actuator",
interface=interface(forward_in=["Command"]),
updates=[("Plant", "value")],
)
system = sensor >> logic >> actuator
system_ir = compile_system("Custom System", root=system)
report = verify(system_ir)
Tip
If you find yourself repeating the same pattern across multiple raw-framework models, that is a signal to consider creating a DSL. All five existing DSLs started as repeated patterns that were factored into a compiler.
Cross-DSL Interoperability¶
All DSLs compile to GDSSpec, which means you can:
-
Compare models across DSLs -- the canonical
h = f . gdecomposition works on any spec, regardless of which DSL produced it. -
Use the same verification pipeline -- generic checks (G-001..G-006) and semantic checks (SC-001..SC-007) work on any compiled system or spec.
-
Query any spec with SpecQuery -- parameter influence, entity update maps, and dependency graphs work uniformly.
from gds import SpecQuery, project_canonical
# Same analysis works regardless of which DSL compiled the spec
canonical = project_canonical(spec)
query = SpecQuery(spec)
print(f"State dimension: {len(canonical.state_space)}")
print(f"Mechanism count: {len(canonical.mechanisms)}")
print(f"Policy count: {len(canonical.policies)}")
For a concrete example of the same problem modeled through three different DSL lenses, see the Rosetta Stone guide.
Summary¶
| Question | Answer |
|---|---|
| "I have accumulating stocks" | Use gds-stockflow |
| "I have feedback control loops" | Use gds-control |
| "I have strategic agents" | Use gds-games |
| "I have software to document" | Use gds-software |
| "I have business processes" | Use gds-business |
| "None of these fit" | Use gds-framework directly |
| "I want to compare across domains" | All compile to GDSSpec -- use the canonical decomposition |