Coverage for pySDC/tutorial/step_7/C_pySDC_with_PETSc.py: 98%
92 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 13:41 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-17 13:41 +0000
1# ---
2# jupyter:
3# jupytext:
4# formats: py:percent
5# kernelspec:
6# display_name: Python 3
7# name: python3
8# language_info:
9# name: python
10# ---
12# %% [markdown]
13# # Part C: Time-parallel pySDC with space-parallel PETSc
14#
15# With rather unfavorable scaling properties, parallel-in-time methods are only really useful when spatial
16# parallelization is maxed out. To work with spatial parallelization, this part shows how to (1) include and work
17# with an external library and (2) set up space- and time-parallel runs. We use the forced heat equation again as our
18# testbed, and [PETSc](https://petsc.org/) for the space-parallel data structures and the linear solver. See
19# `implementations/datatype_classes/petsc_dmda_grid.py` and
20# `implementations/problem_classes/HeatEquation_2D_PETSc_forced.py` for the details of the PETSc bindings.
21#
22# ## Processes in space and in time
23#
24# The world communicator is split twice, by coloring: into space communicators of `num_procs_space` ranks each, which
25# go to the problem class, and across them into time communicators, which go to `controller_MPI`. With 4 ranks and
26# `num_procs_space = 2`, ranks 0 and 1 share space, as do ranks 2 and 3, and ranks 0 and 2 form one time communicator.
28# %%
29import sys
30from pathlib import Path
32import numpy as np
33from mpi4py import MPI
35from pySDC.helpers.stats_helper import get_sorted
37from pySDC.implementations.controller_classes.controller_MPI import controller_MPI
38from pySDC.implementations.problem_classes.HeatEquation_2D_PETSc_forced import heat2d_petsc_forced
39from pySDC.implementations.sweeper_classes.imex_1st_order import imex_1st_order
40from pySDC.implementations.transfer_classes.TransferPETScDMDA import mesh_to_mesh_petsc_dmda
43def main(num_procs_space=None, fname='step_7_C_out.txt'):
44 """
45 Program to demonstrate usage of PETSc data structures and spatial parallelization,
46 combined with parallelization in time.
48 Args:
49 num_procs_space (int): number of ranks to give the space communicator. Defaults to the
50 first command line argument when run as a script, and to 1 otherwise.
51 fname (str): name of the output file written under ``data/``.
52 """
53 # set MPI communicator
54 comm = MPI.COMM_WORLD
56 world_rank = comm.Get_rank()
57 world_size = comm.Get_size()
59 # split world communicator to create space-communicators
60 if num_procs_space is not None:
61 color = int(world_rank / num_procs_space)
62 else:
63 color = int(world_rank / 1)
64 space_comm = comm.Split(color=color)
65 space_rank = space_comm.Get_rank()
67 # split world communicator to create time-communicators
68 if num_procs_space is not None:
69 color = int(world_rank % num_procs_space)
70 else:
71 color = int(world_rank / world_size)
72 time_comm = comm.Split(color=color)
73 time_rank = time_comm.Get_rank()
75 # initialize level parameters
76 level_params = dict()
77 level_params['restol'] = 1e-08
78 level_params['dt'] = 0.125
79 level_params['nsweeps'] = [1]
81 # initialize sweeper parameters
82 sweeper_params = dict()
83 sweeper_params['quad_type'] = 'RADAU-RIGHT'
84 sweeper_params['num_nodes'] = [3]
85 sweeper_params['QI'] = ['LU'] # For the IMEX sweeper, the LU-trick can be activated for the implicit part
86 sweeper_params['initial_guess'] = 'zero'
88 # initialize problem parameters
89 problem_params = dict()
90 problem_params['nu'] = 1.0 # diffusion coefficient
91 problem_params['freq'] = 2 # frequency for the test value
92 problem_params['cnvars'] = [(65, 65)] # number of degrees of freedom for the coarsest level
93 problem_params['refine'] = [1, 0] # number of refinements
94 problem_params['comm'] = space_comm # pass space-communicator to problem class
95 problem_params['sol_tol'] = 1e-12 # set tolerance to PETSc' linear solver
97 # initialize step parameters
98 step_params = dict()
99 step_params['maxiter'] = 50
101 # initialize space transfer parameters
102 space_transfer_params = dict()
103 space_transfer_params['rorder'] = 2
104 space_transfer_params['iorder'] = 2
105 space_transfer_params['periodic'] = False
107 # initialize controller parameters
108 controller_params = dict()
109 controller_params['logger_level'] = 20 if space_rank == 0 else 99 # set level depending on rank
110 controller_params['dump_setup'] = False
112 # fill description dictionary for easy step instantiation
113 description = dict()
114 description['problem_class'] = heat2d_petsc_forced # pass problem class
115 description['problem_params'] = problem_params # pass problem parameters
116 description['sweeper_class'] = imex_1st_order # pass sweeper (see part B)
117 description['sweeper_params'] = sweeper_params # pass sweeper parameters
118 description['level_params'] = level_params # pass level parameters
119 description['step_params'] = step_params # pass step parameters
120 description['space_transfer_class'] = mesh_to_mesh_petsc_dmda # pass spatial transfer class
121 description['space_transfer_params'] = space_transfer_params # pass parameters for spatial transfer
123 # set time parameters
124 t0 = 0.0
125 Tend = 0.25
127 # instantiate controller
128 controller = controller_MPI(controller_params=controller_params, description=description, comm=time_comm)
130 # get initial values on finest level
131 P = controller.S.levels[0].prob
132 uinit = P.u_exact(t0)
134 # call main function to get things done...
135 uend, stats = controller.run(u0=uinit, t0=t0, Tend=Tend)
137 # compute exact solution and compare
138 uex = P.u_exact(Tend)
139 err = abs(uex - uend)
141 # filter statistics by type (number of iterations)
142 iter_counts = get_sorted(stats, type='niter', sortby='time')
144 niters = np.array([item[1] for item in iter_counts])
146 # limit output to space-rank 0 (as before when setting the logger level)
147 if space_rank == 0:
148 Path("data").mkdir(parents=True, exist_ok=True)
149 f = open('data/' + fname, 'a+')
151 out = 'This is time-rank %i...' % time_rank
152 f.write(out + '\n')
153 print(out)
155 # compute and print statistics
156 for item in iter_counts:
157 out = 'Number of iterations for time %4.2f: %2i' % item
158 f.write(out + '\n')
159 print(out)
161 out = ' Mean number of iterations: %4.2f' % np.mean(niters)
162 f.write(out + '\n')
163 print(out)
164 out = ' Range of values for number of iterations: %2i ' % np.ptp(niters)
165 f.write(out + '\n')
166 print(out)
167 out = ' Position of max/min number of iterations: %2i -- %2i' % (
168 int(np.argmax(niters)),
169 int(np.argmin(niters)),
170 )
171 f.write(out + '\n')
172 print(out)
173 out = ' Std and var for number of iterations: %4.2f -- %4.2f' % (float(np.std(niters)), float(np.var(niters)))
174 f.write(out + '\n')
175 print(out)
177 timing = get_sorted(stats, type='timing_run', sortby='time')
179 out = 'Time to solution: %6.4f sec.' % timing[0][1]
180 f.write(out + '\n')
181 print(out)
182 out = 'Error vs. PDE solution: %6.4e' % err
183 f.write(out + '\n')
184 print(out)
186 f.close()
188 assert err < 2e-04, 'ERROR: did not match error tolerance, got %s' % err
189 assert np.mean(niters) <= 12, 'ERROR: number of iterations is too high, got %s' % np.mean(niters)
191 space_comm.Free()
192 time_comm.Free()
195# %% [markdown]
196# ## Running it
197#
198# With `mpirun`, giving the number of ranks in space and the output file, e.g. for 2 in time and 2 in space:
199#
200# ```bash
201# mpirun -np 4 python C_pySDC_with_PETSc.py 2 step_7_C_out_2x2.txt
202# ```
204# %%
205if __name__ == "__main__":
206 # still runnable straight from the shell, as the tutorial text describes:
207 # mpirun -np 4 python C_pySDC_with_PETSc.py 2 step_7_C_out_2x2.txt
208 main(
209 num_procs_space=int(sys.argv[1]) if len(sys.argv) >= 2 else None,
210 fname=sys.argv[2] if len(sys.argv) == 3 else 'step_7_C_out.txt',
211 )
213# %% [markdown]
214# ## Results
215#
216# PETSc does not run in the browser, nor in the environment this website is built in. Our CI runs this part three
217# times, in an environment with PETSc: with 1 and with 2 processes in space, and with 4 processes, 2 in time and 2 in
218# space. Do not expect scaling, due to the CI environment. These are the results of the run that built this page,
219# with 1 process:
220#
221# :::{literalinclude} /../../data/step_7_C_out_1x1.txt
222# :language: text
223# :::
224#
225# With 2 processes in space:
226#
227# :::{literalinclude} /../../data/step_7_C_out_1x2.txt
228# :language: text
229# :::
230#
231# And with 2 in time and 2 in space:
232#
233# :::{literalinclude} /../../data/step_7_C_out_2x2.txt
234# :language: text
235# :::
236#
237# The iterations and the error agree in all three: distributing the spatial problem over processes changes how it is
238# solved, not what, and running the steps in parallel does not change what PFASST computes. Only the time to solution
239# differs, and each time rank prints its own steps.
240#
241# :::{admonition} Important things to note
242# - We need processors in space and time, which can be achieved by `comm.Split` and coloring. The space communicator
243# is then passed to the problem class.
244# :::