Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

the toaster’s own operating cycle

This notebook introduces Cycle, a state def ToastingSystem exhibits, with a heating state whose do action invokes the heat-generation step and transitions that return ready and cancelled to idle; after running it you can trace the toaster’s own operating modes as the model itself defines them.

No earlier chapter built a state machine: the toaster’s modes have never been part of the model before this notebook. Building Cycle here means getting three things right from the start, not repairing them: Cycle needs a real owner, its heating state needs to invoke a real function rather than being an inert label, and ready and cancelled need a way back to idle or the chapter’s own name, “cycle,” would not be true of the model.

state def Cycle {
    entry; then idle;
    state idle;

idle is the machine’s own entry state: waiting holds for any solution, a pop-up toaster and tongs with a blowtorch alike.

    state heating {
        do action generateHeat : GenerateHeat;
    }

heating’s do action invokes GenerateHeat, not the full ApplyHeat: ApplyHeat’s own bread input has no value at this level of decomposition, and only its already-[0..*] parameters stay executable when left unbound in the OpenSysML runtime v0.9.0 (DEFERRED.md D-026, and D-026’s own addendum recording this exact case). GenerateHeat’s own input is already [0..*], so invoking it directly keeps the state genuinely executable: the invocation is real, confirmed by the runtime actually attempting it (an unbound parameter inside GenerateHeat raises from inside the state, not silently). GenerateHeat itself has no body yet, though: Chapter 6 built it as a typed signature only, so nothing is computed when it runs. The state’s own transition table is what changes here, not any quantity: once a state actually carries an action with a computed result and a duration, a trace could yield a derived quantity; that threshold is still not met here.

    state ready;
    state cancelled;

ready and cancelled hold for any solution too: finishing and stopping on demand are both intents, not mechanisms.

    transition first idle accept Start then heating;
    transition first heating accept Finish then ready;
    transition first heating accept Cancel then cancelled;

accept Start, accept Finish and accept Cancel name the events that move the machine between modes. The OpenSysML runtime v0.9.0 keeps a transition’s trigger only as a string and never resolves it against Start, Finish or Cancel: a typo, or a reference to a name the model never declares, loads without error and simply never fires (DEFERRED.md D-023). The tutorial’s own guard, language_gap_findings, catches what the runtime does not; the cell after the negative control below demonstrates it directly.

    transition first ready then idle;
    transition first cancelled then idle;

These two transitions carry no accept: they fire as soon as their source state is entered, with no event required. They are what makes Cycle actually cycle: a finished or a cancelled run returns to idle, ready for another.

state def Cycle {
    entry; then idle;
    state idle;
    state heating {
        do action generateHeat : GenerateHeat;
    }
    state ready;
    state cancelled;
    transition first idle accept Start then heating;
    transition first heating accept Finish then ready;
    transition first heating accept Cancel then cancelled;
    transition first ready then idle;
    transition first cancelled then idle;
}

Cycle is now a complete state def. It still needs an owner: nothing exhibits it yet.

abstract part def ToastingSystem {
    perform action toastBread : ToastBread;
    exhibit state cycle : Cycle;
}

ToastingSystem, not Toaster, is the subject the functional, logical and physical layers all describe (Chapter 1’s own ruling): Toaster is presently the tutorial’s only concrete realization of it. A functional mode machine belongs on the subject itself, so it is ToastingSystem that exhibits Cycle. Toaster, and any usage of it, inherits the machine and can execute it, the same way it already inherits and performs toastBread.

Cycle’s own transition table, drawn: the tutorial’s first state diagram.

Loading...

A state machine referencing an undefined transition target fails to parse. The negative control below points a transition at a state the machine never declares.

Negative control ok: bad.ok=False

The diagnostic reports an unresolved reference: missing_state was never declared as a state of S. This catches an undefined target; it says nothing about an undefined trigger, which the next cell probes directly.

OpenSysML itself: typo_model.ok=True
{'rule': 'unresolved-transition-trigger', 'constraint': "SysML v2.0 formal/2026-03-02: 8.3.18.9 TransitionUsage (/triggerAction : AcceptActionUsage), 8.3.18.8 TransitionFeatureMembership (validateTransitionFeatureMembershipTriggerAction), 8.3.17.2 AcceptActionUsage (PDF p. 341-342, payloadParameter) - a trigger is a structured, resolvable element, so an `accept` trigger's payload name must resolve to a defined element in scope", 'element': 'ToasterDemo::Cycle::@6', 'message': "transition ToasterDemo::Cycle::@6 accepts trigger 'Strat', whose payload 'Strat' resolves to no element in scope"}

The OpenSysML runtime loads the typo cleanly (the OpenSysML itself line printed above is the runtime’s verdict): Strat never fires, and nothing in the runtime says so; sysml-toolkit v0.9.1 resolves trigger names and warns on a broken reference (D-023). The tutorial’s own guard catches it: language_gap_findings flags Strat as an unresolved trigger, close enough to the locally-declared Start to be a plausible typo (D-023). The runtime has a real hole here, and the tutorial supplies the check that closes it: exactly the construct-and-analyze loop this tutorial builds throughout.

Loading...

The same transition drawn with the typo’d trigger: the edge now reads accept Strat, visible in the picture the same way it is invisible to the OpenSysML runtime’s own loader.

ToastingSystem::cycle: kind='stateUsage', id='ToasterDemo::ToastingSystem::cycle'
Start, Finish (run through nominal): ['idle', 'heating', 'ready', 'idle']

ToastingSystem::cycle is a stateUsage, the exhibited occurrence of the Cycle state def; Toaster::cycle does not resolve by that name, the same way Toaster::toastBread does not, since both are inherited members of ToastingSystem, not redeclared on Toaster. That inheritance is a fact about the model’s structure, shown by model.find above, not by the trace below: in the OpenSysML runtime v0.9.0, execute_state’s performer argument has no effect on the result (a documented runtime gap, D-028); the same trace comes back whether performer names nominal, a usage that exhibits nothing at all, or is omitted entirely. The trace below runs Cycle’s own transition table: heating invokes GenerateHeat, ready follows Finish, and the machine returns to idle on its own, a real completion of the modeled cycle. No quantity is computed along the way; what changes is which mode the machine is in.

Start, Cancel: ['idle', 'heating', 'cancelled', 'idle']
Start, Finish, Start, Finish: ['idle', 'heating', 'ready', 'idle', 'heating', 'ready', 'idle']

Both traces above run against the same transition table the first diagram in this notebook already drew — idle → heating → ready → idle and idle → heating → cancelled → idle are two paths through that one picture, not two different machines.

Both traces are derived from Cycle’s own transition table, not entered as a choice: they show the machine can be run twice in a row and return to idle each time, catching a mistake in the table rather than establishing anything about the toaster in use. A trace like this is specification analysis, not a simulation of behavior.

The definitions printed above loaded without error, and model.find and execute_state both confirm Cycle is now part of the model, shown by the traces printed above.

Try the chapter exercise in exercises/ch07/exercise.ipynb: add a BrewCycle state machine mirroring Cycle exactly — an idle entry state, a brewing state whose do action invokes moveWater, two exit states, and completion transitions back to idle — and trace it with execute_state.