Concept: Systems¶
A System is the root of an executable model. It owns the Part tree, the
Requirement tree, and the allocation wiring between them.
There is exactly one System per configured model.
Anatomy of a System¶
from unitflow.catalogs.si import kg, m
from tg_model import Part, Requirement, System
class TankPart(Part):
@classmethod
def define(cls, model):
model.name("tank_part")
model.parameter("capacity_kg", unit=kg)
model.parameter("loaded_mass_kg", unit=kg)
class MassReq(Requirement):
@classmethod
def define(cls, model):
model.name("mass_req")
model.doc("Loaded mass shall not exceed tank capacity.")
capacity = model.parameter("capacity_kg", unit=kg)
loaded = model.parameter("loaded_mass_kg", unit=kg)
margin = model.attribute("margin_kg", unit=kg, expr=capacity - loaded)
model.constraint("within_capacity", expr=margin >= 0 * kg)
class FuelSystem(System):
@classmethod
def define(cls, model):
model.name("fuel_system") # required
# 1. Compose Parts
tank = model.composed_of("tank", TankPart)
# 2. Compose Requirements
reqs = model.composed_of("reqs", MassReq)
# 3. Allocate: wire requirement parameters to Part slots
model.allocate(reqs, tank, inputs={
"capacity_kg": tank.capacity_kg,
"loaded_mass_kg": tank.loaded_mass_kg,
})
The three responsibilities of a System.define():
Compose the Part tree via
model.composed_of(name, PartType).Compose the Requirement tree via
model.composed_of(name, RequirementType).Allocate requirements to Parts via
model.allocate(req_ref, part_ref, inputs={...}).
Scenario parameters on a System¶
Systems can declare their own parameters for scenario values that are not owned by any
single Part. These appear directly on cm.root after instantiation:
class MissionSystem(System):
@classmethod
def define(cls, model):
model.name("mission_system")
# Scenario-level inputs live directly on the System
scenario_dv = model.parameter("scenario_delta_v_m_s", unit=m_per_s)
design_dv = model.parameter("design_delta_v_m_s", unit=m_per_s)
vehicle = model.composed_of("vehicle", VehiclePart)
reqs = model.composed_of("reqs", DeltaVReq)
model.allocate(reqs, vehicle, inputs={
"scenario_dv": scenario_dv,
"design_dv": design_dv,
})
After instantiate, bind these as: cm.evaluate(inputs={cm.root.scenario_delta_v_m_s: 3800 * m_per_s, ...}).
Allocation wiring in depth¶
model.allocate(req_ref, target_ref, inputs={...}) is where requirement parameters get
their values at evaluation time.
req_ref — the RequirementRef to allocate. Obtain it from the return value of
model.composed_of("name", RequirementType). For nested packages, navigate with dot
access: reqs.l1_mission.payload.
target_ref — the PartRef representing the design element being allocated to.
The allocation_target_path in ConstraintResult rows matches the path to this Part.
inputs — maps each parameter name declared in the Requirement class to an
AttributeRef that provides its value at runtime. The ref must point to a slot
accessible from the System — either a direct System parameter, or a slot on a child Part:
# System-level parameter → requirement parameter
model.allocate(reqs.payload, aircraft, inputs={
"scenario_payload_kg": scenario_payload_param, # AttributeRef from model.parameter(...)
"envelope_payload_kg": aircraft.max_payload_kg, # AttributeRef from a child Part slot
})
Every parameter declared in the Requirement must appear in inputs for the
constraint to evaluate correctly (unbound parameters are free inputs that must be
supplied separately).
Allocating nested requirement packages¶
When a Requirement composes children, allocate each child independently:
class L1Reqs(Requirement):
@classmethod
def define(cls, model):
model.name("l1_reqs")
model.doc("L1 requirements.")
model.composed_of("payload", PayloadReq)
model.composed_of("range", RangeReq)
class ProgramSystem(System):
@classmethod
def define(cls, model):
model.name("program_system")
aircraft = model.composed_of("aircraft", Aircraft)
reqs = model.composed_of("reqs", L1Reqs)
# Allocate each leaf package separately
model.allocate(reqs.payload, aircraft, inputs={
"scenario_payload_kg": ...,
"envelope_payload_kg": aircraft.max_payload_kg,
})
model.allocate(reqs.range, aircraft, inputs={
"scenario_range_m": ...,
"envelope_range_m": aircraft.max_range_m,
})
Multiple allocations across different Parts¶
A requirement package can be allocated to different Parts to verify the same property against different targets:
model.allocate(reqs.mass_budget, fuselage, inputs={
"allowable_mass_kg": fuselage.structural_mass_limit_kg,
"actual_mass_kg": fuselage.oem_kg,
})
model.allocate(reqs.mass_budget, wing, inputs={
"allowable_mass_kg": wing.structural_mass_limit_kg,
"actual_mass_kg": wing.oem_kg,
})
Each allocation produces its own ConstraintResult row with distinct allocation_target_path.
System-in-System composition¶
model.composed_of(name, ChildSystemType) also accepts another System subclass as
the child type. The child System’s full Part tree, ports, and value slots become
reachable via dot-access on the returned PartRef:
class EnergySubsystem(System):
@classmethod
def define(cls, model):
model.name("energy_subsystem")
mission_kj = model.parameter("mission_energy_kj", unit=kJ)
battery = model.composed_of("battery", BatteryPart)
req = model.composed_of("energy_req", EnergyCapacityReq)
model.allocate(req, battery, inputs={
"mission_energy_kj": mission_kj,
"usable_capacity_kj": battery.usable_capacity_kj,
})
class AutonomousPlatform(System):
@classmethod
def define(cls, model):
model.name("autonomous_platform")
energy = model.composed_of("energy", EnergySubsystem)
# energy.battery.usable_capacity_kj is accessible as a PartRef
After instantiate, the full tree is navigable: cm.energy.battery.usable_capacity_kj.
Known limitation:
model.allocate()calls declared inside a child System are instantiated within that child’s scope and work correctly. However, allocations defined at the root that reference elements inside a child System (cross-scope allocation) are not currently supported. Keep allocation wiring co-located with the System that owns the requirement and its target Part.
Reference: System methods¶
Method |
Returns |
Description |
|---|---|---|
|
|
Required once. |
|
|
Scenario-level input. |
|
|
Declare a child Part. |
|
|
Mount a requirement tree. |
|
|
Wire requirement parameters to Part slots. |
|
|
External provenance reference. |
|
|
Traceability edge to a citation. |
model.attributeandmodel.constraintare not allowed onSystemand raiseModelDefinitionError. Value logic and checks belong on the owningPartor aRequirementpackage — keepSystem.define()focused on composition and wiring.