Source code for pathsim.blocks.adder
#########################################################################################
##
## ADDER BLOCK
## (blocks/adder.py)
##
## Milan Rother 2024
##
#########################################################################################
# IMPORTS ===============================================================================
import numpy as np
from ._block import Block
from ..optim.operator import Operator
# MISO BLOCKS ===========================================================================
[docs]
class Adder(Block):
"""Summs / adds up all input signals to a single output signal (MISO)
This is how it works in the default case
.. math::
y(t) = \\sum_i u_i(t)
and like this when additional operations are defined
.. math::
y(t) = \\sum_i \\mathrm{op}_i \\cdot u_i(t)
Example
-------
This is the default initialization that just adds up all the inputs:
.. code-block:: python
A = Adder()
and this is the initialization with specific operations that subtracts
the second from first input and neglects all others:
.. code-block:: python
A = Adder('+-')
Note
----
This block is purely algebraic and its operation (`op_alg`) will be called
multiple times per timestep, each time when `Simulation._update(t)` is
called in the global simulation loop.
Parameters
----------
operations : str, optional
optional string of operations to be applied before
summation, i.e. '+-' will compute the difference,
'None' will just perform regular sum
Attributes
----------
_ops : dict
dict that maps string operations to numerical
_ops_array : array_like
operations converted to array
op_alg : Operator
internal algebraic operator
"""
#max number of ports
_n_in_max = None
_n_out_max = 1
#maps for input and output port labels
_port_map_out = {"out": 0}
def __init__(self, operations=None):
super().__init__()
#allowed arithmetic operations
self._ops = {"+":1.0, "-":-1.0, "0":0.0}
self.operations = operations
#are special operations defined?
if self.operations is None:
#create internal algebraic operator
self.op_alg = Operator(
func=sum,
jac=lambda x: np.ones_like(x)
)
else:
#input validation
if not isinstance(self.operations, str):
raise ValueError("'operations' must be string or 'None'")
for op in self.operations:
if op not in self._ops:
raise ValueError(f"operation '{op}' not in {self._ops}")
#construct array from operations
self._ops_array = np.array([self._ops[op] for op in self.operations])
def sum_ops(X):
return sum(x*op for x, op in zip(X, self._ops_array))
def jac_ops(X):
nx, no = len(X), len(self._ops_array)
if nx < no: return self._ops_array[:nx]
return np.pad(self._ops_array, nx-no, mode="constant")
#create internal algebraic operator
self.op_alg = Operator(
func=sum_ops,
jac=jac_ops
)
def __len__(self):
"""Purely algebraic block"""
return 1
[docs]
def update(self, t):
"""update system equation in fixed point loop for
algebraic loops, with optional error control
Parameters
----------
t : float
evaluation time
"""
u = self.inputs.to_array()
y = self.op_alg(u)
self.outputs.update_from_array(y)