Python.py
The quickest way to get a model into a notebook, a test rig or a data pipeline.
# ========================================================= # USER IMPORTS # ========================================================= import numpy as np from typing import List # --- Module external input interface ----------------------------- # Top-level input gates read their values from here. A consumer (e.g. the # testbench) sets inputs each step via set_inputs(); empty -> zeros. _INPUTS = {} def set_inputs(values): global _INPUTS _INPUTS = values def get_input(name, m, p): if not _INPUTS: return np.zeros((m, p)) arr = np.zeros((m, p)) for r in range(m): for c in range(p): key = name if (m == 1 and p == 1) else f"{name}[{r},{c}]" arr[r][c] = _INPUTS.get(key, 0.0) return arr # ========================================================= # GENERATED SIGNAL STORAGE # ========================================================= class Signals: def __init__(self): # sig0: ICore Blocks/Home/DPT_Feedback_Discrete/In1/ICoreDouble-Out-0 self.sig0 = np.zeros((1, 1)) # sig1: ICore Blocks/Home/DPT_Feedback_Discrete/Error/ICoreDouble-Out-0 self.sig1 = np.zeros((1, 1)) # sig2: ICore Blocks/Home/DPT_Feedback_Discrete/Ctrl_Gain/ICoreDouble-Out-0 self.sig2 = np.zeros((1, 1)) # sig3: ICore Blocks/Home/DPT_Feedback_Discrete/Plant/ICoreDouble-Out-0 self.sig3 = np.zeros((1, 1)) # sig4 (boundary output): ICore Blocks/Home/DPT_Feedback_Discrete/ICoreDouble-Out-0 self.sig4 = np.zeros((1, 1)) signals = Signals() # ========================================================= # BLOCK CONFIG PARAMETERS # ========================================================= class BlockParams: def __init__(self): # blk2: ICore Blocks/Home/DPT_Feedback_Discrete/Ctrl_Gain self.blk2_gain = np.array([[1.8]], dtype=float) params = BlockParams() # ========================================================= # GENERATED EXECUTION GRAPH # ========================================================= # ... ICoreIIREmulator - the shared IIR helper emitted with every Python # ... export - elided here; this model's plant uses the inlined form in Blk3 ... class Blk0: def solve(self): signals.sig0 = get_input("sig0", 1, 1) blk0 = Blk0() class Blk1: def solve(self): output = np.zeros((1, 1)) output = output + signals.sig0 output = output - signals.sig3 signals.sig1 = output blk1 = Blk1() class Blk2: def solve(self): output = signals.sig1 * params.blk2_gain[0][0] signals.sig2 = output blk2 = Blk2() class Blk3: def __init__(self): # Discrete transfer-function IIR coefficients (normalized, a0 dropped) self._num = [0, 0.40000000000000002] self._den = [-0.59999999999999998] # Per-entry IIR history: the SISO transfer function is applied to each [p,m] entry. self._u_hist = np.zeros((1, 1, 2)) self._y_hist = np.zeros((1, 1, 1)) def solve(self): u = np.asarray(signals.sig2, dtype=float).reshape(1, 1) out = np.zeros((1, 1)) for r in range(1): for c in range(1): uk = float(u[r, c]) for k in range(1, 0, -1): self._u_hist[r, c, k] = self._u_hist[r, c, k - 1] self._u_hist[r, c, 0] = uk yk = 0.0 for i in range(2): yk += self._num[i] * self._u_hist[r, c, i] for i in range(1): yk -= self._den[i] * self._y_hist[r, c, i] self._y_hist[r, c, 0] = yk out[r, c] = yk signals.sig3 = out blk3 = Blk3() class Blk4: def solve(self): signals.sig4 = signals.sig3 blk4 = Blk4() def execute_blocks(): # Execution order generated automatically from block diagram # blk0: ICore Blocks/Home/DPT_Feedback_Discrete/In1 blk0.solve() # blk1: ICore Blocks/Home/DPT_Feedback_Discrete/Error blk1.solve() # blk2: ICore Blocks/Home/DPT_Feedback_Discrete/Ctrl_Gain blk2.solve() # blk3: ICore Blocks/Home/DPT_Feedback_Discrete/Plant blk3.solve() # blk4: ICore Blocks/Home/DPT_Feedback_Discrete/Out1 blk4.solve()
What Deploy writes for the reference model below, with only the file's
header banner removed. Module-level signals and params, one instance per block, and a
module-level execute_blocks(). Because the matrices are numpy arrays, the sum and
the gain read as whole-array expressions rather than loops - the same reason the matrix signals
behave here exactly as they do in the simulator.
From the diagram to your Python build.
Deploy writes the folder, ICore checks it against the simulation, and your code calls it once per sample.
Deploy writes
<name>_deployableCore.py<name>_testbench.py
Built, run and compared
Built with python3 with numpy, run across the simulation window and compared with the solver sample by sample, against a 0.1 % tolerance.
One call, one sample
Numbers are float / numpy, and 803 of 806 library blocks export to Python.
execute_blocks()Where Python fits.
Module-level state and a module-level execute_blocks(): import it, set
the inputs, call it once per sample. It needs numpy, which is what makes the matrix
signals behave the way they do in the simulator.
Python is the only target that can carry a Python block - your own source, running in the loop - and the two Python-model blocks that run a scikit-learn or custom model. That is why it supports 803 of the 806 library blocks: all but the C block, the Hit Scheduler, which steers a variable-step solver that exported code does not have, and the subsystem block, which writes no code of its own because its contents do.
It is also the fastest target to sanity-check by hand: the testbench prints the same samples the app plots, so a disagreement is visible in a diff rather than in a debugger.
| Target class | Tolerance | Observed |
|---|---|---|
| Software · 7 languages | 0.1 % | ≈1e-11 % |
| HDL · Q16.16 | 1 % | ≈1e-3 % |
One model, ten targets.
Every target page shows the same model, so the ten are directly
comparable: an input, an error junction, a gain of 1.8, a discrete plant and an output,
with the plant's output fed back into the junction. The export-verification suite calls it
DPT_Feedback_Discrete - which is where the names in the file come
from.
See it run on your own model.
Download the application from the customer portal, or read the documentation first - the manual, a page for every block, and the full command reference are public.