This notebook introduces part def; after running it you can declare concrete component types with no content of their own yet.
The previous notebook established ToastingSystem as the abstract system concept, together with the action it performs. This notebook introduces two concrete component types with no attributes or hierarchy yet: HeatingSystem and ControlSystem. They stay bare placeholders in this chapter; later chapters give them mechanisms, policies and interfaces.
from pathlib import Path
import opensysml
from toaster.report import format_diagnostics
conn = opensysml.connect(version="v0.9.0")
# editor.add_part_def(owner='ToasterDemo', name='HeatingSystem') when API ships
HEATING_SYS_DEF = "part def HeatingSystem;"
print(HEATING_SYS_DEF)part def HeatingSystem;
HeatingSystem is a concrete part definition, declared bare (terminated with ;, no body). Unlike abstract part def, it can be instantiated directly, but nothing yet says what it does.
# editor.add_part_def(owner='ToasterDemo', name='ControlSystem') when API ships
CONTROL_SYS_DEF = "part def ControlSystem;"
print(CONTROL_SYS_DEF)part def ControlSystem;
ControlSystem takes the same bare form. Two part definitions can exist side by side with no relationship yet; the next notebook relates the whole they compose into to ToastingSystem by specialization.
TOASTER_INCREMENT = f"{HEATING_SYS_DEF}\n{CONTROL_SYS_DEF}"
source = Path("../../models/ch01-cumulative.sysml").read_text()
model = conn.load_from_content(source, strict=False)
assert model.ok, f"Model failed: {format_diagnostics(model.diagnostics)}"Both part definitions load with no diagnostics. The next cell checks what a bare part def looks like when its terminating syntax is missing.
# Negative control: a bare part def declaration must end with a semicolon (or a body).
bad_source = """
package Bad {
part def HeatingSystem
}
"""
bad = conn.load_from_content(bad_source, strict=False)
assert not bad.ok
print("Expected error:", bad.diagnostics[0].message)Expected error: missing ';' at end of declaration
Omitting the terminating ; (or a body) after a bare part def is a syntax error, reported in bad.diagnostics[0].
hs = model.find("ToasterDemo::HeatingSystem")
assert hs is not None
print(f"kind : {hs.kind}")
print(f"id : {hs.id}")
print()
for e in model.query():
d = e.as_dict()
if d["@type"] == "PartDefinition":
print(f"PartDefinition: {d['qualifiedName']}")kind : partDef
id : ToasterDemo::HeatingSystem
PartDefinition: ToasterDemo::ToastingSystem
PartDefinition: ToasterDemo::HeatingSystem
PartDefinition: ToasterDemo::ControlSystem
PartDefinition: ToasterDemo::Toaster
HeatingSystem and ControlSystem drawn as two boxes with no edge between them: both exist, neither refers to the other yet.
from toaster.render import model_to_dot, render_dot
pair = ("ToasterDemo::HeatingSystem", "ToasterDemo::ControlSystem")
elements = [e for e in model.query() if e.as_dict().get("qualifiedName") in pair]
dot = model_to_dot(model, title="Ch1", elements=elements)
render_dot(dot, Path("../../figures/ch01-part-defs.svg"))
from IPython.display import SVG
SVG(filename="../../figures/ch01-part-defs.svg")The picture repeats what the PartDefinition query above already listed, now with no line joining them.
conn.close()model.find() resolves HeatingSystem, and the PartDefinition query above lists every named part def in the model so far, ControlSystem included.
The two bare part def declarations printed above loaded without error, and the PartDefinition query above lists both names, confirming each is now part of the model.
Try the chapter exercise in exercises/ch01/exercise.ipynb: define BrewUnit and HeatExchanger as two concrete component types and verify they load.