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
pathsim/solvers/dirk2.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## DIAGONALLY IMPLICIT RUNGE KUTTA METHOD
|
|
4
|
+
## (solvers/dirk2.py)
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
from ._solver import ImplicitSolver
|
|
13
|
+
from ..utils.funcs import numerical_jacobian
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# SOLVERS ==============================================================================
|
|
17
|
+
|
|
18
|
+
class DIRK2(ImplicitSolver):
|
|
19
|
+
"""
|
|
20
|
+
The 2-stage SSP-optimal Diagonally Implicit Runge–Kutta (DIRK) method
|
|
21
|
+
of second order, namely the second order RK with the largest radius
|
|
22
|
+
of absolute monotonicity.
|
|
23
|
+
It is also symplectic and the optimal 2-stage second order implicit RK.
|
|
24
|
+
|
|
25
|
+
FROM :
|
|
26
|
+
L. Ferracina and M.N. Spijker.
|
|
27
|
+
Strong stability of singlydiagonally-implicit Runge-Kutta methods.
|
|
28
|
+
Applied Numerical Mathematics, 58:1675–1686, 2008.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
|
|
32
|
+
super().__init__(initial_value, func, jac, tolerance_lte)
|
|
33
|
+
|
|
34
|
+
#counter for runge kutta stages
|
|
35
|
+
self.stage = 0
|
|
36
|
+
|
|
37
|
+
#slope coefficients for stages
|
|
38
|
+
self.Ks = {}
|
|
39
|
+
|
|
40
|
+
#intermediate evaluation times
|
|
41
|
+
self.eval_stages = [1/4, 3/4]
|
|
42
|
+
|
|
43
|
+
#butcher table
|
|
44
|
+
self.BT = {0:[1/4],
|
|
45
|
+
1:[1/2, 1/4]}
|
|
46
|
+
|
|
47
|
+
#final evaluation
|
|
48
|
+
self.A = [1/2, 1/2]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def solve(self, u, t, dt):
|
|
52
|
+
"""
|
|
53
|
+
Solves the implicit update equation via anderson acceleration.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
#update timestep weighted slope
|
|
57
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
58
|
+
|
|
59
|
+
#update fixed-point equation
|
|
60
|
+
slope = 0.0
|
|
61
|
+
for i, b in enumerate(self.BT[self.stage]):
|
|
62
|
+
slope += self.Ks[i] * b
|
|
63
|
+
|
|
64
|
+
#use the jacobian
|
|
65
|
+
if self.jac is not None:
|
|
66
|
+
|
|
67
|
+
#compute jacobian of fixed-point equation
|
|
68
|
+
jac_g = dt * b * self.jac(self.x, u, t)
|
|
69
|
+
|
|
70
|
+
#anderson acceleration step with local newton
|
|
71
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, jac_g)
|
|
72
|
+
|
|
73
|
+
else:
|
|
74
|
+
#anderson acceleration step (pure)
|
|
75
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, None)
|
|
76
|
+
|
|
77
|
+
#return the fixed-point residual
|
|
78
|
+
return err
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def step(self, u, t, dt):
|
|
82
|
+
"""
|
|
83
|
+
performs the timestep update
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
#compute final output in second stage
|
|
87
|
+
if self.stage == 1:
|
|
88
|
+
slope = 0.0
|
|
89
|
+
for i, a in enumerate(self.A):
|
|
90
|
+
slope += self.Ks[i] * a
|
|
91
|
+
self.x = dt*slope + self.x_0
|
|
92
|
+
|
|
93
|
+
#restart anderson accelerator
|
|
94
|
+
self.acc.reset()
|
|
95
|
+
|
|
96
|
+
#wrap around stage counter
|
|
97
|
+
self.stage = (self.stage + 1) % 2
|
|
98
|
+
|
|
99
|
+
#no error estimate available
|
|
100
|
+
return True, 0.0, 1.0
|
|
101
|
+
|
pathsim/solvers/dirk3.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## DIAGONALLY IMPLICIT RUNGE KUTTA METHOD
|
|
4
|
+
## (solvers/dirk3.py)
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
from ._solver import ImplicitSolver
|
|
13
|
+
from ..utils.funcs import numerical_jacobian
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# SOLVERS ==============================================================================
|
|
17
|
+
|
|
18
|
+
class DIRK3(ImplicitSolver):
|
|
19
|
+
"""
|
|
20
|
+
Four-stage, 3rd order, L-stable Diagonally Implicit Runge–Kutta (DIRK) method.
|
|
21
|
+
|
|
22
|
+
(from Wikipedia)
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
|
|
26
|
+
super().__init__(initial_value, func, jac, tolerance_lte)
|
|
27
|
+
|
|
28
|
+
#counter for runge kutta stages
|
|
29
|
+
self.stage = 0
|
|
30
|
+
|
|
31
|
+
#slope coefficients for stages
|
|
32
|
+
self.Ks = {}
|
|
33
|
+
|
|
34
|
+
#intermediate evaluation times
|
|
35
|
+
self.eval_stages = [1/2, 2/3, 1/2, 1.0]
|
|
36
|
+
|
|
37
|
+
#butcher table
|
|
38
|
+
self.BT = {0:[1/2],
|
|
39
|
+
1:[1/6, 1/2],
|
|
40
|
+
2:[-1/2, 1/2, 1/2],
|
|
41
|
+
3:[3/2, -3/2, 1/2, 1/2]}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def solve(self, u, t, dt):
|
|
45
|
+
"""
|
|
46
|
+
Solves the implicit update equation via anderson acceleration.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
#update timestep weighted slope
|
|
50
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
51
|
+
|
|
52
|
+
#update fixed-point equation
|
|
53
|
+
slope = 0.0
|
|
54
|
+
for i, b in enumerate(self.BT[self.stage]):
|
|
55
|
+
slope += self.Ks[i] * b
|
|
56
|
+
|
|
57
|
+
#use the jacobian
|
|
58
|
+
if self.jac is not None:
|
|
59
|
+
|
|
60
|
+
#compute jacobian of fixed-point equation
|
|
61
|
+
jac_g = dt * b * self.jac(self.x, u, t)
|
|
62
|
+
|
|
63
|
+
#anderson acceleration step with local newton
|
|
64
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, jac_g)
|
|
65
|
+
|
|
66
|
+
else:
|
|
67
|
+
#anderson acceleration step (pure)
|
|
68
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, None)
|
|
69
|
+
|
|
70
|
+
#return the fixed-point residual
|
|
71
|
+
return err
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def step(self, u, t, dt):
|
|
75
|
+
"""
|
|
76
|
+
performs the timestep update
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
#restart anderson accelerator
|
|
80
|
+
self.acc.reset()
|
|
81
|
+
|
|
82
|
+
#wrap around stage counter
|
|
83
|
+
self.stage = (self.stage + 1) % 4
|
|
84
|
+
|
|
85
|
+
#no error estimate available
|
|
86
|
+
return True, 0.0, 1.0
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## EMBEDDED DIAGONALLY IMPLICIT RUNGE KUTTA METHOD
|
|
4
|
+
## (solvers/esdirk32.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 ESDIRK32(ImplicitSolver):
|
|
21
|
+
"""
|
|
22
|
+
Williams et.al constructed an ESDIRK method of order 3 with four stages.
|
|
23
|
+
This method is constructed such that it is applicable to index-2
|
|
24
|
+
differential algebraic systems.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
|
|
28
|
+
super().__init__(initial_value, func, jac, tolerance_lte)
|
|
29
|
+
|
|
30
|
+
#counter for runge kutta stages
|
|
31
|
+
self.stage = 0
|
|
32
|
+
|
|
33
|
+
#flag adaptive timestep solver
|
|
34
|
+
self.is_adaptive = True
|
|
35
|
+
|
|
36
|
+
#slope coefficients for stages
|
|
37
|
+
self.Ks = {}
|
|
38
|
+
|
|
39
|
+
#intermediate evaluation times
|
|
40
|
+
self.eval_stages = [0.0, 1.0, 3/2, 1.0]
|
|
41
|
+
|
|
42
|
+
#butcher table
|
|
43
|
+
self.BT = {0:[0.0],
|
|
44
|
+
1:[1/2, 1/2],
|
|
45
|
+
2:[5/8, 3/8, 1/2],
|
|
46
|
+
3:[7/18, 1/3, -2/9, 1/2]}
|
|
47
|
+
|
|
48
|
+
#coefficients for truncation error estimate
|
|
49
|
+
self.TR = [-1/9, -1/6, -2/9, 1/2]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def error_controller(self, dt):
|
|
53
|
+
"""
|
|
54
|
+
compute scaling factor for adaptive timestep
|
|
55
|
+
based on local truncation error estimate and returns both
|
|
56
|
+
"""
|
|
57
|
+
if len(self.Ks)<len(self.TR):
|
|
58
|
+
return True, 0.0, 1.0
|
|
59
|
+
|
|
60
|
+
#compute local truncation error slope
|
|
61
|
+
slope = 0.0
|
|
62
|
+
for i, b in enumerate(self.TR):
|
|
63
|
+
slope += self.Ks[i] * b
|
|
64
|
+
|
|
65
|
+
#compute and clip truncation error
|
|
66
|
+
truncation_error = np.max(np.clip(abs(dt*slope), 1e-18, None))
|
|
67
|
+
|
|
68
|
+
#compute error ratio
|
|
69
|
+
error_ratio = self.tolerance_lte / truncation_error
|
|
70
|
+
success = error_ratio >= 1.0
|
|
71
|
+
|
|
72
|
+
#compute timestep scale
|
|
73
|
+
timestep_rescale = 0.9 * (error_ratio)**(1/3)
|
|
74
|
+
|
|
75
|
+
return success, truncation_error, timestep_rescale
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def solve(self, u, t, dt):
|
|
80
|
+
"""
|
|
81
|
+
Solves the implicit update equation via anderson acceleration.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
#first stage is explicit
|
|
85
|
+
if self.stage == 0:
|
|
86
|
+
return 0.0
|
|
87
|
+
|
|
88
|
+
#update timestep weighted slope
|
|
89
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
90
|
+
|
|
91
|
+
#update fixed-point equation
|
|
92
|
+
slope = 0.0
|
|
93
|
+
for i, b in enumerate(self.BT[self.stage]):
|
|
94
|
+
slope += self.Ks[i] * b
|
|
95
|
+
|
|
96
|
+
#use the jacobian
|
|
97
|
+
if self.jac is not None:
|
|
98
|
+
|
|
99
|
+
#compute jacobian of fixed-point equation
|
|
100
|
+
jac_g = dt * b * self.jac(self.x, u, t)
|
|
101
|
+
|
|
102
|
+
#anderson acceleration step with local newton
|
|
103
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, jac_g)
|
|
104
|
+
|
|
105
|
+
else:
|
|
106
|
+
#anderson acceleration step (pure)
|
|
107
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, None)
|
|
108
|
+
|
|
109
|
+
#return the fixed-point residual
|
|
110
|
+
return err
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def step(self, u, t, dt):
|
|
114
|
+
"""
|
|
115
|
+
performs the timestep update
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
#first stage is explicit
|
|
119
|
+
if self.stage == 0:
|
|
120
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
121
|
+
|
|
122
|
+
#restart anderson accelerator
|
|
123
|
+
self.acc.reset()
|
|
124
|
+
|
|
125
|
+
#error and step size control
|
|
126
|
+
if self.stage < 3:
|
|
127
|
+
self.stage += 1
|
|
128
|
+
return True, 0.0, 1.0
|
|
129
|
+
else:
|
|
130
|
+
self.stage = 0
|
|
131
|
+
return self.error_controller(dt)
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## DIAGONALLY IMPLICIT RUNGE KUTTA METHOD
|
|
4
|
+
## (solvers/esdirk4.py)
|
|
5
|
+
##
|
|
6
|
+
## Milan Rother 2024
|
|
7
|
+
##
|
|
8
|
+
########################################################################################
|
|
9
|
+
|
|
10
|
+
# IMPORTS ==============================================================================
|
|
11
|
+
|
|
12
|
+
from ._solver import ImplicitSolver
|
|
13
|
+
from ..utils.funcs import numerical_jacobian
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# SOLVERS ==============================================================================
|
|
17
|
+
|
|
18
|
+
class ESDIRK4(ImplicitSolver):
|
|
19
|
+
"""
|
|
20
|
+
6-stage, 4th order Diagonally Implicit Runge–Kutta (DIRK) method
|
|
21
|
+
with explicit first stage that is specifically designed to handle
|
|
22
|
+
differential algebraic equations of indices up to two or three.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
|
|
26
|
+
super().__init__(initial_value, func, jac, tolerance_lte)
|
|
27
|
+
|
|
28
|
+
#counter for runge kutta stages
|
|
29
|
+
self.stage = 0
|
|
30
|
+
|
|
31
|
+
#slope coefficients for stages
|
|
32
|
+
self.Ks = {}
|
|
33
|
+
|
|
34
|
+
#intermediate evaluation times
|
|
35
|
+
self.eval_stages = [0.0, 1/2, 1/6, 37/40, 1/2, 1.0]
|
|
36
|
+
|
|
37
|
+
#butcher table
|
|
38
|
+
self.BT = {0:[0.0],
|
|
39
|
+
1:[1/4, 1/4],
|
|
40
|
+
2:[-1/36, -1/18, 1/4],
|
|
41
|
+
3:[-21283/32000, -5143/64000, 90909/64000, 1/4],
|
|
42
|
+
4:[46010759/749250000, -737693/40500000, 10931269/45500000, -1140071/34090875, 1/4],
|
|
43
|
+
5:[89/444, 89/804756, -27/364, -20000/171717, 843750/1140071, 1/4]}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def solve(self, u, t, dt):
|
|
49
|
+
"""
|
|
50
|
+
Solves the implicit update equation via anderson acceleration.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
#first stage is explicit
|
|
54
|
+
if self.stage == 0:
|
|
55
|
+
return 0.0
|
|
56
|
+
|
|
57
|
+
#update timestep weighted slope
|
|
58
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
59
|
+
|
|
60
|
+
#update fixed-point equation
|
|
61
|
+
slope = 0.0
|
|
62
|
+
for i, b in enumerate(self.BT[self.stage]):
|
|
63
|
+
slope += self.Ks[i] * b
|
|
64
|
+
|
|
65
|
+
#use the jacobian
|
|
66
|
+
if self.jac is not None:
|
|
67
|
+
|
|
68
|
+
#compute jacobian of fixed-point equation
|
|
69
|
+
jac_g = dt * b * self.jac(self.x, u, t)
|
|
70
|
+
|
|
71
|
+
#anderson acceleration step with local newton
|
|
72
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, jac_g)
|
|
73
|
+
|
|
74
|
+
else:
|
|
75
|
+
#anderson acceleration step (pure)
|
|
76
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, None)
|
|
77
|
+
|
|
78
|
+
#return the fixed-point residual
|
|
79
|
+
return err
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def step(self, u, t, dt):
|
|
83
|
+
"""
|
|
84
|
+
performs the timestep update
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
#first stage is explicit
|
|
88
|
+
if self.stage == 0:
|
|
89
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
90
|
+
|
|
91
|
+
#restart anderson accelerator
|
|
92
|
+
self.acc.reset()
|
|
93
|
+
|
|
94
|
+
#wrap around stage counter
|
|
95
|
+
self.stage = (self.stage + 1) % 6
|
|
96
|
+
|
|
97
|
+
#no error estimate available
|
|
98
|
+
return True, 0.0, 1.0
|
|
99
|
+
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## EMBEDDED DIAGONALLY IMPLICIT RUNGE KUTTA METHOD
|
|
4
|
+
## (solvers/esdirk32.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 ESDIRK43(ImplicitSolver):
|
|
21
|
+
"""
|
|
22
|
+
6 stage 4-th order ESDIRK method with embedded 3-rd order method for stepsize control.
|
|
23
|
+
The first stage is explicit, followed by 5 implicit stages.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
|
|
27
|
+
super().__init__(initial_value, func, jac, tolerance_lte)
|
|
28
|
+
|
|
29
|
+
#counter for runge kutta stages
|
|
30
|
+
self.stage = 0
|
|
31
|
+
|
|
32
|
+
#flag adaptive timestep solver
|
|
33
|
+
self.is_adaptive = True
|
|
34
|
+
|
|
35
|
+
#slope coefficients for stages
|
|
36
|
+
self.Ks = {}
|
|
37
|
+
|
|
38
|
+
#intermediate evaluation times
|
|
39
|
+
self.eval_stages = [0.0, 1/2, (2-np.sqrt(2))/4, 2012122486997/3467029789466, 1.0, 1.0]
|
|
40
|
+
|
|
41
|
+
#butcher table
|
|
42
|
+
self.BT = {0:[0.0],
|
|
43
|
+
1:[1/4, 1/4],
|
|
44
|
+
2:[-1356991263433/26208533697614, -1356991263433/26208533697614, 1/4],
|
|
45
|
+
3:[-1778551891173/14697912885533, -1778551891173/14697912885533,
|
|
46
|
+
7325038566068/12797657924939, 1/4],
|
|
47
|
+
4:[-24076725932807/39344244018142, -24076725932807/39344244018142,
|
|
48
|
+
9344023789330/6876721947151, 11302510524611/18374767399840, 1/4],
|
|
49
|
+
5:[657241292721/9909463049845, 657241292721/9909463049845,
|
|
50
|
+
1290772910128/5804808736437, 1103522341516/2197678446715, -3/28, 1/4]}
|
|
51
|
+
|
|
52
|
+
#coefficients for truncation error estimate
|
|
53
|
+
self.A1 = [657241292721/9909463049845, 657241292721/9909463049845,
|
|
54
|
+
1290772910128/5804808736437, 1103522341516/2197678446715, -3/28, 1/4]
|
|
55
|
+
self.A2 = [-71925161075/3900939759889, -71925161075/3900939759889,
|
|
56
|
+
2973346383745/8160025745289, 3972464885073/7694851252693,
|
|
57
|
+
-263368882881/4213126269514, 3295468053953/15064441987965]
|
|
58
|
+
self.TR = [a1 - a2 for a1, a2 in zip(self.A1, self.A2)]
|
|
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 and success
|
|
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/4)
|
|
83
|
+
|
|
84
|
+
return success, truncation_error, timestep_rescale
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def solve(self, u, t, dt):
|
|
88
|
+
"""
|
|
89
|
+
Solves the implicit update equation via anderson acceleration.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
#first stage is explicit
|
|
93
|
+
if self.stage == 0:
|
|
94
|
+
return 0.0
|
|
95
|
+
|
|
96
|
+
#update timestep weighted slope
|
|
97
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
98
|
+
|
|
99
|
+
#update fixed-point equation
|
|
100
|
+
slope = 0.0
|
|
101
|
+
for i, b in enumerate(self.BT[self.stage]):
|
|
102
|
+
slope += self.Ks[i] * b
|
|
103
|
+
|
|
104
|
+
#use the jacobian
|
|
105
|
+
if self.jac is not None:
|
|
106
|
+
|
|
107
|
+
#compute jacobian of fixed-point equation
|
|
108
|
+
jac_g = dt * b * self.jac(self.x, u, t)
|
|
109
|
+
|
|
110
|
+
#anderson acceleration step with local newton
|
|
111
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, jac_g)
|
|
112
|
+
|
|
113
|
+
else:
|
|
114
|
+
#anderson acceleration step (pure)
|
|
115
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, None)
|
|
116
|
+
|
|
117
|
+
#return the fixed-point residual
|
|
118
|
+
return err
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def step(self, u, t, dt):
|
|
122
|
+
"""
|
|
123
|
+
performs the timestep update
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
#first stage is explicit
|
|
127
|
+
if self.stage == 0:
|
|
128
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
129
|
+
|
|
130
|
+
#restart anderson accelerator
|
|
131
|
+
self.acc.reset()
|
|
132
|
+
|
|
133
|
+
#error and step size control
|
|
134
|
+
if self.stage < 5:
|
|
135
|
+
self.stage += 1
|
|
136
|
+
return True, 0.0, 1.0
|
|
137
|
+
else:
|
|
138
|
+
self.stage = 0
|
|
139
|
+
return self.error_controller(dt)
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
########################################################################################
|
|
2
|
+
##
|
|
3
|
+
## EMBEDDED DIAGONALLY IMPLICIT RUNGE KUTTA METHOD
|
|
4
|
+
## (solvers/esdirk54.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
|
+
# SOLVERS ==============================================================================
|
|
18
|
+
|
|
19
|
+
class ESDIRK54(ImplicitSolver):
|
|
20
|
+
"""
|
|
21
|
+
7 stage 5-th order L-stable and stiffly accurate ESDIRK method with
|
|
22
|
+
embedded 4-th order method for stepsize control. This integrator is
|
|
23
|
+
suited for moderately stiff problems that require high accuracy.
|
|
24
|
+
The first stage is explicit, followed by 6 implicit stages.
|
|
25
|
+
|
|
26
|
+
FROM :
|
|
27
|
+
Diagonally implicit Runge–Kutta methods for stiff ODEs
|
|
28
|
+
Christopher A.Kennedy, Mark H.Carpenter
|
|
29
|
+
Applied Numerical Mathematics, 2019
|
|
30
|
+
ESDIRK5(4)7L[2]SA2
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
|
|
34
|
+
super().__init__(initial_value, func, jac, tolerance_lte)
|
|
35
|
+
|
|
36
|
+
#counter for runge kutta stages
|
|
37
|
+
self.stage = 0
|
|
38
|
+
|
|
39
|
+
#flag adaptive timestep solver
|
|
40
|
+
self.is_adaptive = True
|
|
41
|
+
|
|
42
|
+
#slope coefficients for stages
|
|
43
|
+
self.Ks = {}
|
|
44
|
+
|
|
45
|
+
#intermediate evaluation times
|
|
46
|
+
self.eval_stages = [0.0, 46/125, 7121331996143/11335814405378, 49/353, 3706679970760/5295570149437, 347/382, 1.0]
|
|
47
|
+
|
|
48
|
+
#butcher table
|
|
49
|
+
self.BT = {0:[0.0],
|
|
50
|
+
1:[23/125, 23/125],
|
|
51
|
+
2:[791020047304/3561426431547, 791020047304/3561426431547, 23/125],
|
|
52
|
+
3:[-158159076358/11257294102345, -158159076358/11257294102345, -85517644447/5003708988389, 23/125],
|
|
53
|
+
4:[-1653327111580/4048416487981, -1653327111580/4048416487981, 1514767744496/9099671765375, 14283835447591/12247432691556, 23/125],
|
|
54
|
+
5:[-4540011970825/8418487046959, -4540011970825/8418487046959, -1790937573418/7393406387169, 10819093665085/7266595846747, 4109463131231/7386972500302, 23/125],
|
|
55
|
+
6:[-188593204321/4778616380481, -188593204321/4778616380481, 2809310203510/10304234040467, 1021729336898/2364210264653, 870612361811/2470410392208, -1307970675534/8059683598661, 23/125]}
|
|
56
|
+
|
|
57
|
+
#coefficients for truncation error estimate
|
|
58
|
+
_A1 = [-188593204321/4778616380481, -188593204321/4778616380481, 2809310203510/10304234040467, 1021729336898/2364210264653, 870612361811/2470410392208, -1307970675534/8059683598661, 23/125]
|
|
59
|
+
_A2 = [-582099335757/7214068459310, -582099335757/7214068459310, 615023338567/3362626566945, 3192122436311/6174152374399, 6156034052041/14430468657929, -1011318518279/9693750372484, 1914490192573/13754262428401]
|
|
60
|
+
self.TR = [_a1 - _a2 for _a1, _a2 in zip(_A1, _A2)]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def error_controller(self, dt):
|
|
64
|
+
"""
|
|
65
|
+
compute scaling factor for adaptive timestep
|
|
66
|
+
based on local truncation error estimate and returns both
|
|
67
|
+
"""
|
|
68
|
+
if len(self.Ks)<len(self.TR):
|
|
69
|
+
return True, 0.0, 1.0
|
|
70
|
+
|
|
71
|
+
#compute local truncation error slope
|
|
72
|
+
slope = 0.0
|
|
73
|
+
for i, b in enumerate(self.TR):
|
|
74
|
+
slope += self.Ks[i] * b
|
|
75
|
+
|
|
76
|
+
#compute and clip truncation error
|
|
77
|
+
truncation_error = np.max(np.clip(abs(dt*slope), 1e-18, None))
|
|
78
|
+
|
|
79
|
+
#compute error ratio and success
|
|
80
|
+
error_ratio = self.tolerance_lte / truncation_error
|
|
81
|
+
success = error_ratio >= 1.0
|
|
82
|
+
|
|
83
|
+
#compute timestep scale
|
|
84
|
+
timestep_rescale = 0.9 * (error_ratio)**(1/5)
|
|
85
|
+
|
|
86
|
+
return success, truncation_error, timestep_rescale
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def solve(self, u, t, dt):
|
|
90
|
+
"""
|
|
91
|
+
Solves the implicit update equation via anderson acceleration.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
#first stage is explicit
|
|
95
|
+
if self.stage == 0:
|
|
96
|
+
return 0.0
|
|
97
|
+
|
|
98
|
+
#update timestep weighted slope
|
|
99
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
100
|
+
|
|
101
|
+
#update fixed-point equation
|
|
102
|
+
slope = 0.0
|
|
103
|
+
for i, b in enumerate(self.BT[self.stage]):
|
|
104
|
+
slope += self.Ks[i] * b
|
|
105
|
+
|
|
106
|
+
#use the jacobian
|
|
107
|
+
if self.jac is not None:
|
|
108
|
+
|
|
109
|
+
#compute jacobian of fixed-point equation
|
|
110
|
+
jac_g = dt * b * self.jac(self.x, u, t)
|
|
111
|
+
|
|
112
|
+
#anderson acceleration step with local newton
|
|
113
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, jac_g)
|
|
114
|
+
|
|
115
|
+
else:
|
|
116
|
+
#anderson acceleration step (pure)
|
|
117
|
+
self.x, err = self.acc.step(self.x, dt*slope + self.x_0, None)
|
|
118
|
+
|
|
119
|
+
#return the fixed-point residual
|
|
120
|
+
return err
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def step(self, u, t, dt):
|
|
124
|
+
"""
|
|
125
|
+
performs the timestep update
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
#first stage is explicit
|
|
129
|
+
if self.stage == 0:
|
|
130
|
+
self.Ks[self.stage] = self.func(self.x, u, t)
|
|
131
|
+
|
|
132
|
+
#restart anderson accelerator
|
|
133
|
+
self.acc.reset()
|
|
134
|
+
|
|
135
|
+
#error and step size control
|
|
136
|
+
if self.stage < 6:
|
|
137
|
+
self.stage += 1
|
|
138
|
+
return True, 0.0, 1.0
|
|
139
|
+
else:
|
|
140
|
+
self.stage = 0
|
|
141
|
+
return self.error_controller(dt)
|