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,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
+
@@ -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)