Concept: Parts¶
A Part is the building block of structural composition in tg_model. It declares
parameters (inputs), attributes (derived values), constraints (checks), and can own
child Parts. After instantiation, Parts form a navigable tree under cm.root.
Minimal Part¶
Every Part subclass must call model.name(...) exactly once. After that, it can
declare any combination of parameters, attributes, and constraints:
from unitflow.catalogs.si import kg, m
from tg_model import Part
class FuelTank(Part):
@classmethod
def define(cls, model):
model.name("fuel_tank")
capacity = model.parameter("capacity_kg", unit=kg)
loaded = model.parameter("loaded_mass_kg", unit=kg)
margin = model.attribute("mass_margin_kg", unit=kg, expr=capacity - loaded)
model.constraint("within_capacity", expr=margin >= 0 * kg)
The compiler raises ModelDefinitionError if model.name(...) is missing or called twice.
Parameters¶
model.parameter(name, unit=...) declares a bindable input slot. Its value is supplied
at evaluation time in the inputs= map:
cm = instantiate(MySystem)
result = cm.evaluate(inputs={cm.root.fuel_tank.capacity_kg: 5000 * kg})
Parameters are free inputs — they have no expression and must be bound before evaluation.
Attributes¶
model.attribute(name, unit=..., expr=...) declares a derived value computed from
an expression over other slots. The expression uses the AttributeRef objects returned
by earlier model.parameter / model.attribute calls:
capacity = model.parameter("capacity_kg", unit=kg)
loaded = model.parameter("loaded_mass_kg", unit=kg)
margin = model.attribute("mass_margin_kg", unit=kg, expr=capacity - loaded)
For values that come from external tools or simulations, use computed_by= instead of
expr=. See Concept: External Compute (Concrete Binding).
Constraints¶
model.constraint(name, expr=...) declares a boolean pass/fail check. The expr=
must be a comparison expression over slot refs:
model.constraint("within_capacity", expr=margin >= 0 * kg)
model.constraint("loaded_positive", expr=loaded > 0 * kg)
Constraint results appear in RunResult.constraint_results.
result.passed is True only when all constraints pass.
Composing Parts¶
Use model.composed_of(name, ChildPartType) to declare a child Part.
This replaces the old model.part(name, Type) call (which no longer exists).
from unitflow.catalogs.si import kg
from tg_model import Part
class Engine(Part):
@classmethod
def define(cls, model):
model.name("engine")
model.parameter("dry_mass_kg", unit=kg)
model.parameter("thrust_kn", unit=kN)
class Wing(Part):
@classmethod
def define(cls, model):
model.name("wing")
model.parameter("structural_mass_kg", unit=kg)
class Airframe(Part):
@classmethod
def define(cls, model):
model.name("airframe")
model.composed_of("engine", Engine)
model.composed_of("wing", Wing)
model.composed_of returns a PartRef you can use in expressions and allocations:
engine = model.composed_of("engine", Engine)
# engine.dry_mass_kg is an AttributeRef to the child's slot
After instantiate, navigate the tree: cm.root.airframe.engine.dry_mass_kg.
Roll-ups¶
A roll-up aggregates a slot across all direct child Parts. Use the rollup helper:
from tg_model import Part, rollup
from unitflow.catalogs.si import kg
class StructurePart(Part):
@classmethod
def define(cls, model):
model.name("structure_part")
model.parameter("mass_kg", unit=kg)
class Assembly(Part):
@classmethod
def define(cls, model):
model.name("assembly")
model.composed_of("frame", StructurePart)
model.composed_of("bracket", StructurePart)
model.composed_of("cover", StructurePart)
total_mass = model.attribute(
"total_mass_kg",
unit=kg,
expr=rollup(model.parts(), "mass_kg"),
)
model.constraint("mass_positive", expr=total_mass > 0 * kg)
model.parts() returns a sentinel representing all direct child Parts declared on this
type. rollup(model.parts(), "slot_name") sums the named slot across those children.
Ports and connections¶
For interface-level wiring between Parts, declare ports and connections:
from tg_model import Part
from unitflow.catalogs.si import W
class PowerSource(Part):
@classmethod
def define(cls, model):
model.name("power_source")
model.parameter("output_power_w", unit=W)
model.port("power_out", direction="out")
class PowerLoad(Part):
@classmethod
def define(cls, model):
model.name("power_load")
model.parameter("required_power_w", unit=W)
model.port("power_in", direction="in")
class PowerSubsystem(Part):
@classmethod
def define(cls, model):
model.name("power_subsystem")
src = model.composed_of("source", PowerSource)
load = model.composed_of("load", PowerLoad)
model.connect(src.power_out, load.power_in)
Ports and connections are structural — they model interfaces and flow, not value transfer. Value flow between Parts uses expressions referencing child slot refs directly.
Reference: Part methods¶
Method |
Returns |
Description |
|---|---|---|
|
|
Required once. Snake_case identifier. |
|
|
Bindable input slot. |
|
|
Derived or computed value. |
|
|
Boolean pass/fail check. |
|
|
Declare a child Part. |
|
|
Structural interface port. |
|
|
Port-to-port connection. |
|
sentinel |
All direct child Parts (for roll-up expressions). |
|
|
External provenance reference. |