This notebook introduces item def; after running it you can declare named signal types and state, in the model itself, what each one denotes.
The previous notebook nested ApplyHeat inside ToastBread using the material flows Chapter 1 already defines (Bread, Toast) and the energy flows this chapter adds. The toasting cycle also has three named nouns Chapter 1 leaves undefined: a start, a finish, and a cancel request. This notebook declares them as item definitions and states, with each one’s own doc, that they denote signals: cycle start, cycle finish, cancel request, not the bread or toast themselves.
from pathlib import Path
import opensysml
from toaster.report import format_diagnostics
conn = opensysml.connect(version="v0.9.0")
# editor.add_item_def(owner='ToasterDemo', name='Start') when API ships
START_DEF = """\
item def Start {
doc /* Signal marking the start of a toasting cycle, not the bread itself. */
}"""
print(START_DEF)
item def Start {
doc /* Signal marking the start of a toasting cycle, not the bread itself. */
}
Start names a signal, not the bread entering the toaster: that material flow is already ToastBread::bread. The doc states this plainly so the model, not the surrounding prose, is the authority on what Start denotes.
# editor.add_item_def(owner='ToasterDemo', name='Finish') when API ships
FINISH_DEF = """\
item def Finish {
doc /* Signal marking the finish of a toasting cycle, not the toast itself. */
}"""
print(FINISH_DEF)
item def Finish {
doc /* Signal marking the finish of a toasting cycle, not the toast itself. */
}
Finish names the paired signal for the cycle’s end, distinct from ToastBread::toast, the toast itself.
# editor.add_item_def(owner='ToasterDemo', name='Cancel') when API ships
CANCEL_DEF = """\
item def Cancel {
doc /* Signal requesting cancellation of an in-progress toasting cycle. */
}"""
print(CANCEL_DEF)
item def Cancel {
doc /* Signal requesting cancellation of an in-progress toasting cycle. */
}
Cancel is the third signal: a request to stop an in-progress cycle. None of the three is wired as an accepted or produced item of ApplyHeat in this chapter; each states its own meaning so a later chapter that does wire them has a fixed denotation to wire to, not an undecided one.
TOASTER_INCREMENT = f"{START_DEF}\n{FINISH_DEF}\n{CANCEL_DEF}"
source = Path("../../models/ch04-cumulative.sysml").read_text()
model = conn.load_from_content(source, strict=False)
assert model.ok, f"Model failed: {format_diagnostics(model.diagnostics)}"
The three item definitions load without diagnostics. The next cell checks what happens when an item definition specializes a type that does not exist.
# Negative control: an item def that specializes an undefined type
# raises "unresolved reference" at the specialization site.
bad_source = """
package Bad {
item def BadItem :> UndefinedBase;
}
"""
bad = conn.load_from_content(bad_source, strict=False)
assert not bad.ok
print("Expected error:", bad.diagnostics[0].message)
Expected error: unresolved reference: UndefinedBase
With the negative control confirmed, the next cell confirms all three item definitions, each carrying its own denotation as a doc, resolve in the loaded model.
for name in ("ToasterDemo::Start", "ToasterDemo::Finish", "ToasterDemo::Cancel"):
sym = model.find(name)
assert sym is not None, f"Not found: {name}"
print(f"{sym.id}: kind={sym.kind}")
conn.close()
ToasterDemo::Start: kind=itemDef
ToasterDemo::Finish: kind=itemDef
ToasterDemo::Cancel: kind=itemDef
item def Start { doc ... } item def Finish { doc ... } item def Cancel { doc ... }, printed above, loaded without error, and model.find() resolves each one, with each kind confirmed above.
Try the chapter exercise in exercises/ch04/exercise.ipynb: add three signal item defs (BrewStart, BrewFinish, BrewCancel) to your coffee maker model, each with a doc stating what it denotes and what it is NOT, and confirm each is findable.