ICore Blocks / Targets / Software

Python.py

The quickest way to get a model into a notebook, a test rig or a data pipeline.

Software Verified with python3 with numpy float / numpy 803 of 806 blocks
DPT_Feedback_Discrete_deployableCore.py
✓ verified · 0.1 %
# =========================================================
# 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.

Export · Verify · Integrate

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.

01 · ExportICore

Deploy writes

  • <name>_deployableCore.py
  • <name>_testbench.py
→ code/<name>/
02 · VerifyICore

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.

observed ≈1e-11 %
03 · IntegrateYour build

One call, one sample

Numbers are float / numpy, and 803 of 806 library blocks export to Python.

execute_blocks()
With verification on, a failed comparison stops the export and says why, so a core that disagrees with the simulation never reaches your folder.
Software target

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.

$ Code Engine → Code Export Verifier → Verify All
Target classToleranceObserved
Software · 7 languages0.1 %≈1e-11 %
HDL · Q16.161 %≈1e-3 %
Python runs the same double-precision arithmetic as the solver, so anything above noise would be a real defect. Passing runs measure about 1e-11 %. See verification.
The reference model

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.

DPT_Feedback_Discrete · 5 blocks
In1 Σ + − Error × 1.8 Ctrl_Gain 0.4z⁻¹ 1 − 0.6z⁻¹ Plant Out1 the previous sample, fed back
Get started

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.