This notebook introduces allocate, the SysML v2 relationship that assigns a behavioral element to a logical component; after running it you can express which component performs which function.
Chapter 4 nested ApplyHeat inside ToastBread as the usage applyHeat, reachable from Toaster’s own inherited toastBread action (Chapter 1’s perform action toastBread : ToastBread on ToastingSystem). Chapter 1 also gave Toaster a heating part typed by HeatingSystem. allocate connects these two: the component that performs the function, named and pointed at directly, rather than left as an unstated intent.
from pathlib import Path
import opensysml
from toaster.report import format_diagnostics
conn = opensysml.connect(version="v0.9.0")
HEATING_SYSTEM_DEF = """\
abstract part def HeatingSystem {
perform action applyHeat : ApplyHeat;
}"""
print(HEATING_SYSTEM_DEF)abstract part def HeatingSystem {
perform action applyHeat : ApplyHeat;
}
HeatingSystem becomes an abstract logical component that performs ApplyHeat: the perform relationship states, in the model, which component carries the function. This is the logical-component idiom this tutorial uses: an abstract part def that performs an action, not yet realized by any concrete part.
# allocate not yet supported by the Editor API: toaster#13 (https://github.com/Open-MBEE/toaster/issues/13) / OpenSysML#599 (https://github.com/Open-MBEE/OpenSysML/issues/599)
# spec: SysML v2 formal/2026-03-02 §7.15.2 (AllocationUsage)
TOASTER_WITH_ALLOCATION = """\
part def Toaster :> ToastingSystem {
attribute cycleTime : ISQ::DurationValue;
part heating : HeatingSystem;
part control : ControlSystem;
allocation heatAllocation allocate toastBread.applyHeat to heating;
}"""
print(TOASTER_WITH_ALLOCATION)part def Toaster :> ToastingSystem {
attribute cycleTime : ISQ::DurationValue;
part heating : HeatingSystem;
part control : ControlSystem;
allocation heatAllocation allocate toastBread.applyHeat to heating;
}
The allocation is named (heatAllocation) so model.query() can find it directly, and it is written as a member of Toaster itself, alongside heating: both ends are features the allocation can reach because it lives in the same body — toastBread, the action Toaster inherits from ToastingSystem, and heating, the part declared right beside it.
TOASTER_INCREMENT = f"{HEATING_SYSTEM_DEF}\n{TOASTER_WITH_ALLOCATION}"
source = Path("../../models/ch05-cumulative.sysml").read_text()
model = conn.load_from_content(source, strict=False)
assert model.ok, f"Model failed: {format_diagnostics(model.diagnostics)}"Both ends of an allocation must resolve to an existing usage. The negative control below allocates from a step that was never declared.
bad_source = """
package BadAlloc {
private import ScalarValues::*;
action def ApplyHeat;
action def ToastBread { action applyHeat : ApplyHeat; }
part def HeatingSystem;
part def Toaster { part heating : HeatingSystem; }
allocation badAlloc allocate ToastBread::undefinedStep to Toaster::heating;
}
"""
bad = conn.load_from_content(bad_source, strict=False)
assert not bad.ok
print(f"Neg control diagnostics: {bad.diagnostics[0].message!r}")Neg control diagnostics: 'unresolved reference: ToastBread::undefinedStep'
The diagnostic reports an unresolved reference: ToastBread::undefinedStep was never declared, so the allocation’s source usage does not exist.
from toaster.query import find_allocations, perform_relationships
allocations = find_allocations(model)
print(f"Allocations: {allocations}")
print(f"Perform relationships: {perform_relationships(model)}")Allocations: [{'id': 'ToasterDemo::Toaster::heatAllocation', 'type': 'AllocationUsage', 'ends': [['ToasterDemo::ToastingSystem::toastBread', 'ToasterDemo::ToastBread::applyHeat'], ['ToasterDemo::Toaster::heating']]}]
Perform relationships: [{'performer': 'ToasterDemo::ToastingSystem', 'action': 'ToasterDemo::ToastBread'}, {'performer': 'ToasterDemo::HeatingSystem', 'action': 'ToasterDemo::ApplyHeat'}]
find_allocations shows heatAllocation’s real ends: the first is the feature chain toastBread.applyHeat — toastBread, declared on ToastingSystem and inherited by Toaster, then applyHeat, nested inside the ToastBread action — and the second is Toaster::heating, a usage, not HeatingSystem the definition. perform_relationships shows HeatingSystem (the definition Toaster::heating is typed by) performing ApplyHeat: the allocation targets the usage; the performer relationship is stated on its type. Usage-level allocation is exactly this distinction, not a detail to blur past.
The assembled definitions loaded without error, and the query results confirm the allocation’s own ends, not just that a component of the right type performs the function.
Try the chapter exercise in exercises/ch05/exercise.ipynb: allocate your coffee maker’s applyWater step (Brew’s own nested action usage, from Chapter 4) to brewUnit (a usage, not BrewUnit the definition), following the pattern this notebook builds.