Block Model

Overview

A block is the fundamental unit of a pySimBlocks model. It encapsulates a discrete-time computation — sources, operators, controllers, physical plants — and exposes a uniform interface that the simulator calls at each step.

Every block inherits from Block and implements at minimum initialize() and output_update().

To add a new block to pySimBlocks — including its GUI metadata and index registration — see Adding a New Block.

Anatomy of a block

Inputs and outputs

Inputs and outputs are plain Python dicts mapping port names to NumPy arrays of shape (n, m). They are declared in __init__ and updated each step.

self.inputs["in"] = None      # declared, not yet connected
self.outputs["out"] = None    # declared, not yet computed

An input is None until a connection is established. An output is None until initialize() or output_update() sets it. Accessing a None input in output_update() should raise a RuntimeError.

State

State is also a dict, split into two separate dicts: state holds the current value x[k], and next_state holds the value computed by state_update() before it is committed.

self.state["x"] = np.zeros((2, 1))
self.next_state["x"] = np.zeros((2, 1))

A block with no state simply leaves both dicts empty. The simulator checks block.has_state to decide whether to call state_update() and commit_state().

Parameters

Parameters are regular Python attributes set in __init__. There is no dedicated container — a gain value, a matrix, a file path are all just attributes.

self.K = np.array(gain)
self.sample_time = sample_time

They are fixed at construction time and should not change during simulation.

Block lifecycle methods

initialize()

Called once before the simulation loop starts, in topological order. Must set a valid initial value for all outputs and state entries. Receives t0, the initial simulation time.

def initialize(self, t0: float) -> None:
    self.state["x"] = np.zeros((2, 1))
    self.outputs["out"] = self.state["x"].copy()

output_update()

Called every step for all active blocks, in topological order. Must compute outputs from state and inputs. Must not modify state.

def output_update(self, t: float, dt: float) -> None:
    self.outputs["out"] = self.state["x"].copy()

state_update()

Called every step, after all output_update() calls. Must write the next state into next_state. Must not modify state or outputs.

def state_update(self, t: float, dt: float) -> None:
    self.next_state["x"] = self.state["x"] + dt * self.inputs["in"]

Only called if block.has_state is True. Blocks with no state can omit this method.

finalize()

Called once after the simulation loop ends. Optional — the base class provides a no-op default. Use it to close files, release resources, or flush buffers.

direct_feedthrough flag

direct_feedthrough is a class-level boolean attribute that tells the simulator whether u[k] appears in output_update(). It defaults to True in the base class and should be overridden when the block’s output does not depend on its inputs at the same step.

class MyIntegrator(Block):
    direct_feedthrough = False

Setting it correctly is critical — it determines which edges appear in the dependency graph and therefore the execution order. An incorrect value either causes unnecessary ordering constraints or, worse, silently produces stale inputs. See Execution Order and Algebraic Loops for details.