def compile_model(model: StockFlowModel) -> GDSSpec:
"""Compile a StockFlowModel into a GDSSpec.
Registers: types, spaces, entities, blocks, wirings, and parameters.
"""
spec = GDSSpec(name=model.name, description=model.description)
# 1. Register types
spec.collect(LevelType, UnconstrainedLevelType, RateType, SignalType)
# 2. Register spaces
spec.collect(LevelSpace, UnconstrainedLevelSpace, RateSpace, SignalSpace)
# 3. Register entities (one per stock)
for stock in model.stocks:
spec.register_entity(_build_stock_entity(stock))
# 4. Register blocks
for conv in model.converters:
spec.register_block(_build_converter_block(conv))
for aux in model.auxiliaries:
spec.register_block(_build_auxiliary_block(aux, model))
for flow in model.flows:
spec.register_block(_build_flow_block(flow, model))
for stock in model.stocks:
spec.register_block(_build_stock_mechanism(stock, model))
# 5. Register spec wirings (document the composition structure)
all_block_names = [b.name for b in spec.blocks.values()]
wires: list[Wire] = []
# Flow → Stock mechanism wirings
for flow in model.flows:
if flow.target:
wires.append(
Wire(
source=flow.name,
target=_accumulation_block_name(flow.target),
space="RateSpace",
)
)
if flow.source:
wires.append(
Wire(
source=flow.name,
target=_accumulation_block_name(flow.source),
space="RateSpace",
)
)
spec.register_wiring(
SpecWiring(
name=f"{model.name} Wiring",
block_names=all_block_names,
wires=wires,
description=f"Auto-generated wiring for stock-flow model {model.name!r}",
)
)
# 6. Register converters as parameters
for conv in model.converters:
spec.register_parameter(
ParameterDef(
name=conv.name,
typedef=SignalType,
description=f"Exogenous parameter: {conv.name}",
)
)
return spec