Advent of SysML v2 | Lesson 19 – State Machine Simulation

Hello and welcome back to the Advent of SysML v2 – a daily, hands-on mini-course designed to bring you up to speed with the new SysML v2 standard and the modern Syside tooling that makes you productive from day one.

By the end of this lesson, you will:

  • Understand how to extract state machine definitions from SysML v2 models
  • Learn how to execute state machines using simulation engines
  • Validate state transition logic before implementation

If you want to dive deeper into the world of SysML v2, check out The SysML v2 Book, where this topic is also discussed in more detail.

From Modeling to Execution

In lesson 18, you learned how to model state-based behavior in SysML v2. You now know how to define states, specify transitions with triggers and guards, and organize complex behaviors using composite and parallel states. You can model how Santa’s sleigh controller reacts to events, how the reindeer monitoring system transitions between operational states, and how these behaviors are structured in your models.

But a model, no matter how carefully crafted, is still just a specification. How do you know your state machine logic is correct? How do you verify that transitions fire when they should, that guards prevent unwanted state changes, and that the system behaves as intended under different conditions?

This is where simulation comes in.

Simulation means taking the behavioral specification from your SysML v2 model and executing it. Instead of implementing the state machine in production code and hoping it works, you can extract the state machine definition from your model and run it through different scenarios to observe how it behaves. If your model specifies that the sleigh should transition from liftOff to flying when altitude reaches 20 meters, simulation lets you feed in altitude values and verify that the transition occurs at exactly that threshold. Not at 19 meters, not at 21 meters, but precisely when the guard condition is satisfied.

The process involves three steps:

  1. Extract the state machine structure from your SysML v2 model, including all states, transitions, guards, and actions you’ve defined.
  2. Load this definition into a simulation engine that can interpret and execute state machine semantics.
  3. Run scenarios by triggering events and observing how the state machine responds.

With SysML v2’s textual notation and Syside Automator’s programmatic access to models, this extraction and simulation process becomes straightforward. Your models aren’t locked in proprietary formats. They’re accessible, queryable, and executable.

Value of Simulation

Why invest time in simulating state machines when you can jump straight to implementation? Because validation before implementation is vastly cheaper than debugging after:

  • Catch logic errors early. A guard condition with the wrong comparison operator (< instead of <=) might seem trivial, but it can cause your system to behave incorrectly at critical thresholds. Simulation reveals these errors when they’re easy to fix, before they’re embedded in production code across multiple subsystems.
  • Test edge cases systematically. What happens when altitude hits exactly the threshold for transitioning between flight modes? What if speed oscillates in the deadzone between two operational states? What happens when you send a landing command while still in a takeoff sequence? Simulation lets you probe these boundary conditions deliberately rather than discovering them in operation.
  • Validate transition logic. State machines can become complex, especially with composite states and multiple transition paths. Simulation provides concrete evidence that your model behaves as intended. When you see a composite state transition between its substates at the exact moment you send a command, and return to the original substate when the operation completes, you have confidence that your logic is correct.
  • Understand system behavior. Sometimes you’re not sure how a state machine will behave under certain sequences of events. Simulation lets you explore questions like: “What happens if we initiate landing while in the middle of a delivery operation?” The model specifies the answer, but the simulation makes it observable.
  • Build confidence before implementation. When you move from model to code, simulation results become test cases. The scenarios you ran in simulation become the basis for unit tests, integration tests, and validation criteria. You’re not guessing what the implementation should do. You’ve already seen the correct behavior.

For systems engineers, simulation transforms models from specifications into executable artifacts that can be tested, validated, and refined before committing to implementation.

Building a State Machine Simulator

Let’s build a complete simulation pipeline for Santa’s Sleigh flight controller. We’ll extract the state machine from a SysML v2 model, load it into a simulation engine, and execute a delivery mission scenario to validate the behavior.

The Demo Model: Sleigh Flight Monitor

Our example is a helicopter-style flight controller for Santa’s sleigh. Here’s the complete SysML v2 model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package L19_State_Simulation {
    private import ScalarValues::*;
    private import SI::*;

    // Events for state transitions
    item def UpdateFlightData;
    item def StartFlight;
    item def AbortTakeoff;
    item def StartLanding;
    item def AbortLanding;
    item def StartDelivery;
    item def DeliveryComplete;

    state def SleighFlightMonitor {
        doc
        /* Monitors the flight state of Santa’s sleigh. Helicopter-style
         * flight model with command-driven takeoff/landing protocols.
         */

        // Flight parameters
        attribute altitude subsets ISQ::length default 0 [m];
        attribute groundSpeed subsets ISQ::speed default 0 [m / s];
        attribute verticalSpeed subsets ISQ::speed default 0 [m / s];

        entry;
            then grounded;

        state grounded {
            doc
            /* On the ground. Ready for takeoff. */
        }
            accept StartFlight then liftOff;

        state liftOff {
            doc
            /* Transient state: Automated takeoff protocol.
             * Climbing to cruise altitude. */
        }
            accept UpdateFlightData if altitude >= 20 [m] then flying;
            accept AbortTakeoff if altitude >= 1 [m] then descending;
            accept AbortTakeoff if altitude < 1 [m] then grounded;

        state flying {
            doc
            /* Cruising flight. Normal forward flight operations. */
        }
            accept UpdateFlightData if groundSpeed < 2 [m / s] then hovering;
            accept StartLanding then descending;

        state hovering {
            doc
            /* Hovering in place. Composite state for delivery operations. */

            entry;
                then idle;

            state idle {
                doc
                /* Hovering idle, waiting for delivery command. */
            }
                accept StartDelivery then giftMode;

            state giftMode {
                doc
                /* Actively delivering gifts. */
            }
                accept DeliveryComplete then idle;
        }
            accept UpdateFlightData if groundSpeed > 5 [m / s] then flying;
            accept StartLanding then descending;

        state descending {
            doc
            /* Transient state: Automated landing protocol.
             * Controlled descent to ground. */
        }
            accept UpdateFlightData if altitude <= 0 [m] then grounded;
            accept AbortLanding if altitude > 10 [m] then flying;
    }
}

Key elements of this model:

Events (lines 6-12) define the commands and updates that trigger transitions:

  • UpdateFlightData – Sensor data updates (altitude, speed)
  • StartFlight, StartLanding – Pilot commands for major phase changes
  • AbortTakeoff, AbortLanding – Emergency abort commands
  • StartDelivery, DeliveryComplete – Delivery workflow commands

Flight parameters (lines 21-23) track the sleigh’s state:

  • altitude – Height above ground in meters
  • groundSpeed – Horizontal velocity in m/s
  • verticalSpeed – Rate of climb (+) or descent (-)

Five states manage the flight phases:

  • grounded (line 28) – On the ground, ready for takeoff
  • liftOff (line 34) – Automated takeoff protocol (transient state)
  • flying (line 43) – Cruising flight between destinations
  • hovering (lines 50) – Stationary in air (composite state with idle and giftMode substates)
  • descending (line 72) – Automated landing protocol (transient state)

Transitions use guard conditions to automatically switch states when parameters cross thresholds. For example, line 39 shows accept UpdateFlightData if altitude >= 20 [m] then flying, which transitions from liftOff to flying when the sleigh reaches 20 meters in altitude. Similarly, line 47 uses groundSpeed < 2 [m/s] to detect when the sleigh has slowed to a hover. Major phase changes, like takeoff and landing, require explicit commands (StartFlight, StartLanding) for deliberate pilot control.

Extracting State Definitions

The first step is extracting the state machine structure from your SysML v2 model. With Syside Automator, you can programmatically traverse the model to find states, transitions, guards, and attributes.

Here’s the extraction process:

1. Load the SysML model:

1
2
3
import syside

model, diagnostics = syside.load_model([model_file_path])

2. Find the state machine element:

1
2
3
4
5
6
7
def find_element_by_name(model, name):
    for element in model.elements(syside.Element, include_subtypes=True):
        if element.name == name:
            return element
    return None

state_machine = find_element_by_name(model, “SleighFlightMonitor”)

3. Extract states recursively:

Each state is represented as a StateUsage in the SysML model. For composite states like hovering, you need to recursively extract substates:

1
2
3
4
5
6
7
8
9
def build_state_object(state):
    substates = [x for x in state.owned_elements
                 if type(x) is syside.StateUsage]

    return {
        “name”: state.name,
        “states”: [build_state_object(s) for s in substates],
        “transitions”: []  # populated separately
    }

4. Extract transitions with guards:

Transitions in SysML v2 are defined using accept statements with optional guard expressions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def build_transition_object(transition):
    trigger_event = transition.trigger_action.payload_parameter.feature_target.heritage[0][1]
   
    guard_str = “”
    if transition.guard_expression is not None:
        guard_str = syside.pprint(transition.guard_expression).strip()
    # Remove units ([kg], [m/s], etc.) from expression
    guard_str = re.sub(r“\s*\[[\w\s/]+\], “”, guard_str)

    return {
        “source”: transition.source.name,
        “target”: transition.target.name,
        “event”: trigger_event.name,
        “guard”: guard_str
    }

This extracts the critical information: which event triggers the transition, what guard condition must be satisfied, and what the source and target states are.

5. Extract simulation parameters:

1
2
3
4
attributes = find_owned_elements_by_type(state_machine, syside.AttributeUsage)
parameters = {}
for attribute in attributes:
    parameters[attribute.name] = evaluate_feature(attribute, state_machine)

At the end of extraction, you have a complete structural representation of the state machine: all states (including composite structure), all transitions (with events and guards), and all simulation parameters with their initial values.

Simulation Engines

Once you’ve extracted the state machine structure, you need a simulation engine to execute it. We use Sismic, a Python library specifically designed for statechart simulation. Sismic implements the full semantics of hierarchical state machines, including composite states, transition priorities, and event processing.

Why Sismic?

  • Supports hierarchical and composite states (critical for hovering:idle and hovering:giftMode)
  • Evaluates guard conditions dynamically based on context variables
  • Provides step-by-step execution with observable state changes
  • Open source and well-documented

Sismic requires state machines to be defined in YAML format. Since you’ve already extracted the structure from SysML, generating Sismic YAML involves building the document programmatically:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def generate_sismic_yaml(state_machine_data):
    # Build preamble with context variables
    preamble = \n.join([
        f{name} = {value}
        for name, value in state_machine_data[“parameters”].items()
    ])

    # Start YAML structure
    yaml_parts = [
        f“statechart:”,
        f”  name: {state_machine_data[‘name’]},
        f”  preamble: |”,
        *[f”    {line} for line in preamble.split(\n)],
        f”  root state:”,
        f”    name: root”,
        f”    initial: {state_machine_data[‘initial_state’]},
        f”    states:”
    ]

    # Generate each state with its transitions
    for state in state_machine_data[“states”]:
        yaml_parts.append(f”      - name: {state[‘name’]})
        if state[“transitions”]:
            yaml_parts.append(f”        transitions:”)
            for trans in state[“transitions”]:
                yaml_parts.append(f”          - target: {trans[‘target’]})
                yaml_parts.append(f”            event: {trans[‘event’]})
                if trans.get(“guard”):
                    yaml_parts.append(f”            guard: {trans[‘guard’]})

    return \n.join(yaml_parts)

Note: The code examples above are simplified for clarity.

For our SleighFlightMonitor, the generated YAML includes:

  • Preamble: Initializes context variables (altitude, groundSpeed, verticalSpeed)
  • States: All five top-level states plus the two substates of hovering
  • Transitions: Each with its triggering event and guard condition

Once you have the YAML, load it into Sismic:

1
2
3
4
5
6
from sismic.io import import_from_yaml
from sismic.interpreter import Interpreter

statechart = import_from_yaml(yaml_string)
interpreter = Interpreter(statechart)
interpreter.execute()  # Initialize to starting state

The interpreter is now ready to simulate the state machine.

Running the Simulation

With the state machine loaded in Sismic, you can execute scenarios by feeding events and updating context variables.

Our simulation script supports two modes:

  • Interactive mode for step-by-step exploration during design and troubleshooting
  • Scenario mode for running complete test sequences from a CSV file

Let’s demonstrate with a delivery mission scenario.

Scenario: One-way delivery mission

Santa’s sleigh takes off from the North Pole, flies to a house, hovers to deliver a gift, and lands at the destination.

Phase 1: Takeoff (t=0 to t=26)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
statechart = import_from_yaml(yaml_string)
interpreter = Interpreter(statechart)
interpreter.execute()  # Initialize to starting state

# t=5: Send takeoff command
interpreter.queue(Event(“StartFlight”))
interpreter.execute()
# State: grounded → liftOff

# t=6 to t=26: Update flight parameters as sleigh climbs
interpreter.context[“altitude”] = 1
interpreter.context[“groundSpeed”] = 0
interpreter.context[“verticalSpeed”] = 1
interpreter.queue(Event(“UpdateFlightData”))
interpreter.execute()
# State: liftOff (altitude < 20m, still climbing)

# … altitude increases each step …

# t=11: Altitude reaches 32m
interpreter.context[“altitude”] = 32
interpreter.context[“groundSpeed”] = 2
interpreter.context[“verticalSpeed”] = 13
interpreter.queue(Event(“UpdateFlightData”))
interpreter.execute()
# State: liftOff → flying (altitude >= 20m guard satisfied!)

At t=11, when the altitude reaches 32 meters, the guard condition altitude >= 20 is satisfied, and the state machine automatically transitions from liftOff to flying. This validates that the threshold logic is correct.

Phase 2: Arrival and hover (t=27 to t=35)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# t=26: Reached cruise altitude
interpreter.context[“altitude”] = 248
interpreter.context[“groundSpeed”] = 10
interpreter.context[“verticalSpeed”] = 0
interpreter.queue(Event(“UpdateFlightData”))
interpreter.execute()
# State: flying

# t=27-35: Sleigh decelerates as it approaches the house
# … groundSpeed decreases from 10 → 9 → 7 → 5 → 3 → 1 → 0 …

# t=35: Fully stopped
interpreter.context[“altitude”] = 248
interpreter.context[“groundSpeed”] = 0  # Speed drops below 2 m/s
interpreter.context[“verticalSpeed”] = 0
interpreter.queue(Event(“UpdateFlightData”))
interpreter.execute()
# State: flying → hovering:idle (groundSpeed < 2 m/s guard satisfied!)

When ground speed drops below 2 m/s at t=35, the state machine transitions from flying to hovering:idle. The sleigh is now stationary at 248 meters altitude, ready for delivery.

Phase 3: Gift delivery (t=40 to t=50)

1
2
3
4
5
6
7
8
9
# t=40: Start delivery
interpreter.queue(Event(“StartDelivery”))
interpreter.execute()
# State: hovering:idle → hovering:giftMode

# t=50: Delivery complete
interpreter.queue(Event(“DeliveryComplete”))
interpreter.execute()
# State: hovering:giftMode → hovering:idle

The composite state behavior is working correctly: the StartDelivery command transitions from idle to giftMode within the hovering superstate, and DeliveryComplete returns to idle.

Phase 4: Repositioning (t=55 to t=64)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# t=55-57: Sleigh accelerates for repositioning
interpreter.context[“altitude”] = 248
interpreter.context[“groundSpeed”] = 4
interpreter.context[“verticalSpeed”] = 0
interpreter.queue(Event(“UpdateFlightData”))
interpreter.execute()
# State: hovering:idle (speed < 5 m/s, still hovering)

# t=58: Speed crosses threshold
interpreter.context[“altitude”] = 248
interpreter.context[“groundSpeed”] = 7  # Exceeds 5 m/s threshold
interpreter.context[“verticalSpeed”] = 0
interpreter.queue(Event(“UpdateFlightData”))
interpreter.execute()
# State: hovering:idle → flying (groundSpeed > 5 m/s guard satisfied!)

# t=61: Slowing back down
interpreter.context[“altitude”] = 248
interpreter.context[“groundSpeed”] = 4  # Drops below 2 m/s threshold
interpreter.context[“verticalSpeed”] = 0
interpreter.queue(Event(“UpdateFlightData”))
interpreter.execute()
# State: flying → hovering:idle (groundSpeed < 2 m/s guard satisfied!)

# t=64: Back to stationary
interpreter.context[“altitude”] = 248
interpreter.context[“groundSpeed”] = 0
interpreter.context[“verticalSpeed”] = 0
interpreter.queue(Event(“UpdateFlightData”))
interpreter.execute()
# State: hovering:idle (repositioning complete)

This phase demonstrates the deadzone behavior and bidirectional transitions. The sleigh briefly accelerates above 5 m/s (triggering hovering → flying), then decelerates below 2 m/s (triggering flying → hovering). This validates that the state machine correctly handles transitions in both directions and that the speed deadzone (2-5 m/s) prevents oscillation.

Phase 5: Landing (t=68 to t=105)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# t=68: Initiate landing protocol
interpreter.queue(Event(“StartLanding”))
interpreter.execute()
# State: hovering:idle → descending

# t=69 to t=99: Altitude decreases during controlled descent
# … altitude decreases from 248 → 247 → 245 → … → 1 → 0 …

# t=99: Touchdown
interpreter.context[“altitude”] = 0
interpreter.context[“groundSpeed”] = 0
interpreter.context[“verticalSpeed”] = –1
interpreter.queue(Event(“UpdateFlightData”))
interpreter.execute()
# State: descending → grounded (altitude <= 0 guard satisfied!)

# t=100-105: Sleigh fully stopped on the ground
interpreter.context[“altitude”] = 0
interpreter.context[“groundSpeed”] = 0
interpreter.context[“verticalSpeed”] = 0
interpreter.queue(Event(“UpdateFlightData”))
interpreter.execute()
# State: grounded (mission complete)

At t=99, when the altitude reaches 0, the guard condition altitude <= 0 triggers the transition from descending to grounded. The sleigh continues to report grounded status through t=105, confirming stable landing. The mission is complete.

Observing the results:

Throughout the simulation, you can query the interpreter’s state:

1
2
print(interpreter.configuration)  # Shows all active states, including substates
# Output at t=45: [‘root’, ‘hovering’, ‘giftMode’]

The full scenario demonstrates:

  • Command-driven transitions: StartFlight, StartDelivery, StartLanding
  • Guard-based transitions: altitude >= 20, groundSpeed < 2, altitude <= 0
  • Composite state behavior: Transitioning between idle and giftMode within hovering
  • Transient states: liftOff and descending as protocol phases

By visualizing the state transitions and plotting the flight parameters over time, you can confirm that every transition happens exactly when it should, that guard conditions are evaluated correctly, and that the overall behavior matches your specification.

Challenge for Tomorrow

State machines are powerful tools for modeling reactive behavior, and simulation brings them to life. Now it’s your turn to build and validate your own state machine simulations.

Head over to Syside Cloud and complete today’s challenge:

  1. Extract the state machine from the model you built yesterday using the techniques demonstrated in this lesson.
  2. Play around with the simulation in the Interactive Mode.
  3. Build and simulate a scenario: a success path, a failure scenario, or an edge case.
  4. Observe and validate that the transitions occur as expected and that your guards work correctly.

Share your experiences and what you learned in the community forum: Did the simulation reveal any issues with your model? Were there edge cases you hadn’t considered? How did seeing the state machine execute change your understanding of the behavior? We’d love to see what you’re building and help troubleshoot any challenges.

Summary

In this episode, you learned how to take the state machines you modeled in Lesson 18 and bring them to life through simulation. You saw how to extract state machine definitions from SysML v2 models using Syside Automator, generate executable representations for simulation engines like Sismic, and execute them through realistic scenarios to validate behavior.

Simulation transforms models from static specifications into dynamic, testable artifacts. It reveals logic errors early, validates transition behavior, and builds confidence before implementation. By simulating Santa’s sleigh flight controller through a complete delivery mission, you validated that takeoff protocols, cruise flight, hovering behaviors, gift delivery workflows, and landing sequences all work exactly as specified, with guard conditions triggering at the right thresholds and command events driving intentional state changes.

If you haven’t yet done so, head to Syside Cloud to do the challenge. Don’t forget to come back tomorrow for another episode of the Advent of SysML v2!

Cookies

Learn SysML v2 through the Advent of SysML v2 Challenge!