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.
- pathsim/__init__.py +3 -0
- pathsim/blocks/__init__.py +14 -0
- pathsim/blocks/_block.py +209 -0
- pathsim/blocks/adder.py +30 -0
- pathsim/blocks/amplifier.py +34 -0
- pathsim/blocks/delay.py +70 -0
- pathsim/blocks/differentiator.py +70 -0
- pathsim/blocks/function.py +82 -0
- pathsim/blocks/integrator.py +66 -0
- pathsim/blocks/lti.py +155 -0
- pathsim/blocks/multiplier.py +30 -0
- pathsim/blocks/ode.py +86 -0
- pathsim/blocks/rf/__init__.py +4 -0
- pathsim/blocks/rf/filters.py +169 -0
- pathsim/blocks/rf/noise.py +218 -0
- pathsim/blocks/rf/sources.py +163 -0
- pathsim/blocks/rf/wienerhammerstein.py +338 -0
- pathsim/blocks/rng.py +57 -0
- pathsim/blocks/scope.py +224 -0
- pathsim/blocks/sources.py +71 -0
- pathsim/blocks/spectrum.py +316 -0
- pathsim/connection.py +112 -0
- pathsim/simulation.py +652 -0
- pathsim/solvers/__init__.py +25 -0
- pathsim/solvers/_solver.py +403 -0
- pathsim/solvers/bdf.py +240 -0
- pathsim/solvers/dirk2.py +101 -0
- pathsim/solvers/dirk3.py +86 -0
- pathsim/solvers/esdirk32.py +131 -0
- pathsim/solvers/esdirk4.py +99 -0
- pathsim/solvers/esdirk43.py +139 -0
- pathsim/solvers/esdirk54.py +141 -0
- pathsim/solvers/esdirk85.py +200 -0
- pathsim/solvers/euler.py +81 -0
- pathsim/solvers/rk4.py +61 -0
- pathsim/solvers/rkbs32.py +101 -0
- pathsim/solvers/rkck54.py +108 -0
- pathsim/solvers/rkdp54.py +111 -0
- pathsim/solvers/rkdp87.py +116 -0
- pathsim/solvers/rkf45.py +102 -0
- pathsim/solvers/rkf78.py +111 -0
- pathsim/solvers/rkv65.py +103 -0
- pathsim/solvers/ssprk22.py +62 -0
- pathsim/solvers/ssprk33.py +65 -0
- pathsim/solvers/ssprk34.py +74 -0
- pathsim/subsystem.py +267 -0
- pathsim/utils/__init__.py +0 -0
- pathsim/utils/adaptivebuffer.py +87 -0
- pathsim/utils/anderson.py +180 -0
- pathsim/utils/funcs.py +205 -0
- pathsim/utils/gilbert.py +110 -0
- pathsim/utils/progresstracker.py +90 -0
- pathsim/utils/realtimeplotter.py +230 -0
- pathsim/utils/statespacerealizations.py +116 -0
- pathsim/utils/waveforms.py +36 -0
- pathsim-0.2.0.dist-info/LICENSE.txt +21 -0
- pathsim-0.2.0.dist-info/METADATA +149 -0
- pathsim-0.2.0.dist-info/RECORD +109 -0
- pathsim-0.2.0.dist-info/WHEEL +5 -0
- pathsim-0.2.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/blocks/__init__.py +0 -0
- tests/blocks/test_adder.py +85 -0
- tests/blocks/test_amplifier.py +66 -0
- tests/blocks/test_block.py +138 -0
- tests/blocks/test_delay.py +122 -0
- tests/blocks/test_differentiator.py +102 -0
- tests/blocks/test_function.py +165 -0
- tests/blocks/test_integrator.py +92 -0
- tests/blocks/test_lti.py +162 -0
- tests/blocks/test_multiplier.py +87 -0
- tests/blocks/test_ode.py +125 -0
- tests/blocks/test_rng.py +109 -0
- tests/blocks/test_scope.py +196 -0
- tests/blocks/test_sources.py +119 -0
- tests/blocks/test_spectrum.py +119 -0
- tests/solvers/__init__.py +0 -0
- tests/solvers/test_bdf.py +364 -0
- tests/solvers/test_dirk2.py +138 -0
- tests/solvers/test_dirk3.py +137 -0
- tests/solvers/test_esdirk32.py +158 -0
- tests/solvers/test_esdirk4.py +138 -0
- tests/solvers/test_esdirk43.py +158 -0
- tests/solvers/test_esdirk54.py +160 -0
- tests/solvers/test_esdirk85.py +157 -0
- tests/solvers/test_euler.py +223 -0
- tests/solvers/test_rk4.py +138 -0
- tests/solvers/test_rkbs32.py +159 -0
- tests/solvers/test_rkck54.py +157 -0
- tests/solvers/test_rkdp54.py +159 -0
- tests/solvers/test_rkdp87.py +157 -0
- tests/solvers/test_rkf45.py +159 -0
- tests/solvers/test_rkf78.py +160 -0
- tests/solvers/test_rkv65.py +160 -0
- tests/solvers/test_solver.py +119 -0
- tests/solvers/test_ssprk22.py +136 -0
- tests/solvers/test_ssprk33.py +136 -0
- tests/solvers/test_ssprk34.py +136 -0
- tests/test_connection.py +176 -0
- tests/test_simulation.py +271 -0
- tests/test_subsystem.py +182 -0
- tests/utils/__init__.py +0 -0
- tests/utils/test_adaptivebuffer.py +111 -0
- tests/utils/test_anderson.py +142 -0
- tests/utils/test_funcs.py +143 -0
- tests/utils/test_gilbert.py +108 -0
- tests/utils/test_progresstracker.py +144 -0
- tests/utils/test_realtimeplotter.py +122 -0
- tests/utils/test_statespacerealizations.py +107 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## EMBEDDED DIAGONALLY IMPLICIT RUNGE KUTTA METHOD
|
|
4
|
+
## (solvers/esdirk85.py)
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from ._solver import ImplicitSolver
|
|
15
|
+
from ..utils.funcs import numerical_jacobian
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# SOLVERS ==============================================================================
|
|
19
|
+
|
|
20
|
+
class ESDIRK85(ImplicitSolver):
|
|
21
|
+
"""
|
|
22
|
+
16 stage 8-th order L-stable, stiffly accurate, stage order 2 ESDIRK method with
|
|
23
|
+
embedded 5-th order method for stepsize control. This very high order integrator
|
|
24
|
+
is suited for very stiff problems that require very high accuracy but is also
|
|
25
|
+
relatively expensive due to the insane 15 implicit (1 explicit) stages.
|
|
26
|
+
|
|
27
|
+
This method is a real beast and it remains to be seen how practical it is.
|
|
28
|
+
|
|
29
|
+
FROM :
|
|
30
|
+
VERY HIGH-ORDER A-STABLE STIFFLY ACCURATE DIAGONALLY
|
|
31
|
+
IMPLICIT RUNGE-KUTTA METHODS WITH ERROR ESTIMATORS
|
|
32
|
+
YOUSEF ALAMRI AND DAVID I. KETCHESON
|
|
33
|
+
ESDIRK(16,8)[2]SAL-[(16,5)]
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
|
|
37
|
+
super().__init__(initial_value, func, jac, tolerance_lte)
|
|
38
|
+
|
|
39
|
+
#counter for runge kutta stages
|
|
40
|
+
self.stage = 0
|
|
41
|
+
|
|
42
|
+
#flag adaptive timestep solver
|
|
43
|
+
self.is_adaptive = True
|
|
44
|
+
|
|
45
|
+
#slope coefficients for stages
|
|
46
|
+
self.Ks = {}
|
|
47
|
+
|
|
48
|
+
#intermediate evaluation times
|
|
49
|
+
self.eval_stages = [0.0,
|
|
50
|
+
0.234637638717043,
|
|
51
|
+
0.558545926594724,
|
|
52
|
+
0.562667638694992,
|
|
53
|
+
0.697898381329126,
|
|
54
|
+
0.956146958839776,
|
|
55
|
+
0.812903043340468,
|
|
56
|
+
0.148256733818785,
|
|
57
|
+
0.944650387704291,
|
|
58
|
+
0.428471803715736,
|
|
59
|
+
0.984131639774509,
|
|
60
|
+
0.320412672954752,
|
|
61
|
+
0.974077670791771,
|
|
62
|
+
0.852850433853921,
|
|
63
|
+
0.823320301074444,
|
|
64
|
+
1.0]
|
|
65
|
+
|
|
66
|
+
#butcher table
|
|
67
|
+
self.BT = {0:[0.0],
|
|
68
|
+
1:[0.117318819358521,0.117318819358521],
|
|
69
|
+
2:[0.0557014605974616,0.385525646638742,0.117318819358521],
|
|
70
|
+
3:[0.063493276428895,0.373556126263681,0.0082994166438953,0.117318819358521],
|
|
71
|
+
4:[0.0961351856230088,0.335558324517178,0.207077765910132,-0.0581917140797146,0.117318819358521],
|
|
72
|
+
5:[0.0497669214238319,0.384288616546039,0.0821728117583936,0.120337007107103,0.202262782645888,0.117318819358521],
|
|
73
|
+
6:[0.00626710666809847,0.496491452640725,-0.111303249827358,0.170478821683603,0.166517073971103,-0.0328669811542241,0.117318819358521],
|
|
74
|
+
7:[0.0463439767281591,0.00306724391019652,-0.00816305222386205,-0.0353302599538294,0.0139313601702569,-0.00992014507967429,0.0210087909090165,0.117318819358521],
|
|
75
|
+
8:[0.111574049232048,0.467639166482209,0.237773114804619,0.0798895699267508,0.109580615914593,0.0307353103825936,-0.0404391509541147,-0.16942110744293,0.117318819358521],
|
|
76
|
+
9:[-0.0107072484863877,-0.231376703354252,0.017541113036611,0.144871527682418,-0.041855459769806,0.0841832168332261,-0.0850020937282192,0.486170343825899,-0.0526717116822739,0.117318819358521],
|
|
77
|
+
10:[-0.0142238262314935,0.14752923682514,0.238235830732566,0.037950291904103,0.252075123381518,0.0474266904224567,-0.00363139069342027,0.274081442388563,-0.0599166970745255,-0.0527138812389185,0.117318819358521],
|
|
78
|
+
11:[-0.11837020183211,-0.635712481821264,0.239738832602538,0.330058936651707,-0.325784087988237,-0.0506514314589253,-0.281914404487009,0.852596345144291,0.651444614298805,-0.103476387303591,-0.354835880209975,0.117318819358521,],
|
|
79
|
+
12:[-0.00458164025442349,0.296219694015248,0.322146049419995,0.15917778285238,0.284864871688843,0.185509526463076,-0.0784621067883274,0.166312223692047,-0.284152486083397,-0.357125104338944,0.078437074055306,0.0884129667114481,0.117318819358521],
|
|
80
|
+
13:[-0.0545561913848106,0.675785423442753,0.423066443201941,-0.000165300126841193,0.104252994793763,-0.105763019303021,-0.15988308809318,0.0515050001032011,0.56013979290924,-0.45781539708603,-0.255870699752664,0.026960254296416,-0.0721245985053681,0.117318819358521],
|
|
81
|
+
14:[0.0649253995775223,-0.0216056457922249,-0.073738139377975,0.0931033310077225,-0.0194339577299149,-0.0879623837313009,0.057125517179467,0.205120850488097,0.132576503537441,0.489416890627328,-0.1106765720501,-0.081038793996096,0.0606031613503788,-0.00241467937442272,0.117318819358521],
|
|
82
|
+
15:[0.0459979286336779,0.0780075394482806,0.015021874148058,0.195180277284195,-0.00246643310153235,0.0473977117068314,-0.0682773558610363,0.19568019123878,-0.0876765449323747,0.177874852409192,-0.337519251582222,-0.0123255553640736,0.311573291192553,0.0458604327754991,0.278352222645651,0.117318819358521]}
|
|
83
|
+
|
|
84
|
+
#coefficients for truncation error estimate
|
|
85
|
+
_A1 = [0.0459979286336779,
|
|
86
|
+
0.0780075394482806,
|
|
87
|
+
0.015021874148058,
|
|
88
|
+
0.195180277284195,
|
|
89
|
+
-0.00246643310153235,
|
|
90
|
+
0.0473977117068314,
|
|
91
|
+
-0.0682773558610363,
|
|
92
|
+
0.19568019123878,
|
|
93
|
+
-0.0876765449323747,
|
|
94
|
+
0.177874852409192,
|
|
95
|
+
-0.337519251582222,
|
|
96
|
+
-0.0123255553640736,
|
|
97
|
+
0.311573291192553,
|
|
98
|
+
0.0458604327754991,
|
|
99
|
+
0.278352222645651,
|
|
100
|
+
0.117318819358521]
|
|
101
|
+
_A2 = [0.0603373529853206,
|
|
102
|
+
0.175453809423998,
|
|
103
|
+
0.0537707777611352,
|
|
104
|
+
0.195309248607308,
|
|
105
|
+
0.0135893741970232,
|
|
106
|
+
-0.0221160259296707,
|
|
107
|
+
-0.00726526156430691,
|
|
108
|
+
0.102961059369124,
|
|
109
|
+
0.000900215457460583,
|
|
110
|
+
0.0547959465692338,
|
|
111
|
+
-0.334995726863153,
|
|
112
|
+
0.0464409662093384,
|
|
113
|
+
0.301388101652194,
|
|
114
|
+
0.00524851570622031,
|
|
115
|
+
0.229538601845236,
|
|
116
|
+
0.124643044573514]
|
|
117
|
+
self.TR = [_a1 - _a2 for _a1, _a2 in zip(_A1, _A2)]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def error_controller(self, dt):
|
|
121
|
+
"""
|
|
122
|
+
compute scaling factor for adaptive timestep
|
|
123
|
+
based on local truncation error estimate and returns both
|
|
124
|
+
"""
|
|
125
|
+
if len(self.Ks)<len(self.TR):
|
|
126
|
+
return True, 0.0, 1.0
|
|
127
|
+
|
|
128
|
+
#compute local truncation error slope
|
|
129
|
+
slope = 0.0
|
|
130
|
+
for i, b in enumerate(self.TR):
|
|
131
|
+
slope += self.Ks[i] * b
|
|
132
|
+
|
|
133
|
+
#compute and clip truncation error
|
|
134
|
+
truncation_error = np.max(np.clip(abs(dt*slope), 1e-18, None))
|
|
135
|
+
|
|
136
|
+
#compute error ratio and success
|
|
137
|
+
error_ratio = self.tolerance_lte / truncation_error
|
|
138
|
+
success = error_ratio >= 1.0
|
|
139
|
+
|
|
140
|
+
#compute timestep scale
|
|
141
|
+
timestep_rescale = 0.9 * (error_ratio)**(1/6)
|
|
142
|
+
|
|
143
|
+
return success, truncation_error, timestep_rescale
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def solve(self, u, t, dt):
|
|
147
|
+
"""
|
|
148
|
+
Solves the implicit update equation via anderson acceleration.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
#first stage is explicit
|
|
152
|
+
if self.stage == 0:
|
|
153
|
+
return 0.0
|
|
154
|
+
|
|
155
|
+
#update timestep weighted slope
|
|
156
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
157
|
+
|
|
158
|
+
#update fixed-point equation
|
|
159
|
+
slope = 0.0
|
|
160
|
+
for i, b in enumerate(self.BT[self.stage]):
|
|
161
|
+
slope += self.Ks[i] * b
|
|
162
|
+
|
|
163
|
+
#use the jacobian
|
|
164
|
+
if self.jac is not None:
|
|
165
|
+
|
|
166
|
+
#compute jacobian of fixed-point equation
|
|
167
|
+
jac_g = dt * b * self.jac(self.x, u, t)
|
|
168
|
+
|
|
169
|
+
#anderson acceleration step with local newton
|
|
170
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, jac_g)
|
|
171
|
+
|
|
172
|
+
else:
|
|
173
|
+
#anderson acceleration step (pure)
|
|
174
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, None)
|
|
175
|
+
|
|
176
|
+
#return the fixed-point residual
|
|
177
|
+
return err
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def step(self, u, t, dt):
|
|
181
|
+
"""
|
|
182
|
+
performs the timestep update
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
#first stage is explicit
|
|
186
|
+
if self.stage == 0:
|
|
187
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
188
|
+
|
|
189
|
+
#restart anderson accelerator
|
|
190
|
+
self.acc.reset()
|
|
191
|
+
|
|
192
|
+
#error and step size control
|
|
193
|
+
if self.stage < 15:
|
|
194
|
+
self.stage += 1
|
|
195
|
+
return True, 0.0, 1.0
|
|
196
|
+
else:
|
|
197
|
+
self.stage = 0
|
|
198
|
+
return self.error_controller(dt)
|
|
199
|
+
|
|
200
|
+
|
pathsim/solvers/euler.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## EXPLICIT and IMPLICIT EULER INTEGRATORS
|
|
4
|
+
## (solvers/euler.py)
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
from ._solver import ExplicitSolver, ImplicitSolver
|
|
13
|
+
from ..utils.funcs import numerical_jacobian
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# SOLVERS ==============================================================================
|
|
17
|
+
|
|
18
|
+
class EUF(ExplicitSolver):
|
|
19
|
+
"""
|
|
20
|
+
Class that performs explicit (forward) euler integration
|
|
21
|
+
it holds the state and implements the timestep update.
|
|
22
|
+
|
|
23
|
+
Use this only if the function to integrate is super smooth
|
|
24
|
+
or multistep/multistage methods cant be used.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def step(self, u, t, dt):
|
|
28
|
+
"""
|
|
29
|
+
performs the explicit forward timestep for (t+dt)
|
|
30
|
+
based on the state and input at (t)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
#update state with euler step
|
|
34
|
+
self.x = self.x_0 + dt * self.func(self.x, u, t)
|
|
35
|
+
|
|
36
|
+
#no error estimate available
|
|
37
|
+
return True, 0.0, 1.0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class EUB(ImplicitSolver):
|
|
41
|
+
"""
|
|
42
|
+
Class that performs implicit (backward) euler integration
|
|
43
|
+
it holds the state and implements the solution of the
|
|
44
|
+
implicit update equation at each timestep.
|
|
45
|
+
|
|
46
|
+
Its an absolute classic and ok for moderately stiff problems
|
|
47
|
+
that dont require super high accuracy.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def solve(self, u, t, dt):
|
|
51
|
+
"""
|
|
52
|
+
Solves the implicit update equation via anderson acceleration.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
#update the fixed point equation
|
|
56
|
+
g = self.x_0 + dt * self.func(self.x, u, t)
|
|
57
|
+
|
|
58
|
+
#use the numerical jacobian
|
|
59
|
+
if self.jac is not None:
|
|
60
|
+
|
|
61
|
+
#compute numerical jacobian
|
|
62
|
+
jac_g = dt * self.jac(self.x, u, t)
|
|
63
|
+
|
|
64
|
+
#anderson acceleration step with local newton
|
|
65
|
+
self.x, err = self.acc.step(self.x, g, jac_g)
|
|
66
|
+
|
|
67
|
+
else:
|
|
68
|
+
#anderson acceleration step (pure)
|
|
69
|
+
self.x, err = self.acc.step(self.x, g, None)
|
|
70
|
+
|
|
71
|
+
#return the fixed-point residual
|
|
72
|
+
return err
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def step(self, u, t, dt):
|
|
76
|
+
|
|
77
|
+
#reset anderson accelerator
|
|
78
|
+
self.acc.reset()
|
|
79
|
+
|
|
80
|
+
#no error estimate available
|
|
81
|
+
return True, 0.0, 1.0
|
pathsim/solvers/rk4.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## CLASSICAL EXPLICIT RUNGE-KUTTA INTEGRATOR
|
|
4
|
+
## (solvers/rk4.py)
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
from ._solver import ExplicitSolver
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# SOLVERS ==============================================================================
|
|
16
|
+
|
|
17
|
+
class RK4(ExplicitSolver):
|
|
18
|
+
"""
|
|
19
|
+
'The' classical 4-th order 4-stage Runge-Kutta method.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
|
|
23
|
+
super().__init__(initial_value, func, jac, tolerance_lte)
|
|
24
|
+
|
|
25
|
+
#counter for runge kutta stages
|
|
26
|
+
self.stage = 0
|
|
27
|
+
|
|
28
|
+
#slope coefficients for stages
|
|
29
|
+
self.Ks = {}
|
|
30
|
+
|
|
31
|
+
#intermediate evaluation times
|
|
32
|
+
self.eval_stages = [0.0, 0.5, 0.5, 1.0]
|
|
33
|
+
|
|
34
|
+
#butcher table
|
|
35
|
+
self.BT = {0:[1/2],
|
|
36
|
+
1:[0.0, 1/2],
|
|
37
|
+
2:[0.0, 0.0, 1.0],
|
|
38
|
+
3:[1/6, 2/6, 2/6, 1/6]}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def step(self, u, t, dt):
|
|
42
|
+
"""
|
|
43
|
+
performs the (explicit) timestep for (t+dt)
|
|
44
|
+
based on the state and input at (t)
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
#buffer intermediate slope
|
|
48
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
49
|
+
|
|
50
|
+
#update state at stage
|
|
51
|
+
slope = 0.0
|
|
52
|
+
for i, b in enumerate(self.BT[self.stage]):
|
|
53
|
+
slope += self.Ks[i] * b
|
|
54
|
+
self.x = dt * slope + self.x_0
|
|
55
|
+
|
|
56
|
+
#wrap around stage counter
|
|
57
|
+
self.stage = (self.stage + 1) % 4
|
|
58
|
+
|
|
59
|
+
#no error estimate available
|
|
60
|
+
return True, 0.0, 1.0
|
|
61
|
+
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## EXPLICIT ADAPTIVE TIMESTEPPING RUNGE-KUTTA INTEGRATORS
|
|
4
|
+
## (solvers/rkbs32.py)
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from ._solver import ExplicitSolver
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# SOLVERS ==============================================================================
|
|
18
|
+
|
|
19
|
+
class RKBS32(ExplicitSolver):
|
|
20
|
+
"""
|
|
21
|
+
The Bogacki–Shampine method is a Runge–Kutta method of order three with four stages.
|
|
22
|
+
It has an embedded second-order method which can be used to implement adaptive
|
|
23
|
+
step size. The Bogacki–Shampine method is implemented in the 'ode3' for fixed
|
|
24
|
+
step solver and 'ode23' for a variable step solver function in MATLAB.
|
|
25
|
+
|
|
26
|
+
This is the adaptive variant. It is a good choice of low accuracy is acceptable.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
|
|
30
|
+
super().__init__(initial_value, func, jac, tolerance_lte)
|
|
31
|
+
|
|
32
|
+
#counter for runge kutta stages
|
|
33
|
+
self.stage = 0
|
|
34
|
+
|
|
35
|
+
#flag adaptive timestep solver
|
|
36
|
+
self.is_adaptive = True
|
|
37
|
+
|
|
38
|
+
#slope coefficients for stages
|
|
39
|
+
self.Ks = {}
|
|
40
|
+
|
|
41
|
+
#intermediate evaluation times
|
|
42
|
+
self.eval_stages = [0.0, 1/2, 3/4, 1.0]
|
|
43
|
+
|
|
44
|
+
#extended butcher table
|
|
45
|
+
self.BT = {0:[1/2],
|
|
46
|
+
1:[0.0 , 3/4],
|
|
47
|
+
2:[2/9 , 1/3, 4/9]}
|
|
48
|
+
|
|
49
|
+
#coefficients for truncation error estimate
|
|
50
|
+
self.TR = [-5/72, 1/12, 1/9, -1/8]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def error_controller(self, dt):
|
|
54
|
+
"""
|
|
55
|
+
compute scaling factor for adaptive timestep
|
|
56
|
+
based on local truncation error estimate and returns both
|
|
57
|
+
"""
|
|
58
|
+
if len(self.Ks)<len(self.TR):
|
|
59
|
+
return True, 0.0, 1.0
|
|
60
|
+
|
|
61
|
+
#compute local truncation error slope
|
|
62
|
+
slope = 0.0
|
|
63
|
+
for i, b in enumerate(self.TR):
|
|
64
|
+
slope += self.Ks[i] * b
|
|
65
|
+
|
|
66
|
+
#compute and clip truncation error
|
|
67
|
+
truncation_error = np.max(np.clip(abs(dt*slope), 1e-18, None))
|
|
68
|
+
|
|
69
|
+
#compute error ratio
|
|
70
|
+
error_ratio = self.tolerance_lte / truncation_error
|
|
71
|
+
success = error_ratio >= 1.0
|
|
72
|
+
|
|
73
|
+
#compute timestep scale
|
|
74
|
+
timestep_rescale = 0.9 * (error_ratio)**(1/3)
|
|
75
|
+
|
|
76
|
+
return success, truncation_error, timestep_rescale
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def step(self, u, t, dt):
|
|
80
|
+
"""
|
|
81
|
+
performs the (explicit) timestep for (t+dt)
|
|
82
|
+
based on the state and input at (t)
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
#buffer intermediate slope
|
|
86
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
87
|
+
|
|
88
|
+
#error and step size control
|
|
89
|
+
if self.stage < 3:
|
|
90
|
+
|
|
91
|
+
#update state at stage
|
|
92
|
+
slope = 0.0
|
|
93
|
+
for i, b in enumerate(self.BT[self.stage]):
|
|
94
|
+
slope += self.Ks[i] * b
|
|
95
|
+
self.x = dt*slope + self.x_0
|
|
96
|
+
|
|
97
|
+
self.stage += 1
|
|
98
|
+
return True, 0.0, 1.0
|
|
99
|
+
else:
|
|
100
|
+
self.stage = 0
|
|
101
|
+
return self.error_controller(dt)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## EXPLICIT ADAPTIVE TIMESTEPPING RUNGE-KUTTA INTEGRATORS
|
|
4
|
+
## (solvers/rkck54.py)
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from ._solver import ExplicitSolver
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# SOLVERS ==============================================================================
|
|
18
|
+
|
|
19
|
+
class RKCK54(ExplicitSolver):
|
|
20
|
+
"""
|
|
21
|
+
6-stage 5-th order with embedded 4-th order Runge-Kutta method from Cash and Karp
|
|
22
|
+
with 5-th order truncation error estimate for the 4-th order solution that can be
|
|
23
|
+
used to adaptively control the timestep. The 5-th order method is used for
|
|
24
|
+
timestepping (local extrapolation) and the difference to the 5-th order solution
|
|
25
|
+
is used as an estimate for the local truncation error of the 4-th order solution.
|
|
26
|
+
|
|
27
|
+
This is the fixed order Cash-Karp scheme without early quitting.
|
|
28
|
+
|
|
29
|
+
The method balances the accuracy of the 5-th and 4-th order solution and
|
|
30
|
+
has enhanced stability properties compared to Fehlberg or Dormand-Prince
|
|
31
|
+
methods. This makes it suitable for slightly stiff problems.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
|
|
35
|
+
super().__init__(initial_value, func, jac, tolerance_lte)
|
|
36
|
+
|
|
37
|
+
#counter for runge kutta stages
|
|
38
|
+
self.stage = 0
|
|
39
|
+
|
|
40
|
+
#flag adaptive timestep solver
|
|
41
|
+
self.is_adaptive = True
|
|
42
|
+
|
|
43
|
+
#slope coefficients for stages
|
|
44
|
+
self.Ks = {}
|
|
45
|
+
|
|
46
|
+
#intermediate evaluation times
|
|
47
|
+
self.eval_stages = [0.0, 1/5, 3/10, 3/5, 1, 7/8]
|
|
48
|
+
|
|
49
|
+
#extended butcher table
|
|
50
|
+
self.BT = {0:[ 1/5],
|
|
51
|
+
1:[ 3/40, 9/40],
|
|
52
|
+
2:[ 3/10, -9/10, 6/5],
|
|
53
|
+
3:[ -11/54, 5/2, -70/27, 35/27],
|
|
54
|
+
4:[1631/55296, 175/512, 575/13824, 44275/110592, 253/4096],
|
|
55
|
+
5:[ 37/378, 0, 250/621, 125/594, 0, 512/1771]}
|
|
56
|
+
|
|
57
|
+
#coefficients for local truncation error estimate
|
|
58
|
+
self.TR = [-277/64512, 0, 6925/370944, -6925/202752, -277/14336, 277/7084]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def error_controller(self, dt):
|
|
62
|
+
"""
|
|
63
|
+
compute scaling factor for adaptive timestep
|
|
64
|
+
based on local truncation error estimate and returns both
|
|
65
|
+
"""
|
|
66
|
+
if len(self.Ks)<len(self.TR):
|
|
67
|
+
return True, 0.0, 1.0
|
|
68
|
+
|
|
69
|
+
#compute local truncation error slope
|
|
70
|
+
slope = 0.0
|
|
71
|
+
for i, b in enumerate(self.TR):
|
|
72
|
+
slope += self.Ks[i] * b
|
|
73
|
+
|
|
74
|
+
#compute and clip truncation error
|
|
75
|
+
truncation_error = np.max(np.clip(abs(dt*slope), 1e-18, None))
|
|
76
|
+
|
|
77
|
+
#compute error ratio
|
|
78
|
+
error_ratio = self.tolerance_lte / truncation_error
|
|
79
|
+
success = error_ratio >= 1.0
|
|
80
|
+
|
|
81
|
+
#compute timestep scale
|
|
82
|
+
timestep_rescale = 0.9 * (error_ratio)**(1/5)
|
|
83
|
+
|
|
84
|
+
return success, truncation_error, timestep_rescale
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def step(self, u, t, dt):
|
|
88
|
+
"""
|
|
89
|
+
performs the (explicit) timestep for (t+dt)
|
|
90
|
+
based on the state and input at (t)
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
#buffer intermediate slope
|
|
94
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
95
|
+
|
|
96
|
+
#update state at stage
|
|
97
|
+
slope = 0.0
|
|
98
|
+
for i, b in enumerate(self.BT[self.stage]):
|
|
99
|
+
slope += self.Ks[i] * b
|
|
100
|
+
self.x = dt * slope + self.x_0
|
|
101
|
+
|
|
102
|
+
#error and step size control
|
|
103
|
+
if self.stage < 5:
|
|
104
|
+
self.stage += 1
|
|
105
|
+
return True, 0.0, 1.0
|
|
106
|
+
else:
|
|
107
|
+
self.stage = 0
|
|
108
|
+
return self.error_controller(dt)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## EXPLICIT ADAPTIVE TIMESTEPPING RUNGE-KUTTA INTEGRATORS
|
|
4
|
+
## (solvers/rkdp54.py)
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from ._solver import ExplicitSolver
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# SOLVERS ==============================================================================
|
|
18
|
+
|
|
19
|
+
class RKDP54(ExplicitSolver):
|
|
20
|
+
"""
|
|
21
|
+
Dormand–Prince method with seven Runge-Kutta stages is 5-th order
|
|
22
|
+
accurate with an embedded 4-th order method. The 5-th order method
|
|
23
|
+
is used for timestepping (local extrapolation) and the difference
|
|
24
|
+
to the 5-th order solution is used as an estimate for the local
|
|
25
|
+
truncation error of the 4-th order solaution.
|
|
26
|
+
|
|
27
|
+
Wikipedia:
|
|
28
|
+
As of 2023, Dormand–Prince is the default method
|
|
29
|
+
in the 'ode45' solver for MATLAB
|
|
30
|
+
|
|
31
|
+
Great choice for all kinds of problems that require high accuracy
|
|
32
|
+
and where the adaptive timestepping doesnt cause problems.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
|
|
36
|
+
super().__init__(initial_value, func, jac, tolerance_lte)
|
|
37
|
+
|
|
38
|
+
#counter for runge kutta stages
|
|
39
|
+
self.stage = 0
|
|
40
|
+
|
|
41
|
+
#flag adaptive timestep solver
|
|
42
|
+
self.is_adaptive = True
|
|
43
|
+
|
|
44
|
+
#slope coefficients for stages
|
|
45
|
+
self.Ks = {}
|
|
46
|
+
|
|
47
|
+
#intermediate evaluation times
|
|
48
|
+
self.eval_stages = [0.0, 1/5, 3/10, 4/5, 8/9, 1.0, 1.0]
|
|
49
|
+
|
|
50
|
+
#extended butcher table
|
|
51
|
+
self.BT = {0:[ 1/5],
|
|
52
|
+
1:[ 3/40, 9/40],
|
|
53
|
+
2:[ 44/45, -56/15, 32/9],
|
|
54
|
+
3:[19372/6561, -25360/2187, 64448/6561, -212/729],
|
|
55
|
+
4:[ 9017/3168, -355/33, 46732/5247, 49/176, -5103/18656],
|
|
56
|
+
5:[ 35/384, 0, 500/1113, 125/192, -2187/6784, 11/84]}
|
|
57
|
+
|
|
58
|
+
#coefficients for local truncation error estimate
|
|
59
|
+
self.TR = [71/57600, 0, - 71/16695, 71/1920, -17253/339200, 22/525, -1/40]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def error_controller(self, dt):
|
|
63
|
+
"""
|
|
64
|
+
compute scaling factor for adaptive timestep
|
|
65
|
+
based on local truncation error estimate and returns both
|
|
66
|
+
"""
|
|
67
|
+
if len(self.Ks)<len(self.TR):
|
|
68
|
+
return True, 0.0, 1.0
|
|
69
|
+
|
|
70
|
+
#compute local truncation error slope
|
|
71
|
+
slope = 0.0
|
|
72
|
+
for i, b in enumerate(self.TR):
|
|
73
|
+
slope += self.Ks[i] * b
|
|
74
|
+
|
|
75
|
+
#compute and clip truncation error
|
|
76
|
+
truncation_error = np.max(np.clip(abs(dt*slope), 1e-18, None))
|
|
77
|
+
|
|
78
|
+
#compute error ratio
|
|
79
|
+
error_ratio = self.tolerance_lte / truncation_error
|
|
80
|
+
success = error_ratio >= 1.0
|
|
81
|
+
|
|
82
|
+
#compute timestep scale
|
|
83
|
+
timestep_rescale = 0.9 * (error_ratio)**(1/5)
|
|
84
|
+
|
|
85
|
+
return success, truncation_error, timestep_rescale
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def step(self, u, t, dt):
|
|
89
|
+
"""
|
|
90
|
+
performs the (explicit) timestep for (t+dt)
|
|
91
|
+
based on the state and input at (t)
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
#buffer intermediate slope
|
|
95
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
96
|
+
|
|
97
|
+
#error and step size control
|
|
98
|
+
if self.stage < 6:
|
|
99
|
+
|
|
100
|
+
#update state at stage
|
|
101
|
+
slope = 0.0
|
|
102
|
+
for i, b in enumerate(self.BT[self.stage]):
|
|
103
|
+
slope += self.Ks[i] * b
|
|
104
|
+
self.x = dt * slope + self.x_0
|
|
105
|
+
|
|
106
|
+
#increment stage counter
|
|
107
|
+
self.stage += 1
|
|
108
|
+
return True, 0.0, 1.0
|
|
109
|
+
else:
|
|
110
|
+
self.stage = 0
|
|
111
|
+
return self.error_controller(dt)
|