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,403 @@
1
+ ########################################################################################
2
+ ##
3
+ ## BASE NUMERICAL INTEGRATOR CLASS
4
+ ## (solvers/_solver.py)
5
+ ##
6
+ ## (c) Milan Rother 2023/24
7
+ ##
8
+ ########################################################################################
9
+
10
+ # IMPORTS ==============================================================================
11
+
12
+ import numpy as np
13
+
14
+ from ..utils.anderson import (
15
+ AndersonAcceleration,
16
+ NewtonAndersonAcceleration
17
+ )
18
+
19
+ # BASE SOLVER CLASS ====================================================================
20
+
21
+ class Solver:
22
+ """
23
+ Base skeleton class for solver definition. Defines the basic solver methods and the metadata.
24
+
25
+ Specific solvers need to implement (some of) the base class methods defined here.
26
+ This depends on the type of solver (implicit/explicit, multistage, adaptive).
27
+
28
+ INPUTS :
29
+ initial_value : (float or array) initial condition / integration constant
30
+ func : (callable) function to integrate with state 'x', input 'u' and time 't' dependency
31
+ jac : (callable or None) jacobian of 'func' with respect to 'x', depending on 'x', 'u' and 't', if 'None', no jacobian is used
32
+ tolerance_lte : (float) absolute tolerance for local truncation error (for solvers with error estimate)
33
+ """
34
+
35
+ def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
36
+
37
+ #set buffer, initial state and initial condition
38
+ self.x_0 = self.x = self.initial_value = initial_value
39
+
40
+ #right hand side function for integration
41
+ self.func = func
42
+
43
+ #jacobian of right hand side function
44
+ self.jac = jac
45
+
46
+ #tolerance for local truncation error (for adaptive solvers)
47
+ self.tolerance_lte = tolerance_lte
48
+
49
+ #flag to identify adaptive/fixed timestep solvers
50
+ self.is_adaptive = False
51
+
52
+ #current evaluation stage for multistage solvers
53
+ self.stage = 0
54
+
55
+ #intermediate evaluation times for multistage solvers as ratios between [t, t+dt]
56
+ self.eval_stages = [0.0]
57
+
58
+
59
+ def __str__(self):
60
+ return self.__class__.__name__
61
+
62
+
63
+ def stages(self, t, dt):
64
+ """
65
+ Generator that yields the intermediate evaluation
66
+ time during the timestep 't + ratio * dt'.
67
+ """
68
+ for ratio in self.eval_stages:
69
+ yield t + ratio * dt
70
+
71
+
72
+ def get(self):
73
+ """
74
+ Returns current internal state of the solver.
75
+ """
76
+ return self.x
77
+
78
+
79
+ def set(self, x):
80
+ """
81
+ Sets the internal state of the integration engine.
82
+ This method is required for event based simulations,
83
+ and to handle discontinuities in state variables.
84
+ """
85
+
86
+ #overwrite internal state with value
87
+ self.x = self.x_0 = x
88
+
89
+ #reset stage counter
90
+ self.stage = 0
91
+
92
+
93
+ def reset(self):
94
+ """"
95
+ Resets integration engine to initial state.
96
+ """
97
+
98
+ #overwrite state with initial value
99
+ self.x = self.x_0 = self.initial_value
100
+
101
+ #reset stage counter
102
+ self.stage = 0
103
+
104
+
105
+ def buffer(self):
106
+ """
107
+ Saves the current state to an internal state buffer, which is
108
+ especially relevant for multistage and implicit solvers.
109
+ """
110
+ self.x_0 = self.x
111
+
112
+
113
+ def change(self, Solver, tolerance_lte=None):
114
+ """
115
+ Change the integration engine to a new type and initialize
116
+ with previous solver arguments so it can continue from where
117
+ the 'old' solver stopped.
118
+ """
119
+
120
+ #check if new tolerance is defined
121
+ tol = self.tolerance_lte if tolerance_lte is None else tolerance_lte
122
+
123
+ #create new engine from self
124
+ engine = Solver(self.initial_value, self.func, self.jac, tol)
125
+
126
+ #set internal state of new engine from self
127
+ engine.set(self.get())
128
+
129
+ return engine
130
+
131
+
132
+ # methods for adaptive timestep solvers --------------------------------------------
133
+
134
+ def error_controller(self):
135
+ """
136
+ Returns the estimated local truncation error and scaling factor
137
+ for the timestep, only relevant for adaptive timestepping methods.
138
+ """
139
+ return True, 0.0, 1.0
140
+
141
+
142
+ def revert(self):
143
+ """
144
+ Revert integration engine to previous timestep, this is only relevant
145
+ for adaptive methods where the simulation timestep 'dt' is rescaled and
146
+ the engine step is recomputed with the smaller timestep.
147
+ """
148
+
149
+ #reset internal state to previous state
150
+ self.x = self.x_0
151
+
152
+ #reset stage counter
153
+ self.stage = 0
154
+
155
+
156
+ # methods for timestepping ---------------------------------------------------------
157
+
158
+ def step(self, u, t, dt):
159
+ """
160
+ performs the explicit timestep for (t+dt) based
161
+ on the state and input at (t)
162
+
163
+ returns the local truncation error estimate and the
164
+ rescale factor for the timestep if the solver is adaptive.
165
+ """
166
+ return True, 0.0, 1.0
167
+
168
+
169
+ # EXTENDED BASE SOLVER CLASSES =========================================================
170
+
171
+ class ExplicitSolver(Solver):
172
+ """
173
+ Base class for explicit solver definition.
174
+
175
+ INPUTS :
176
+ initial_value : (float or array) initial condition / integration constant
177
+ func : (callable) function to integrate with state (x), input (u) and time (t) dependency
178
+ jac : (callable or None) jacobian of 'func' with respect to 'x', depending on 'x', 'u' and 't', if 'None', no jacobian is used
179
+ tolerance_lte : (float) absolute tolerance for local truncation error (for solvers with error estimate)
180
+ """
181
+
182
+ def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
183
+ super().__init__(initial_value, func, jac, tolerance_lte)
184
+
185
+ #flag to identify implicit/explicit solvers
186
+ self.is_explicit = True
187
+ self.is_implicit = False
188
+
189
+ #intermediate evaluation times for multistage solvers as ratios between [t, t+dt]
190
+ self.eval_stages = [0.0]
191
+
192
+
193
+ # method for direct integration ----------------------------------------------------
194
+
195
+ def integrate_singlestep(self, time=0.0, dt=0.1):
196
+ """
197
+ Directly integrate the function 'func' for a single timestep 'dt' with
198
+ explicit solvers. This method is primarily intended for testing purposes.
199
+
200
+ INPUTS :
201
+ time_start : (float) starting time for timestep
202
+ dt : (float) timestep
203
+ """
204
+
205
+ #buffer current state
206
+ self.buffer()
207
+
208
+ #iterate solver stages (explicit updates)
209
+ for t in self.stages(time, dt):
210
+ success, error, scale = self.step(0.0, t, dt)
211
+
212
+ return success, error, scale
213
+
214
+
215
+ def integrate(self,
216
+ time_start=0.0,
217
+ time_end=1.0,
218
+ dt=0.1,
219
+ dt_min=0.0,
220
+ dt_max=None,
221
+ adaptive=True):
222
+ """
223
+ Directly integrate the function 'func' from 'time_start' to 'time_end' with
224
+ timestep 'dt' for explicit solvers. This method is primarily intended for
225
+ testing purposes.
226
+
227
+ INPUTS :
228
+ time_start : (float) starting time for integration
229
+ time_end : (float) end time for integration
230
+ dt : (float) timestep or initial timestep for adaptive solvers
231
+ dt_min : (float) lower bound for timestep, default '0.0'
232
+ dt_max : (float) upper bound for timestep, default 'None'
233
+ adaptive : (bool) usa adaptive timestepping if available
234
+ """
235
+
236
+ #output lists with initial state
237
+ output_states = [self.x]
238
+ output_times = [time_start]
239
+
240
+ #integration starting time
241
+ time = time_start
242
+
243
+ #step until duration is reached
244
+ while time < time_end + dt:
245
+
246
+ #perform single timestep
247
+ success, error, scale = self.integrate_singlestep(time, dt)
248
+
249
+ #check if timestep was successful
250
+ if adaptive and not success:
251
+ self.revert()
252
+ else:
253
+ time += dt
254
+ output_states.append(self.x)
255
+ output_times.append(time)
256
+
257
+ #rescale and apply bounds to timestep
258
+ if adaptive:
259
+ if scale*dt < dt_min:
260
+ raise RuntimeError("Error control requires timestep smaller 'dt_min'!")
261
+ dt = np.clip(scale*dt, dt_min, dt_max)
262
+
263
+ #return the evaluation times and the states
264
+ return np.array(output_times), np.array(output_states)
265
+
266
+
267
+ class ImplicitSolver(Solver):
268
+ """
269
+ Base class for implicit solver definition.
270
+
271
+ INPUTS :
272
+ INPUTS :
273
+ initial_value : (float or array) initial condition / integration constant
274
+ func : (callable) function to integrate with state 'x', input 'u' and time 't' dependency
275
+ jac : (callable or None) jacobian of 'func' with respect to 'x', depending on 'x', 'u' and 't', if 'None', no jacobian is used
276
+ tolerance_lte : (float) absolute tolerance for local truncation error (for solvers with error estimate)
277
+ """
278
+
279
+ def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
280
+ super().__init__(initial_value, func, jac, tolerance_lte)
281
+
282
+ #flag to identify implicit/explicit solvers
283
+ self.is_explicit = False
284
+ self.is_implicit = True
285
+
286
+ #intermediate evaluation times for multistage solvers as ratios between [t, t+dt]
287
+ self.eval_stages = [1.0]
288
+
289
+ #initialize anderson accelerator for solving the implicit update equation
290
+ self.acc = NewtonAndersonAcceleration(m=5, restart=False)
291
+
292
+
293
+ # methods for timestepping ---------------------------------------------------------
294
+
295
+ def solve(self, u, t, dt):
296
+ """
297
+ Advances the solution of the implicit update equation of the solver
298
+ with Anderson Acceleration and tracks the evolution of the solution
299
+ by providing the residual norm of the fixed-point solution.
300
+ """
301
+ return 0.0
302
+
303
+
304
+ # method for direct integration ----------------------------------------------------
305
+
306
+ def integrate_singlestep(self,
307
+ time=0.0,
308
+ dt=0.1,
309
+ tolerance_fpi=1e-12,
310
+ max_iterations=5000):
311
+ """
312
+ Directly integrate the function 'func' for a single timestep 'dt' with
313
+ implicit solvers. This method is primarily intended for testing purposes.
314
+
315
+ INPUTS :
316
+ time_start : (float) starting time for timestep
317
+ dt : (float) timestep
318
+ tolerance_fpi : (float) tolerance for fixed-point solver
319
+ max_iterations : (int) maximum number of fixed-point solver iterations
320
+ """
321
+
322
+ #buffer current state
323
+ self.buffer()
324
+
325
+ #flag for solver success
326
+ success_sol = True
327
+
328
+ #iterate solver stages (implicit updates)
329
+ for t in self.stages(time, dt):
330
+
331
+ #iteratively solve implicit update equation
332
+ for _ in range(max_iterations):
333
+ error_sol = self.solve(0.0, t, dt)
334
+ if error_sol < tolerance_fpi:
335
+ break
336
+
337
+ #catch convergence error
338
+ if error_sol > tolerance_fpi:
339
+ if success_sol: success_sol = False
340
+
341
+ #perform explicit component of timestep
342
+ success, error, scale = self.step(0.0, t, dt)
343
+
344
+ #step successful in total
345
+ success_total = success and success_sol
346
+
347
+ return success_total, error, scale
348
+
349
+
350
+ def integrate(self,
351
+ time_start=0.0,
352
+ time_end=1.0,
353
+ dt=0.1,
354
+ dt_min=0.0,
355
+ dt_max=None,
356
+ adaptive=True,
357
+ tolerance_fpi=1e-12,
358
+ max_iterations=5000):
359
+ """
360
+ Directly integrate the function 'func' from 'time_start' to 'time_end' with
361
+ timestep 'dt' for implicit solvers. This method is primarily intended for
362
+ testing purposes.
363
+
364
+ INPUTS :
365
+ time_start : (float) starting time for integration
366
+ time_end : (float) end time for integration
367
+ dt : (float) timestep or initial timestep for adaptive solvers
368
+ dt_min : (float) lower bound for timestep, default '0.0'
369
+ dt_max : (float) upper bound for timestep, default 'None'
370
+ adaptive : (bool) use adaptive timestepping if available
371
+ tolerance_fpi : (float) tolerance for fixed-point solver
372
+ max_iterations : (int) maximum number of fixed-point solver iterations
373
+ """
374
+
375
+ #output lists with initial state
376
+ output_states = [self.x]
377
+ output_times = [time_start]
378
+
379
+ #integration starting time
380
+ time = time_start
381
+
382
+ #step until duration is reached
383
+ while time < time_end + dt:
384
+
385
+ #integrate for single timestep
386
+ success, error, scale = self.integrate_singlestep(time, dt, tolerance_fpi, max_iterations)
387
+
388
+ #check if timestep was successful and adaptive
389
+ if adaptive and not success:
390
+ self.revert()
391
+ else:
392
+ time += dt
393
+ output_states.append(self.x)
394
+ output_times.append(time)
395
+
396
+ #rescale and apply bounds to timestep
397
+ if adaptive:
398
+ if scale*dt < dt_min:
399
+ raise RuntimeError("Error control requires timestep smaller 'dt_min'!")
400
+ dt = np.clip(scale*dt, dt_min, dt_max)
401
+
402
+ #return the evaluation times and the states
403
+ return np.array(output_times), np.array(output_states)
pathsim/solvers/bdf.py ADDED
@@ -0,0 +1,240 @@
1
+ ########################################################################################
2
+ ##
3
+ ## BACKWARD DIFFERENTIATION FORMULAS
4
+ ## (solvers/bdf.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 BDF2(ImplicitSolver):
19
+ """
20
+ 2nd order backward differentiation formula
21
+ with order ramp up for the initial steps.
22
+ """
23
+
24
+ def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
25
+ super().__init__(initial_value, func, jac, tolerance_lte)
26
+
27
+ #bdf coefficients
28
+ self.K = {1:[1.0], 2:[-1/3, 4/3]}
29
+ self.F = {1:1.0, 2:2/3}
30
+
31
+ #bdf solution buffer
32
+ self.B = [self.initial_value]
33
+
34
+
35
+ def reset(self):
36
+ """"
37
+ Resets integration engine to initial state.
38
+ """
39
+
40
+ #reset buffer state with initial value
41
+ self.B = [self.initial_value]
42
+
43
+ #overwrite state with initial value
44
+ self.x = self.x_0 = self.initial_value
45
+
46
+
47
+ def solve(self, u, t, dt):
48
+ """
49
+ Solves the implicit update equation via anderson acceleration.
50
+ """
51
+
52
+ #buffer length for BDF order selection
53
+ n = len(self.B)
54
+
55
+ #fixed-point function update
56
+ g = self.F[n] * dt * self.func(self.x, u, t) + sum(b*k for b, k in zip(self.B, self.K[n]))
57
+
58
+ #use the jacobian
59
+ if self.jac is not None:
60
+
61
+ #compute jacobian
62
+ jac_g = self.F[n] * 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
+ Performs the timestep by buffereing the previous state.
78
+ """
79
+
80
+ #reset anderson accelerator
81
+ self.acc.reset()
82
+
83
+ #add to buffer
84
+ self.B.append(self.x)
85
+ if len(self.B) > 2:
86
+ self.B.pop(0)
87
+
88
+ return True, 0.0, 1.0
89
+
90
+
91
+ class BDF3(ImplicitSolver):
92
+ """
93
+ 3rd order backward differentiation formula
94
+ with order ramp up for the initial steps.
95
+ """
96
+
97
+ def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
98
+ super().__init__(initial_value, func, jac, tolerance_lte)
99
+
100
+ #bdf coefficients
101
+ self.K = {1:[1.0],
102
+ 2:[-1/3, 4/3],
103
+ 3:[2/11, -9/11, 18/11]}
104
+ self.F = {1:1.0, 2:2/3, 3:6/11}
105
+
106
+ #bdf solution buffer
107
+ self.B = [self.initial_value]
108
+
109
+
110
+ def reset(self):
111
+ """"
112
+ Resets integration engine to initial state.
113
+ """
114
+
115
+ #reset buffer state with initial value
116
+ self.B = [self.initial_value]
117
+
118
+ #overwrite state with initial value
119
+ self.x = self.x_0 = self.initial_value
120
+
121
+
122
+ def solve(self, u, t, dt):
123
+ """
124
+ Solves the implicit update equation via anderson acceleration.
125
+ """
126
+
127
+ #buffer length for BDF order selection
128
+ n = len(self.B)
129
+
130
+ #fixed-point function update
131
+ g = self.F[n] * dt * self.func(self.x, u, t) + sum(b*k for b, k in zip(self.B, self.K[n]))
132
+
133
+ #use the jacobian
134
+ if self.jac is not None:
135
+
136
+ #compute jacobian
137
+ jac_g = self.F[n] * dt * self.jac(self.x, u, t)
138
+
139
+ #anderson acceleration step with local newton
140
+ self.x, err = self.acc.step(self.x, g, jac_g)
141
+
142
+ else:
143
+ #anderson acceleration step (pure)
144
+ self.x, err = self.acc.step(self.x, g, None)
145
+
146
+ #return the fixed-point residual
147
+ return err
148
+
149
+
150
+
151
+ def step(self, u, t, dt):
152
+ """
153
+ Performs the timestep by buffereing the previous state.
154
+ """
155
+
156
+ #reset anderson accelerator
157
+ self.acc.reset()
158
+
159
+ #add to buffer
160
+ self.B.append(self.x)
161
+ if len(self.B) > 3:
162
+ self.B.pop(0)
163
+
164
+ return True, 0.0, 1.0
165
+
166
+
167
+ class BDF4(ImplicitSolver):
168
+ """
169
+ 4th order backward differentiation formula
170
+ with order ramp up for the initial steps.
171
+ """
172
+
173
+ def __init__(self, initial_value=0, func=lambda x, u, t: u, jac=None, tolerance_lte=1e-6):
174
+ super().__init__(initial_value, func, jac, tolerance_lte)
175
+
176
+ #bdf coefficients
177
+ self.K = {1:[1.0],
178
+ 2:[-1/3, 4/3],
179
+ 3:[2/11, -9/11, 18/11],
180
+ 4:[-3/25, 16/25, -36/25, 48/25]}
181
+ self.F = {1:1.0, 2:2/3, 3:6/11, 4:12/25}
182
+
183
+ #bdf solution buffer
184
+ self.B = [self.initial_value]
185
+
186
+
187
+ def reset(self):
188
+ """"
189
+ Resets integration engine to initial state.
190
+ """
191
+
192
+ #reset buffer state with initial value
193
+ self.B = [self.initial_value]
194
+
195
+ #overwrite state with initial value
196
+ self.x = self.x_0 = self.initial_value
197
+
198
+
199
+ def solve(self, u, t, dt):
200
+ """
201
+ Solves the implicit update equation via anderson acceleration.
202
+ """
203
+
204
+ #buffer length for BDF order selection
205
+ n = len(self.B)
206
+
207
+ #fixed-point function update
208
+ g = self.F[n] * dt * self.func(self.x, u, t) + sum(b*k for b, k in zip(self.B, self.K[n]))
209
+
210
+ #use the jacobian
211
+ if self.jac is not None:
212
+
213
+ #compute jacobian
214
+ jac_g = self.F[n] * dt * self.jac(self.x, u, t)
215
+
216
+ #anderson acceleration step with local newton
217
+ self.x, err = self.acc.step(self.x, g, jac_g)
218
+
219
+ else:
220
+ #anderson acceleration step (pure)
221
+ self.x, err = self.acc.step(self.x, g, None)
222
+
223
+ #return the fixed-point residual
224
+ return err
225
+
226
+
227
+ def step(self, u, t, dt):
228
+ """
229
+ Performs the timestep by buffereing the previous state.
230
+ """
231
+
232
+ #reset anderson accelerator
233
+ self.acc.reset()
234
+
235
+ #add to buffer
236
+ self.B.append(self.x)
237
+ if len(self.B) > 4:
238
+ self.B.pop(0)
239
+
240
+ return True, 0.0, 1.0