Skip to content

Getting Started: Build Your First Model

A progressive 5-stage tutorial that teaches GDS fundamentals using a thermostat control system as the running example. Each stage builds on the previous one, introducing new concepts incrementally.

Prerequisites

  • Python 3.12+
  • Install the required packages:
pip install gds-framework gds-viz gds-control

Or, if working from the monorepo:

uv sync --all-packages

Learning Path

Stage Concepts
1. Minimal Model Entity, BoundaryAction, Mechanism, >> composition, GDSSpec
2. Feedback Policy, .loop() temporal composition, parameters
3. DSL Shortcut gds-control DSL: ControlModel, compile_model, compile_to_system
4. Verification & Viz Generic checks (G-001..G-006), semantic checks, Mermaid visualization
5. Query API SpecQuery: parameter influence, entity updates, causal chains

Stage 1 -- Minimal Model

The simplest possible GDS model: a heater (BoundaryAction) warms a room (Entity with one state variable). Two blocks composed with >>.

  • BoundaryAction: exogenous input -- no forward_in ports
  • Mechanism: state update -- writes to entity variables, no backward ports
  • >>: sequential composition via token-matched port wiring

Types and Entity

from gds.types.typedef import TypeDef
from gds.state import Entity, StateVariable

Temperature = TypeDef(
    name="Temperature",
    python_type=float,
    description="Temperature in degrees Celsius",
)

HeatRate = TypeDef(
    name="HeatRate",
    python_type=float,
    constraint=lambda x: x >= 0,
    description="Heat input rate (watts)",
)

room = Entity(
    name="Room",
    variables={
        "temperature": StateVariable(
            name="temperature",
            typedef=Temperature,
            symbol="T",
            description="Current room temperature",
        ),
    },
    description="The room being heated",
)

Blocks and Composition

from gds.blocks.roles import BoundaryAction, Mechanism
from gds.types.interface import Interface, port

# BoundaryAction: exogenous heat input
heater = BoundaryAction(
    name="Heater",
    interface=Interface(
        forward_out=(port("Heat Signal"),),
    ),
)

# Mechanism: state update
update_temperature = Mechanism(
    name="Update Temperature",
    interface=Interface(
        forward_in=(port("Heat Signal"),),
    ),
    updates=[("Room", "temperature")],
)

# Sequential composition -- tokens "heat" and "signal" overlap
pipeline = heater >> update_temperature

Structural Diagram

%%{init:{"theme":"neutral"}}%%
flowchart TD
    classDef boundary fill:#93c5fd,stroke:#2563eb,stroke-width:2px,color:#1e3a5f
    classDef mechanism fill:#86efac,stroke:#16a34a,stroke-width:2px,color:#14532d
    Heater([Heater]):::boundary
    Update_Temperature[[Update Temperature]]:::mechanism
    Heater --Heat Signal--> Update_Temperature

Stage 2 -- Adding Feedback

Extend the minimal model with observation and control:

  • A Sensor (Policy) reads the room temperature
  • A Controller (Policy) decides the heat command using a setpoint parameter
  • A TemporalLoop (.loop()) feeds updated temperature back to the sensor across timesteps

New operators: | (parallel composition) and .loop() (temporal feedback).

Blocks

from gds.blocks.roles import BoundaryAction, Mechanism, Policy
from gds.types.interface import Interface, port

sensor = Policy(
    name="Sensor",
    interface=Interface(
        forward_in=(port("Temperature Reading"),),
        forward_out=(port("Temperature Observation"),),
    ),
)

controller = Policy(
    name="Controller",
    interface=Interface(
        forward_in=(
            port("Temperature Observation"),
            port("Heat Signal"),
        ),
        forward_out=(port("Heat Command"),),
    ),
    params_used=["setpoint"],
)

update_temperature = Mechanism(
    name="Update Temperature",
    interface=Interface(
        forward_in=(port("Heat Command"),),
        forward_out=(port("Temperature Reading"),),
    ),
    updates=[("Room", "temperature")],
)

Composition with Temporal Loop

from gds.blocks.composition import Wiring
from gds.ir.models import FlowDirection

input_tier = heater | sensor
forward_pipeline = input_tier >> controller >> update_temperature

system_with_loop = forward_pipeline.loop(
    [
        Wiring(
            source_block="Update Temperature",
            source_port="Temperature Reading",
            target_block="Sensor",
            target_port="Temperature Reading",
            direction=FlowDirection.COVARIANT,
        )
    ],
)

Structural Diagram

%%{init:{"theme":"neutral"}}%%
flowchart TD
    classDef boundary fill:#93c5fd,stroke:#2563eb,stroke-width:2px,color:#1e3a5f
    classDef policy fill:#fcd34d,stroke:#d97706,stroke-width:2px,color:#78350f
    classDef generic fill:#cbd5e1,stroke:#64748b,stroke-width:1px,color:#1e293b
    Heater([Heater]):::boundary
    Sensor[Sensor]:::generic
    Controller[Controller]:::generic
    Update_Temperature[Update Temperature]:::generic
    Heater --Heat Signal--> Controller
    Sensor --Temperature Observation--> Controller
    Controller --Heat Command--> Update_Temperature
    Update_Temperature -.Temperature Reading..-> Sensor

Note the dashed arrow from Update Temperature back to Sensor -- this is the temporal loop (.loop()), indicating cross-timestep feedback.


Stage 3 -- DSL Shortcut

Rebuild the same thermostat using the gds-control DSL. Declare states, inputs, sensors, and controllers -- the compiler generates all types, spaces, entities, blocks, wirings, and the temporal loop automatically.

~15 lines of DSL vs ~60 lines of manual GDS construction.

ControlModel Declaration

from gds_control.dsl.compile import compile_model, compile_to_system
from gds_control.dsl.elements import Controller, Input, Sensor, State
from gds_control.dsl.model import ControlModel

model = ControlModel(
    name="Thermostat DSL",
    states=[
        State(name="temperature", initial=20.0),
    ],
    inputs=[
        Input(name="heater"),
    ],
    sensors=[
        Sensor(name="temp_sensor", observes=["temperature"]),
    ],
    controllers=[
        Controller(
            name="thermo",
            reads=["temp_sensor", "heater"],
            drives=["temperature"],
        ),
    ],
    description="Thermostat built with the gds-control DSL",
)

spec = compile_model(model)       # -> GDSSpec
system = compile_to_system(model)  # -> SystemIR

DSL Element to GDS Role Mapping

DSL Element GDS Role
State("temperature") Mechanism + Entity
Input("heater") BoundaryAction
Sensor("temp_sensor") Policy (observer)
Controller("thermo") Policy (decision logic)

Canonical Decomposition

The canonical projection separates the system into the formal h = f . g form:

%%{init:{"theme":"neutral"}}%%
flowchart LR
    classDef boundary fill:#93c5fd,stroke:#2563eb,stroke-width:2px,color:#1e3a5f
    classDef policy fill:#fcd34d,stroke:#d97706,stroke-width:2px,color:#78350f
    classDef mechanism fill:#86efac,stroke:#16a34a,stroke-width:2px,color:#14532d
    classDef param fill:#fdba74,stroke:#ea580c,stroke-width:2px,color:#7c2d12
    classDef state fill:#5eead4,stroke:#0d9488,stroke-width:2px,color:#134e4a
    X_t(["X_t<br/>value"]):::state
    X_next(["X_{t+1}<br/>value"]):::state
    Theta{{"Θ<br/>heater"}}:::param
    subgraph U ["Boundary (U)"]
        heater[heater]:::boundary
    end
    subgraph g ["Policy (g)"]
        temp_sensor[temp_sensor]:::policy
        thermo[thermo]:::policy
    end
    subgraph f ["Mechanism (f)"]
        temperature_Dynamics[temperature Dynamics]:::mechanism
    end
    X_t --> U
    U --> g
    g --> f
    temperature_Dynamics -.-> |temperature.value| X_next
    Theta -.-> g
    Theta -.-> f
    style U fill:#dbeafe,stroke:#60a5fa,stroke-width:1px,color:#1e40af
    style g fill:#fef3c7,stroke:#fbbf24,stroke-width:1px,color:#92400e
    style f fill:#dcfce7,stroke:#4ade80,stroke-width:1px,color:#166534

Stage 4 -- Verification & Visualization

GDS provides two layers of verification:

  1. Generic checks (G-001..G-006) on SystemIR -- structural topology
  2. Semantic checks (SC-001..SC-007) on GDSSpec -- domain properties

Plus three Mermaid diagram views of the compiled system.

Running Verification

from gds.verification.engine import verify
from gds.verification.generic_checks import (
    check_g001_domain_codomain_matching,
    check_g003_direction_consistency,
    check_g004_dangling_wirings,
    check_g005_sequential_type_compatibility,
    check_g006_covariant_acyclicity,
)

report = verify(system, checks=[
    check_g001_domain_codomain_matching,
    check_g003_direction_consistency,
    check_g004_dangling_wirings,
    check_g005_sequential_type_compatibility,
    check_g006_covariant_acyclicity,
])

for finding in report.findings:
    status = "PASS" if finding.passed else "FAIL"
    print(f"[{finding.check_id}] {status}: {finding.message}")

Three Visualization Views

The compiled block graph showing blocks as nodes and wirings as arrows.

%%{init:{"theme":"neutral"}}%%
flowchart TD
    classDef boundary fill:#93c5fd,stroke:#2563eb,stroke-width:2px,color:#1e3a5f
    classDef generic fill:#cbd5e1,stroke:#64748b,stroke-width:1px,color:#1e293b
    heater([heater]):::boundary
    temp_sensor[temp_sensor]:::generic
    thermo[thermo]:::generic
    temperature_Dynamics[temperature Dynamics]:::generic
    heater --heater Reference--> thermo
    temp_sensor --temp_sensor Measurement--> thermo
    thermo --thermo Control--> temperature_Dynamics
    temperature_Dynamics -.temperature State..-> temp_sensor

Blocks grouped by GDS role: Boundary (U), Policy (g), Mechanism (f).

%%{init:{"theme":"neutral"}}%%
flowchart TD
    classDef boundary fill:#93c5fd,stroke:#2563eb,stroke-width:2px,color:#1e3a5f
    classDef policy fill:#fcd34d,stroke:#d97706,stroke-width:2px,color:#78350f
    classDef mechanism fill:#86efac,stroke:#16a34a,stroke-width:2px,color:#14532d
    classDef entity fill:#e2e8f0,stroke:#475569,stroke-width:2px,color:#0f172a
    subgraph boundary ["Boundary (U)"]
        heater([heater]):::boundary
    end
    subgraph policy ["Policy (g)"]
        temp_sensor[temp_sensor]:::policy
        thermo[thermo]:::policy
    end
    subgraph mechanism ["Mechanism (f)"]
        temperature_Dynamics[[temperature Dynamics]]:::mechanism
    end
    entity_temperature[("temperature<br/>value")]:::entity
    temperature_Dynamics -.-> entity_temperature
    thermo --ControlSpace--> temperature_Dynamics
    style boundary fill:#dbeafe,stroke:#60a5fa,stroke-width:1px,color:#1e40af
    style policy fill:#fef3c7,stroke:#fbbf24,stroke-width:1px,color:#92400e
    style mechanism fill:#dcfce7,stroke:#4ade80,stroke-width:1px,color:#166534

The formal h = f . g decomposition diagram (same as Stage 3 above).


Stage 5 -- Query API

SpecQuery provides static analysis over a GDSSpec without running any simulation. It answers structural questions about information flow, parameter influence, and causal chains.

Usage

from gds.query import SpecQuery

query = SpecQuery(spec)

# Which blocks does each parameter affect?
query.param_to_blocks()
# -> {'heater': ['heater']}

# Which mechanisms update each entity variable?
query.entity_update_map()
# -> {'temperature': {'value': ['temperature Dynamics']}}

# Group blocks by GDS role
query.blocks_by_kind()
# -> {'boundary': ['heater'], 'policy': ['temp_sensor', 'thermo'],
#     'mechanism': ['temperature Dynamics'], ...}

# Which blocks can transitively affect temperature.value?
query.blocks_affecting("temperature", "value")
# -> ['temperature Dynamics', 'thermo', 'temp_sensor', 'heater']

# Full block-to-block dependency DAG
query.dependency_graph()

Summary

You have built a complete GDS specification for a thermostat system, progressing through five stages:

  1. Minimal model -- types, entity, two blocks, sequential composition
  2. Feedback -- policies, parameters, temporal loop
  3. DSL -- same system in 15 lines with gds-control
  4. Verification -- structural and semantic checks, three diagram views
  5. Query -- static analysis of parameter influence and causal chains

From here, explore the example models or the Rosetta Stone guide to see the same system through different DSL lenses.

Interactive Notebook

Source code for packages/gds-examples/notebooks/getting_started.py

Tip: paste this code into an empty cell, and the marimo editor will create cells for you

"""Interactive Getting Started guide for GDS — marimo notebook.

A progressive 5-stage tutorial that teaches GDS fundamentals using a
thermostat control system. Run with: marimo run notebooks/getting_started.py
"""
# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "gds-examples",
#     "marimo>=0.20.0",
# ]
# ///

import marimo

__generated_with = "0.13.0"
app = marimo.App(width="medium", app_title="GDS Getting Started Guide")


@app.cell
def _():
    import marimo as mo

    return (mo,)


@app.cell
def _(mo):
    mo.md(
        """
        # Build Your First GDS Model

        This interactive notebook walks you through **five stages** of building
        a Generalized Dynamical Systems (GDS) specification for a thermostat
        control system.

        | Stage | What You Learn |
        |-------|---------------|
        | **1 -- Minimal Model** | Types, Entity, BoundaryAction, Mechanism, `>>` |
        | **2 -- Feedback** | Policy, parameters, temporal loop (`.loop()`) |
        | **3 -- DSL Shortcut** | `gds-control` ControlModel, canonical |
        | **4 -- Verification & Viz** | Generic/semantic checks, Mermaid diagrams |
        | **5 -- Query API** | `SpecQuery` for static analysis of your spec |

        Use the dropdown below to jump to a specific stage, or scroll through
        the notebook to follow the full progression.
        """
    )
    return


@app.cell
def _(mo):
    _stage_selector = mo.ui.dropdown(
        options={
            "Stage 1 -- Minimal Model": "stage1",
            "Stage 2 -- Feedback": "stage2",
            "Stage 3 -- DSL Shortcut": "stage3",
            "Stage 4 -- Verification & Visualization": "stage4",
            "Stage 5 -- Query API": "stage5",
        },
        value="Stage 1 -- Minimal Model",
        label="Jump to stage:",
    )
    return (_stage_selector,)


# ══════════════════════════════════════════════════════════════
# Stage 1: Minimal Model
# ══════════════════════════════════════════════════════════════


@app.cell
def _(mo):
    mo.md(
        """
        ---

        ## Stage 1 -- Minimal Model

        The simplest possible GDS model: a **heater** (BoundaryAction) warms a
        **room** (Entity with one state variable). Two blocks composed with `>>`.

        - **BoundaryAction**: exogenous input -- no `forward_in` ports
        - **Mechanism**: state update -- writes to entity variables, no backward ports
        - **`>>`**: sequential composition via token-matched port wiring
        """
    )
    return


@app.cell
def _(mo):
    from gds_examples.getting_started.stage1_minimal import build_spec as _build_spec_s1
    from gds_examples.getting_started.stage1_minimal import (
        build_system as _build_system_s1,
    )

    _spec_s1 = _build_spec_s1()
    _system_s1 = _build_system_s1()

    _summary_s1 = mo.md(
        f"""
        ### What Stage 1 Creates

        | Component | Count | Details |
        |-----------|------:|---------|
        | Entities  | {len(_spec_s1.entities)} | {", ".join(_spec_s1.entities.keys())} |
        | Blocks    | {len(_spec_s1.blocks)} | {", ".join(_spec_s1.blocks.keys())} |
        | Wirings   | {len(_system_s1.wirings)} | auto-wired via token overlap |
        | Parameters | {len(_spec_s1.parameters)} | *(none yet -- added in stage 2)* |
        """
    )

    from gds_viz import system_to_mermaid as _s2m

    _mermaid_s1 = mo.mermaid(_s2m(_system_s1))

    mo.vstack(
        [
            _summary_s1,
            mo.md("### Structural Diagram"),
            _mermaid_s1,
        ]
    )
    return


# ══════════════════════════════════════════════════════════════
# Stage 2: Feedback
# ══════════════════════════════════════════════════════════════


@app.cell
def _(mo):
    mo.md(
        """
        ---

        ## Stage 2 -- Adding Feedback

        Extend the minimal model with **observation and control**:

        - A **Sensor** (Policy) reads the room temperature
        - A **Controller** (Policy) decides the heat command
          using a `setpoint` parameter
        - A **TemporalLoop** (`.loop()`) feeds updated temperature back to the sensor
          across timesteps

        New operators: `|` (parallel composition) and `.loop()` (temporal feedback).
        """
    )
    return


@app.cell
def _(mo):
    from gds_examples.getting_started.stage2_feedback import (
        build_spec as _build_spec_s2,
    )
    from gds_examples.getting_started.stage2_feedback import (
        build_system as _build_system_s2,
    )

    _spec_s2 = _build_spec_s2()
    _system_s2 = _build_system_s2()

    _temporal_s2 = [w for w in _system_s2.wirings if w.is_temporal]

    _comparison = mo.md(
        f"""
        ### Stage 1 vs Stage 2

        | Property | Stage 1 | Stage 2 | Change |
        |----------|--------:|--------:|--------|
        | Blocks   | 2 | {len(_system_s2.blocks)} | +Sensor, +Controller (Policy role) |
        | Wirings  | 1 | {len(_system_s2.wirings)} | +inter-tier wiring, +temporal |
        | Temporal | 0 | {len(_temporal_s2)} | `.loop()` feeds state back to sensor |
        | Parameters | 0 | {len(_spec_s2.parameters)} | `setpoint` for controller |
        """
    )

    from gds_viz import system_to_mermaid as _s2m_2

    _mermaid_s2 = mo.mermaid(_s2m_2(_system_s2))

    mo.vstack(
        [
            _comparison,
            mo.md("### Structural Diagram (with Temporal Loop)"),
            _mermaid_s2,
        ]
    )
    return


# ══════════════════════════════════════════════════════════════
# Stage 3: DSL Shortcut
# ══════════════════════════════════════════════════════════════


@app.cell
def _(mo):
    mo.md(
        """
        ---

        ## Stage 3 -- DSL Shortcut

        Rebuild the same thermostat using the **gds-control** DSL. Declare
        states, inputs, sensors, and controllers -- the compiler generates all
        types, spaces, entities, blocks, wirings, and the temporal loop
        automatically.

        **~15 lines of DSL vs ~60 lines of manual GDS construction.**
        """
    )
    return


@app.cell
def _(mo):
    from gds_examples.getting_started.stage3_dsl import (
        build_canonical as _build_canonical_s3,
    )
    from gds_examples.getting_started.stage3_dsl import (
        build_spec as _build_spec_s3,
    )
    from gds_examples.getting_started.stage3_dsl import (
        build_system as _build_system_s3,
    )

    _spec_s3 = _build_spec_s3()
    _system_s3 = _build_system_s3()
    _canonical_s3 = _build_canonical_s3()
    _n_temporal_s3 = len([w for w in _system_s3.wirings if w.is_temporal])

    _dsl_comparison = mo.md(
        f"""
        ### Manual (Stage 2) vs DSL (Stage 3)

        | Property | Manual | DSL | Notes |
        |----------|-------:|----:|-------|
        | Blocks   | 4 | {len(_spec_s3.blocks)} | Same count |
        | Entities | 1 | {len(_spec_s3.entities)} | `Room` vs `temperature` |
        | Types    | 3 | {len(_spec_s3.types)} | DSL generates type set |
        | Temporal | 1 | {_n_temporal_s3} | Same structure |

        ### Canonical Decomposition: h = f . g

        The canonical projection separates the system into:
        - **X** (state): {len(_canonical_s3.state_variables)} variable(s)
        - **U** (boundary): {len(_canonical_s3.boundary_blocks)} block(s)
        - **g** (policy): {len(_canonical_s3.policy_blocks)} block(s)
        - **f** (mechanism): {len(_canonical_s3.mechanism_blocks)} block(s)
        """
    )

    from gds_viz import canonical_to_mermaid as _c2m

    _canonical_mermaid = mo.mermaid(_c2m(_canonical_s3))

    mo.vstack(
        [
            _dsl_comparison,
            mo.md("### Canonical Diagram"),
            _canonical_mermaid,
        ]
    )
    return


# ══════════════════════════════════════════════════════════════
# Stage 4: Verification & Visualization
# ══════════════════════════════════════════════════════════════


@app.cell
def _(mo):
    mo.md(
        """
        ---

        ## Stage 4 -- Verification & Visualization

        GDS provides two layers of verification:

        1. **Generic checks (G-001..G-006)** on `SystemIR` -- structural topology
        2. **Semantic checks (SC-001..SC-007)** on `GDSSpec` -- domain properties

        Plus three Mermaid diagram views of the compiled system.
        """
    )
    return


@app.cell
def _(mo):
    from gds_examples.getting_started.stage3_dsl import (
        build_spec as _build_spec_s4,
    )
    from gds_examples.getting_started.stage3_dsl import (
        build_system as _build_system_s4,
    )
    from gds_examples.getting_started.stage4_verify_viz import (
        generate_architecture_view as _gen_arch,
    )
    from gds_examples.getting_started.stage4_verify_viz import (
        generate_canonical_view as _gen_canon,
    )
    from gds_examples.getting_started.stage4_verify_viz import (
        generate_structural_view as _gen_struct,
    )
    from gds_examples.getting_started.stage4_verify_viz import (
        run_generic_checks as _run_generic,
    )
    from gds_examples.getting_started.stage4_verify_viz import (
        run_semantic_checks as _run_semantic,
    )

    _system_s4 = _build_system_s4()
    _spec_s4 = _build_spec_s4()

    # -- Generic checks --
    _report = _run_generic(_system_s4)
    _generic_rows = "\n".join(
        f"| {f.check_id} | {'PASS' if f.passed else 'FAIL'} | {f.message} |"
        for f in _report.findings
    )
    _generic_table = mo.md(
        "### Generic Checks (SystemIR)\n\n"
        "| Check | Result | Message |\n"
        "|-------|--------|---------|"
        + ("\n" + _generic_rows if _generic_rows else "")
        + f"\n\n**Summary**: {_report.checks_passed}/{_report.checks_total} passed,"
        f" {_report.errors} errors"
    )

    # -- Semantic checks --
    _semantic_results = _run_semantic(_spec_s4)
    _semantic_rows = "\n".join(f"| {line} |" for line in _semantic_results)
    _semantic_table = mo.md(
        "### Semantic Checks (GDSSpec)\n\n"
        "| Result |\n"
        "|--------|" + ("\n" + _semantic_rows if _semantic_rows else "")
    )

    # -- Mermaid views in tabs --
    _structural_mermaid = mo.mermaid(_gen_struct(_system_s4))
    _architecture_mermaid = mo.mermaid(_gen_arch(_spec_s4))
    _canonical_mermaid_s4 = mo.mermaid(_gen_canon(_spec_s4))

    _diagram_tabs = mo.ui.tabs(
        {
            "Structural": _structural_mermaid,
            "Architecture": _architecture_mermaid,
            "Canonical": _canonical_mermaid_s4,
        }
    )

    mo.vstack(
        [
            _generic_table,
            _semantic_table,
            mo.md("### Diagrams"),
            _diagram_tabs,
        ]
    )
    return


# ══════════════════════════════════════════════════════════════
# Stage 5: Query API
# ══════════════════════════════════════════════════════════════


@app.cell
def _(mo):
    mo.md(
        """
        ---

        ## Stage 5 -- Query API

        `SpecQuery` provides static analysis over a `GDSSpec` without running
        any simulation. It answers structural questions about information flow,
        parameter influence, and causal chains.
        """
    )
    return


@app.cell
def _(mo):
    from gds_examples.getting_started.stage5_query import (
        build_query as _build_query,
    )
    from gds_examples.getting_started.stage5_query import (
        show_blocks_by_role as _show_blocks_by_role,
    )
    from gds_examples.getting_started.stage5_query import (
        show_causal_chain as _show_causal_chain,
    )
    from gds_examples.getting_started.stage5_query import (
        show_dependency_graph as _show_dep_graph,
    )
    from gds_examples.getting_started.stage5_query import (
        show_entity_updates as _show_entity_updates,
    )
    from gds_examples.getting_started.stage5_query import (
        show_param_influence as _show_param_influence,
    )

    _query = _build_query()

    # -- Parameter influence --
    _param_map = _show_param_influence(_query)
    _param_rows = "\n".join(
        f"| `{param}` | {', '.join(blocks)} |" for param, blocks in _param_map.items()
    )
    _param_table = mo.md(
        "### Parameter Influence\n\n"
        "Which blocks does each parameter affect?\n\n"
        "| Parameter | Blocks |\n"
        "|-----------|--------|" + ("\n" + _param_rows if _param_rows else "")
    )

    # -- Entity updates --
    _entity_map = _show_entity_updates(_query)
    _entity_rows_list = []
    for _ent, _vars in _entity_map.items():
        for _var, _mechs in _vars.items():
            _entity_rows_list.append(f"| `{_ent}` | `{_var}` | {', '.join(_mechs)} |")
    _entity_rows = "\n".join(_entity_rows_list)
    _entity_table = mo.md(
        "### Entity Update Map\n\n"
        "Which mechanisms update each entity variable?\n\n"
        "| Entity | Variable | Mechanisms |\n"
        "|--------|----------|------------|"
        + ("\n" + _entity_rows if _entity_rows else "")
    )

    # -- Blocks by role --
    _by_role = _show_blocks_by_role(_query)
    _role_rows = "\n".join(
        f"| **{role}** | {', '.join(blocks)} |"
        for role, blocks in _by_role.items()
        if blocks
    )
    _role_table = mo.md(
        "### Blocks by Role\n\n"
        "| Role | Blocks |\n"
        "|------|--------|" + ("\n" + _role_rows if _role_rows else "")
    )

    # -- Causal chain --
    _affecting = _show_causal_chain(_query, "temperature", "value")
    _causal_table = mo.md(
        "### Causal Chain: temperature.value\n\n"
        "Blocks that can transitively affect `temperature.value`:\n\n"
        + ", ".join(f"`{b}`" for b in _affecting)
    )

    # -- Dependency graph --
    _dep_graph = _show_dep_graph(_query)
    _dep_rows = "\n".join(
        f"| `{src}` | {', '.join(f'`{t}`' for t in targets)} |"
        for src, targets in _dep_graph.items()
    )
    _dep_table = mo.md(
        "### Dependency Graph\n\n"
        "Block-to-block information flow:\n\n"
        "| Source | Targets |\n"
        "|--------|---------|" + ("\n" + _dep_rows if _dep_rows else "")
    )

    mo.vstack(
        [
            _param_table,
            _entity_table,
            _role_table,
            _causal_table,
            _dep_table,
        ]
    )
    return


@app.cell
def _(mo):
    mo.md(
        """
        ---

        ## Summary

        You have built a complete GDS specification for a thermostat system,
        progressing through five stages:

        1. **Minimal model** -- types, entity, two blocks, sequential composition
        2. **Feedback** -- policies, parameters, temporal loop
        3. **DSL** -- same system in 15 lines with `gds-control`
        4. **Verification** -- structural and semantic checks, three diagram views
        5. **Query** -- static analysis of parameter influence and causal chains

        From here, explore the other examples (`sir_epidemic`, `lotka_volterra`,
        `prisoners_dilemma`) or build your own domain model.
        """
    )
    return


if __name__ == "__main__":
    app.run()

To run the notebook locally:

uv run marimo run packages/gds-examples/notebooks/getting_started.py

Run the test suite:

uv run --package gds-examples pytest packages/gds-examples/tests/test_getting_started.py -v

Source Files

File Purpose
stage1_minimal.py Minimal heater model
stage2_feedback.py Feedback loop with policies
stage3_dsl.py gds-control DSL version
stage4_verify_viz.py Verification and visualization
stage5_query.py SpecQuery API
getting_started.py Interactive marimo notebook