pathsim 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. pathsim/__init__.py +3 -0
  2. pathsim/blocks/__init__.py +14 -0
  3. pathsim/blocks/_block.py +209 -0
  4. pathsim/blocks/adder.py +30 -0
  5. pathsim/blocks/amplifier.py +34 -0
  6. pathsim/blocks/delay.py +70 -0
  7. pathsim/blocks/differentiator.py +70 -0
  8. pathsim/blocks/function.py +82 -0
  9. pathsim/blocks/integrator.py +66 -0
  10. pathsim/blocks/lti.py +155 -0
  11. pathsim/blocks/multiplier.py +30 -0
  12. pathsim/blocks/ode.py +86 -0
  13. pathsim/blocks/rf/__init__.py +4 -0
  14. pathsim/blocks/rf/filters.py +169 -0
  15. pathsim/blocks/rf/noise.py +218 -0
  16. pathsim/blocks/rf/sources.py +163 -0
  17. pathsim/blocks/rf/wienerhammerstein.py +338 -0
  18. pathsim/blocks/rng.py +57 -0
  19. pathsim/blocks/scope.py +224 -0
  20. pathsim/blocks/sources.py +71 -0
  21. pathsim/blocks/spectrum.py +316 -0
  22. pathsim/connection.py +112 -0
  23. pathsim/simulation.py +652 -0
  24. pathsim/solvers/__init__.py +25 -0
  25. pathsim/solvers/_solver.py +403 -0
  26. pathsim/solvers/bdf.py +240 -0
  27. pathsim/solvers/dirk2.py +101 -0
  28. pathsim/solvers/dirk3.py +86 -0
  29. pathsim/solvers/esdirk32.py +131 -0
  30. pathsim/solvers/esdirk4.py +99 -0
  31. pathsim/solvers/esdirk43.py +139 -0
  32. pathsim/solvers/esdirk54.py +141 -0
  33. pathsim/solvers/esdirk85.py +200 -0
  34. pathsim/solvers/euler.py +81 -0
  35. pathsim/solvers/rk4.py +61 -0
  36. pathsim/solvers/rkbs32.py +101 -0
  37. pathsim/solvers/rkck54.py +108 -0
  38. pathsim/solvers/rkdp54.py +111 -0
  39. pathsim/solvers/rkdp87.py +116 -0
  40. pathsim/solvers/rkf45.py +102 -0
  41. pathsim/solvers/rkf78.py +111 -0
  42. pathsim/solvers/rkv65.py +103 -0
  43. pathsim/solvers/ssprk22.py +62 -0
  44. pathsim/solvers/ssprk33.py +65 -0
  45. pathsim/solvers/ssprk34.py +74 -0
  46. pathsim/subsystem.py +267 -0
  47. pathsim/utils/__init__.py +0 -0
  48. pathsim/utils/adaptivebuffer.py +87 -0
  49. pathsim/utils/anderson.py +180 -0
  50. pathsim/utils/funcs.py +205 -0
  51. pathsim/utils/gilbert.py +110 -0
  52. pathsim/utils/progresstracker.py +90 -0
  53. pathsim/utils/realtimeplotter.py +230 -0
  54. pathsim/utils/statespacerealizations.py +116 -0
  55. pathsim/utils/waveforms.py +36 -0
  56. pathsim-0.2.0.dist-info/LICENSE.txt +21 -0
  57. pathsim-0.2.0.dist-info/METADATA +149 -0
  58. pathsim-0.2.0.dist-info/RECORD +109 -0
  59. pathsim-0.2.0.dist-info/WHEEL +5 -0
  60. pathsim-0.2.0.dist-info/top_level.txt +2 -0
  61. tests/__init__.py +0 -0
  62. tests/blocks/__init__.py +0 -0
  63. tests/blocks/test_adder.py +85 -0
  64. tests/blocks/test_amplifier.py +66 -0
  65. tests/blocks/test_block.py +138 -0
  66. tests/blocks/test_delay.py +122 -0
  67. tests/blocks/test_differentiator.py +102 -0
  68. tests/blocks/test_function.py +165 -0
  69. tests/blocks/test_integrator.py +92 -0
  70. tests/blocks/test_lti.py +162 -0
  71. tests/blocks/test_multiplier.py +87 -0
  72. tests/blocks/test_ode.py +125 -0
  73. tests/blocks/test_rng.py +109 -0
  74. tests/blocks/test_scope.py +196 -0
  75. tests/blocks/test_sources.py +119 -0
  76. tests/blocks/test_spectrum.py +119 -0
  77. tests/solvers/__init__.py +0 -0
  78. tests/solvers/test_bdf.py +364 -0
  79. tests/solvers/test_dirk2.py +138 -0
  80. tests/solvers/test_dirk3.py +137 -0
  81. tests/solvers/test_esdirk32.py +158 -0
  82. tests/solvers/test_esdirk4.py +138 -0
  83. tests/solvers/test_esdirk43.py +158 -0
  84. tests/solvers/test_esdirk54.py +160 -0
  85. tests/solvers/test_esdirk85.py +157 -0
  86. tests/solvers/test_euler.py +223 -0
  87. tests/solvers/test_rk4.py +138 -0
  88. tests/solvers/test_rkbs32.py +159 -0
  89. tests/solvers/test_rkck54.py +157 -0
  90. tests/solvers/test_rkdp54.py +159 -0
  91. tests/solvers/test_rkdp87.py +157 -0
  92. tests/solvers/test_rkf45.py +159 -0
  93. tests/solvers/test_rkf78.py +160 -0
  94. tests/solvers/test_rkv65.py +160 -0
  95. tests/solvers/test_solver.py +119 -0
  96. tests/solvers/test_ssprk22.py +136 -0
  97. tests/solvers/test_ssprk33.py +136 -0
  98. tests/solvers/test_ssprk34.py +136 -0
  99. tests/test_connection.py +176 -0
  100. tests/test_simulation.py +271 -0
  101. tests/test_subsystem.py +182 -0
  102. tests/utils/__init__.py +0 -0
  103. tests/utils/test_adaptivebuffer.py +111 -0
  104. tests/utils/test_anderson.py +142 -0
  105. tests/utils/test_funcs.py +143 -0
  106. tests/utils/test_gilbert.py +108 -0
  107. tests/utils/test_progresstracker.py +144 -0
  108. tests/utils/test_realtimeplotter.py +122 -0
  109. tests/utils/test_statespacerealizations.py +107 -0
@@ -0,0 +1,163 @@
1
+ #########################################################################################
2
+ ##
3
+ ## SPECIAL RF SOURCES
4
+ ## (blocks/rf/sources.py)
5
+ ##
6
+ ## this module implements some premade source blocks
7
+ ## that produce waveforms for RF simulations
8
+ ##
9
+ ## Milan Rother 2024
10
+ ##
11
+ #########################################################################################
12
+
13
+ # IMPORTS ===============================================================================
14
+
15
+ import numpy as np
16
+
17
+ from .._block import Block
18
+
19
+
20
+ # HELPER FUNCTIONS ======================================================================
21
+
22
+ def gaussian(t, f_max):
23
+ """
24
+ gaussian pulse with its maximum at t=0
25
+ """
26
+ tau = 0.5 / f_max
27
+ return np.exp(-(t/tau)**2)
28
+
29
+
30
+ def triangle_wave(t, f):
31
+ """
32
+ triangle wave with amplitude '1' and frequency 'f'
33
+ """
34
+ return 2 * abs(t*f - np.floor(t*f + 0.5)) - 1
35
+
36
+
37
+ def square_wave(t, f):
38
+ """
39
+ square wave with amplitude '1' and frequency 'f'
40
+ """
41
+ return np.sign(np.sin(2*np.pi*f*t))
42
+
43
+
44
+ # SOURCE BLOCKS =========================================================================
45
+
46
+ class SquareWaveSource(Block):
47
+
48
+ def __init__(self, frequency=1, amplitude=1):
49
+ super().__init__()
50
+
51
+ self.amplitude = amplitude
52
+ self.frequency = frequency
53
+
54
+
55
+ def update(self, t):
56
+ self.outputs[0] = self.amplitude * square_wave(t, self.frequency)
57
+ return 0.0
58
+
59
+
60
+ class TriangleWaveSource(Block):
61
+
62
+ def __init__(self, frequency=1, amplitude=1):
63
+ super().__init__()
64
+
65
+ self.amplitude = amplitude
66
+ self.frequency = frequency
67
+
68
+
69
+ def update(self, t):
70
+ self.outputs[0] = self.amplitude * triangle_wave(t, self.frequency)
71
+ return 0.0
72
+
73
+
74
+ class SinusoidalSource(Block):
75
+
76
+ def __init__(self, frequency=1, amplitude=1, phase=0):
77
+ super().__init__()
78
+
79
+ self.amplitude = amplitude
80
+ self.frequency = frequency
81
+ self.phase = phase
82
+
83
+
84
+ def update(self, t):
85
+ omega = 2*np.pi*self.frequency
86
+ self.outputs[0] = self.amplitude * np.sin(omega*t + self.phase)
87
+ return 0.0
88
+
89
+
90
+ class GaussianPulseSource(Block):
91
+
92
+ def __init__(self, amplitude=1, f_max=1e3, tau=0.0):
93
+ super().__init__()
94
+
95
+ self.amplitude = amplitude
96
+ self.f_max = f_max
97
+ self.tau = tau
98
+
99
+
100
+ def update(self, t):
101
+ self.outputs[0] = self.amplitude * gaussian(t-self.tau, self.f_max)
102
+ return 0.0
103
+
104
+
105
+ class StepSource(Block):
106
+
107
+ def __init__(self, amplitude=1, tau=0.0):
108
+ super().__init__()
109
+
110
+ self.amplitude = amplitude
111
+ self.tau = tau
112
+
113
+
114
+ def update(self, t):
115
+ self.outputs[0] = self.amplitude * float(t > self.tau)
116
+ return 0.0
117
+
118
+
119
+ class ChirpSource(Block):
120
+
121
+ def __init__(self, amplitude=1, f0=1, BW=1, T=1):
122
+ super().__init__()
123
+
124
+ #parameters of chirp signal
125
+ self.amplitude = amplitude
126
+ self.f0 = f0
127
+ self.BW = BW
128
+ self.T = T
129
+
130
+
131
+ def set_solver(self, Solver, tolerance_lte=1e-6):
132
+
133
+ #change solver if already initialized
134
+ if self.engine is not None:
135
+ self.engine = self.engine.change(Solver, tolerance_lte)
136
+ return #quit early
137
+
138
+ #initialize the numerical integration engine with kernel
139
+ def _f(x, u, t): return self.BW * (1 + triangle_wave(t, 1/self.T))/2
140
+ self.engine = Solver(self.f0, _f, None, tolerance_lte)
141
+
142
+
143
+ def update(self, t):
144
+ #compute implicit balancing update
145
+ phase = 2 * np.pi * self.engine.get()
146
+ self.outputs[0] = self.amplitude * np.sin(phase)
147
+ return 0.0
148
+
149
+
150
+ def solve(self, t, dt):
151
+ #advance solution of implicit update equation
152
+ self.engine.solve(0.0, t, dt)
153
+
154
+ #no error for chirp source
155
+ return 0.0
156
+
157
+
158
+ def step(self, t, dt):
159
+ #compute update step with integration engine
160
+ self.engine.step(0.0, t, dt)
161
+
162
+ #no error control for chirp source
163
+ return True, 0.0, 1.0
@@ -0,0 +1,338 @@
1
+ #########################################################################################
2
+ ##
3
+ ## WIENER AND HAMMERSTEIN NONLINEAR DYNAMICAL MODELS
4
+ ## (blocks/wienerhammerstein.py)
5
+ ##
6
+ ## Milan Rother 2024
7
+ ##
8
+ #########################################################################################
9
+
10
+ # IMPORTS ===============================================================================
11
+
12
+ import numpy as np
13
+
14
+ from .._block import Block
15
+
16
+ from ...utils.funcs import (
17
+ max_error_dicts,
18
+ dict_to_array,
19
+ array_to_dict,
20
+ auto_jacobian
21
+ )
22
+
23
+ from ...utils.gilbert import (
24
+ gilbert_realization
25
+ )
26
+
27
+
28
+ # BLOCKS ================================================================================
29
+
30
+ class WienerHammersteinModel(Block):
31
+ """
32
+ Wiener-Hammerstein nonlinear dynamical model.
33
+ It consists of a dynamical LTI system, followed
34
+ by a static nonlinearity, followed by another
35
+ dynamical LTI system.
36
+
37
+ u -> LTI_1 -> f(.) -> LTI_2 -> y
38
+
39
+ The LTI systems are implemented as linear statespace
40
+ models with ABCD system matrices.
41
+ The system matrices are realized from the poles, residues
42
+ and constant of the corresponding transfer functions.
43
+
44
+ Can serve as a behavioral model for nonlinear
45
+ dynamical systems such as RF components.
46
+
47
+ This implementation is inherently MIMO-capable
48
+ if the IO dimensions of the two LTI systems and
49
+ the nonlinearity match.
50
+
51
+ INPUTS :
52
+ Poles_1 : (array of complex) poles of 1st LTI system transfer function
53
+ Residues_1 : (array of arrays) residues of 1st LTI system transfer function
54
+ Const_1 : (array of arrays) constant term of 1st LTI system transfer function
55
+ Poles_2 : (array of complex) poles of 2nd LTI system transfer function
56
+ Residues_2 : (array of arrays) residues of 2nd LTI system transfer function
57
+ Const_2 : (array of arrays) constant term of 2nd LTI system transfer function
58
+ func : (callable) function that defines the system nonlienarity, can be MIMO
59
+
60
+ """
61
+
62
+ def __init__(self,
63
+ Poles_1=[],
64
+ Residues_1=[],
65
+ Const_1=1.0,
66
+ Poles_2=[],
67
+ Residues_2=[],
68
+ Const_2=1.0,
69
+ func=lambda x: x,
70
+ jac=None):
71
+
72
+ super().__init__()
73
+
74
+ #nonlinearity
75
+ self.func = func
76
+
77
+ #jacobian of nonlinearity
78
+ self.jac = auto_jacobian(self.func) if jac is None else jac
79
+
80
+ #Statespace realization of 1st LTI transfer function
81
+ self.A_1, self.B_1, self.C_1, self.D_1 = gilbert_realization(Poles_1, Residues_1, Const_1)
82
+
83
+ #Statespace realization of 2nd LTI transfer function
84
+ self.A_2, self.B_2, self.C_2, self.D_2 = gilbert_realization(Poles_2, Residues_2, Const_2)
85
+
86
+ #get statespace dimensions after realization
87
+ _, n_in = self.B_1.shape
88
+ n_out, _ = self.C_2.shape
89
+
90
+ self.n_1, _ = self.A_1.shape
91
+ self.n_2, _ = self.A_2.shape
92
+
93
+ #set io channels
94
+ self.inputs = {i:0.0 for i in range(n_in)}
95
+ self.outputs = {i:0.0 for i in range(n_out)}
96
+
97
+
98
+ def __len__(self):
99
+ #check if direct passthrough exists
100
+ return int((np.any(self.D_1) and np.any(self.D_2)))
101
+
102
+
103
+ def set_solver(self, Solver, tolerance_lte=1e-6):
104
+
105
+ #change solver if already initialized
106
+ if self.engine is not None:
107
+ self.engine = self.engine.change(Solver, tolerance_lte)
108
+ return #quit early
109
+
110
+ #right hand side function for ODE
111
+ def _f(x, u, t):
112
+ x_1, x_2 = x[:self.n_1], x[self.n_1:]
113
+ dx_1 = np.dot(self.A_1, x_1) + np.dot(self.B_1, u)
114
+ dx_2 = np.dot(self.A_2, x_2) + np.dot(self.B_2, self.func(np.dot(self.C_1, x_1) + np.dot(self.D_1, u)))
115
+ return np.hstack([dx_1, dx_2])
116
+
117
+ def _jac(x, u, t):
118
+ x_1, x_2 = x[:self.n_1], x[self.n_1:]
119
+ J_12 = np.zeros_like(self.A_1)
120
+ J_21 = np.dot(self.B_2, np.dot(self.C_1, self.jac(np.dot(self.C_1, x_1) + np.dot(self.D_1, u))))
121
+ return np.block([[self.A_1, J_12], [J_21, self.A_2]])
122
+
123
+ #combined solver
124
+ self.engine = Solver(np.zeros(self.n_1+self.n_2), _f, _jac, tolerance_lte)
125
+
126
+
127
+ def update(self, t):
128
+ #compute implicit balancing update
129
+ prev_outputs = self.outputs.copy()
130
+
131
+ x = self.engine.get()
132
+ x_1, x_2 = x[:self.n_1], x[self.n_1:]
133
+
134
+ u = dict_to_array(self.inputs)
135
+
136
+ y_1 = np.dot(self.C_1, x_1) + np.dot(self.D_1, u)
137
+ y_2 = np.dot(self.C_2, x_2) + np.dot(self.D_2, self.func(y_1))
138
+
139
+ self.outputs = array_to_dict(y_2)
140
+ return max_error_dicts(prev_outputs, self.outputs)
141
+
142
+
143
+ def solve(self, t, dt):
144
+ #advance solution of implicit update equation
145
+ return self.engine.solve(dict_to_array(self.inputs), t, dt)
146
+
147
+
148
+ def step(self, t, dt):
149
+ #compute update step with integration engine
150
+ return self.engine.step(dict_to_array(self.inputs), t, dt)
151
+
152
+
153
+
154
+
155
+
156
+
157
+ class HammersteinModel(Block):
158
+ """
159
+ Hammerstein nonlinear dynamical model. It consists of a
160
+ dynamical LTI system, followed by a static nonlinearity.
161
+
162
+ u -> f(.) -> LTI -> y
163
+
164
+ The LTI system is implemented as a linear statespace
165
+ model with ABCD system matrices.
166
+ The system matrices are realized from the poles, residues
167
+ and constant of the corresponding transfer function.
168
+
169
+ Can serve as a behavioral model for nonlinear
170
+ dynamical systems such as RF components.
171
+
172
+ This implementation is inherently MIMO-capable
173
+ if the IO dimensions of the LTI system and
174
+ the nonlinearity match.
175
+
176
+ INPUTS :
177
+ Poles : (array of complex) poles of LTI system transfer function
178
+ Residues : (array of arrays) residues of LTI system transfer function
179
+ Const : (array of arrays) constant term of LTI system transfer function
180
+ func : (callable) function that defines the system nonlienarity, can be MIMO
181
+ """
182
+
183
+ def __init__(self,
184
+ Poles=[],
185
+ Residues=[],
186
+ Const=1.0,
187
+ func=lambda x: x):
188
+
189
+ super().__init__()
190
+
191
+ #nonlinearity
192
+ self.func = func
193
+
194
+ #Statespace realization of transfer function
195
+ self.A, self.B, self.C, self.D = gilbert_realization(Poles, Residues, Const)
196
+
197
+ #get statespace dimensions after realization
198
+ _, n_in = self.B.shape
199
+ n_out, _ = self.C.shape
200
+
201
+ #set io channels
202
+ self.inputs = {i:0.0 for i in range(n_in)}
203
+ self.outputs = {i:0.0 for i in range(n_out)}
204
+
205
+
206
+ def __len__(self):
207
+ #check if direct passthrough exists
208
+ return int(np.any(self.D))
209
+
210
+
211
+ def set_solver(self, Solver, tolerance_lte=1e-6):
212
+
213
+ #change solver if already initialized
214
+ if self.engine is not None:
215
+ self.engine = self.engine.change(Solver, tolerance_lte)
216
+ return #quit early
217
+
218
+ #right hand side function for ODE
219
+ def _f(x, u, t): return np.dot(self.A, x) + np.dot(self.B, u)
220
+ def _jac(x, u, t): return self.A
221
+
222
+ #solver
223
+ n, _ = self.A.shape
224
+ self.engine = Solver(np.zeros(n), _f, _jac, tolerance_lte)
225
+
226
+
227
+ def update(self, t):
228
+ #compute implicit balancing update
229
+ prev_outputs = self.outputs.copy()
230
+ u = dict_to_array(self.inputs)
231
+ y = np.dot(self.C, self.engine.get()) + np.dot(self.D, self.func(u))
232
+ self.outputs = array_to_dict(y)
233
+ return max_error_dicts(prev_outputs, self.outputs)
234
+
235
+ def solve(self, t, dt):
236
+ #advance solution of implicit update equation
237
+ u = dict_to_array(self.inputs)
238
+ return self.engine.solve(self.func(u), t, dt)
239
+
240
+ def step(self, t, dt):
241
+ #compute update step with integration engine
242
+ u = dict_to_array(self.inputs)
243
+ return self.engine.step(self.func(u), t, dt)
244
+
245
+
246
+
247
+
248
+
249
+
250
+
251
+ class WienerModel(Block):
252
+
253
+ """
254
+ Wiener nonlinear dynamical model. It consists of
255
+ a static nonlinearity, followed by a dynamical LTI system.
256
+
257
+ u -> LTI -> f(.) -> y
258
+
259
+ The LTI system is implemented as a linear statespace
260
+ model with ABCD system matrices.
261
+ The system matrices are realized from the poles, residues
262
+ and constant of the corresponding transfer function.
263
+
264
+ Can serve as a behavioral model for nonlinear
265
+ dynamical systems such as RF components.
266
+
267
+ This implementation is inherently MIMO-capable
268
+ if the IO dimensions of the LTI system and
269
+ the nonlinearity match.
270
+
271
+ INPUTS :
272
+ Poles : (array of complex) poles of LTI system transfer function
273
+ Residues : (array of arrays) residues of LTI system transfer function
274
+ Const : (array of arrays) constant term of LTI system transfer function
275
+ func : (callable) function that defines the system nonlienarity, can be MIMO
276
+ """
277
+
278
+ def __init__(self,
279
+ b=None,
280
+ a=None,
281
+ Poles=[],
282
+ Residues=[],
283
+ Const=1.0,
284
+ func=lambda x: x):
285
+
286
+ super().__init__()
287
+
288
+ #nonlinearity
289
+ self.func = func
290
+
291
+ #Statespace realization of transfer function
292
+ self.A, self.B, self.C, self.D = gilbert_realization(Poles, Residues, Const)
293
+
294
+ #get statespace dimensions after realization
295
+ _, n_in = self.B.shape
296
+ n_out, _ = self.C.shape
297
+
298
+ #set io channels
299
+ self.inputs = {i:0.0 for i in range(n_in)}
300
+ self.outputs = {i:0.0 for i in range(n_out)}
301
+
302
+ def __len__(self):
303
+ #check if direct passthrough exists
304
+ return int(np.any(self.D))
305
+
306
+
307
+ def set_solver(self, Solver, tolerance_lte=1e-6):
308
+
309
+ #change solver if already initialized
310
+ if self.engine is not None:
311
+ self.engine = self.engine.change(Solver, tolerance_lte)
312
+ return #quit early
313
+
314
+ #right hand side function for ODE
315
+ def _f(x, u, t): return np.dot(self.A, x) + np.dot(self.B, u)
316
+ def _jac(x, u, t): return self.A
317
+
318
+ #solver
319
+ n, _ = self.A.shape
320
+ self.engine = Solver(np.zeros(n), _f, _jac, tolerance_lte)
321
+
322
+
323
+ def update(self, t):
324
+ #compute implicit balancing update
325
+ prev_outputs = self.outputs.copy()
326
+ y = np.dot(self.C, self.engine.get()) + np.dot(self.D, dict_to_array(self.inputs))
327
+ self.outputs = array_to_dict(self.func(y))
328
+ return max_error_dicts(prev_outputs, self.outputs)
329
+
330
+
331
+ def solve(self, t, dt):
332
+ #advance solution of implicit update equation
333
+ return self.engine.solve(dict_to_array(self.inputs), t, dt)
334
+
335
+
336
+ def step(self, t, dt):
337
+ #compute update step with integration engine
338
+ return self.engine.step(dict_to_array(self.inputs), t, dt)
pathsim/blocks/rng.py ADDED
@@ -0,0 +1,57 @@
1
+ #########################################################################################
2
+ ##
3
+ ## RANDOM NUMBER GENERATOR BLOCK (rng.py)
4
+ ##
5
+ ## Milan Rother 2024
6
+ ##
7
+ #########################################################################################
8
+
9
+ # IMPORTS ===============================================================================
10
+
11
+ import numpy as np
12
+
13
+ from ._block import Block
14
+
15
+
16
+ # NOISE SOURCE BLOCKS ===================================================================
17
+
18
+ class RNG(Block):
19
+ """
20
+ Generates a random output value beteween -1 and 1
21
+ from a uniform distribution.
22
+
23
+ If no 'sampling_rate' (None) is specified, every
24
+ simulation timestep gets a random value.
25
+
26
+ INPUTS :
27
+ sampling_rate : (float or None) number of samples per second
28
+ """
29
+
30
+ def __init__(self, sampling_rate=None):
31
+ super().__init__()
32
+
33
+ self.sampling_rate = sampling_rate
34
+ self.n_samples = 0
35
+
36
+
37
+ def __len__(self):
38
+ return 0
39
+
40
+
41
+ def reset(self):
42
+ #reset inputs and outputs
43
+ self.inputs = {0:0.0}
44
+ self.outputs = {0:0.0}
45
+
46
+ #reset noise samples
47
+ self.n_samples = 0
48
+
49
+
50
+ def sample(self, t):
51
+ """
52
+ Sample from a normal distribution after successful timestep.
53
+ """
54
+ if (self.sampling_rate is None or
55
+ self.n_samples < t * self.sampling_rate):
56
+ self.outputs[0] = 2.0*np.random.rand() - 1.0
57
+ self.n_samples += 1