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.