Generated reference › Python Code — Control Systems/User Defined
kind: generated#block#control-systems-user-defined

Python Code — Control Systems/User Defined

.py

Control_Systems/User_Defined/Python_Code · 1 input / 1 output port(s) at insert · exports to Python

Description#

The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.

Python Code

Control Systems / User Defined

Runs Python you write, once per simulation step, in the embedded interpreter. Use it for anything the library has no block for – a nonlinearity, a lookup, a hand-written controller.

The contract

Your code defines one function:

def compute(t, u, state):

  • t – the current simulation time, in seconds.
  • u – a list of numpy matrices, one per input port. Each one carries its port's own dtype – bool_ for a Boolean port, int32 for an ICoreInt32, float32 for an ICoreSingle, float64 for a plain double – so u[i].dtype is how your code asks what a port is. A String port is not an array at all: it arrives as a Python str.
  • state – a dict that persists between steps; use it for anything the block must remember.
  • returns – a list of numpy matrices, one per output port. Return whatever dtype is natural – float, int or bool all work, and the port applies its own type to the value on the way in. An array of strings for a numeric port is refused by name, and the message says which dtype it was. A String output returns a str (anything str() accepts is taken).

Ports

  • Inputs and Outputs – one of each by default, and both counts are user-editable. The list u and the returned list follow the port order on the canvas. Every port is ICoreDouble unless it was retyped; its type reaches your code as the array's dtype (above).

Parameters

  • Python Code – edited in the code editor, opened from the Edit Code button on the config dialog's toolbar rather than in the parameter pane.
  • Sampling Time (s) – how often compute is called. Zero or less inherits the solver's rate.

Code export

Python only – your code is embedded, indented, into the generated module. The other nine targets (MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text) are not supported: none can carry a Python interpreter, so an export to any of them fails loudly for this block rather than emitting something that does not run. Replace the block with library blocks, or with a state space, when the model has to reach those targets.

Simulink bridge

Both directions, to simulink/User-Defined Functions/Python Code. The ports cross as SymbolSpec symbols (u1…, y1…, in port order) so the wiring survives, and the code crosses into the block's Output Code as text – Python Code ↔ OutputCode. The two contracts differ: ICore calls your compute once per step, while the Python Code block runs its output code directly over the symbols, so the code needs adapting after crossing before the model runs on the other side. Sampling Time (s) → SampleTime, as on every block.

Notes

  • Discrete by nature: the block is stepped, never integrated, since Python state updates are per-sample.
  • A runtime error in your code is reported once, with the block's path, and stops the run.

Code facts#

FactValue
registered typeControl_Systems/User_Defined/Python_Code
familyControl_Systems/User_Defined
solver environment classICoreBlock_0_Control_Systems_1_User_Defined_2_Python_Code
sourcesrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/User_Defined/Python_Code/ICoreBlock_0_Control_Systems_1_User_Defined_2_Python_Code.cpp
headersrc/ICoreBlocks/ICoreBlockLibrary/Blocks/Control_Systems/User_Defined/Python_Code/ICoreBlock_0_Control_Systems_1_User_Defined_2_Python_Code.h
default size on canvas100 × 80 px
ports at insert1 in, 1 out
code generators implementedPython

Ports#

#DirectionSignal typeDescription label
1inICoreDouble—
2outICoreDouble—

Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.

Configuration variables#

Config variableDefaultSimulink parameter
CONFIG_PYTHON_CODE (unresolved)DEFAULT_CODE_TEMPLATE—

Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.

supportSupport::Both
Simulink pathsimulink/User-Defined Functions/Python Code
port-count rulePortsParam::
SampleTime parameteryes
portsderived from a SymbolSpec (addSymbol), not a port-count parameter
ICore configSimulink parameterValue translation
Python CodeOutputCodepasses through

Caveat (shown to the user): the code crosses into the Python Code block's OutputCode as text, but the contracts differ: ICore calls compute(t, u, state) while Simulink runs the output code directly over its SymbolSpec symbols (u1.., y1..), so the code needs adapting after crossing

Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h

Description vs code#

The checker has a blind spot here — it could not resolve something (a grouped port bullet, a computed config name), which is reported and never counted as a pass. A reader has to settle it:

  • B0 every stimulus in the sample errored — cross-checks skipped

The verdict above is tools/docs/check_block_descriptions.py (P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.

File banner (developer view)#

The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).

Python Code block — user-editable compute(t, u, state) over numpy matrices The user's source lives in a private config variable (edited via the config dialog's "Edit Code" toolbar button, persisted base64 by the recipe serializer). Contract:

def compute(t, u, state): # t: time [s], u: list of numpy matrices (input ... # ports, in order), state: dict persisting between return [y0, ...] # steps -> one numpy matrix per output port

Each u entry carries its PORT'S OWN dtype (bool_ / intN / float32 / float64), which is how a Python block asks what a port is; a String port arrives as a str and its output is returned as one.

Simulation: discrete-only block; compute_h_discrete runs the code in the embedded interpreter (ICorePythonRuntime). Output port sizes are inferred at build time by a throw-away trial call on zero inputs. Code export: Python only — every other language keeps the base class's empty default, which the exporters report as unsupported.

Sample results#

No stimulus produced a sampled output in this rig — Python runtime unavailable at block: ICore Blocks/Home/Python Code. That is a fact about the single-block rig, not a verdict on the block: an offline batch fit, a block whose output only appears at onSolverFinish, or one that needs a driven environment cannot be exercised alone.

Category unsampled · sample time 0.1 · 60 steps · commit c01902987 · produced by docsSample --out <folder> --steps 60

Sample data: docs/generated/samples/Control_Systems__User_Defined__Python_Code.json