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
pathsim/blocks/lti.py ADDED
@@ -0,0 +1,155 @@
1
+ #########################################################################################
2
+ ##
3
+ ## LINEAR TIME INVARIANT DYNAMICAL BLOCKS (blocks/lti.py)
4
+ ##
5
+ ## This module defines linear time invariant dynamical blocks
6
+ ##
7
+ ## Milan Rother 2024
8
+ ##
9
+ #########################################################################################
10
+
11
+ # IMPORTS ===============================================================================
12
+
13
+ import numpy as np
14
+
15
+ from ._block import Block
16
+
17
+ from ..utils.funcs import (
18
+ max_error_dicts,
19
+ dict_to_array,
20
+ array_to_dict
21
+ )
22
+
23
+ from ..utils.gilbert import (
24
+ gilbert_realization
25
+ )
26
+
27
+
28
+ # LTI BLOCKS ============================================================================
29
+
30
+ class StateSpace(Block):
31
+ """
32
+ This block integrates a LTI MIMO state space model with the structure
33
+
34
+ d/dt x = A x + B u
35
+ y = C x + D u
36
+
37
+ where A, B, C and D are the state space matrices, x is the state,
38
+ u the input and y the output vector.
39
+
40
+ INPUTS :
41
+ A, B, C, D : (numpy arrays) state space matrices
42
+ initial_value : (array of floars) initial state / initial condition
43
+ """
44
+
45
+ def __init__(self,
46
+ A=-1.0, B=1.0, C=-1.0, D=1.0,
47
+ initial_value=None):
48
+ super().__init__()
49
+
50
+ #statespace matrices with input shape validation
51
+ self.A = np.atleast_2d(A)
52
+ self.B = np.atleast_1d(B)
53
+ self.C = np.atleast_1d(C)
54
+ self.D = np.atleast_1d(D)
55
+
56
+ #get statespace dimensions
57
+ n, _ = self.A.shape
58
+ if self.B.ndim == 1: n_in = 1
59
+ else: _, n_in = self.B.shape
60
+ if self.C.ndim == 1: n_out = 1
61
+ else: n_out, _ = self.C.shape
62
+
63
+ #set io channels
64
+ self.inputs = {i:0.0 for i in range(n_in)}
65
+ self.outputs = {i:0.0 for i in range(n_out)}
66
+
67
+ #initial condition
68
+ self.initial_value = np.zeros(n) if initial_value is None else initial_value
69
+
70
+
71
+ def __len__(self):
72
+ #check if direct passthrough exists
73
+ return int(np.any(self.D))
74
+
75
+
76
+ def set_solver(self, Solver, tolerance_lte=1e-6):
77
+
78
+ if self.engine is None:
79
+
80
+ def _f(x, u, t):
81
+ return np.dot(self.A, x) + np.dot(self.B, u)
82
+ def _jac(x, u, t):
83
+ return self.A
84
+
85
+ #initialize the integration engine with right hand side
86
+ self.engine = Solver(self.initial_value, _f, _jac, tolerance_lte)
87
+
88
+ else:
89
+
90
+ #change solver if already initialized
91
+ self.engine = self.engine.change(Solver, tolerance_lte)
92
+
93
+
94
+ def update(self, t):
95
+ #compute implicit balancing update
96
+ prev_outputs = self.outputs.copy()
97
+ u = dict_to_array(self.inputs)
98
+ y = np.dot(self.C, self.engine.get()) + np.dot(self.D, u)
99
+ self.outputs = array_to_dict(y)
100
+ return max_error_dicts(prev_outputs, self.outputs)
101
+
102
+
103
+ def solve(self, t, dt):
104
+ #advance solution of implicit update equation and update outputs
105
+ return self.engine.solve(dict_to_array(self.inputs), t, dt)
106
+
107
+
108
+ def step(self, t, dt):
109
+ #compute update step with integration engine and update outputs
110
+ return self.engine.step(dict_to_array(self.inputs), t, dt)
111
+
112
+
113
+ class TransferFunction(StateSpace):
114
+ """
115
+ This block integrates a LTI (MIMO for pole residue) transfer function.
116
+
117
+ The transfer function is defined in pole-residue form
118
+
119
+ H(s) = Const + sum( Residues / (s - Poles) )
120
+
121
+ where 'Poles' are the scalar poles of the transfer function and
122
+ 'Residues' are the possibly matrix valued (in MIMO case) residues of
123
+ the transfer function. 'Const' has same shape as 'Residues'.
124
+
125
+ Upon initialization, the state space realization of the transfer
126
+ function is computed using a minimal gilbert realization.
127
+
128
+ The resulting statespace model of the form
129
+
130
+ d/dt x = A x + B u
131
+ y = C x + D u
132
+
133
+ is handled the same as the 'StateSpace' block, where A, B, C and D
134
+ are the state space matrices, x is the internal state, u the input and
135
+ y the output vector.
136
+
137
+ INPUTS :
138
+ Poles : (list or array of scalars) transfer function poles
139
+ Residues : (list or array of scalars or arrays) transfer function residues
140
+ Const : (scalar or array) constant term of transfer function
141
+ """
142
+
143
+ def __init__(self,
144
+ Poles=[],
145
+ Residues=[],
146
+ Const=0.0):
147
+
148
+ #model parameters of transfer function in pole-residue form
149
+ self.Const, self.Poles, self.Residues = Const, Poles, Residues
150
+
151
+ #Statespace realization of transfer function
152
+ A, B, C, D = gilbert_realization(Poles, Residues, Const)
153
+
154
+ #initialize statespace model
155
+ super().__init__(A, B, C, D)
@@ -0,0 +1,30 @@
1
+ #########################################################################################
2
+ ##
3
+ ## REDUCTION BLOCKS (blocks/multiplier.py)
4
+ ##
5
+ ## This module defines static 'Multiplier' block
6
+ ##
7
+ ## Milan Rother 2024
8
+ ##
9
+ #########################################################################################
10
+
11
+ # IMPORTS ===============================================================================
12
+
13
+ import numpy as np
14
+
15
+ from ._block import Block
16
+
17
+ from ..utils.funcs import dict_to_array
18
+
19
+
20
+ # MISO BLOCKS ===========================================================================
21
+
22
+ class Multiplier(Block):
23
+ """
24
+ multiplies / product of all input signals (MISO)
25
+ """
26
+
27
+ def update(self, t):
28
+ prev_output = self.outputs[0]
29
+ self.outputs[0] = np.prod(dict_to_array(self.inputs), axis=0)
30
+ return abs(prev_output - self.outputs[0])
pathsim/blocks/ode.py ADDED
@@ -0,0 +1,86 @@
1
+ #########################################################################################
2
+ ##
3
+ ## ORDINARY DIFFERENTIAL EQUATION BLOCK
4
+ ## (blocks/ode.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
+ dict_to_array,
18
+ array_to_dict,
19
+ auto_jacobian
20
+ )
21
+
22
+
23
+ # BLOCKS ================================================================================
24
+
25
+ class ODE(Block):
26
+ """
27
+ This block implements an ordinary differential equation (ODE)
28
+ defined by its right hand side
29
+
30
+ d/dt x = func(x, u, t)
31
+
32
+ with inhomogenity (input) u and state vector x. The function
33
+ can be nonlinear and the ODE can be of arbitrary order.
34
+ The block utilizes the integration engine to solve the ODE
35
+ by integrating the 'func' right hand side function.
36
+
37
+ INPUTS :
38
+ func : (callable object) right hand side function of ODE
39
+ initial_value : (array of floats) initial state / initial condition
40
+ jac : (callable or None) jacobian of 'func' or 'None'
41
+ """
42
+
43
+ def __init__(self,
44
+ func=lambda x, u, t: -x,
45
+ initial_value=0.0,
46
+ jac=None):
47
+
48
+ super().__init__()
49
+
50
+ #right hand side function of ODE
51
+ self.func = func
52
+
53
+ #initial condition
54
+ self.initial_value = initial_value
55
+
56
+ #jacobian of 'func'
57
+ self.jac = jac
58
+
59
+
60
+ def __len__(self):
61
+ return 0
62
+
63
+
64
+ def set_solver(self, Solver, tolerance_lte=1e-6):
65
+ #change solver if already initialized
66
+ if self.engine is not None:
67
+ self.engine = self.engine.change(Solver, tolerance_lte)
68
+ return #quit early
69
+ #initialize the integration engine with right hand side
70
+ _jac = auto_jacobian(self.func) if self.jac is None else self.jac
71
+ self.engine = Solver(self.initial_value, self.func, _jac, tolerance_lte)
72
+
73
+
74
+ def update(self, t):
75
+ self.outputs = array_to_dict(self.engine.get())
76
+ return 0
77
+
78
+
79
+ def solve(self, t, dt):
80
+ #advance solution of implicit update equation and update block outputs
81
+ return self.engine.solve(dict_to_array(self.inputs), t, dt)
82
+
83
+
84
+ def step(self, t, dt):
85
+ #compute update step with integration engine and update block outputs
86
+ return self.engine.step(dict_to_array(self.inputs), t, dt)
@@ -0,0 +1,4 @@
1
+ from .sources import *
2
+ from .filters import *
3
+ from .noise import *
4
+ from .wienerhammerstein import *
@@ -0,0 +1,169 @@
1
+ #########################################################################################
2
+ ##
3
+ ## RF FILTERS (filters.py)
4
+ ##
5
+ ## Milan Rother 2024
6
+ ##
7
+ #########################################################################################
8
+
9
+ # IMPORTS ===============================================================================
10
+
11
+ import numpy as np
12
+
13
+ from scipy.signal import butter, tf2ss
14
+
15
+ from math import factorial
16
+
17
+ from ..lti import StateSpace
18
+
19
+
20
+
21
+ # FILTER BLOCKS =========================================================================
22
+
23
+ class ButterworthLowpassFilter(StateSpace):
24
+
25
+ """
26
+ Direct implementation of a low pass butterworth filter block.
27
+
28
+ Follows the same structure as the 'StateSpace' block in the
29
+ 'pathsim.blocks' module. The numerator and denominator of the
30
+ filter transfer function are generated and then the transfer
31
+ function is realized as a state space model.
32
+
33
+ INPUTS :
34
+ Fc : (float) corner frequency of the filter in [Hz]
35
+ n : (int) filter order
36
+ """
37
+
38
+ def __init__(self, Fc, n):
39
+
40
+ #filter parameters
41
+ self.Fc = Fc
42
+ self.n = n
43
+
44
+ #use scipy.signal for filter design
45
+ num, den = butter(n, 2*np.pi*Fc, btype="low", analog=True, output="ba")
46
+
47
+ #initialize parent block
48
+ super().__init__(*tf2ss(num, den))
49
+
50
+
51
+ class ButterworthHighpassFilter(StateSpace):
52
+
53
+ """
54
+ Direct implementation of a high pass butterworth filter block.
55
+
56
+ Follows the same structure as the 'StateSpace' block in the
57
+ 'pathsim.blocks' module. The numerator and denominator of the
58
+ filter transfer function are generated and then the transfer
59
+ function is realized as a state space model.
60
+
61
+ INPUTS :
62
+ Fc : (float) corner frequency of the filter in [Hz]
63
+ n : (int) filter order
64
+ """
65
+
66
+ def __init__(self, Fc, n):
67
+
68
+ #filter parameters
69
+ self.Fc = Fc
70
+ self.n = n
71
+
72
+ #use scipy.signal for filter design
73
+ num, den = butter(n, 2*np.pi*Fc, btype="high", analog=True, output="ba")
74
+
75
+ #initialize parent block
76
+ super().__init__(*tf2ss(num, den))
77
+
78
+
79
+ class ButterworthBandpassFilter(StateSpace):
80
+
81
+ """
82
+ Direct implementation of a bandpass butterworth filter block.
83
+
84
+ Follows the same structure as the 'StateSpace' block in the
85
+ 'pathsim.blocks' module. The numerator and denominator of the
86
+ filter transfer function are generated and then the transfer
87
+ function is realized as a state space model.
88
+
89
+ INPUTS :
90
+ Fc : (list, tuple) corner frequencies of the filter in [Hz]
91
+ n : (int) filter order
92
+ """
93
+
94
+ def __init__(self, Fc, n):
95
+
96
+ #filter parameters
97
+ self.Fc = np.asarray(Fc)
98
+ self.n = n
99
+
100
+ if len(Fc) != 2:
101
+ raise ValueError("'ButterworthBandpassFilter' requires two corner frequencies!")
102
+
103
+ #use scipy.signal for filter design
104
+ num, den = butter(n, 2*np.pi*self.Fc, btype="bandpass", analog=True, output="ba")
105
+
106
+ #initialize parent block
107
+ super().__init__(*tf2ss(num, den))
108
+
109
+
110
+ class ButterworthBandstopFilter(StateSpace):
111
+
112
+ """
113
+ Direct implementation of a bandstop butterworth filter block.
114
+
115
+ Follows the same structure as the 'StateSpace' block in the
116
+ 'pathsim.blocks' module. The numerator and denominator of the
117
+ filter transfer function are generated and then the transfer
118
+ function is realized as a state space model.
119
+
120
+ INPUTS :
121
+ Fc : (list, tuple) corner frequencies of the filter in [Hz]
122
+ n : (int) filter order
123
+ """
124
+
125
+ def __init__(self, Fc, n):
126
+
127
+ #filter parameters
128
+ self.Fc = np.asarray(Fc)
129
+ self.n = n
130
+
131
+ if len(Fc) != 2:
132
+ raise ValueError("'ButterworthBandstopFilter' requires two corner frequencies!")
133
+
134
+ #use scipy.signal for filter design
135
+ num, den = butter(n, 2*np.pi*self.Fc, btype="bandstop", analog=True, output="ba")
136
+
137
+ #initialize parent block
138
+ super().__init__(*tf2ss(num, den))
139
+
140
+
141
+ class AllpassFilter(StateSpace):
142
+
143
+ """
144
+ Direct implementation of an Allpass filter using Pade approximants.
145
+ The transfer function of the ideal allpass is
146
+
147
+ H(s) = exp(-sT) = exp(-sT/2) / exp(sT/2)
148
+
149
+ where T is the time delay. This implementation uses Pade approximation
150
+ of the exponential to create a n-th order LTI statespace model that is
151
+ used for the numerical integration internally.
152
+
153
+ INPUTS :
154
+ T : (float) time delay of the allpass in [s]
155
+ n : (int) order of the pade approximation
156
+ """
157
+
158
+ def __init__(self, T, n=1):
159
+
160
+ #filter parameters
161
+ self.T = T
162
+ self.n = n
163
+
164
+ #taylor approximations for numerator and denominator
165
+ num = [(-T/2)**i/factorial(i) for i in range(n+1)]
166
+ den = [ (T/2)**i/factorial(i) for i in range(n+1)]
167
+
168
+ #initialize parent block
169
+ super().__init__(*tf2ss(num, den))
@@ -0,0 +1,218 @@
1
+ #########################################################################################
2
+ ##
3
+ ## SPECIAL RF NOISE SOURCES
4
+ ## (blocks/rf/noise.py)
5
+ ##
6
+ ## this module implements some noise sources for RF simulations
7
+ ##
8
+ ## Milan Rother 2024
9
+ ##
10
+ #########################################################################################
11
+
12
+ # IMPORTS ===============================================================================
13
+
14
+ import numpy as np
15
+
16
+ from .._block import Block
17
+
18
+
19
+ # NOISE SOURCE BLOCKS ===================================================================
20
+
21
+ class WhiteNoise(Block):
22
+ """
23
+ White noise source with uniform spectral density. Samples from distribution
24
+ with 'sampling_rate' and holds noise values constant for time bins.
25
+
26
+ If no 'sampling_rate' (None) is specified, every simulation timestep
27
+ gets a new noise values. This is the default setting.
28
+
29
+ INPUTS :
30
+ spectral_density : (float) noise spectral density
31
+ sampling_rate : (float or None) frequency with which the noise is sampled
32
+ """
33
+
34
+ def __init__(self, spectral_density=1, sampling_rate=None):
35
+ super().__init__()
36
+
37
+ self.spectral_density = spectral_density
38
+ self.sampling_rate = sampling_rate
39
+ self.sigma = np.sqrt(spectral_density)
40
+ self.n_samples = 0
41
+
42
+ def reset(self):
43
+ #reset inputs and outputs
44
+ self.inputs = {0:0.0}
45
+ self.outputs = {0:0.0}
46
+
47
+ #reset noise samples
48
+ self.n_samples = 0
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] = np.random.normal(scale=self.sigma)
57
+ self.n_samples += 1
58
+
59
+
60
+ class OneOverFNoise(Block):
61
+ """
62
+ 1/f noise source that is realized by integrating white noise using a
63
+ numerical integrator. Samples from distribution with 'sampling_rate'
64
+ and holds noise values constant for time bins.
65
+
66
+ If no 'sampling_rate' (None) is specified, every simulation timestep
67
+ gets a new noise values. This is the default setting.
68
+
69
+ INPUTS :
70
+ spectral_density : (float) noise spectral density
71
+ sampling_rate : (float or None) frequency with which the noise is sampled
72
+ """
73
+
74
+ def __init__(self, spectral_density=1, sampling_rate=None):
75
+ super().__init__()
76
+
77
+ #parameters of noise signal
78
+ self.spectral_density = spectral_density
79
+ self.sampling_rate = sampling_rate
80
+ self.sigma = np.sqrt(spectral_density)
81
+ self.white_noise_value = 0.0
82
+ self.n_samples = 0
83
+
84
+
85
+ def set_solver(self, Solver, tolerance_lte=1e-6):
86
+
87
+ #change solver if already initialized
88
+ if self.engine is not None:
89
+ self.engine = self.engine.change(Solver, tolerance_lte)
90
+ return #quit early
91
+
92
+ #initialize the numerical integration engine with kernel
93
+ def _f(x, u, t): return u
94
+ self.engine = Solver(0.0, _f, None, tolerance_lte)
95
+
96
+
97
+ def reset(self):
98
+ #reset inputs and outputs
99
+ self.inputs = {0:0.0}
100
+ self.outputs = {0:0.0}
101
+
102
+ #reset noise samples
103
+ self.white_noise_value = 0.0
104
+ self.n_samples = 0
105
+
106
+ #reset engine
107
+ self.engine.reset()
108
+
109
+
110
+ def update(self, t):
111
+ #set outputs
112
+ self.outputs[0] = self.engine.get()
113
+ return 0.0
114
+
115
+
116
+ def sample(self, t):
117
+ """
118
+ Sample from a normal distribution after successful timestep.
119
+ """
120
+ if (self.sampling_rate is None or
121
+ self.n_samples < t * self.sampling_rate):
122
+ self.white_noise_value = np.random.normal(scale=self.sigma)
123
+ self.n_samples += 1
124
+
125
+
126
+ def solve(self, t, dt):
127
+ #advance solution of implicit update equation
128
+ self.engine.solve(self.white_noise_value, t, dt)
129
+ return 0.0
130
+
131
+
132
+ def step(self, t, dt):
133
+ #compute update step with integration engine
134
+ self.engine.step(self.white_noise_value, t, dt)
135
+
136
+ #no error control for noise source
137
+ return True, 0.0, 1.0
138
+
139
+
140
+ class SinusoidalPhaseNoiseSource(Block):
141
+
142
+ def __init__(self, frequency=1, amplitude=1, phase=0, sig_cum=0, sig_white=0, sampling_rate=10):
143
+ super().__init__()
144
+
145
+ self.amplitude = amplitude
146
+ self.frequency = frequency
147
+ self.phase = phase
148
+
149
+ self.sampling_rate = sampling_rate
150
+
151
+ self.omega = 2 * np.pi * self.frequency
152
+
153
+ self.sig_cum = sig_cum
154
+ self.sig_white = sig_white
155
+
156
+ #initial noise sampling
157
+ self.noise_1 = np.random.normal()
158
+ self.noise_2 = np.random.normal()
159
+
160
+
161
+ def set_solver(self, Solver, tolerance_lte=1e-6):
162
+
163
+ #change solver if already initialized
164
+ if self.engine is not None:
165
+ self.engine = self.engine.change(Solver, tolerance_lte)
166
+ return #quit early
167
+
168
+ #initialize the numerical integration engine with kernel
169
+ def _f(x, u, t): return u
170
+ self.engine = Solver(0.0, _f, None, tolerance_lte)
171
+
172
+
173
+ def reset(self):
174
+ #reset inputs and outputs
175
+ self.inputs = {0:0.0}
176
+ self.outputs = {0:0.0}
177
+
178
+ #reset bin counter
179
+ self.n_samples = 0
180
+ self.t_max = 0
181
+
182
+ #reset engine
183
+ self.engine.reset()
184
+
185
+
186
+ def update(self, t):
187
+
188
+ #compute phase error
189
+ phase_error = self.sig_white * self.noise_1 + self.sig_cum * self.engine.get()
190
+
191
+ #set output
192
+ self.outputs[0] = self.amplitude * np.sin(self.omega*t + self.phase + phase_error)
193
+ return 0.0
194
+
195
+
196
+ def sample(self, t):
197
+ """
198
+ Sample from a normal distribution after successful timestep.
199
+ """
200
+ if (self.sampling_rate is None or
201
+ self.n_samples < t * self.sampling_rate):
202
+ self.noise_1 = np.random.normal()
203
+ self.noise_2 = np.random.normal()
204
+ self.n_samples += 1
205
+
206
+
207
+ def solve(self, t, dt):
208
+ #advance solution of implicit update equation
209
+ self.engine.solve(self.noise_2, t, dt)
210
+ return 0.0
211
+
212
+
213
+ def step(self, t, dt):
214
+ #compute update step with integration engine
215
+ self.engine.step(self.noise_2, t, dt)
216
+
217
+ #no error control for noise source
218
+ return True, 0.0, 1.0