diff --git a/scripts/config/pathsim/blocks.json b/scripts/config/pathsim/blocks.json index bcc5e34e..5367ef8d 100644 --- a/scripts/config/pathsim/blocks.json +++ b/scripts/config/pathsim/blocks.json @@ -46,6 +46,7 @@ "Algebraic": [ "Adder", "Multiplier", + "Divider", "Amplifier", "Function", "Sin", @@ -60,11 +61,23 @@ "Mod", "Clip", "Pow", + "Atan2", + "Rescale", + "Alias", "Switch", "LUT", "LUT1D" ], + "Logic": [ + "GreaterThan", + "LessThan", + "Equal", + "LogicAnd", + "LogicOr", + "LogicNot" + ], + "Mixed": [ "SampleHold", "FIR", diff --git a/src/lib/constants/python.ts b/src/lib/constants/python.ts index 29a28f0a..9ed9043a 100644 --- a/src/lib/constants/python.ts +++ b/src/lib/constants/python.ts @@ -28,6 +28,7 @@ export const BLOCK_CATEGORY_ORDER: string[] = [ 'Sources', 'Dynamic', 'Algebraic', + 'Logic', 'Mixed', 'Recording', 'Subsystem' diff --git a/src/lib/nodes/generated/blocks.ts b/src/lib/nodes/generated/blocks.ts index af8c6fe8..75e3af2e 100644 --- a/src/lib/nodes/generated/blocks.ts +++ b/src/lib/nodes/generated/blocks.ts @@ -405,12 +405,17 @@ export const extractedBlocks: Record = "Delay": { "blockClass": "Delay", "description": "Delays the input signal by a time constant 'tau' in seconds.", - "docstringHtml": "

Delays the input signal by a time constant 'tau' in seconds.

\n

Mathematically this block creates a time delay of the input signal like this:

\n
\n\\begin{equation*}\ny(t) =\n\\begin{cases}\nx(t - \\tau) & , t \\geq \\tau \\\\\n0 & , t < \\tau\n\\end{cases}\n\\end{equation*}\n
\n
\n

Note

\n

The internal adaptive buffer uses interpolation for the evaluation. This is\nrequired to be compatible with variable step solvers. It has a drawback however.\nThe order of the ode solver used will degrade when this block is used, due to\nthe interpolation.

\n
\n
\n

Note

\n

This block supports vector input, meaning we can have multiple parallel\ndelay paths through this block.

\n
\n
\n

Example

\n

The block is initialized like this:

\n
\n#5 time units delay\nD = Delay(tau=5)\n
\n
\n
\n

Parameters

\n
\n
tau : float
\n
delay time constant
\n
\n
\n
\n

Attributes

\n
\n
_buffer : AdaptiveBuffer
\n
internal interpolatable adaptive rolling buffer
\n
\n
\n", + "docstringHtml": "

Delays the input signal by a time constant 'tau' in seconds.

\n

Supports two modes of operation:

\n

Continuous mode (default, sampling_period=None):\nUses an adaptive interpolating buffer for continuous-time delay.

\n
\n\\begin{equation*}\ny(t) =\n\\begin{cases}\nx(t - \\tau) & , t \\geq \\tau \\\\\n0 & , t < \\tau\n\\end{cases}\n\\end{equation*}\n
\n

Discrete mode (sampling_period provided):\nUses a ring buffer with scheduled sampling events for N-sample delay,\nwhere N = round(tau / sampling_period).

\n
\n\\begin{equation*}\ny[k] = x[k - N]\n\\end{equation*}\n
\n
\n

Note

\n

In continuous mode, the internal adaptive buffer uses interpolation for\nthe evaluation. This is required to be compatible with variable step solvers.\nIt has a drawback however. The order of the ode solver used will degrade\nwhen this block is used, due to the interpolation.

\n
\n
\n

Note

\n

This block supports vector input, meaning we can have multiple parallel\ndelay paths through this block.

\n
\n
\n

Example

\n

Continuous-time delay:

\n
\n#5 time units delay\nD = Delay(tau=5)\n
\n

Discrete-time N-sample delay (10 samples):

\n
\nD = Delay(tau=0.01, sampling_period=0.001)\n
\n
\n
\n

Parameters

\n
\n
tau : float
\n
delay time constant in seconds
\n
sampling_period : float, None
\n
sampling period for discrete mode, default is continuous mode
\n
\n
\n
\n

Attributes

\n
\n
_buffer : AdaptiveBuffer
\n
internal interpolatable adaptive rolling buffer (continuous mode)
\n
_ring : deque
\n
internal ring buffer for N-sample delay (discrete mode)
\n
\n
\n", "params": { "tau": { "type": "number", "default": "0.001", - "description": "delay time constant" + "description": "delay time constant in seconds" + }, + "sampling_period": { + "type": "any", + "default": null, + "description": "sampling period for discrete mode, default is continuous mode" } }, "inputs": null, @@ -896,6 +901,27 @@ export const extractedBlocks: Record = "out" ] }, + "Divider": { + "blockClass": "Divider", + "description": "Multiplies and divides input signals (MISO).", + "docstringHtml": "

Multiplies and divides input signals (MISO).

\n

This is the default behavior (multiply all):

\n
\n\\begin{equation*}\ny(t) = \\prod_i u_i(t)\n\\end{equation*}\n
\n

and this is the behavior with an operations string:

\n
\n\\begin{equation*}\ny(t) = \\frac{\\prod_{i \\in M} u_i(t)}{\\prod_{j \\in D} u_j(t)}\n\\end{equation*}\n
\n

where \\(M\\) is the set of inputs with * and \\(D\\) the set with /.

\n
\n

Example

\n

Default initialization multiplies the first input and divides by the second:

\n
\nD = Divider()\n
\n

Multiply the first two inputs and divide by the third:

\n
\nD = Divider('**/')\n
\n

Raise an error instead of producing inf when a denominator input is zero:

\n
\nD = Divider('**/', zero_div='raise')\n
\n

Clamp the denominator to machine epsilon so the output stays finite:

\n
\nD = Divider('**/', zero_div='clamp')\n
\n
\n
\n

Note

\n

This block is purely algebraic and its operation (op_alg) will be called\nmultiple times per timestep, each time when Simulation._update(t) is\ncalled in the global simulation loop.

\n
\n
\n

Parameters

\n
\n
operations : str, optional
\n
String of * and / characters indicating which inputs are\nmultiplied (*) or divided (/). Inputs beyond the length of\nthe string default to *. Defaults to '*/' (divide second\ninput by first).
\n
zero_div : str, optional
\n

Behaviour when a denominator input is zero. One of:

\n
\n
'warn' (default)
\n
Propagates inf and emits a RuntimeWarning — numpy's\nstandard behaviour.
\n
'raise'
\n
Raises ZeroDivisionError.
\n
'clamp'
\n
Clamps the denominator magnitude to machine epsilon\n(numpy.finfo(float).eps), preserving sign, so the output\nstays large-but-finite rather than inf.
\n
\n
\n
\n
\n
\n

Attributes

\n
\n
_ops : dict
\n
Maps operation characters to exponent values (+1 or -1).
\n
_ops_array : numpy.ndarray
\n
Exponents (+1 for *, -1 for /) converted to an array.
\n
op_alg : Operator
\n
Internal algebraic operator.
\n
\n
\n", + "params": { + "operations": { + "type": "string", + "default": "\"*/\"", + "description": "String of ``*`` and ``/`` characters indicating which inputs are multiplied (``*``) or divided (``/``). Inputs beyond the length of the string default to ``*``. Defaults to ``'*/'`` (divide second input by first)." + }, + "zero_div": { + "type": "string", + "default": "\"warn\"", + "description": "Behaviour when a denominator input is zero. One of:" + } + }, + "inputs": null, + "outputs": [ + "out" + ] + }, "Amplifier": { "blockClass": "Amplifier", "description": "Amplifies the input signal by multiplication with a constant gain term.", @@ -1043,12 +1069,67 @@ export const extractedBlocks: Record = "inputs": null, "outputs": null }, + "Atan2": { + "blockClass": "Atan2", + "description": "Two-argument arctangent block.", + "docstringHtml": "

Two-argument arctangent block.

\n

Computes the four-quadrant arctangent of two inputs:

\n
\n\\begin{equation*}\ny = \\mathrm{atan2}(a, b)\n\\end{equation*}\n
\n
\n

Note

\n

This block takes exactly two inputs (a, b) and produces one output.\nThe first input is the y-coordinate, the second is the x-coordinate,\nmatching the convention of numpy.arctan2(y, x).

\n
\n
\n

Attributes

\n
\n
op_alg : Operator
\n
internal algebraic operator
\n
\n
\n", + "params": {}, + "inputs": [ + "a", + "b" + ], + "outputs": [ + "y" + ] + }, + "Rescale": { + "blockClass": "Rescale", + "description": "Linear rescaling / mapping block.", + "docstringHtml": "

Linear rescaling / mapping block.

\n

Maps the input linearly from range [i0, i1] to range [o0, o1].\nOptionally saturates the output to [o0, o1].

\n
\n\\begin{equation*}\ny = o_0 + \\frac{(x - i_0) \\cdot (o_1 - o_0)}{i_1 - i_0}\n\\end{equation*}\n
\n

This block supports vector inputs.

\n
\n

Parameters

\n
\n
i0 : float
\n
input range lower bound
\n
i1 : float
\n
input range upper bound
\n
o0 : float
\n
output range lower bound
\n
o1 : float
\n
output range upper bound
\n
saturate : bool
\n
if True, clamp output to [min(o0,o1), max(o0,o1)]
\n
\n
\n
\n

Attributes

\n
\n
op_alg : Operator
\n
internal algebraic operator
\n
\n
\n", + "params": { + "i0": { + "type": "number", + "default": "0.0", + "description": "input range lower bound" + }, + "i1": { + "type": "number", + "default": "1.0", + "description": "input range upper bound" + }, + "o0": { + "type": "number", + "default": "0.0", + "description": "output range lower bound" + }, + "o1": { + "type": "number", + "default": "1.0", + "description": "output range upper bound" + }, + "saturate": { + "type": "boolean", + "default": "false", + "description": "if True, clamp output to [min(o0,o1), max(o0,o1)]" + } + }, + "inputs": null, + "outputs": null + }, + "Alias": { + "blockClass": "Alias", + "description": "Signal alias / pass-through block.", + "docstringHtml": "

Signal alias / pass-through block.

\n

Passes the input directly to the output without modification.\nThis is useful for signal renaming in model composition.

\n
\n\\begin{equation*}\ny = x\n\\end{equation*}\n
\n

This block supports vector inputs.

\n
\n

Attributes

\n
\n
op_alg : Operator
\n
internal algebraic operator
\n
\n
\n", + "params": {}, + "inputs": null, + "outputs": null + }, "Switch": { "blockClass": "Switch", "description": "Switch block that selects between its inputs.", - "docstringHtml": "

Switch block that selects between its inputs.

\n
\n

Example

\n

The block is initialized like this:

\n
\n#default None -> no passthrough\ns1 = Switch()\n\n#selecting port 2 as passthrough\ns2 = Switch(2)\n\n#change the state of the switch to port 3\ns2.select(3)\n
\n

Sets block output depending on self.state like this:

\n
\nstate == None -> outputs[0] = 0\n\nstate == 0 -> outputs[0] = inputs[0]\n\nstate == 1 -> outputs[0] = inputs[1]\n\nstate == 2 -> outputs[0] = inputs[2]\n\n...\n
\n
\n
\n

Parameters

\n
\n
state : int, None
\n
state of the switch
\n
\n
\n", + "docstringHtml": "

Switch block that selects between its inputs.

\n
\n

Example

\n

The block is initialized like this:

\n
\n#default None -> no passthrough\ns1 = Switch()\n\n#selecting port 2 as passthrough\ns2 = Switch(2)\n\n#change the state of the switch to port 3\ns2.select(3)\n
\n

Sets block output depending on self.switch_state like this:

\n
\nswitch_state == None -> outputs[0] = 0\n\nswitch_state == 0 -> outputs[0] = inputs[0]\n\nswitch_state == 1 -> outputs[0] = inputs[1]\n\nswitch_state == 2 -> outputs[0] = inputs[2]\n\n...\n
\n
\n
\n

Parameters

\n
\n
switch_state : int, None
\n
state of the switch
\n
\n
\n", "params": { - "state": { + "switch_state": { "type": "any", "default": null, "description": "state of the switch" @@ -1102,6 +1183,85 @@ export const extractedBlocks: Record = "inputs": null, "outputs": null }, + "GreaterThan": { + "blockClass": "GreaterThan", + "description": "Greater-than comparison block.", + "docstringHtml": "

Greater-than comparison block.

\n

Compares two inputs and outputs 1.0 if a > b, else 0.0.

\n
\n\\begin{equation*}\ny =\n\\begin{cases}\n1 & , a > b \\\\\n0 & , a \\leq b\n\\end{cases}\n\\end{equation*}\n
\n
\n

Attributes

\n
\n
op_alg : Operator
\n
internal algebraic operator
\n
\n
\n", + "params": {}, + "inputs": [ + "a", + "b" + ], + "outputs": [ + "y" + ] + }, + "LessThan": { + "blockClass": "LessThan", + "description": "Less-than comparison block.", + "docstringHtml": "

Less-than comparison block.

\n

Compares two inputs and outputs 1.0 if a < b, else 0.0.

\n
\n\\begin{equation*}\ny =\n\\begin{cases}\n1 & , a < b \\\\\n0 & , a \\geq b\n\\end{cases}\n\\end{equation*}\n
\n
\n

Attributes

\n
\n
op_alg : Operator
\n
internal algebraic operator
\n
\n
\n", + "params": {}, + "inputs": [ + "a", + "b" + ], + "outputs": [ + "y" + ] + }, + "Equal": { + "blockClass": "Equal", + "description": "Equality comparison block.", + "docstringHtml": "

Equality comparison block.

\n

Compares two inputs and outputs 1.0 if |a - b| <= tolerance, else 0.0.

\n
\n\\begin{equation*}\ny =\n\\begin{cases}\n1 & , |a - b| \\leq \\epsilon \\\\\n0 & , |a - b| > \\epsilon\n\\end{cases}\n\\end{equation*}\n
\n
\n

Parameters

\n
\n
tolerance : float
\n
comparison tolerance for floating point equality
\n
\n
\n
\n

Attributes

\n
\n
op_alg : Operator
\n
internal algebraic operator
\n
\n
\n", + "params": { + "tolerance": { + "type": "number", + "default": "1e-12", + "description": "comparison tolerance for floating point equality" + } + }, + "inputs": [ + "a", + "b" + ], + "outputs": [ + "y" + ] + }, + "LogicAnd": { + "blockClass": "LogicAnd", + "description": "Logical AND block.", + "docstringHtml": "

Logical AND block.

\n

Outputs 1.0 if both inputs are nonzero, else 0.0.

\n
\n\\begin{equation*}\ny = a \\land b\n\\end{equation*}\n
\n
\n

Attributes

\n
\n
op_alg : Operator
\n
internal algebraic operator
\n
\n
\n", + "params": {}, + "inputs": [ + "a", + "b" + ], + "outputs": [ + "y" + ] + }, + "LogicOr": { + "blockClass": "LogicOr", + "description": "Logical OR block.", + "docstringHtml": "

Logical OR block.

\n

Outputs 1.0 if either input is nonzero, else 0.0.

\n
\n\\begin{equation*}\ny = a \\lor b\n\\end{equation*}\n
\n
\n

Attributes

\n
\n
op_alg : Operator
\n
internal algebraic operator
\n
\n
\n", + "params": {}, + "inputs": [ + "a", + "b" + ], + "outputs": [ + "y" + ] + }, + "LogicNot": { + "blockClass": "LogicNot", + "description": "Logical NOT block.", + "docstringHtml": "

Logical NOT block.

\n

Outputs 1.0 if input is zero, else 0.0.

\n
\n\\begin{equation*}\ny = \\lnot x\n\\end{equation*}\n
\n
\n

Attributes

\n
\n
op_alg : Operator
\n
internal algebraic operator
\n
\n
\n", + "params": {}, + "inputs": null, + "outputs": null + }, "SampleHold": { "blockClass": "SampleHold", "description": "Samples the inputs periodically and produces them at the output.", @@ -1521,7 +1681,8 @@ 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", "Amplifier", "Function", "Sin", "Cos", "Tan", "Tanh", "Abs", "Sqrt", "Exp", "Log", "Log10", "Mod", "Clip", "Pow", "Switch", "LUT", "LUT1D"], + Algebraic: ["Adder", "Multiplier", "Divider", "Amplifier", "Function", "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"], Recording: ["Scope", "Spectrum"], Chemical: ["Process", "Bubbler4", "Splitter", "GLC"], @@ -1531,8 +1692,10 @@ export const blockImportPaths: Record = { "ADC": "pathsim.blocks", "Abs": "pathsim.blocks", "Adder": "pathsim.blocks", + "Alias": "pathsim.blocks", "Amplifier": "pathsim.blocks", "AntiWindupPID": "pathsim.blocks", + "Atan2": "pathsim.blocks", "Backlash": "pathsim.blocks", "Bubbler4": "pathsim_chem.tritium", "ButterworthBandpassFilter": "pathsim.blocks", @@ -1551,18 +1714,25 @@ export const blockImportPaths: Record = { "Deadband": "pathsim.blocks", "Delay": "pathsim.blocks", "Differentiator": "pathsim.blocks", + "Divider": "pathsim.blocks", "DynamicalSystem": "pathsim.blocks", + "Equal": "pathsim.blocks", "Exp": "pathsim.blocks", "FIR": "pathsim.blocks", "Function": "pathsim.blocks", "GLC": "pathsim_chem.tritium", "GaussianPulseSource": "pathsim.blocks", + "GreaterThan": "pathsim.blocks", "Integrator": "pathsim.blocks", "LUT": "pathsim.blocks", "LUT1D": "pathsim.blocks", "LeadLag": "pathsim.blocks", + "LessThan": "pathsim.blocks", "Log": "pathsim.blocks", "Log10": "pathsim.blocks", + "LogicAnd": "pathsim.blocks", + "LogicNot": "pathsim.blocks", + "LogicOr": "pathsim.blocks", "Mod": "pathsim.blocks", "Multiplier": "pathsim.blocks", "ODE": "pathsim.blocks", @@ -1576,6 +1746,7 @@ export const blockImportPaths: Record = { "RandomNumberGenerator": "pathsim.blocks", "RateLimiter": "pathsim.blocks", "Relay": "pathsim.blocks", + "Rescale": "pathsim.blocks", "SampleHold": "pathsim.blocks", "Scope": "pathsim.blocks", "Sin": "pathsim.blocks", diff --git a/src/lib/nodes/shapes/registry.ts b/src/lib/nodes/shapes/registry.ts index e000d82e..a46ded2e 100644 --- a/src/lib/nodes/shapes/registry.ts +++ b/src/lib/nodes/shapes/registry.ts @@ -87,6 +87,7 @@ const categoryShapeMap: Record = { Sources: 'pill', Dynamic: 'rect', Algebraic: 'rect', + Logic: 'rect', Mixed: 'mixed', Recording: 'pill', Subsystem: 'rect' diff --git a/src/lib/nodes/uiConfig.ts b/src/lib/nodes/uiConfig.ts index b5ca3337..ca3a89b6 100644 --- a/src/lib/nodes/uiConfig.ts +++ b/src/lib/nodes/uiConfig.ts @@ -41,7 +41,8 @@ function parseOperationsString(value: unknown): string[] | null { export const portLabelParams: Record = { Scope: { param: 'labels', direction: 'input' }, Spectrum: { param: 'labels', direction: 'input' }, - Adder: { param: 'operations', direction: 'input', parser: parseOperationsString } + Adder: { param: 'operations', direction: 'input', parser: parseOperationsString }, + Divider: { param: 'operations', direction: 'input', parser: parseOperationsString } }; /** @@ -77,6 +78,11 @@ export const syncPortBlocks = new Set([ 'Mod', 'Clip', 'Pow', + 'Rescale', + 'Alias', + + // Logic blocks (element-wise) + 'LogicNot', // Mixed blocks (parallel sampling) 'SampleHold' diff --git a/src/lib/types/nodes.ts b/src/lib/types/nodes.ts index c1583847..0d247201 100644 --- a/src/lib/types/nodes.ts +++ b/src/lib/types/nodes.ts @@ -44,6 +44,7 @@ export type NodeCategory = | 'Sources' | 'Dynamic' | 'Algebraic' + | 'Logic' | 'Mixed' | 'Recording' | 'Subsystem';