-
-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathfunction.py
More file actions
237 lines (156 loc) · 6.23 KB
/
function.py
File metadata and controls
237 lines (156 loc) · 6.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
#########################################################################################
##
## MIMO FUNCTION BLOCK
## (pathsim/blocks/function.py)
##
#########################################################################################
# IMPORTS ===============================================================================
import numpy as np
from ._block import Block
from ..optim.operator import Operator, DynamicOperator
# MIMO BLOCKS ===========================================================================
class Function(Block):
"""Arbitrary MIMO function block, defined by a function or `lambda` expression.
The function can have multiple arguments that are then provided
by the input channels of the function block.
Form multi input, the function has to specify multiple arguments
and for multi output, the aoutputs have to be provided as a
tuple or list.
In the context of the global system, this block implements algebraic
components of the global system ODE/DAE.
.. math::
\\vec{y} = \\mathrm{func}(\\vec{u})
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.
Therefore `func` must be purely algebraic and not introduce states,
delay, etc. For interfacing with external stateful APIs, use the
`Wrapper` block.
Note
-----
If the outputs are provided as a single numpy array, they are
considered a single output. For MIMO, output has to be tuple.
Example
-------
consider the function:
.. code-block:: python
from pathsim.blocks import Function
def f(a, b, c):
return a**2, a*b, b/c
fn = Function(f)
then, when the block is updated, the input channels of the block are
assigned to the function arguments following this scheme:
.. code-block::
inputs[0] -> a
inputs[1] -> b
inputs[2] -> c
and the function outputs are assigned to the
output channels of the block in the same way:
.. code-block::
a**2 -> outputs[0]
a*b -> outputs[1]
b/c -> outputs[2]
Because the `Function` block only has a single argument, it can be
used to decorate a function and make it a `PathSim` block. This might
be handy in some cases to keep definitions concise and localized
in the code:
.. code-block:: python
from pathsim.blocks import Function
#does the same as the definition above
@Function
def fn(a, b, c):
return a**2, a*b, b/c
#'fn' is now a PathSim block
Parameters
----------
func : callable
MIMO function that defines algebraic block IO behaviour, signature `func(*tuple)`
Attributes
----------
op_alg : Operator
internal algebraic operator that wraps `func`
"""
def __init__(self, func=lambda x: x):
super().__init__()
#some checks to ensure that function works correctly
if not callable(func):
raise ValueError(f"'{func}' is not callable")
#function defining the block update
self.func = func
self.op_alg = Operator(func=lambda x: func(*x))
def update(self, t):
"""Evaluate function block as part of algebraic component
of global system DAE.
Parameters
----------
t : float
evaluation time
"""
#apply operator to get output
y = self.op_alg(self.inputs.to_array())
self.outputs.update_from_array(y)
class DynamicalFunction(Block):
"""Arbitrary MIMO time and input dependent function block.
The function signature needs two arguments `f(u, t)` where `u` is
the (possibly vectorial) block input and `t` is a time dependency.
.. math::
\\vec{y} = \\mathrm{func}(\\vec{u}, t)
Note
----
This block does essentially the same as `Function` but with different
requirements for the signature of the function to be wrapped.
Block inputs are packed into an array `u` and this block additionally
accepts time dependency in the function provided.
Thats where the prefix `Dynamical..` comes from.
Example
-------
Lets say we want to implement a super simple model for a voltage controlled
oscillator (VCO), where the block input controls the frequency of a sine wave
at the output.
.. code-block:: python
import numpy as np
from pathsim.blocks import DynamicalFunction
f_0 = 100
def f_vco(u, t):
return np.sin(2*np.pi*f_0*u*t)
vco = DynamicalFunction(f_vco)
Using it as a decorator also works:
.. code-block:: python
import numpy as np
from pathsim.blocks import DynamicalFunction
f_0 = 100
@DynamicalFunction
def vco(u, t):
return np.sin(2*np.pi*f_0*u*t)
#'vco' is now a PathSim block
Parameters
----------
func : callable
function that defines algebraic block IO behaviour with time dependency,
signature `func(u, t)` where `u` is `numpy.ndarray` and `t` is `float`
Attributes
----------
op_alg : DynamicOperator
internal operator that wraps `func`
"""
def __init__(self, func=lambda u, t: u):
super().__init__()
#some checks to ensure that function works correctly
if not callable(func):
raise ValueError(f"'{func}' is not callable")
#function defining the block update
self.func = func
self.op_alg = DynamicOperator(lambda x, u, t: func(u, t))
def update(self, t):
"""Evaluate function with time dependency as part of algebraic
component of global system DAE.
Parameters
----------
t : float
evaluation time
"""
#apply operator to get output
y = self.op_alg(None, self.inputs.to_array(), t)
self.outputs.update_from_array(y)