From 32a19287d49b33f1a8f0baf2098286540ee78d60 Mon Sep 17 00:00:00 2001 From: milanofthe Date: Thu, 7 May 2026 09:45:03 +0200 Subject: [PATCH] icons + library entries for new pathsim discrete and Polynomial blocks --- scripts/config/pathsim/blocks.json | 9 +- src/lib/components/icons/blocks/curves.ts | 11 + src/lib/components/icons/blocks/registry.ts | 9 + .../icons/blocks/svg/TappedDelay.svg | 8 + src/lib/constants/python.ts | 2 +- src/lib/nodes/generated/blocks.ts | 218 ++++++++++++++++-- src/lib/nodes/shapes/registry.ts | 2 +- src/lib/nodes/uiConfig.ts | 8 +- 8 files changed, 244 insertions(+), 23 deletions(-) create mode 100644 src/lib/components/icons/blocks/svg/TappedDelay.svg diff --git a/scripts/config/pathsim/blocks.json b/scripts/config/pathsim/blocks.json index a9b55aae..a97694a0 100644 --- a/scripts/config/pathsim/blocks.json +++ b/scripts/config/pathsim/blocks.json @@ -49,6 +49,7 @@ "Divider", "Amplifier", "Function", + "Polynomial", "Sin", "Cos", "Tan", @@ -78,9 +79,15 @@ "LogicNot" ], - "Mixed": [ + "Discrete": [ "SampleHold", + "FirstOrderHold", "FIR", + "DiscreteIntegrator", + "DiscreteDerivative", + "DiscreteStateSpace", + "DiscreteTransferFunction", + "TappedDelay", "ADC", "DAC", "Counter", diff --git a/src/lib/components/icons/blocks/curves.ts b/src/lib/components/icons/blocks/curves.ts index d27c3073..fc7b37b7 100644 --- a/src/lib/components/icons/blocks/curves.ts +++ b/src/lib/components/icons/blocks/curves.ts @@ -647,6 +647,17 @@ export function sampleHoldSamples(n = 6): Sample[] { return out; } +/** First-order hold — piecewise-linear interpolation through sample points. + * Same sample values as SampleHold so the two icons read as a pair. */ +export function firstOrderHoldSamples(n = 6): Sample[] { + const samplesY = [0.1, 0.3, 0.55, 0.75, 0.55, 0.85]; + const out: Sample[] = []; + for (let i = 0; i < n; i++) { + out.push([i / (n - 1), samplesY[i]]); + } + return out; +} + export function backlashSamples(): Sample[] { return [ [-1, -0.7], diff --git a/src/lib/components/icons/blocks/registry.ts b/src/lib/components/icons/blocks/registry.ts index 3192b5f6..084e8878 100644 --- a/src/lib/components/icons/blocks/registry.ts +++ b/src/lib/components/icons/blocks/registry.ts @@ -108,6 +108,15 @@ export const iconRegistry: Record = { DynamicalSystem: { kind: 'math', latex: '\\begin{aligned}\\dot{x} &= f(x, u, t)\\\\ y &= g(x, u, t)\\end{aligned}' }, DynamicalFunction: { kind: 'math', latex: 'f(u, t)' }, Function: { kind: 'math', latex: 'f(u)' }, + Polynomial: { kind: 'math', latex: 'y = \\sum_{k=0}^{n} c_k\\,u^{n-k}' }, + + /* --- Discrete-time blocks --- */ + FirstOrderHold: { kind: 'plot', samples: () => C.firstOrderHoldSamples() }, + DiscreteIntegrator: { kind: 'math', latex: '\\dfrac{T}{z-1}' }, + DiscreteDerivative: { kind: 'math', latex: '\\dfrac{z-1}{T\\,z}' }, + DiscreteStateSpace: { kind: 'math', latex: '\\begin{aligned}x[k{+}1] &= Ax[k]{+}Bu[k]\\\\ y[k] &= Cx[k]{+}Du[k]\\end{aligned}' }, + DiscreteTransferFunction: { kind: 'math', latex: 'H(z) = \\dfrac{B(z)}{A(z)}' }, + TappedDelay: { kind: 'svg', name: 'TappedDelay' }, /* --- Geometric SVGs (kept as files) --- */ Adder: { kind: 'svg', name: 'Adder' }, diff --git a/src/lib/components/icons/blocks/svg/TappedDelay.svg b/src/lib/components/icons/blocks/svg/TappedDelay.svg new file mode 100644 index 00000000..ad4328a2 --- /dev/null +++ b/src/lib/components/icons/blocks/svg/TappedDelay.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/lib/constants/python.ts b/src/lib/constants/python.ts index 9ed9043a..0a5664e2 100644 --- a/src/lib/constants/python.ts +++ b/src/lib/constants/python.ts @@ -29,7 +29,7 @@ export const BLOCK_CATEGORY_ORDER: string[] = [ 'Dynamic', 'Algebraic', 'Logic', - 'Mixed', + 'Discrete', 'Recording', 'Subsystem' ]; diff --git a/src/lib/nodes/generated/blocks.ts b/src/lib/nodes/generated/blocks.ts index 5a2c4123..a2f58e66 100644 --- a/src/lib/nodes/generated/blocks.ts +++ b/src/lib/nodes/generated/blocks.ts @@ -950,6 +950,20 @@ export const extractedBlocks: Record = "inputs": null, "outputs": null }, + "Polynomial": { + "blockClass": "Polynomial", + "description": "Polynomial operator block.", + "docstringHtml": "

Polynomial operator block.

\n

Evaluates a polynomial in the input. The coefficients follow the\nnumpy.polyval convention, with the highest order term first:

\n
\n\\begin{equation*}\n\\vec{y} = c_0 \\vec{u}^n + c_1 \\vec{u}^{n-1} + \\dots + c_{n-1} \\vec{u} + c_n\n\\end{equation*}\n
\n

This block supports vector inputs (the polynomial is evaluated\nelement-wise).

\n
\n

Example

\n

Quadratic \\(y = 2 u^2 + 3 u + 1\\):

\n
\np = Polynomial(coeffs=[2, 3, 1])\n
\n
\n
\n

Parameters

\n
\n
coeffs : array_like
\n
polynomial coefficients in descending order of power,\nfollowing the numpy.polyval convention
\n
\n
\n
\n

Attributes

\n
\n
op_alg : Operator
\n
internal algebraic operator
\n
\n
\n", + "params": { + "coeffs": { + "type": "array", + "default": "[1.0, 0.0]", + "description": "polynomial coefficients in descending order of power, following the ``numpy.polyval`` convention" + } + }, + "inputs": null, + "outputs": null + }, "Sin": { "blockClass": "Sin", "description": "Sine operator block.", @@ -1264,18 +1278,37 @@ export const extractedBlocks: Record = }, "SampleHold": { "blockClass": "SampleHold", - "description": "Samples the inputs periodically and produces them at the output.", - "docstringHtml": "

Samples the inputs periodically and produces them at the output.

\n
\n

Parameters

\n
\n
T : float
\n
sampling period
\n
tau : float
\n
delay
\n
\n
\n
\n

Attributes

\n
\n
events : list[Schedule]
\n
internal scheduled event for periodic sampling
\n
\n
\n", + "description": "Zero-order hold: samples the input periodically and holds it at the output.", + "docstringHtml": "

Zero-order hold: samples the input periodically and holds it at the output.

\n
\n\\begin{equation*}\ny(t) = u(k T + \\tau), \\quad k T + \\tau \\leq t < (k+1) T + \\tau\n\\end{equation*}\n
\n
\n

Note

\n

Supports vector input — each channel is sampled independently.

\n
\n
\n

Parameters

\n
\n
T : float
\n
sampling period
\n
tau : float
\n
delay before first sample
\n
\n
\n
\n

Attributes

\n
\n
events : list[Schedule]
\n
internal scheduled event for periodic sampling
\n
\n
\n", "params": { "T": { - "type": "integer", - "default": "1", + "type": "number", + "default": "1.0", "description": "sampling period" }, "tau": { - "type": "integer", - "default": "0", - "description": "delay Attributes ----------" + "type": "number", + "default": "0.0", + "description": "delay before first sample" + } + }, + "inputs": null, + "outputs": null + }, + "FirstOrderHold": { + "blockClass": "FirstOrderHold", + "description": "First-order hold reconstructor.", + "docstringHtml": "

First-order hold reconstructor.

\n

Reconstructs a continuous signal from periodic samples using linear\nextrapolation across one sampling interval. Causal (one-sample-lag)\nvariant matching the Simulink First-Order Hold block.

\n

Between two consecutive sample times \\(t_{k-1}\\) and \\(t_k\\),\nthe output is

\n
\n\\begin{equation*}\ny(t) = u_{k-1} + \\frac{u_{k-1} - u_{k-2}}{T} (t - t_{k-1})\n\\end{equation*}\n
\n

During the very first interval (only one sample captured) the output\nis held at the most recent sample.

\n
\n

Note

\n

Supports vector input — each channel is extrapolated independently.

\n
\n
\n

Parameters

\n
\n
T : float
\n
sampling period
\n
tau : float
\n
delay before first sample
\n
\n
\n
\n

Attributes

\n
\n
events : list[Schedule]
\n
internal scheduled event for periodic sampling
\n
\n
\n", + "params": { + "T": { + "type": "number", + "default": "1.0", + "description": "sampling period" + }, + "tau": { + "type": "number", + "default": "0.0", + "description": "delay before first sample" } }, "inputs": null, @@ -1283,23 +1316,139 @@ export const extractedBlocks: Record = }, "FIR": { "blockClass": "FIR", - "description": "Models a discrete-time Finite-Impulse-Response (FIR) filter.", - "docstringHtml": "

Models a discrete-time Finite-Impulse-Response (FIR) filter.

\n

This block applies an FIR filter to an input signal sampled periodically.\nThe output at each sample time is a weighted sum of the current and a finite number\nof past input samples. The operation is triggered by a scheduled event.

\n

Functionality:

\n
\n\\begin{equation*}\ny[n] = b[0] x[n] + b[1] x[n-1] + \\dots + b[N] x[n-N]\n\\end{equation*}\n
\n

where b are the filter coefficients and N is the filter order (number of\ncoefficients - 1).

\n
    \n
  1. Samples the input inputs[0] at intervals of T, starting after delay tau.
  2. \n
  3. Stores the current and past len(coefficients) - 1 input samples in an internal buffer.
  4. \n
  5. Computes the filter output using the dot product of the coefficients\nand the buffered input samples.
  6. \n
  7. Outputs the result on outputs[0].
  8. \n
  9. Holds the output constant between updates.
  10. \n
\n
\n

Parameters

\n
\n
coeffs : array_like
\n
List or numpy array of FIR filter coefficients [b0, b1, ..., bN].\nThe number of coefficients determines the filter's order and memory.
\n
T : float, optional
\n
Sampling period (time between input samples and output updates). Default is 1.
\n
tau : float, optional
\n
Initial delay before the first sample is processed. Default is 0.
\n
\n
\n
\n

Input Ports

\n
\n
inputs[0] : float
\n
Input signal sample at the current time step.
\n
\n
\n
\n

Output Ports

\n
\n
outputs[0] : float
\n
Filtered output signal sample.
\n
\n
\n
\n

Attributes

\n
\n
buffer : deque
\n
Internal buffer storing the most recent input samples.
\n
events : list[Schedule]
\n
Internal scheduled event triggering the filter calculation.
\n
\n
\n", + "description": "Discrete-time Finite-Impulse-Response (FIR) filter.", + "docstringHtml": "

Discrete-time Finite-Impulse-Response (FIR) filter.

\n

Applies an FIR filter to a periodically sampled input signal.

\n
\n\\begin{equation*}\ny[n] = b_0 x[n] + b_1 x[n-1] + \\dots + b_N x[n-N]\n\\end{equation*}\n
\n

where b are the filter coefficients and N is the filter order\n(number of coefficients minus one). The output is held constant\nbetween sample times.

\n
\n

Note

\n

Supports vector input — the same coefficients are applied to each\nchannel in parallel.

\n
\n
\n

Parameters

\n
\n
coeffs : array_like
\n
FIR filter coefficients [b0, b1, ..., bN]
\n
T : float
\n
sampling period
\n
tau : float
\n
delay before first sample
\n
\n
\n
\n

Attributes

\n
\n
events : list[Schedule]
\n
internal scheduled event for periodic filter evaluation
\n
\n
\n", "params": { "coeffs": { "type": "array", "default": "[1.0]", - "description": "List or numpy array of FIR filter coefficients [b0, b1, ..., bN]. The number of coefficients determines the filter's order and memory." + "description": "FIR filter coefficients ``[b0, b1, ..., bN]``" }, "T": { - "type": "integer", - "default": "1", - "description": "Sampling period (time between input samples and output updates). Default is 1." + "type": "number", + "default": "1.0", + "description": "sampling period" }, "tau": { - "type": "integer", - "default": "0", - "description": "Initial delay before the first sample is processed. Default is 0." + "type": "number", + "default": "0.0", + "description": "delay before first sample" + } + }, + "inputs": null, + "outputs": null + }, + "DiscreteIntegrator": { + "blockClass": "DiscreteIntegrator", + "description": "Discrete-time integrator (forward Euler).", + "docstringHtml": "

Discrete-time integrator (forward Euler).

\n
\n\\begin{equation*}\ny[k+1] = y[k] + T \\, u[k]\n\\end{equation*}\n
\n

The output at sample k is the accumulated sum of past inputs;\nthe current input u[k] only enters the next sample.

\n
\n

Note

\n

Supports vector input — each channel is integrated independently.\nPass an array as initial_value to set per-channel initial values.

\n
\n
\n

Parameters

\n
\n
T : float
\n
sampling period
\n
tau : float
\n
delay before first sample
\n
initial_value : float, array_like
\n
initial integrator output y[0]
\n
\n
\n
\n

Attributes

\n
\n
events : list[Schedule]
\n
internal scheduled event for periodic update
\n
\n
\n", + "params": { + "T": { + "type": "number", + "default": "1.0", + "description": "sampling period" + }, + "tau": { + "type": "number", + "default": "0.0", + "description": "delay before first sample" + }, + "initial_value": { + "type": "number", + "default": "0.0", + "description": "initial integrator output ``y[0]``" + } + }, + "inputs": null, + "outputs": null + }, + "DiscreteDerivative": { + "blockClass": "DiscreteDerivative", + "description": "Discrete-time backward-difference derivative.", + "docstringHtml": "

Discrete-time backward-difference derivative.

\n
\n\\begin{equation*}\ny[k] = \\frac{u[k] - u[k-1]}{T}\n\\end{equation*}\n
\n
\n

Note

\n

Supports vector input — each channel is differentiated independently.

\n
\n
\n

Parameters

\n
\n
T : float
\n
sampling period
\n
tau : float
\n
delay before first sample
\n
\n
\n
\n

Attributes

\n
\n
events : list[Schedule]
\n
internal scheduled event for periodic update
\n
\n
\n", + "params": { + "T": { + "type": "number", + "default": "1.0", + "description": "sampling period" + }, + "tau": { + "type": "number", + "default": "0.0", + "description": "delay before first sample" + } + }, + "inputs": null, + "outputs": null + }, + "DiscreteStateSpace": { + "blockClass": "DiscreteStateSpace", + "description": "Discrete-time MIMO state space block.", + "docstringHtml": "

Discrete-time MIMO state space block.

\n
\n\\begin{equation*}\n\\begin{align}\n x[k+1] &= \\mathbf{A}\\, x[k] + \\mathbf{B}\\, u[k] \\\\\n y[k] &= \\mathbf{C}\\, x[k] + \\mathbf{D}\\, u[k]\n\\end{align}\n\\end{equation*}\n
\n
\n

Note

\n

The output port reflects y[k] for the duration of the current\nsample interval (zero-order hold between updates). The direct\nfeedthrough term D u[k] is computed at the sample event, so the\nblock has no algebraic passthrough between updates.

\n
\n
\n

Parameters

\n
\n
A, B, C, D : array_like
\n
discrete state space matrices
\n
T : float
\n
sampling period
\n
tau : float
\n
delay before first sample
\n
initial_value : array_like, None
\n
initial state x[0]
\n
\n
\n
\n

Attributes

\n
\n
events : list[Schedule]
\n
internal scheduled event for periodic update
\n
\n
\n", + "params": { + "A": { + "type": "number", + "default": "0.0", + "description": "" + }, + "B": { + "type": "number", + "default": "1.0", + "description": "" + }, + "C": { + "type": "number", + "default": "1.0", + "description": "" + }, + "D": { + "type": "number", + "default": "0.0", + "description": "discrete state space matrices" + }, + "T": { + "type": "number", + "default": "1.0", + "description": "sampling period" + }, + "tau": { + "type": "number", + "default": "0.0", + "description": "delay before first sample" + }, + "initial_value": { + "type": "any", + "default": null, + "description": "initial state ``x[0]``" + } + }, + "inputs": null, + "outputs": null + }, + "DiscreteTransferFunction": { + "blockClass": "DiscreteTransferFunction", + "description": "Discrete-time SISO transfer function in numerator/denominator form.", + "docstringHtml": "

Discrete-time SISO transfer function in numerator/denominator form.

\n
\n\\begin{equation*}\nH(z) = \\frac{b_0 z^M + b_1 z^{M-1} + \\dots + b_M}{a_0 z^N + a_1 z^{N-1} + \\dots + a_N}\n\\end{equation*}\n
\n

Realized internally as a DiscreteStateSpace via the controllable\ncanonical form returned by scipy.signal.tf2ss.

\n
\n

Parameters

\n
\n
Num : array_like
\n
numerator polynomial coefficients (highest power of z first)
\n
Den : array_like
\n
denominator polynomial coefficients (highest power of z first)
\n
T : float
\n
sampling period
\n
tau : float
\n
delay before first sample
\n
\n
\n", + "params": { + "Num": { + "type": "array", + "default": "[1.0]", + "description": "numerator polynomial coefficients (highest power of z first)" + }, + "Den": { + "type": "array", + "default": "[1.0, 0.0]", + "description": "denominator polynomial coefficients (highest power of z first)" + }, + "T": { + "type": "number", + "default": "1.0", + "description": "sampling period" + }, + "tau": { + "type": "number", + "default": "0.0", + "description": "delay before first sample" } }, "inputs": [ @@ -1309,6 +1458,32 @@ export const extractedBlocks: Record = "out" ] }, + "TappedDelay": { + "blockClass": "TappedDelay", + "description": "Tapped delay line.", + "docstringHtml": "

Tapped delay line.

\n

Outputs the current and N-1 past samples of the input as parallel\nsignals. The block has N outputs:

\n
\n\\begin{equation*}\ny_i[k] = u[k - i], \\quad i = 0, 1, \\dots, N-1\n\\end{equation*}\n
\n
\n

Parameters

\n
\n
N : int
\n
number of taps (output ports)
\n
T : float
\n
sampling period
\n
tau : float
\n
delay before first sample
\n
\n
\n
\n

Attributes

\n
\n
events : list[Schedule]
\n
internal scheduled event for periodic shift
\n
\n
\n", + "params": { + "N": { + "type": "integer", + "default": "2", + "description": "number of taps (output ports)" + }, + "T": { + "type": "number", + "default": "1.0", + "description": "sampling period" + }, + "tau": { + "type": "number", + "default": "0.0", + "description": "delay before first sample" + } + }, + "inputs": [ + "in" + ], + "outputs": null + }, "ADC": { "blockClass": "ADC", "description": "Models an ideal Analog-to-Digital Converter (ADC).", @@ -1571,9 +1746,9 @@ export const extractedBlocks: Record = export const blockConfig: Record = { Sources: ["Constant", "Source", "SinusoidalSource", "StepSource", "PulseSource", "TriangleWaveSource", "SquareWaveSource", "GaussianPulseSource", "ChirpPhaseNoiseSource", "ClockSource", "WhiteNoise", "PinkNoise", "RandomNumberGenerator"], Dynamic: ["Integrator", "Differentiator", "Delay", "ODE", "DynamicalSystem", "StateSpace", "PT1", "PT2", "LeadLag", "PID", "AntiWindupPID", "RateLimiter", "Backlash", "Deadband", "TransferFunctionNumDen", "TransferFunctionZPG", "ButterworthLowpassFilter", "ButterworthHighpassFilter", "ButterworthBandpassFilter", "ButterworthBandstopFilter"], - Algebraic: ["Adder", "Multiplier", "Divider", "Amplifier", "Function", "Sin", "Cos", "Tan", "Tanh", "Abs", "Sqrt", "Exp", "Log", "Log10", "Mod", "Clip", "Pow", "Atan2", "Rescale", "Alias", "Switch", "LUT", "LUT1D"], + Algebraic: ["Adder", "Multiplier", "Divider", "Amplifier", "Function", "Polynomial", "Sin", "Cos", "Tan", "Tanh", "Abs", "Sqrt", "Exp", "Log", "Log10", "Mod", "Clip", "Pow", "Atan2", "Rescale", "Alias", "Switch", "LUT", "LUT1D"], Logic: ["GreaterThan", "LessThan", "Equal", "LogicAnd", "LogicOr", "LogicNot"], - Mixed: ["SampleHold", "FIR", "ADC", "DAC", "Counter", "CounterUp", "CounterDown", "Relay", "Wrapper"], + Discrete: ["SampleHold", "FirstOrderHold", "FIR", "DiscreteIntegrator", "DiscreteDerivative", "DiscreteStateSpace", "DiscreteTransferFunction", "TappedDelay", "ADC", "DAC", "Counter", "CounterUp", "CounterDown", "Relay", "Wrapper"], Recording: ["Scope", "Spectrum"], }; @@ -1602,11 +1777,16 @@ export const blockImportPaths: Record = { "Deadband": "pathsim.blocks", "Delay": "pathsim.blocks", "Differentiator": "pathsim.blocks", + "DiscreteDerivative": "pathsim.blocks", + "DiscreteIntegrator": "pathsim.blocks", + "DiscreteStateSpace": "pathsim.blocks", + "DiscreteTransferFunction": "pathsim.blocks", "Divider": "pathsim.blocks", "DynamicalSystem": "pathsim.blocks", "Equal": "pathsim.blocks", "Exp": "pathsim.blocks", "FIR": "pathsim.blocks", + "FirstOrderHold": "pathsim.blocks", "Function": "pathsim.blocks", "GaussianPulseSource": "pathsim.blocks", "GreaterThan": "pathsim.blocks", @@ -1627,6 +1807,7 @@ export const blockImportPaths: Record = { "PT1": "pathsim.blocks", "PT2": "pathsim.blocks", "PinkNoise": "pathsim.blocks", + "Polynomial": "pathsim.blocks", "Pow": "pathsim.blocks", "PulseSource": "pathsim.blocks", "RandomNumberGenerator": "pathsim.blocks", @@ -1646,6 +1827,7 @@ export const blockImportPaths: Record = { "Switch": "pathsim.blocks", "Tan": "pathsim.blocks", "Tanh": "pathsim.blocks", + "TappedDelay": "pathsim.blocks", "TransferFunctionNumDen": "pathsim.blocks", "TransferFunctionZPG": "pathsim.blocks", "TriangleWaveSource": "pathsim.blocks", diff --git a/src/lib/nodes/shapes/registry.ts b/src/lib/nodes/shapes/registry.ts index b82b69e2..4443c325 100644 --- a/src/lib/nodes/shapes/registry.ts +++ b/src/lib/nodes/shapes/registry.ts @@ -88,7 +88,7 @@ const categoryShapeMap: Record = { Dynamic: 'rect', Algebraic: 'rect', Logic: 'rect', - Mixed: 'mixed', + Discrete: 'mixed', Recording: 'pill', Subsystem: 'rect', Chemical: 'rect' diff --git a/src/lib/nodes/uiConfig.ts b/src/lib/nodes/uiConfig.ts index ca3a89b6..389a0430 100644 --- a/src/lib/nodes/uiConfig.ts +++ b/src/lib/nodes/uiConfig.ts @@ -78,12 +78,16 @@ export const syncPortBlocks = new Set([ 'Mod', 'Clip', 'Pow', + 'Polynomial', 'Rescale', 'Alias', // Logic blocks (element-wise) 'LogicNot', - // Mixed blocks (parallel sampling) - 'SampleHold' + // Discrete blocks (parallel sampling / discrete dynamics) + 'SampleHold', + 'FirstOrderHold', + 'DiscreteIntegrator', + 'DiscreteDerivative' ]);