Coverage for pySDC/projects/Resilience/AC.py: 19%
162 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 14:51 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 14:51 +0000
1# script to run an Allen-Cahn problem
2from pySDC.implementations.problem_classes.AllenCahn_2D_FFT import allencahn2d_imex
3from pySDC.implementations.controller_classes.controller_nonMPI import controller_nonMPI
4from pySDC.core.hooks import Hooks
5from pySDC.projects.Resilience.hook import hook_collection, LogData
6from pySDC.projects.Resilience.strategies import merge_descriptions
7import matplotlib.pyplot as plt
8import numpy as np
10from pySDC.core.errors import ConvergenceError
13class allencahn_imex_timeforcing_adaptivity(allencahn2d_imex):
14 r"""
15 Add more source terms to `allencahn_imex_timeforcing` such that the time-scale changes and we can benefit from adaptivity.
16 """
18 def __init__(self, time_freq=2.0, time_dep_strength=1e-2, *args, **kwargs):
19 super().__init__(*args, **kwargs)
20 self._makeAttributeAndRegister('time_freq', 'time_dep_strength', localVars=locals(), readOnly=True)
22 def eval_f(self, u, t):
23 f = super().eval_f(u, t)
24 time_mod = self.get_time_dep_fac(self.time_freq, self.time_dep_strength, t)
26 if self.eps > 0:
27 f.expl = -2.0 / self.eps**2 * u * (1.0 - u) * (1.0 - 2.0 * u)
29 # build sum over RHS without driving force
30 Rt = float(np.sum(f.impl + f.expl))
32 # build sum over driving force term
33 Ht = float(np.sum(6.0 * u * (1.0 - u)))
35 # add/subtract time-dependent driving force
36 if Ht != 0.0:
37 dw = Rt / Ht * time_mod
38 else:
39 dw = 0.0
41 f.expl -= 6.0 * dw * u * (1.0 - u)
43 return f
45 @staticmethod
46 def get_time_dep_fac(time_freq, time_dep_strength, t):
47 return 1 - time_dep_strength * np.sin(time_freq * 2 * np.pi / 0.032 * t)
50class monitor(Hooks):
51 phase_thresh = 0.0 # count everything above this threshold to the high phase.
53 def __init__(self):
54 """
55 Initialization of Allen-Cahn monitoring
56 """
57 super().__init__()
59 self.init_radius = None
61 def get_exact_radius(self, t):
62 return np.sqrt(max(self.init_radius**2 - 2.0 * t, 0))
64 @classmethod
65 def get_radius(cls, u, dx):
66 c = np.count_nonzero(u > cls.phase_thresh)
67 return np.sqrt(c / np.pi) * dx
69 @staticmethod
70 def get_interface_width(u, L):
71 # TODO: How does this generalize to different phase transitions?
72 rows1 = np.where(u[L.prob.init[0][0] // 2, : L.prob.init[0][0] // 2] > -0.99)
73 rows2 = np.where(u[L.prob.init[0][0] // 2, : L.prob.init[0][0] // 2] < 0.99)
75 return (rows2[0][-1] - rows1[0][0]) * L.prob.dx / L.prob.eps
77 def pre_run(self, step, level_number):
78 """
79 Record radius of the blob, exact radius and interface width.
81 Args:
82 step (pySDC.Step.step): the current step
83 level_number (int): the current level number
84 """
85 super().pre_run(step, level_number)
86 L = step.levels[0]
88 radius = self.get_radius(L.u[0], L.prob.dx)
89 self.init_radius = L.prob.radius
91 if L.time == 0.0:
92 self.add_to_stats(
93 process=step.status.slot,
94 time=L.time,
95 level=-1,
96 iter=step.status.iter,
97 sweep=L.status.sweep,
98 type='computed_radius',
99 value=radius,
100 )
101 self.add_to_stats(
102 process=step.status.slot,
103 time=L.time,
104 level=-1,
105 iter=step.status.iter,
106 sweep=L.status.sweep,
107 type='exact_radius',
108 value=self.init_radius,
109 )
111 def post_step(self, step, level_number):
112 """
113 Record radius of the blob, exact radius and interface width.
115 Args:
116 step (pySDC.Step.step): the current step
117 level_number (int): the current level number
118 """
119 super().post_step(step, level_number)
121 # some abbreviations
122 L = step.levels[0]
124 radius = self.get_radius(L.uend, L.prob.dx)
126 exact_radius = self.get_exact_radius(L.time + L.dt)
128 self.add_to_stats(
129 process=step.status.slot,
130 time=L.time + L.dt,
131 level=-1,
132 iter=step.status.iter,
133 sweep=L.status.sweep,
134 type='computed_radius',
135 value=radius,
136 )
137 self.add_to_stats(
138 process=step.status.slot,
139 time=L.time + L.dt,
140 level=-1,
141 iter=step.status.iter,
142 sweep=L.status.sweep,
143 type='exact_radius',
144 value=exact_radius,
145 )
148def run_AC(
149 custom_description=None,
150 num_procs=1,
151 Tend=1e-2,
152 hook_class=LogData,
153 fault_stuff=None,
154 custom_controller_params=None,
155 imex=False,
156 u0=None,
157 t0=None,
158 use_MPI=False,
159 live_plot=False,
160 FFT=True,
161 time_forcing=True,
162 **kwargs,
163):
164 """
165 Args:
166 custom_description (dict): Overwrite presets
167 num_procs (int): Number of steps for MSSDC
168 Tend (float): Time to integrate to
169 hook_class (pySDC.Hook): A hook to store data
170 fault_stuff (dict): A dictionary with information on how to add faults
171 custom_controller_params (dict): Overwrite presets
172 imex (bool): Solve the problem IMEX or fully implicit
173 u0 (dtype_u): Initial value
174 t0 (float): Starting time
175 use_MPI (bool): Whether or not to use MPI
177 Returns:
178 dict: The stats object
179 controller: The controller
180 bool: If the code crashed
181 """
182 if custom_description is not None:
183 problem_params = custom_description.get('problem_params', {})
184 if 'imex' in problem_params.keys():
185 imex = problem_params['imex']
186 problem_params.pop('imex', None)
187 if 'FFT' in problem_params.keys():
188 FFT = problem_params['FFT']
189 problem_params.pop('FFT', None)
191 # import problem and sweeper class
192 if time_forcing:
193 problem_class = allencahn_imex_timeforcing_adaptivity
194 from pySDC.projects.Resilience.sweepers import imex_1st_order_efficient as sweeper_class
195 elif FFT:
196 from pySDC.implementations.problem_classes.AllenCahn_2D_FFT import allencahn2d_imex as problem_class
197 from pySDC.projects.Resilience.sweepers import imex_1st_order_efficient as sweeper_class
198 elif imex:
199 from pySDC.implementations.problem_classes.AllenCahn_2D_FD import allencahn_semiimplicit as problem_class
200 from pySDC.projects.Resilience.sweepers import imex_1st_order_efficient as sweeper_class
201 else:
202 from pySDC.implementations.problem_classes.AllenCahn_2D_FD import allencahn_fullyimplicit as problem_class
203 from pySDC.projects.Resilience.sweepers import generic_implicit_efficient as sweeper_class
205 level_params = {}
206 level_params['dt'] = 1e-4
207 level_params['restol'] = 1e-8
209 sweeper_params = {}
210 sweeper_params['quad_type'] = 'RADAU-RIGHT'
211 sweeper_params['num_nodes'] = 3
212 sweeper_params['QI'] = 'LU'
213 sweeper_params['QE'] = 'PIC'
215 # problem params
216 fd_params = {
217 'newton_tol': 1e-9,
218 'order': 2,
219 }
220 problem_params = {
221 'nvars': (128, 128),
222 'init_type': 'circle',
223 }
224 if not FFT:
225 problem_params = {**problem_params, **fd_params}
227 step_params = {}
228 step_params['maxiter'] = 5
230 controller_params = {}
231 controller_params['logger_level'] = 30
232 controller_params['hook_class'] = (
233 hook_collection + (hook_class if type(hook_class) == list else [hook_class]) + ([LivePlot] if live_plot else [])
234 )
235 controller_params['mssdc_jac'] = False
237 if custom_controller_params is not None:
238 controller_params = {**controller_params, **custom_controller_params}
240 description = {}
241 description['problem_class'] = problem_class
242 description['problem_params'] = problem_params
243 description['sweeper_class'] = sweeper_class
244 description['sweeper_params'] = sweeper_params
245 description['level_params'] = level_params
246 description['step_params'] = step_params
248 if custom_description is not None:
249 description = merge_descriptions(description, custom_description)
251 t0 = 0.0 if t0 is None else t0
253 controller_args = {
254 'controller_params': controller_params,
255 'description': description,
256 }
257 if use_MPI:
258 from mpi4py import MPI
259 from pySDC.implementations.controller_classes.controller_MPI import controller_MPI
261 comm = kwargs.get('comm', MPI.COMM_WORLD)
262 controller = controller_MPI(**controller_args, comm=comm)
263 P = controller.S.levels[0].prob
264 else:
265 controller = controller_nonMPI(**controller_args, num_procs=num_procs)
266 P = controller.MS[0].levels[0].prob
268 uinit = P.u_exact(t0) if u0 is None else u0
270 if fault_stuff is not None:
271 from pySDC.projects.Resilience.fault_injection import prepare_controller_for_faults
273 prepare_controller_for_faults(controller, fault_stuff)
275 crash = False
276 try:
277 uend, stats = controller.run(u0=uinit, t0=t0, Tend=Tend)
278 except ConvergenceError as e:
279 print(f'Warning: Premature termination!: {e}')
280 stats = controller.return_stats()
281 crash = True
282 return stats, controller, crash
285def plot_solution(stats): # pragma: no cover
286 import matplotlib.pyplot as plt
287 from pySDC.helpers.stats_helper import get_sorted
289 fig, ax = plt.subplots(1, 1)
291 u = get_sorted(stats, type='u', recomputed=False)
292 for me in u: # pun intended
293 ax.imshow(me[1], vmin=-1, vmax=1)
294 ax.set_title(f't={me[0]:.2e}')
295 plt.pause(1e-1)
297 plt.show()
300class LivePlot(Hooks): # pragma: no cover
301 def __init__(self):
302 super().__init__()
303 self.fig, self.axs = plt.subplots(1, 3, figsize=(12, 4))
304 self.radius = []
305 self.exact_radius = []
306 self.t = []
307 self.dt = []
309 def post_step(self, step, level_number):
310 super().post_step(step, level_number)
311 L = step.levels[level_number]
312 self.t += [step.time + step.dt]
314 # plot solution
315 self.axs[0].cla()
316 if len(L.uend.shape) > 1:
317 self.axs[0].imshow(L.uend, vmin=0.0, vmax=1.0)
319 # plot radius
320 self.axs[1].cla()
321 radius, _ = LogRadius.compute_radius(step.levels[level_number])
322 exact_radius = LogRadius.exact_radius(step.levels[level_number])
324 self.radius += [radius]
325 self.exact_radius += [exact_radius]
326 self.axs[1].plot(self.t, self.exact_radius, label='exact')
327 self.axs[1].plot(self.t, self.radius, label='numerical')
328 self.axs[1].set_ylim([0, 0.26])
329 self.axs[1].set_xlim([0, 0.03])
330 self.axs[1].legend(frameon=False)
331 self.axs[1].set_title(r'Radius')
332 else:
333 self.axs[0].plot(L.prob.xvalues, L.prob.u_exact(t=L.time + L.dt), label='exact')
334 self.axs[0].plot(L.prob.xvalues, L.uend, label='numerical')
335 self.axs[0].set_title(f't = {step.time + step.dt:.2e}')
337 # plot step size
338 self.axs[2].cla()
339 self.dt += [step.dt]
340 self.axs[2].plot(self.t, self.dt)
341 self.axs[2].set_yscale('log')
342 self.axs[2].axhline(step.levels[level_number].prob.eps ** 2, label=r'$\epsilon^2$', color='black', ls='--')
343 self.axs[2].legend(frameon=False)
344 self.axs[2].set_xlim([0, 0.03])
345 self.axs[2].set_title(r'$\Delta t$')
347 if step.status.restart:
348 for me in [self.radius, self.exact_radius, self.t, self.dt]:
349 try:
350 me.pop(-1)
351 except (TypeError, IndexError):
352 pass
354 plt.pause(1e-9)
357class LogRadius(Hooks):
358 @staticmethod
359 def compute_radius(L):
360 c = np.count_nonzero(L.u[0] > 0.0)
361 radius = np.sqrt(c / np.pi) * L.prob.dx
363 rows, cols = np.where(L.u[0] > 0.0)
365 rows1 = np.where(L.u[0][int((L.prob.init[0][0]) / 2), : int((L.prob.init[0][0]) / 2)] > -0.99)
366 rows2 = np.where(L.u[0][int((L.prob.init[0][0]) / 2), : int((L.prob.init[0][0]) / 2)] < 0.99)
367 interface_width = (rows2[0][-1] - rows1[0][0]) * L.prob.dx / L.prob.eps
369 return radius, interface_width
371 @staticmethod
372 def exact_radius(L):
373 init_radius = L.prob.radius
374 return np.sqrt(max(init_radius**2 - 2.0 * (L.time + L.dt), 0))
376 def pre_run(self, step, level_number):
377 """
378 Overwrite standard pre run hook
380 Args:
381 step (pySDC.Step.step): the current step
382 level_number (int): the current level number
383 """
384 super().pre_run(step, level_number)
385 L = step.levels[0]
387 radius, interface_width = self.compute_radius(L)
388 exact_radius = self.exact_radius(L)
390 if L.time == 0.0:
391 self.add_to_stats(
392 process=step.status.slot,
393 time=L.time,
394 level=-1,
395 iter=step.status.iter,
396 sweep=L.status.sweep,
397 type='computed_radius',
398 value=radius,
399 )
400 self.add_to_stats(
401 process=step.status.slot,
402 time=L.time,
403 level=-1,
404 iter=step.status.iter,
405 sweep=L.status.sweep,
406 type='exact_radius',
407 value=exact_radius,
408 )
409 self.add_to_stats(
410 process=step.status.slot,
411 time=L.time,
412 level=-1,
413 iter=step.status.iter,
414 sweep=L.status.sweep,
415 type='interface_width',
416 value=interface_width,
417 )
419 def post_run(self, step, level_number):
420 """
421 Args:
422 step (pySDC.Step.step): the current step
423 level_number (int): the current level number
424 """
425 super().post_run(step, level_number)
427 L = step.levels[0]
429 exact_radius = self.exact_radius(L)
430 radius, interface_width = self.compute_radius(L)
432 self.add_to_stats(
433 process=step.status.slot,
434 time=L.time + L.dt,
435 level=-1,
436 iter=step.status.iter,
437 sweep=L.status.sweep,
438 type='computed_radius',
439 value=radius,
440 )
441 self.add_to_stats(
442 process=step.status.slot,
443 time=L.time + L.dt,
444 level=-1,
445 iter=step.status.iter,
446 sweep=L.status.sweep,
447 type='exact_radius',
448 value=exact_radius,
449 )
450 self.add_to_stats(
451 process=step.status.slot,
452 time=L.time + L.dt,
453 level=-1,
454 iter=step.status.iter,
455 sweep=L.status.sweep,
456 type='interface_width',
457 value=interface_width,
458 )
459 self.add_to_stats(
460 process=step.status.slot,
461 time=L.time + L.dt,
462 level=level_number,
463 iter=step.status.iter,
464 sweep=L.status.sweep,
465 type='e_global_post_run',
466 value=abs(radius - exact_radius),
467 )
468 self.add_to_stats(
469 process=step.status.slot,
470 time=L.time + L.dt,
471 level=level_number,
472 iter=step.status.iter,
473 sweep=L.status.sweep,
474 type='e_global_rel_post_run',
475 value=abs(radius - exact_radius) / abs(exact_radius),
476 )
479if __name__ == '__main__':
481 stats, _, _ = run_AC()
482 plot_solution(stats)