Getting Started¶
Installation¶
For development (monorepo):
From Specification to Simulation¶
The typical workflow: build a GDS specification from a domain model, supply behavioral functions, then simulate.
from gds_domains.control import (
State, Input, Sensor, Controller,
ControlModel, compile_model,
)
from gds_analysis import spec_to_model
# 1. Build a GDS specification from a control model
model = ControlModel(
name="Thermostat",
states=[State(name="temperature", initial=20.0)],
inputs=[Input(name="setpoint")],
sensors=[Sensor(name="thermometer", observes=["temperature"])],
controllers=[
Controller(name="PID", reads=["thermometer", "setpoint"], drives=["temperature"]),
],
)
spec = compile_model(model)
# 2. Supply behavioral functions (R3 -- not in the spec)
sim_model = spec_to_model(
spec,
policies={
"thermometer": lambda state, params, **kw: {"reading": state["temperature"]},
"PID": lambda state, params, **kw: {
"command": params["Kp"] * (params["setpoint"] - state["temperature"])
},
},
sufs={
"temperature": lambda state, params, signal=None, **kw: (
"temperature",
state["temperature"] + signal["command"] * 0.1,
),
},
initial_state={"temperature": 20.0},
params={"setpoint": 22.0, "Kp": 0.3},
)
# 3. Run via gds-sim
from gds_sim import Simulation
results = Simulation(model=sim_model, timesteps=50, runs=1).run()
print(f"Final temperature: {results['temperature'][-1]:.1f}")
Guarded Policies¶
Enforce AdmissibleInputConstraint at runtime:
from gds_analysis import guarded_policy
# Wrap a policy to enforce admissibility
safe_policy = guarded_policy(
policy_fn=my_policy,
constraint=lambda state, action: action["power"] <= state["max_power"],
fallback=lambda state, params, **kw: {"power": 0.0},
)
If the constraint predicate returns False, the fallback function is called instead. Without a fallback, ConstraintViolationError is raised.
Computing Reachable Sets¶
Explore what states are reachable from an initial condition:
from gds_analysis import reachable_set, reachable_graph, configuration_space
# R(x) = set of states reachable in one step from x
reached = reachable_set(
spec,
sim_model,
state={"temperature": 20.0},
input_samples=[
{"command": 0.0},
{"command": 1.0},
{"command": -1.0},
],
)
print(f"Reachable states: {len(reached)}")
# Build graph over sampled states and extract configuration space
graph = reachable_graph(spec, sim_model, states=sampled_states, input_samples=inputs)
x_c = configuration_space(graph) # largest SCC -- mutually reachable states
Trajectory Analysis¶
Measure convergence along a simulation trajectory:
from gds_analysis import trajectory_distances
distances = trajectory_distances(
results,
metric=lambda s1, s2: abs(s1["temperature"] - s2["temperature"]),
)
# Check convergence: distances should decrease
print(f"Initial distance: {distances[0]:.3f}")
print(f"Final distance: {distances[-1]:.3f}")
Next Steps¶
- Analysis Overview -- architecture and key functions
- Framework -- GDS specification and structural annotations
- PSUU -- systematic parameter exploration over simulations