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