Coverage for pySDC/projects/GPU/configs/base_config.py: 85%
177 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 12:30 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-11-02 12:30 +0000
1from pySDC.core.convergence_controller import ConvergenceController
2import pickle
3import numpy as np
6def get_config(args):
7 name = args['config']
8 if name[:2] == 'GS':
9 from pySDC.projects.GPU.configs.GS_configs import get_config as _get_config
10 elif name[:5] == 'RBC3D':
11 from pySDC.projects.RayleighBenard.RBC3D_configs import get_config as _get_config
12 elif name[:3] == 'RBC':
13 from pySDC.projects.GPU.configs.RBC_configs import get_config as _get_config
14 else:
15 raise NotImplementedError(f'There is no configuration called {name!r}!')
17 return _get_config(args)
20def get_comms(n_procs_list, comm_world=None, _comm=None, _tot_rank=0, _rank=None, useGPU=False):
21 from mpi4py import MPI
23 comm_world = MPI.COMM_WORLD if comm_world is None else comm_world
24 _comm = comm_world if _comm is None else _comm
25 _rank = comm_world.rank if _rank is None else _rank
27 if len(n_procs_list) > 0:
28 color = _tot_rank + _rank // n_procs_list[0]
29 new_comm = comm_world.Split(color)
31 assert new_comm.size == n_procs_list[0]
33 if useGPU:
34 import cupy_backends
36 try:
37 import cupy
38 from pySDC.helpers.NCCL_communicator import NCCLComm
40 new_comm = NCCLComm(new_comm)
41 except (
42 ImportError,
43 cupy_backends.cuda.api.runtime.CUDARuntimeError,
44 cupy_backends.cuda.libs.nccl.NcclError,
45 ):
46 print('Warning: Communicator is MPI instead of NCCL in spite of using GPUs!')
48 return [new_comm] + get_comms(
49 n_procs_list[1:],
50 comm_world,
51 _comm=new_comm,
52 _tot_rank=_tot_rank + _comm.size * new_comm.rank,
53 _rank=_comm.rank // new_comm.size,
54 useGPU=useGPU,
55 )
56 else:
57 return []
60class Config(object):
61 sweeper_type = None
62 Tend = None
63 base_path = './'
64 logging_time_increment = 0.5
66 def __init__(self, args, comm_world=None):
67 from mpi4py import MPI
69 self.args = args
70 self.comm_world = MPI.COMM_WORLD if comm_world is None else comm_world
71 self.n_procs_list = args["procs"]
72 if args['mode'] in ['run', 'benchmark']:
73 distribution = args.get('distribution', 'time_first')
74 if distribution in ['space_first', 'space_major']:
75 self.comms = get_comms(
76 n_procs_list=self.n_procs_list[::-1], useGPU=args['useGPU'], comm_world=self.comm_world
77 )[::-1]
78 elif distribution in ['time_first', 'time_major']:
79 self.comms = get_comms(
80 n_procs_list=self.n_procs_list, useGPU=args['useGPU'], comm_world=self.comm_world
81 )
82 else:
83 raise NotImplementedError
84 else:
85 self.comms = [MPI.COMM_SELF, MPI.COMM_SELF, MPI.COMM_SELF]
86 self.ranks = [me.rank for me in self.comms]
88 def get_file_name(self):
89 res = self.args['res']
90 return f'{self.base_path}/data/{type(self).__name__}-res{res}.pySDC'
92 def get_hook(self, hook_class, key=None, **attributes):
93 """
94 Get a subclass of a hook configured through class attributes, the same one for every call with the same key.
96 Setting the attributes on `hook_class` itself would configure every other run in the process. The subclass is
97 kept because the restart counter set on it has to reach the hook that does the logging and `LogStats`.
99 Args:
100 hook_class (pySDC.Hook): The hook to configure
101 key: Distinguishes differently configured subclasses of the same hook
102 attributes: Class attributes to set on the subclass
104 Returns:
105 pySDC.Hook: The configured subclass
106 """
107 hooks = self.__dict__.setdefault('_hooks', {})
108 if (hook_class, key) not in hooks:
109 hooks[(hook_class, key)] = type(hook_class.__name__, (hook_class,), {})
110 hook = hooks[(hook_class, key)]
111 for name, value in attributes.items():
112 setattr(hook, name, value)
113 return hook
115 def get_LogToFile(self, *args, **kwargs):
116 if self.comms[1].rank > 0:
117 return None
118 from pySDC.implementations.hooks.log_solution import LogToFile
120 return self.get_hook(
121 LogToFile,
122 filename=self.get_file_name(),
123 time_increment=self.logging_time_increment,
124 allow_overwriting=True,
125 )
127 def get_description(self, *args, MPIsweeper=False, useGPU=False, **kwargs):
128 description = {}
129 description['problem_class'] = None
130 description['problem_params'] = {'useGPU': useGPU, 'comm': self.comms[2]}
131 description['sweeper_class'] = self.get_sweeper(useMPI=MPIsweeper)
132 description['sweeper_params'] = {'initial_guess': 'copy'}
133 description['level_params'] = {}
134 description['step_params'] = {}
135 description['convergence_controllers'] = {}
137 hook = self.get_LogToFile()
138 if hook:
139 path = self.get_file_name()[:-6]
140 description['convergence_controllers'][LogStats] = {'path': path, 'hook': hook}
142 if MPIsweeper:
143 description['sweeper_params']['comm'] = self.comms[1]
144 return description
146 def get_controller_params(self, *args, logger_level=15, **kwargs):
147 from pySDC.implementations.hooks.log_work import LogWork
148 from pySDC.implementations.hooks.log_step_size import LogStepSize
149 from pySDC.implementations.hooks.log_restarts import LogRestarts
151 controller_params = {}
152 controller_params['logger_level'] = logger_level if self.comm_world.rank == 0 else 40
153 controller_params['hook_class'] = [LogWork, LogStepSize, LogRestarts]
154 logToFile = self.get_LogToFile()
155 if logToFile:
156 controller_params['hook_class'] += [logToFile]
157 controller_params['mssdc_jac'] = False
158 return controller_params
160 def get_sweeper(self, useMPI):
161 if useMPI and self.sweeper_type == 'IMEX':
162 from pySDC.implementations.sweeper_classes.imex_1st_order_MPI import imex_1st_order_MPI as sweeper
163 elif not useMPI and self.sweeper_type == 'IMEX':
164 from pySDC.implementations.sweeper_classes.imex_1st_order import imex_1st_order as sweeper
165 elif useMPI and self.sweeper_type == 'generic_implicit':
166 from pySDC.implementations.sweeper_classes.generic_implicit_MPI import generic_implicit_MPI as sweeper
167 elif not useMPI and self.sweeper_type == 'generic_implicit':
168 from pySDC.implementations.sweeper_classes.generic_implicit import generic_implicit as sweeper
169 else:
170 raise NotImplementedError(f'Don\'t know the sweeper for {self.sweeper_type=}')
172 return sweeper
174 def prepare_caches(self, prob):
175 pass
177 def get_path(self, *args, ranks=None, **kwargs):
178 ranks = self.ranks if ranks is None else ranks
179 return f'{type(self).__name__}{self.args_to_str()}-{ranks[0]}-{ranks[2]}'
181 def args_to_str(self, args=None):
182 args = self.args if args is None else args
183 name = ''
185 name = f'{name}-res_{args["res"]}'
186 name = f'{name}-useGPU_{args["useGPU"]}'
187 name = f'{name}-procs_{args["procs"][0]}_{args["procs"][1]}_{args["procs"][2]}'
188 return name
190 def plot(self, P, idx, num_procs_list):
191 raise NotImplementedError
193 def get_initial_condition(self, P, *args, restart_idx=0, **kwargs):
194 if restart_idx == 0:
195 return P.u_exact(t=0), 0
196 else:
198 from pySDC.helpers.fieldsIO import FieldsIO
200 P.setUpFieldsIO()
201 outfile = FieldsIO.fromFile(self.get_file_name())
203 t0, solution = outfile.readField(restart_idx)
204 solution = solution[: P.spectral.ncomponents, ...]
206 u0 = P.u_init
208 if P.spectral_space:
209 u0[...] = P.transform(solution)
210 else:
211 u0[...] = solution
213 return u0, t0
215 LogToFile = self.get_LogToFile()
216 file = LogToFile.load(restart_idx)
217 LogToFile.counter = restart_idx
218 u0 = P.u_init
219 if hasattr(P, 'spectral_space'):
220 if P.spectral_space:
221 u0[...] = P.transform(file['u'])
222 else:
223 u0[...] = file['u']
224 else:
225 u0[...] = file['u']
226 return u0, file['t']
229class LogStats(ConvergenceController):
231 def get_stats_path(self, index=0):
232 return f'{self.params.path}_{index:06d}-stats.pickle'
234 def merge_all_stats(self, controller):
235 hook = self.params.hook
237 stats = {}
238 for i in range(hook.counter - 1):
239 try:
240 with open(self.get_stats_path(index=i), 'rb') as file:
241 _stats = pickle.load(file)
242 stats = {**stats, **_stats}
243 except (FileNotFoundError, EOFError):
244 print(f'Warning: No stats found at path {self.get_stats_path(index=i)}')
246 stats = {**stats, **controller.return_stats()}
247 return stats
249 def reset_stats(self, controller):
250 for hook in controller.hooks:
251 hook.reset_stats()
252 self.logger.debug('Reset stats')
254 def setup(self, controller, params, *args, **kwargs):
255 params['control_order'] = 999
256 if 'hook' not in params.keys():
257 from pySDC.implementations.hooks.log_solution import LogToFile
259 params['hook'] = LogToFile
261 self.counter = params['hook'].counter
262 return super().setup(controller, params, *args, **kwargs)
264 def post_step_processing(self, controller, S, **kwargs):
265 hook = self.params.hook
267 P = S.levels[0].prob
269 while self.counter < hook.counter:
270 path = self.get_stats_path(index=hook.counter - 2)
271 stats = controller.return_stats()
272 store = True
273 if hasattr(S.levels[0].sweep, 'comm') and S.levels[0].sweep.comm.rank > 0:
274 store = False
275 elif P.comm.rank > 0:
276 store = False
277 if store:
278 with open(path, 'wb') as file:
279 pickle.dump(stats, file)
280 self.log(f'Stored stats in {path!r}', S)
281 # print(stats)
282 self.reset_stats(controller)
283 self.counter = hook.counter
285 def post_run_processing(self, controller, S, **kwargs):
286 self.post_step_processing(controller, S, **kwargs)
288 stats = self.merge_all_stats(controller)
290 def return_stats():
291 return stats
293 controller.return_stats = return_stats