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.
Reference: System methods¶
Method |
Returns |
Description |
|---|---|---|
|
|
Required once. |
|
|
Scenario-level input. |
|
|
Derived system-level value. |
|
|
System-level boolean check. |
|
|
Declare a child Part. |
|
|
Mount a requirement tree. |
|
|
Wire requirement parameters to Part slots. |
|
|
External provenance reference. |
|
|
Traceability edge to a citation. |