Simulation tasks

Use this page when you already have an FCSTM model and need to run a concrete simulation task. For the first guided run, start with FCSTM simulation first run. For execution-order reasoning, see Execution semantics explanation. For exact command forms, settings, export formats and Python API boundaries, see Simulation reference.

Quick command reminder

This section is a task reminder. The complete lookup tables live in Simulation reference.

pyfcstm simulate currently exposes a small CLI surface:

pyfcstm simulate options

Option

Meaning

-i / --input-code TEXT

Entry FCSTM file. This option is required.

-e / --execute TEXT

Run semicolon-separated batch commands instead of starting the REPL.

--no-color

Disable ANSI color in command output.

-h / --help

Show the CLI help text.

The batch string and the REPL share the same command names. The most useful commands are:

Simulator commands

Command

Use it for

cycle [count] [events...]

Execute one or more cycles. Omit count for one cycle; event names such as cycle Start are passed to that cycle.

current

Show the current cycle, current state and persistent variables.

events

Show events available from the current state.

history [n|all]

Show recent history rows or the full retained history.

init <state_path> [var=value...]

Rebuild the runtime as a hot start at a state with explicit variables.

setting [key] [value]

Show or change display/history/logging settings.

export <path>

Export history; the format is inferred from the file extension.

clear

Reset the runtime using the current settings.

help

Show command help.

quit / exit

Leave the REPL.

Run batch commands

Use -e when you want a reproducible transcript in CI, docs or bug reports. Separate commands with semicolons:

pyfcstm simulate -i docs/source/tutorials/cli/simple_machine.fcstm \
  -e "cycle; events; cycle Start; current; cycle Stop; history 3" \
  --no-color

The generated transcript used by the short tutorial is kept here:

────────────────────────────────────────────────────────────
>>> cycle
────────────────────────────────────────────────────────────
Cycle: 1
Current State: SimpleMachine.Idle
Variables:
  counter = 0

────────────────────────────────────────────────────────────
>>> events
────────────────────────────────────────────────────────────
Available Events:
  • Start (SimpleMachine.Idle.Start)

────────────────────────────────────────────────────────────
>>> cycle Start
────────────────────────────────────────────────────────────
Cycle: 2
Current State: SimpleMachine.Running
Variables:
  counter = 0

────────────────────────────────────────────────────────────
>>> current
────────────────────────────────────────────────────────────
Cycle: 2
Current State: SimpleMachine.Running
Variables:
  counter = 0

────────────────────────────────────────────────────────────
>>> cycle Stop
────────────────────────────────────────────────────────────
Cycle: 3
Current State: SimpleMachine.Stopped
Variables:
  counter = 1

────────────────────────────────────────────────────────────
>>> history 3
────────────────────────────────────────────────────────────
 Cycle          State          counter
---------------------------------------
   1     SimpleMachine.Idle       0
   2    SimpleMachine.Running     0
   3    SimpleMachine.Stopped     1

Use the interactive REPL

Omit -e to start the REPL:

pyfcstm simulate -i docs/source/tutorials/cli/simple_machine.fcstm --no-color

Then type commands one at a time:

cycle
events
cycle Start
current
history 3
quit

Interactive mode adds command history, completion and suggestions, but command semantics are the same as batch mode.

Inject events

A command such as cycle Start injects an event into that cycle. In Python, pass the event list to SimulationRuntime.cycle:

Event injection through the Python runtime
#!/usr/bin/env python3
from pyfcstm.dsl import parse_with_grammar_entry
from pyfcstm.model import parse_dsl_node_to_state_machine
from pyfcstm.simulate import SimulationRuntime

dsl_code = """
def int counter = 0;

state System {
    [*] -> Idle;

    state Idle;
    state Active;

    Idle -> Active :: Start;
    Active -> Idle :: Stop;
}
"""

# Parse and create state machine
ast = parse_with_grammar_entry(dsl_code, 'state_machine_dsl')
sm = parse_dsl_node_to_state_machine(ast)

# Create runtime
runtime = SimulationRuntime(sm)

# Initialize
runtime.cycle()
print(f"Initial: state={'.'.join(runtime.current_state.path)}")

# Trigger Start event
runtime.cycle(['Start'])
print(f"After 'Start': state={'.'.join(runtime.current_state.path)}")

# Trigger Stop event
runtime.cycle(['Stop'])
print(f"After 'Stop': state={'.'.join(runtime.current_state.path)}")

# Try Start again
runtime.cycle(['Start'])
print(f"After 'Start' again: state={'.'.join(runtime.current_state.path)}")

Output:

Initial: state=System.Idle
After 'Start': state=System.Active
After 'Stop': state=System.Idle
After 'Start' again: state=System.Active

Event names in examples rely on DSL event scoping. For syntax facts, use DSL reference; this page only covers how the simulator receives events. For supported runtime event input forms and event-accounting fields in CycleResult, use Simulation reference.

Hot start at a state

Use the REPL init command when you need to inspect a later state without replaying all earlier cycles:

init System.Active counter=10 flag=1
cycle
current

Use the Python API when embedding the same idea in tests:

runtime = SimulationRuntime(
    sm,
    initial_state="System.Active",
    initial_vars={"counter": 10, "flag": 1},
)

Hot start has deliberate boundaries:

  • initial_vars must provide every declared persistent variable.

  • enter actions are skipped for the pre-built path.

  • during actions run normally after the first cycle starts.

  • a composite hot-start target must have a valid path to a stoppable leaf, or the runtime reports a DFS validation error.

Implement abstract handlers

Abstract lifecycle actions are implemented by registering Python handlers. Use @abstract_handler on methods whose names can be arbitrary; the decorator argument names the DSL action path:

Abstract handlers in a simulation runtime
#!/usr/bin/env python3
from pyfcstm.dsl import parse_with_grammar_entry
from pyfcstm.model import parse_dsl_node_to_state_machine
from pyfcstm.simulate import SimulationRuntime, abstract_handler

dsl_code = """
def int counter = 0;

state System {
    [*] -> Active;

    state Active {
        enter abstract Init;
        during abstract Monitor;
        exit abstract Cleanup;

        during {
            counter = counter + 1;
        }
    }

    state Done;

    Active -> Done : if [counter >= 5];
}
"""

# Define handler class
class MyHandlers:
    @abstract_handler('System.Active.Init')
    def handle_init(self, ctx):
        print(f"[Init] state={ctx.get_full_state_path()}, counter={ctx.get_var('counter')}")

    @abstract_handler('System.Active.Monitor')
    def handle_monitor(self, ctx):
        print(f"[Monitor] counter={ctx.get_var('counter')}")

    @abstract_handler('System.Active.Cleanup')
    def handle_cleanup(self, ctx):
        print(f"[Cleanup] final_counter={ctx.get_var('counter')}")

# Parse and create state machine
ast = parse_with_grammar_entry(dsl_code, 'state_machine_dsl')
sm = parse_dsl_node_to_state_machine(ast)

# Create runtime and register handlers
runtime = SimulationRuntime(sm)
handlers = MyHandlers()
runtime.register_handlers_from_object(handlers)

# Execute cycles
for i in range(1, 7):
    runtime.cycle()
    print(f"Cycle {i}: state={'.'.join(runtime.current_state.path)}")

Output:

[Init] state=System.Active, counter=0
[Monitor] counter=0
Cycle 1: state=System.Active
[Monitor] counter=1
Cycle 2: state=System.Active
[Monitor] counter=2
Cycle 3: state=System.Active
[Monitor] counter=3
Cycle 4: state=System.Active
[Monitor] counter=4
Cycle 5: state=System.Active
[Cleanup] final_counter=5
Cycle 6: state=System.Done

Handler context is read-only. Typical helpers are ctx.get_full_state_path(), ctx.get_var(name), ctx.has_var(name), ctx.active_leaf and ctx.action_stage.

Export history

Use history when you only need terminal output. Use export <path> when you want a file for later analysis. The simulator infers the format from the extension; current task docs should prefer small examples such as:

cycle
cycle Start
history 2
export run.json

Use JSON or JSONL for machine processing, CSV for spreadsheets, and YAML for human-readable snapshots. Keep exported files outside the source tree unless the file is a checked-in demo output regenerated by the docs build. The exact per-format fields are listed in Simulation reference.

Tune display settings

setting without arguments lists current settings. setting key value changes one value for the current session. Common examples are:

setting table_max_rows 10
setting history_size 200
setting log_level info
setting color off

history_size controls retained rows. color is also affected by the CLI --no-color option. The complete setting table, defaults and accepted value forms are in Simulation reference.

Debug a failing model

A compact debugging loop is:

  1. Run pyfcstm inspect first if parsing or diagnostics are suspicious.

  2. Use pyfcstm simulate -i docs/source/tutorials/cli/simple_machine.fcstm -e "cycle; events; current" --no-color to get a stable transcript.

  3. Add only one event at a time.

  4. Use history or export after the failure.

  5. If hot start fails, check that every variable is provided and that the target composite can reach a stoppable leaf without requiring a missing event.

Do not use simulation output as a substitute for target-runtime tests. It is a fast model-level check that should be paired with generated-runtime tests when you depend on target-language behavior.