Python Code — Control Systems/User Defined
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.
- 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.
Ports
- Inputs and Outputs – one of each by default, and both
counts are user-editable. The list
uand the returned list follow the port order on the canvas.
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
computeis 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#
| Fact | Value |
|---|---|
| registered type | Control_Systems/User_Defined/Python_Code |
| family | Control_Systems/User_Defined |
| solver environment class | ICoreBlock_0_Control_Systems_1_User_Defined_2_Python_Code |
| source | src/ICoreSDK/ICoreBlockLibrary/Blocks/Control_Systems/User_Defined/Python_Code/ICoreBlock_0_Control_Systems_1_User_Defined_2_Python_Code.cpp |
| header | src/ICoreSDK/ICoreBlockLibrary/Blocks/Control_Systems/User_Defined/Python_Code/ICoreBlock_0_Control_Systems_1_User_Defined_2_Python_Code.h |
| default size on canvas | 100 × 80 px |
| ports at insert | 1 in, 1 out |
| code generators implemented | Python |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | — |
| 2 | out | ICoreDouble | — |
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 variable | Default | Simulink 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.
Simulink bridge#
| support | Support::Both |
| Simulink path | simulink/User-Defined Functions/Python Code |
| port-count rule | PortsParam:: |
SampleTime parameter | yes |
| ports | derived from a SymbolSpec (addSymbol), not a port-count parameter |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
Python Code | OutputCode | passes 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/ICoreSDK/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
⚠ Mismatch. Fixed at the source — the description or the code, whichever is wrong (R-D9) — never explained away on a docs page:
B7no ICoreSimulinkBlockCatalog::registerEntry — the bridge treats an unregistered type as unsupported, so the block owes an entry
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
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#
| t | in ICoreDouble-Out-0 | out ICoreDouble-Out-0 |
|---|---|---|
| 0 | -2 | -2 |
| 0.4 | 0.5 | 0.5 |
| 0.8 | -2 | -2 |
| 1.2 | 0.5 | 0.5 |
| 1.6 | -2 | -2 |
| 2 | 0.5 | 0.5 |
| 2.4 | -2 | -2 |
| 2.8 | 0.5 | 0.5 |
| 3.2 | -2 | -2 |
| 3.6 | 0.5 | 0.5 |
| 4 | -2 | -2 |
| 4.4 | 0.5 | 0.5 |
| 4.8 | -2 | -2 |
| 5.2 | 0.5 | 0.5 |
Every 4th of 60 samples, from the table stimulus.
The same rig also ran:
| Stimulus | What it is | Output range |
|---|---|---|
impulse | Impulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair) | 0 … 1 |
ramp | Ramp: slope 1 from t = 0 | 0 … 5.8 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | -1 … 0.9996 |
step | Step: 0 -> 1 at t = 1 s | 0 … 1 |
Plotted: table — Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample
Category static · sample time 0.1 · 60 steps · commit ccf005c8 · produced by docsSample --out <folder> --steps 60 · data docs/generated/samples/Control_Systems__User_Defined__Python_Code.json · the SVG is generated from those numbers by tools/docs/plot_svg.py, so it is a run and not a drawing (R-D10).