vortex-nwp 2.0.0b1__py3-none-any.whl → 2.1.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 (141) hide show
  1. vortex/__init__.py +75 -47
  2. vortex/algo/__init__.py +3 -2
  3. vortex/algo/components.py +944 -618
  4. vortex/algo/mpitools.py +802 -497
  5. vortex/algo/mpitools_templates/__init__.py +1 -0
  6. vortex/algo/serversynctools.py +34 -33
  7. vortex/config.py +19 -22
  8. vortex/data/__init__.py +9 -3
  9. vortex/data/abstractstores.py +593 -655
  10. vortex/data/containers.py +217 -162
  11. vortex/data/contents.py +65 -39
  12. vortex/data/executables.py +93 -102
  13. vortex/data/flow.py +40 -34
  14. vortex/data/geometries.py +228 -132
  15. vortex/data/handlers.py +436 -227
  16. vortex/data/outflow.py +15 -15
  17. vortex/data/providers.py +185 -163
  18. vortex/data/resources.py +48 -42
  19. vortex/data/stores.py +540 -417
  20. vortex/data/sync_templates/__init__.py +0 -0
  21. vortex/gloves.py +114 -87
  22. vortex/layout/__init__.py +1 -8
  23. vortex/layout/contexts.py +150 -84
  24. vortex/layout/dataflow.py +353 -202
  25. vortex/layout/monitor.py +264 -128
  26. vortex/nwp/__init__.py +5 -2
  27. vortex/nwp/algo/__init__.py +14 -5
  28. vortex/nwp/algo/assim.py +205 -151
  29. vortex/nwp/algo/clim.py +683 -517
  30. vortex/nwp/algo/coupling.py +447 -225
  31. vortex/nwp/algo/eda.py +437 -229
  32. vortex/nwp/algo/eps.py +403 -231
  33. vortex/nwp/algo/forecasts.py +416 -275
  34. vortex/nwp/algo/fpserver.py +683 -307
  35. vortex/nwp/algo/ifsnaming.py +205 -145
  36. vortex/nwp/algo/ifsroot.py +215 -122
  37. vortex/nwp/algo/monitoring.py +137 -76
  38. vortex/nwp/algo/mpitools.py +330 -190
  39. vortex/nwp/algo/odbtools.py +637 -353
  40. vortex/nwp/algo/oopsroot.py +454 -273
  41. vortex/nwp/algo/oopstests.py +90 -56
  42. vortex/nwp/algo/request.py +287 -206
  43. vortex/nwp/algo/stdpost.py +878 -522
  44. vortex/nwp/data/__init__.py +22 -4
  45. vortex/nwp/data/assim.py +125 -137
  46. vortex/nwp/data/boundaries.py +121 -68
  47. vortex/nwp/data/climfiles.py +193 -211
  48. vortex/nwp/data/configfiles.py +73 -69
  49. vortex/nwp/data/consts.py +426 -401
  50. vortex/nwp/data/ctpini.py +59 -43
  51. vortex/nwp/data/diagnostics.py +94 -66
  52. vortex/nwp/data/eda.py +50 -51
  53. vortex/nwp/data/eps.py +195 -146
  54. vortex/nwp/data/executables.py +440 -434
  55. vortex/nwp/data/fields.py +63 -48
  56. vortex/nwp/data/gridfiles.py +183 -111
  57. vortex/nwp/data/logs.py +250 -217
  58. vortex/nwp/data/modelstates.py +180 -151
  59. vortex/nwp/data/monitoring.py +72 -99
  60. vortex/nwp/data/namelists.py +254 -202
  61. vortex/nwp/data/obs.py +400 -308
  62. vortex/nwp/data/oopsexec.py +22 -20
  63. vortex/nwp/data/providers.py +90 -65
  64. vortex/nwp/data/query.py +71 -82
  65. vortex/nwp/data/stores.py +49 -36
  66. vortex/nwp/data/surfex.py +136 -137
  67. vortex/nwp/syntax/__init__.py +1 -1
  68. vortex/nwp/syntax/stdattrs.py +173 -111
  69. vortex/nwp/tools/__init__.py +2 -2
  70. vortex/nwp/tools/addons.py +22 -17
  71. vortex/nwp/tools/agt.py +24 -12
  72. vortex/nwp/tools/bdap.py +16 -5
  73. vortex/nwp/tools/bdcp.py +4 -1
  74. vortex/nwp/tools/bdm.py +3 -0
  75. vortex/nwp/tools/bdmp.py +14 -9
  76. vortex/nwp/tools/conftools.py +728 -378
  77. vortex/nwp/tools/drhook.py +12 -8
  78. vortex/nwp/tools/grib.py +65 -39
  79. vortex/nwp/tools/gribdiff.py +22 -17
  80. vortex/nwp/tools/ifstools.py +82 -42
  81. vortex/nwp/tools/igastuff.py +167 -143
  82. vortex/nwp/tools/mars.py +14 -2
  83. vortex/nwp/tools/odb.py +234 -125
  84. vortex/nwp/tools/partitioning.py +61 -37
  85. vortex/nwp/tools/satrad.py +27 -12
  86. vortex/nwp/util/async.py +83 -55
  87. vortex/nwp/util/beacon.py +10 -10
  88. vortex/nwp/util/diffpygram.py +174 -86
  89. vortex/nwp/util/ens.py +144 -63
  90. vortex/nwp/util/hooks.py +30 -19
  91. vortex/nwp/util/taskdeco.py +28 -24
  92. vortex/nwp/util/usepygram.py +278 -172
  93. vortex/nwp/util/usetnt.py +31 -17
  94. vortex/sessions.py +72 -39
  95. vortex/syntax/__init__.py +1 -1
  96. vortex/syntax/stdattrs.py +410 -171
  97. vortex/syntax/stddeco.py +31 -22
  98. vortex/toolbox.py +327 -192
  99. vortex/tools/__init__.py +11 -2
  100. vortex/tools/actions.py +110 -121
  101. vortex/tools/addons.py +111 -92
  102. vortex/tools/arm.py +42 -22
  103. vortex/tools/compression.py +72 -69
  104. vortex/tools/date.py +11 -4
  105. vortex/tools/delayedactions.py +242 -132
  106. vortex/tools/env.py +75 -47
  107. vortex/tools/folder.py +342 -171
  108. vortex/tools/grib.py +341 -162
  109. vortex/tools/lfi.py +423 -216
  110. vortex/tools/listings.py +109 -40
  111. vortex/tools/names.py +218 -156
  112. vortex/tools/net.py +655 -299
  113. vortex/tools/parallelism.py +93 -61
  114. vortex/tools/prestaging.py +55 -31
  115. vortex/tools/schedulers.py +172 -105
  116. vortex/tools/services.py +403 -334
  117. vortex/tools/storage.py +293 -358
  118. vortex/tools/surfex.py +24 -24
  119. vortex/tools/systems.py +1234 -643
  120. vortex/tools/targets.py +156 -100
  121. vortex/util/__init__.py +1 -1
  122. vortex/util/config.py +378 -327
  123. vortex/util/empty.py +2 -2
  124. vortex/util/helpers.py +56 -24
  125. vortex/util/introspection.py +18 -12
  126. vortex/util/iosponge.py +8 -4
  127. vortex/util/roles.py +4 -6
  128. vortex/util/storefunctions.py +39 -13
  129. vortex/util/structs.py +3 -3
  130. vortex/util/worker.py +29 -17
  131. vortex_nwp-2.1.0.dist-info/METADATA +67 -0
  132. vortex_nwp-2.1.0.dist-info/RECORD +144 -0
  133. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/WHEEL +1 -1
  134. vortex/layout/appconf.py +0 -109
  135. vortex/layout/jobs.py +0 -1276
  136. vortex/layout/nodes.py +0 -1424
  137. vortex/layout/subjobs.py +0 -464
  138. vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
  139. vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
  140. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info/licenses}/LICENSE +0 -0
  141. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/top_level.txt +0 -0
vortex/algo/mpitools.py CHANGED
@@ -70,6 +70,7 @@ Note: Namelists and environment changes are orchestrated as follows:
70
70
 
71
71
  import collections
72
72
  import collections.abc
73
+ import importlib
73
74
  import itertools
74
75
  import locale
75
76
  import re
@@ -92,6 +93,7 @@ logger = loggers.getLogger(__name__)
92
93
 
93
94
  class MpiException(Exception):
94
95
  """Raise an exception in the parallel execution mode."""
96
+
95
97
  pass
96
98
 
97
99
 
@@ -99,89 +101,98 @@ class MpiTool(footprints.FootprintBase):
99
101
  """Root class for any :class:`MpiTool` subclass."""
100
102
 
101
103
  _abstract = True
102
- _collector = ('mpitool', )
104
+ _collector = ("mpitool",)
103
105
  _footprint = dict(
104
- info = 'MpiTool class in charge of a particular MPI implementation',
105
- attr = dict(
106
- sysname = dict(
107
- info = 'The current OS name (e.g. Linux)',
106
+ info="MpiTool class in charge of a particular MPI implementation",
107
+ attr=dict(
108
+ sysname=dict(
109
+ info="The current OS name (e.g. Linux)",
108
110
  ),
109
- mpiname = dict(
110
- info = 'The MPI implementation one wishes to use',
111
+ mpiname=dict(
112
+ info="The MPI implementation one wishes to use",
111
113
  ),
112
- mpilauncher = dict(
113
- info = 'The MPI launcher command to be used',
114
- optional = True
114
+ mpilauncher=dict(
115
+ info="The MPI launcher command to be used", optional=True
115
116
  ),
116
- mpiopts = dict(
117
- info = 'Extra arguments for the MPI command',
118
- optional = True,
119
- default = ''
117
+ mpiopts=dict(
118
+ info="Extra arguments for the MPI command",
119
+ optional=True,
120
+ default="",
120
121
  ),
121
- mpiwrapstd = dict(
122
- info = "When using the Vortex' global wrapper redirect stderr/stdout",
123
- type = bool,
124
- optional = True,
125
- default = False,
126
- doc_visibility = footprints.doc.visibility.ADVANCED,
127
- doc_zorder = -90,
122
+ mpiwrapstd=dict(
123
+ info="When using the Vortex' global wrapper redirect stderr/stdout",
124
+ type=bool,
125
+ optional=True,
126
+ default=False,
127
+ doc_visibility=footprints.doc.visibility.ADVANCED,
128
+ doc_zorder=-90,
128
129
  ),
129
- mpibind_topology = dict(
130
- optional = True,
131
- default = "numapacked",
132
- doc_visibility = footprints.doc.visibility.ADVANCED,
133
- doc_zorder = -90,
130
+ mpibind_topology=dict(
131
+ optional=True,
132
+ default="numapacked",
133
+ doc_visibility=footprints.doc.visibility.ADVANCED,
134
+ doc_zorder=-90,
134
135
  ),
135
- optsep = dict(
136
- info = 'Separator between MPI options and the program name',
137
- optional = True,
138
- default = '--'
136
+ optsep=dict(
137
+ info="Separator between MPI options and the program name",
138
+ optional=True,
139
+ default="--",
139
140
  ),
140
- optprefix = dict(
141
- info = 'MPI options prefix',
142
- optional = True,
143
- default = '--'
141
+ optprefix=dict(
142
+ info="MPI options prefix", optional=True, default="--"
144
143
  ),
145
- optmap = dict(
146
- info = ('Mapping between MpiBinaryDescription objects ' +
147
- 'internal data and actual command line options'),
148
- type = footprints.FPDict,
149
- optional = True,
150
- default = footprints.FPDict(nn='nn', nnp='nnp', openmp='openmp')
144
+ optmap=dict(
145
+ info=(
146
+ "Mapping between MpiBinaryDescription objects "
147
+ + "internal data and actual command line options"
148
+ ),
149
+ type=footprints.FPDict,
150
+ optional=True,
151
+ default=footprints.FPDict(nn="nn", nnp="nnp", openmp="openmp"),
151
152
  ),
152
- binsep = dict(
153
- info = 'Separator between multiple binary groups',
154
- optional = True,
155
- default = '--'
153
+ binsep=dict(
154
+ info="Separator between multiple binary groups",
155
+ optional=True,
156
+ default="--",
156
157
  ),
157
- basics = dict(
158
- type = footprints.FPList,
159
- optional = True,
160
- default = footprints.FPList(['system', 'env', 'target', 'context', 'ticket', ])
158
+ basics=dict(
159
+ type=footprints.FPList,
160
+ optional=True,
161
+ default=footprints.FPList(
162
+ [
163
+ "system",
164
+ "env",
165
+ "target",
166
+ "context",
167
+ "ticket",
168
+ ]
169
+ ),
161
170
  ),
162
- bindingmethod = dict(
163
- info = 'How to bind the MPI processes',
164
- values = ['vortex', ],
165
- access = 'rwx',
166
- optional = True,
167
- doc_visibility = footprints.doc.visibility.ADVANCED,
168
- doc_zorder = -90,
171
+ bindingmethod=dict(
172
+ info="How to bind the MPI processes",
173
+ values=[
174
+ "vortex",
175
+ ],
176
+ access="rwx",
177
+ optional=True,
178
+ doc_visibility=footprints.doc.visibility.ADVANCED,
179
+ doc_zorder=-90,
169
180
  ),
170
- )
181
+ ),
171
182
  )
172
183
 
173
- _envelope_bit_kind = 'basicenvelopebit'
174
- _envelope_wrapper_tpl = '@envelope_wrapper_default.tpl'
175
- _wrapstd_wrapper_tpl = '@wrapstd_wrapper_default.tpl'
176
- _envelope_wrapper_name = './global_envelope_wrapper.py'
177
- _wrapstd_wrapper_name = './global_wrapstd_wrapper.py'
178
- _envelope_rank_var = 'MPIRANK'
184
+ _envelope_bit_kind = "basicenvelopebit"
185
+ _envelope_wrapper_tpl = "envelope_wrapper_default.tpl"
186
+ _wrapstd_wrapper_tpl = "wrapstd_wrapper_default.tpl"
187
+ _envelope_wrapper_name = "./global_envelope_wrapper.py"
188
+ _wrapstd_wrapper_name = "./global_wrapstd_wrapper.py"
189
+ _envelope_rank_var = "MPIRANK"
179
190
  _supports_manual_ranks_mapping = False
180
191
  _needs_mpilib_specific_mpienv = True
181
192
 
182
193
  def __init__(self, *args, **kw):
183
194
  """After parent initialization, set master, options and basics to undefined."""
184
- logger.debug('Abstract mpi tool init %s', self.__class__)
195
+ logger.debug("Abstract mpi tool init %s", self.__class__)
185
196
  super().__init__(*args, **kw)
186
197
  self._launcher = self.mpilauncher or self.generic_mpiname
187
198
  self._binaries = []
@@ -192,29 +203,31 @@ class MpiTool(footprints.FootprintBase):
192
203
  self._ranks_map_cache = None
193
204
  self._complex_ranks_map = None
194
205
  for k in self.basics:
195
- self.__dict__['_' + k] = None
206
+ self.__dict__["_" + k] = None
196
207
 
197
208
  @property
198
209
  def realkind(self):
199
- return 'mpitool'
210
+ return "mpitool"
200
211
 
201
212
  @property
202
213
  def generic_mpiname(self):
203
- return self.mpiname.split('-')[0]
214
+ return self.mpiname.split("-")[0]
204
215
 
205
216
  def __getattr__(self, key):
206
217
  """Have a look to basics values provided by some proxy."""
207
218
  if key in self.basics:
208
- return getattr(self, '_' + key)
219
+ return getattr(self, "_" + key)
209
220
  else:
210
- raise AttributeError('Attribute [%s] is not a basic mpitool attribute' % key)
221
+ raise AttributeError(
222
+ "Attribute [%s] is not a basic mpitool attribute" % key
223
+ )
211
224
 
212
225
  def import_basics(self, obj, attrs=None):
213
226
  """Import some current values such as system, env, target and context from provided ``obj``."""
214
227
  if attrs is None:
215
228
  attrs = self.basics
216
229
  for k in [x for x in attrs if x in self.basics and hasattr(obj, x)]:
217
- setattr(self, '_' + k, getattr(obj, k))
230
+ setattr(self, "_" + k, getattr(obj, k))
218
231
  for bin_obj in self.binaries:
219
232
  bin_obj.import_basics(obj, attrs=None)
220
233
 
@@ -227,7 +240,9 @@ class MpiTool(footprints.FootprintBase):
227
240
 
228
241
  def _set_launcher(self, value):
229
242
  """Set current launcher mpi name. Should be some special trick, so issue a warning."""
230
- logger.warning('Setting a new value [%s] to mpi launcher [%s].', value, self)
243
+ logger.warning(
244
+ "Setting a new value [%s] to mpi launcher [%s].", value, self
245
+ )
231
246
  self._launcher = value
232
247
 
233
248
  launcher = property(_get_launcher, _set_launcher)
@@ -242,11 +257,22 @@ class MpiTool(footprints.FootprintBase):
242
257
 
243
258
  def _set_envelope(self, value):
244
259
  """Set the envelope description."""
245
- if not (isinstance(value, collections.abc.Iterable) and
246
- all([isinstance(b, dict) and
247
- all([bk in ('nn', 'nnp', 'openmp', 'np') for bk in b.keys()])
248
- for b in value])):
249
- raise ValueError('This should be an Iterable of dictionaries.')
260
+ if not (
261
+ isinstance(value, collections.abc.Iterable)
262
+ and all(
263
+ [
264
+ isinstance(b, dict)
265
+ and all(
266
+ [
267
+ bk in ("nn", "nnp", "openmp", "np")
268
+ for bk in b.keys()
269
+ ]
270
+ )
271
+ for b in value
272
+ ]
273
+ )
274
+ ):
275
+ raise ValueError("This should be an Iterable of dictionaries.")
250
276
  self._valid_envelope(value)
251
277
  self._envelope = list()
252
278
  for e in value:
@@ -271,22 +297,33 @@ class MpiTool(footprints.FootprintBase):
271
297
  for a_bin in self.binaries:
272
298
  if a_bin.group is None:
273
299
  # The usual (and easy) case
274
- new_envelope.append({k: v for k, v in a_bin.options.items()
275
- if k in ('nn', 'nnp', 'openmp', 'np')})
300
+ new_envelope.append(
301
+ {
302
+ k: v
303
+ for k, v in a_bin.options.items()
304
+ if k in ("nn", "nnp", "openmp", "np")
305
+ }
306
+ )
276
307
  elif a_bin.group in groups:
277
308
  # Deal with group of binaries
278
309
  group = groups.pop(a_bin.group)
279
- n_nodes = {g_bin.options.get('nn', None) for g_bin in group}
310
+ n_nodes = {g_bin.options.get("nn", None) for g_bin in group}
280
311
  if None in n_nodes:
281
- raise ValueError('To build a proper envelope, ' +
282
- '"nn" needs to be specified in all binaries')
312
+ raise ValueError(
313
+ "To build a proper envelope, "
314
+ + '"nn" needs to be specified in all binaries'
315
+ )
283
316
  done_nodes = 0
284
317
  for n_node in sorted(n_nodes):
285
318
  new_desc = {}
286
- new_desc['nn'] = n_node - done_nodes
287
- new_desc['nnp'] = 0
288
- for g_bin in [g_bin for g_bin in group if g_bin.options['nn'] >= n_node]:
289
- new_desc['nnp'] += g_bin.options['nnp']
319
+ new_desc["nn"] = n_node - done_nodes
320
+ new_desc["nnp"] = 0
321
+ for g_bin in [
322
+ g_bin
323
+ for g_bin in group
324
+ if g_bin.options["nn"] >= n_node
325
+ ]:
326
+ new_desc["nnp"] += g_bin.options["nnp"]
290
327
  new_envelope.append(new_desc)
291
328
  done_nodes = n_node
292
329
  self.envelope = new_envelope
@@ -301,17 +338,27 @@ class MpiTool(footprints.FootprintBase):
301
338
 
302
339
  def _set_binaries(self, value):
303
340
  """Set the list of :class:`MpiBinaryDescription` objects associated with this instance."""
304
- if not (isinstance(value, collections.abc.Iterable) and
305
- all([isinstance(b, MpiBinary) for b in value])):
306
- raise ValueError('This should be an Iterable of MpiBinary instances.')
341
+ if not (
342
+ isinstance(value, collections.abc.Iterable)
343
+ and all([isinstance(b, MpiBinary) for b in value])
344
+ ):
345
+ raise ValueError(
346
+ "This should be an Iterable of MpiBinary instances."
347
+ )
307
348
  has_bin_groups = not all([b.group is None for b in value])
308
349
  if not (self._supports_manual_ranks_mapping or not has_bin_groups):
309
- raise ValueError('Binary groups are not supported by this MpiTool class')
350
+ raise ValueError(
351
+ "Binary groups are not supported by this MpiTool class"
352
+ )
310
353
  has_bin_distribution = not all([b.distribution is None for b in value])
311
- if not (self._supports_manual_ranks_mapping or not has_bin_distribution):
312
- raise ValueError('Binary distribution option is not supported by this MpiTool class')
354
+ if not (
355
+ self._supports_manual_ranks_mapping or not has_bin_distribution
356
+ ):
357
+ raise ValueError(
358
+ "Binary distribution option is not supported by this MpiTool class"
359
+ )
313
360
  self._binaries = value
314
- if not self.envelope and self.bindingmethod == 'vortex':
361
+ if not self.envelope and self.bindingmethod == "vortex":
315
362
  self._set_envelope_from_binaries()
316
363
  elif not self.envelope and (has_bin_groups or has_bin_distribution):
317
364
  self._set_envelope_from_binaries()
@@ -328,8 +375,12 @@ class MpiTool(footprints.FootprintBase):
328
375
  def _mpilib_data(self):
329
376
  """From the binaries, try to detect MPI library and mpirun paths."""
330
377
  if self._mpilib_data_cache is None:
331
- mpilib_guesses = ('libmpi.so', 'libmpi_mt.so',
332
- 'libmpi_dbg.so', 'libmpi_dbg_mt.so')
378
+ mpilib_guesses = (
379
+ "libmpi.so",
380
+ "libmpi_mt.so",
381
+ "libmpi_dbg.so",
382
+ "libmpi_dbg_mt.so",
383
+ )
333
384
  shp = self.system.path
334
385
  mpilib_data = set()
335
386
  for binary in self.binaries:
@@ -351,19 +402,24 @@ class MpiTool(footprints.FootprintBase):
351
402
  mpilib = shp.normpath(mpilib)
352
403
  mpitoolsdir = None
353
404
  mpidir = shp.dirname(shp.dirname(mpilib))
354
- if shp.exists(shp.join(mpidir, 'bin', 'mpirun')):
355
- mpitoolsdir = shp.join(mpidir, 'bin')
356
- if not mpitoolsdir and shp.exists(shp.join(mpidir, '..', 'bin', 'mpirun')):
357
- mpitoolsdir = shp.normpath(shp.join(mpidir, '..', 'bin'))
405
+ if shp.exists(shp.join(mpidir, "bin", "mpirun")):
406
+ mpitoolsdir = shp.join(mpidir, "bin")
407
+ if not mpitoolsdir and shp.exists(
408
+ shp.join(mpidir, "..", "bin", "mpirun")
409
+ ):
410
+ mpitoolsdir = shp.normpath(
411
+ shp.join(mpidir, "..", "bin")
412
+ )
358
413
  if mpilib and mpitoolsdir:
359
- mpilib_data.add((shp.realpath(mpilib),
360
- shp.realpath(mpitoolsdir)))
414
+ mpilib_data.add(
415
+ (shp.realpath(mpilib), shp.realpath(mpitoolsdir))
416
+ )
361
417
  # All the binary must use the same library !
362
418
  if len(mpilib_data) == 0:
363
- logger.info('No MPI library was detected.')
419
+ logger.info("No MPI library was detected.")
364
420
  self._mpilib_data_cache = ()
365
421
  elif len(mpilib_data) > 1:
366
- logger.error('Multiple MPI library were detected.')
422
+ logger.error("Multiple MPI library were detected.")
367
423
  self._mpilib_data_cache = ()
368
424
  else:
369
425
  self._mpilib_data_cache = mpilib_data.pop()
@@ -373,8 +429,11 @@ class MpiTool(footprints.FootprintBase):
373
429
  for line in rclines:
374
430
  matched = regex.match(line)
375
431
  if matched:
376
- logger.info('MPI implementation detected: %s (%s)',
377
- which, ' '.join(matched.groups()))
432
+ logger.info(
433
+ "MPI implementation detected: %s (%s)",
434
+ which,
435
+ " ".join(matched.groups()),
436
+ )
378
437
  return [which] + [int(res) for res in matched.groups()]
379
438
  return False
380
439
 
@@ -386,7 +445,7 @@ class MpiTool(footprints.FootprintBase):
386
445
  mpi_lib, mpi_tools_dir = self._mpilib_data()
387
446
  ld_libs_extra = set()
388
447
  sh = self.system
389
- mpirun_path = sh.path.join(mpi_tools_dir, 'mpirun')
448
+ mpirun_path = sh.path.join(mpi_tools_dir, "mpirun")
390
449
  if sh.path.exists(mpirun_path):
391
450
  try:
392
451
  libs = sh.ldd(mpirun_path)
@@ -399,30 +458,47 @@ class MpiTool(footprints.FootprintBase):
399
458
  for lib, libpath in sh.ldd(binary.master).items():
400
459
  if libpath:
401
460
  libscache[lib] = sh.path.dirname(libpath)
402
- for missing_lib in [lib for lib, libname in libs.items()
403
- if libname is None]:
461
+ for missing_lib in [
462
+ lib for lib, libname in libs.items() if libname is None
463
+ ]:
404
464
  if missing_lib in libscache:
405
465
  ld_libs_extra.add(libscache[missing_lib])
406
466
  with self.env.clone() as localenv:
407
467
  for libpath in ld_libs_extra:
408
- localenv.setgenericpath('LD_LIBRARY_PATH', libpath)
409
- rc = sh.spawn([mpirun_path, '--version'], output=True, fatal=False)
468
+ localenv.setgenericpath("LD_LIBRARY_PATH", libpath)
469
+ rc = sh.spawn(
470
+ [mpirun_path, "--version"], output=True, fatal=False
471
+ )
410
472
  if rc:
411
473
  id_res = self._mpilib_match_result(
412
- re.compile(r'^.*Intel.*MPI.*Version\s+(\d+)\s+Update\s+(\d+)',
413
- re.IGNORECASE),
414
- rc, 'intelmpi')
474
+ re.compile(
475
+ r"^.*Intel.*MPI.*Version\s+(\d+)\s+Update\s+(\d+)",
476
+ re.IGNORECASE,
477
+ ),
478
+ rc,
479
+ "intelmpi",
480
+ )
415
481
  id_res = id_res or self._mpilib_match_result(
416
- re.compile(r'^.*Open\s*MPI.*\s+(\d+)\.(\d+)(?:\.(\d+))?',
417
- re.IGNORECASE),
418
- rc, 'openmpi')
482
+ re.compile(
483
+ r"^.*Open\s*MPI.*\s+(\d+)\.(\d+)(?:\.(\d+))?",
484
+ re.IGNORECASE,
485
+ ),
486
+ rc,
487
+ "openmpi",
488
+ )
419
489
  if id_res:
420
490
  ld_libs_extra = tuple(sorted(ld_libs_extra))
421
- self._mpilib_identification_cache = tuple([mpi_lib, mpi_tools_dir, ld_libs_extra] +
422
- id_res)
491
+ self._mpilib_identification_cache = tuple(
492
+ [mpi_lib, mpi_tools_dir, ld_libs_extra] + id_res
493
+ )
423
494
  if self._mpilib_identification_cache is None:
424
495
  ld_libs_extra = tuple(sorted(ld_libs_extra))
425
- self._mpilib_identification_cache = (mpi_lib, mpi_tools_dir, ld_libs_extra, 'unknown')
496
+ self._mpilib_identification_cache = (
497
+ mpi_lib,
498
+ mpi_tools_dir,
499
+ ld_libs_extra,
500
+ "unknown",
501
+ )
426
502
  return self._mpilib_identification_cache
427
503
 
428
504
  def _get_sources(self):
@@ -432,7 +508,7 @@ class MpiTool(footprints.FootprintBase):
432
508
  def _set_sources(self, value):
433
509
  """Set the list of of directories that may contain source files."""
434
510
  if not isinstance(value, collections.abc.Iterable):
435
- raise ValueError('This should be an Iterable.')
511
+ raise ValueError("This should be an Iterable.")
436
512
  self._sources = value
437
513
 
438
514
  sources = property(_get_sources, _set_sources)
@@ -446,14 +522,16 @@ class MpiTool(footprints.FootprintBase):
446
522
  klast = None
447
523
  options = collections.defaultdict(list)
448
524
  for optdef in shlex.split(self._actual_mpiopts()):
449
- if optdef.startswith('-'):
450
- optdef = optdef.lstrip('-')
525
+ if optdef.startswith("-"):
526
+ optdef = optdef.lstrip("-")
451
527
  options[optdef].append([])
452
528
  klast = optdef
453
529
  elif klast is not None:
454
530
  options[klast][-1].append(optdef)
455
531
  else:
456
- raise MpiException('Badly shaped mpi option around {!s}'.format(optdef))
532
+ raise MpiException(
533
+ "Badly shaped mpi option around {!s}".format(optdef)
534
+ )
457
535
  return options
458
536
 
459
537
  def _hook_binary_mpiopts(self, binary, options):
@@ -466,14 +544,18 @@ class MpiTool(footprints.FootprintBase):
466
544
  if self._ranks_map_cache is None:
467
545
  self._complex_ranks_map = False
468
546
  if not self.envelope:
469
- raise RuntimeError('Ranks mapping should always be used within an envelope.')
547
+ raise RuntimeError(
548
+ "Ranks mapping should always be used within an envelope."
549
+ )
470
550
  # First deal with bingroups
471
551
  ranks_map = dict()
472
552
  has_bin_groups = not all([b.group is None for b in self.binaries])
473
553
  cursor = 0 # The MPI rank we are currently processing
474
554
  if has_bin_groups:
475
555
  if not self._supports_manual_ranks_mapping:
476
- raise RuntimeError('This MpiTool class does not supports ranks mapping.')
556
+ raise RuntimeError(
557
+ "This MpiTool class does not supports ranks mapping."
558
+ )
477
559
  self._complex_ranks_map = True
478
560
  cursor0 = 0 # The first available "real" slot
479
561
  group_cache = collections.defaultdict(list)
@@ -487,24 +569,42 @@ class MpiTool(footprints.FootprintBase):
487
569
  if not reserved:
488
570
  # It is the first time this group of binaries is seen
489
571
  # Find out what are the binaries in this group
490
- bin_buddies = [bin_b for bin_b in self.binaries
491
- if bin_b.group == a_bin.group]
492
- if all(['nn' in bin_b.options for bin_b in bin_buddies]):
572
+ bin_buddies = [
573
+ bin_b
574
+ for bin_b in self.binaries
575
+ if bin_b.group == a_bin.group
576
+ ]
577
+ if all(
578
+ [
579
+ "nn" in bin_b.options
580
+ for bin_b in bin_buddies
581
+ ]
582
+ ):
493
583
  # Each of the binary descriptions should define the number of nodes
494
- max_nn = max([bin_b.options['nn'] for bin_b in bin_buddies])
584
+ max_nn = max(
585
+ [
586
+ bin_b.options["nn"]
587
+ for bin_b in bin_buddies
588
+ ]
589
+ )
495
590
  for i_node in range(max_nn):
496
591
  for bin_b in bin_buddies:
497
- if bin_b.options['nn'] > i_node:
498
- group_cache[bin_b].extend(range(cursor0,
499
- cursor0 +
500
- bin_b.options['nnp']))
501
- cursor0 += bin_b.options['nnp']
592
+ if bin_b.options["nn"] > i_node:
593
+ group_cache[bin_b].extend(
594
+ range(
595
+ cursor0,
596
+ cursor0
597
+ + bin_b.options["nnp"],
598
+ )
599
+ )
600
+ cursor0 += bin_b.options["nnp"]
502
601
  else:
503
602
  # If the number of nodes is not defined, revert to the number of tasks.
504
603
  # This will probably result in strange results !
505
604
  for bin_b in bin_buddies:
506
- group_cache[bin_b].extend(range(cursor0,
507
- cursor0 + bin_b.nprocs))
605
+ group_cache[bin_b].extend(
606
+ range(cursor0, cursor0 + bin_b.nprocs)
607
+ )
508
608
  cursor0 += bin_b.nprocs
509
609
  reserved = group_cache[a_bin]
510
610
  for rank in range(a_bin.nprocs):
@@ -517,37 +617,58 @@ class MpiTool(footprints.FootprintBase):
517
617
  ranks_map[rank + cursor] = rank + cursor
518
618
  cursor += a_bin.nprocs
519
619
  # Then deal with distribution
520
- do_bin_distribution = not all([b.distribution in (None, "continuous")
521
- for b in self.binaries])
620
+ do_bin_distribution = not all(
621
+ [b.distribution in (None, "continuous") for b in self.binaries]
622
+ )
522
623
  if self._complex_ranks_map or do_bin_distribution:
523
624
  if not self.envelope:
524
- raise RuntimeError('Ranks mapping shoudl always be used within an envelope.')
625
+ raise RuntimeError(
626
+ "Ranks mapping shoudl always be used within an envelope."
627
+ )
525
628
  if do_bin_distribution:
526
629
  if not self._supports_manual_ranks_mapping:
527
- raise RuntimeError('This MpiTool class does not supports ranks mapping.')
630
+ raise RuntimeError(
631
+ "This MpiTool class does not supports ranks mapping."
632
+ )
528
633
  self._complex_ranks_map = True
529
- if all(['nn' in b.options and 'nnp' in b.options for b in self.envelope]):
634
+ if all(
635
+ [
636
+ "nn" in b.options and "nnp" in b.options
637
+ for b in self.envelope
638
+ ]
639
+ ):
530
640
  # Extract node information
531
641
  node_cursor = 0
532
642
  nodes_id = list()
533
643
  for e_bit in self.envelope:
534
- for _ in range(e_bit.options['nn']):
535
- nodes_id.extend([node_cursor, ] * e_bit.options['nnp'])
644
+ for _ in range(e_bit.options["nn"]):
645
+ nodes_id.extend(
646
+ [
647
+ node_cursor,
648
+ ]
649
+ * e_bit.options["nnp"]
650
+ )
536
651
  node_cursor += 1
537
652
  # Re-order ranks given the distribution
538
653
  cursor = 0
539
654
  for a_bin in self.binaries:
540
655
  if a_bin.distribution == "roundrobin":
541
656
  # The current list of ranks
542
- actual_ranks = [ranks_map[i]
543
- for i in range(cursor, cursor + a_bin.nprocs)]
657
+ actual_ranks = [
658
+ ranks_map[i]
659
+ for i in range(cursor, cursor + a_bin.nprocs)
660
+ ]
544
661
  # Find the node number associated with each rank
545
- nodes_dict = collections.defaultdict(collections.deque)
662
+ nodes_dict = collections.defaultdict(
663
+ collections.deque
664
+ )
546
665
  for rank in actual_ranks:
547
666
  nodes_dict[nodes_id[rank]].append(rank)
548
667
  # Create a new list of ranks in a round-robin manner
549
668
  actual_ranks = list()
550
- iter_nodes = itertools.cycle(sorted(nodes_dict.keys()))
669
+ iter_nodes = itertools.cycle(
670
+ sorted(nodes_dict.keys())
671
+ )
551
672
  for _ in range(a_bin.nprocs):
552
673
  av_ranks = None
553
674
  while not av_ranks:
@@ -558,8 +679,10 @@ class MpiTool(footprints.FootprintBase):
558
679
  ranks_map[cursor + i] = actual_ranks[i]
559
680
  cursor += a_bin.nprocs
560
681
  else:
561
- logger.warning('Cannot enforce binary distribution if the envelope' +
562
- 'does not contain nn/nnp information')
682
+ logger.warning(
683
+ "Cannot enforce binary distribution if the envelope"
684
+ + "does not contain nn/nnp information"
685
+ )
563
686
  # Cache the final result !
564
687
  self._ranks_map_cache = ranks_map
565
688
  return self._ranks_map_cache
@@ -577,10 +700,12 @@ class MpiTool(footprints.FootprintBase):
577
700
  if not self.mpiwrapstd:
578
701
  return None
579
702
  # Create the launchwrapper
580
- wtpl = config.load_template(self.ticket,
581
- self._wrapstd_wrapper_tpl,
582
- encoding='utf-8')
583
- with open(self._wrapstd_wrapper_name, 'w', encoding='utf-8') as fhw:
703
+ with importlib.resources.path(
704
+ "vortex.algo.mpitools_templates",
705
+ self._wrapstd_wrapper_tpl,
706
+ ) as tplpath:
707
+ wtpl = config.load_template(tplpath, encoding="utf-8")
708
+ with open(self._wrapstd_wrapper_name, "w", encoding="utf-8") as fhw:
584
709
  fhw.write(
585
710
  wtpl.substitute(
586
711
  python=self.system.executable,
@@ -599,12 +724,14 @@ class MpiTool(footprints.FootprintBase):
599
724
  wrapstd = self._wrapstd_mkwrapper()
600
725
  for bin_obj in self.binaries:
601
726
  if bin_obj.master is None:
602
- raise MpiException('No master defined before launching MPI')
727
+ raise MpiException("No master defined before launching MPI")
603
728
  # If there are no options, do not bother...
604
729
  if len(bin_obj.expanded_options()):
605
730
  if effective > 0 and self.binsep:
606
731
  cmdl.append(self.binsep)
607
- e_options = self._hook_binary_mpiopts(bin_obj, bin_obj.expanded_options())
732
+ e_options = self._hook_binary_mpiopts(
733
+ bin_obj, bin_obj.expanded_options()
734
+ )
608
735
  for k in sorted(e_options.keys()):
609
736
  if k in self.optmap:
610
737
  cmdl.append(self.optprefix + str(self.optmap[k]))
@@ -620,8 +747,9 @@ class MpiTool(footprints.FootprintBase):
620
747
 
621
748
  def _envelope_fix_envelope_bit(self, e_bit, e_desc):
622
749
  """Set the envelope fake binary options."""
623
- e_bit.options = {k: v for k, v in e_desc.items()
624
- if k not in ('openmp', 'np')}
750
+ e_bit.options = {
751
+ k: v for k, v in e_desc.items() if k not in ("openmp", "np")
752
+ }
625
753
  e_bit.master = self._envelope_wrapper_name
626
754
 
627
755
  def _envelope_mkwrapper_todostack(self):
@@ -630,18 +758,23 @@ class MpiTool(footprints.FootprintBase):
630
758
  todostack = dict()
631
759
  for bin_obj in self.binaries:
632
760
  if bin_obj.master is None:
633
- raise MpiException('No master defined before launching MPI')
761
+ raise MpiException("No master defined before launching MPI")
634
762
  # If there are no options, do not bother...
635
763
  if bin_obj.options and bin_obj.nprocs != 0:
636
764
  if not bin_obj.nprocs:
637
- raise ValueError('nranks must be provided when using envelopes')
765
+ raise ValueError(
766
+ "nranks must be provided when using envelopes"
767
+ )
638
768
  for mpirank in range(ranksidx, ranksidx + bin_obj.nprocs):
639
769
  if bin_obj.allowbind:
640
- ranks_bsize[mpirank] = bin_obj.options.get('openmp', 1)
770
+ ranks_bsize[mpirank] = bin_obj.options.get("openmp", 1)
641
771
  else:
642
772
  ranks_bsize[mpirank] = -1
643
- todostack[mpirank] = (bin_obj.master, bin_obj.arguments,
644
- bin_obj.options.get('openmp', None))
773
+ todostack[mpirank] = (
774
+ bin_obj.master,
775
+ bin_obj.arguments,
776
+ bin_obj.options.get("openmp", None),
777
+ )
645
778
  ranksidx += bin_obj.nprocs
646
779
  return todostack, ranks_bsize
647
780
 
@@ -651,18 +784,25 @@ class MpiTool(footprints.FootprintBase):
651
784
  ranks_idx = 0
652
785
  dispensers_map = dict()
653
786
  for e_bit in self.envelope:
654
- if 'nn' in e_bit.options and 'nnp' in e_bit.options:
655
- for _ in range(e_bit.options['nn']):
656
- cpu_disp = self.system.cpus_ids_dispenser(topology=self.mpibind_topology)
787
+ if "nn" in e_bit.options and "nnp" in e_bit.options:
788
+ for _ in range(e_bit.options["nn"]):
789
+ cpu_disp = self.system.cpus_ids_dispenser(
790
+ topology=self.mpibind_topology
791
+ )
657
792
  if not cpu_disp:
658
- raise MpiException('Unable to detect the CPU layout with topology: {:s}'
659
- .format(self.mpibind_topology, ))
660
- for _ in range(e_bit.options['nnp']):
793
+ raise MpiException(
794
+ "Unable to detect the CPU layout with topology: {:s}".format(
795
+ self.mpibind_topology,
796
+ )
797
+ )
798
+ for _ in range(e_bit.options["nnp"]):
661
799
  dispensers_map[ranks_idx] = (cpu_disp, totalnodes)
662
800
  ranks_idx += 1
663
801
  totalnodes += 1
664
802
  else:
665
- logger.error("Cannot compute a proper binding without nn/nnp information")
803
+ logger.error(
804
+ "Cannot compute a proper binding without nn/nnp information"
805
+ )
666
806
  raise MpiException("Vortex binding error.")
667
807
  return dispensers_map
668
808
 
@@ -674,72 +814,113 @@ class MpiTool(footprints.FootprintBase):
674
814
  # Actually generate the binding map
675
815
  ranks_idx = 0
676
816
  for e_bit in self.envelope:
677
- for _ in range(e_bit.options['nn']):
678
- for _ in range(e_bit.options['nnp']):
679
- cpu_disp, i_node = dispensers_map[self._ranks_mapping[ranks_idx]]
817
+ for _ in range(e_bit.options["nn"]):
818
+ for _ in range(e_bit.options["nnp"]):
819
+ cpu_disp, i_node = dispensers_map[
820
+ self._ranks_mapping[ranks_idx]
821
+ ]
680
822
  if ranks_bsize.get(ranks_idx, 1) != -1:
681
823
  try:
682
- binding_stack[ranks_idx] = cpu_disp(ranks_bsize.get(ranks_idx, 1))
824
+ binding_stack[ranks_idx] = cpu_disp(
825
+ ranks_bsize.get(ranks_idx, 1)
826
+ )
683
827
  except (StopIteration, IndexError):
684
828
  # When CPU dispensers are exhausted (it might happened if more tasks
685
829
  # than available CPUs are requested).
686
- dispensers_map = self._envelope_mkwrapper_cpu_dispensers()
687
- cpu_disp, i_node = dispensers_map[self._ranks_mapping[ranks_idx]]
688
- binding_stack[ranks_idx] = cpu_disp(ranks_bsize.get(ranks_idx, 1))
830
+ dispensers_map = (
831
+ self._envelope_mkwrapper_cpu_dispensers()
832
+ )
833
+ cpu_disp, i_node = dispensers_map[
834
+ self._ranks_mapping[ranks_idx]
835
+ ]
836
+ binding_stack[ranks_idx] = cpu_disp(
837
+ ranks_bsize.get(ranks_idx, 1)
838
+ )
689
839
  else:
690
- binding_stack[ranks_idx] = set(self.system.cpus_info.cpus.keys())
840
+ binding_stack[ranks_idx] = set(
841
+ self.system.cpus_info.cpus.keys()
842
+ )
691
843
  binding_node[ranks_idx] = i_node
692
844
  ranks_idx += 1
693
845
  return binding_stack, binding_node
694
846
 
695
847
  def _envelope_mkwrapper_tplsubs(self, todostack, bindingstack):
696
- return dict(python=self.system.executable,
697
- sitepath=self.system.path.join(self.ticket.glove.siteroot, 'site'),
698
- mpirankvariable=self._envelope_rank_var,
699
- todolist=("\n".join([" {:d}: ('{:s}', [{:s}], {:s}),".format(
700
- mpi_r,
701
- what[0],
702
- ', '.join(["'{:s}'".format(a) for a in what[1]]),
703
- str(what[2]))
704
- for mpi_r, what in sorted(todostack.items())])),
705
- bindinglist=("\n".join([" {:d}: [{:s}],".format(
706
- mpi_r,
707
- ', '.join(['{:d}'.format(a) for a in what]))
708
- for mpi_r, what in sorted(bindingstack.items())])))
848
+ return dict(
849
+ python=self.system.executable,
850
+ sitepath=self.system.path.join(self.ticket.glove.siteroot, "site"),
851
+ mpirankvariable=self._envelope_rank_var,
852
+ todolist=(
853
+ "\n".join(
854
+ [
855
+ " {:d}: ('{:s}', [{:s}], {:s}),".format(
856
+ mpi_r,
857
+ what[0],
858
+ ", ".join(["'{:s}'".format(a) for a in what[1]]),
859
+ str(what[2]),
860
+ )
861
+ for mpi_r, what in sorted(todostack.items())
862
+ ]
863
+ )
864
+ ),
865
+ bindinglist=(
866
+ "\n".join(
867
+ [
868
+ " {:d}: [{:s}],".format(
869
+ mpi_r, ", ".join(["{:d}".format(a) for a in what])
870
+ )
871
+ for mpi_r, what in sorted(bindingstack.items())
872
+ ]
873
+ )
874
+ ),
875
+ )
709
876
 
710
877
  def _envelope_mkwrapper(self, cmdl):
711
878
  """Generate the wrapper script used when an envelope is defined."""
712
879
  # Generate the dictionary that associate rank numbers and programs
713
880
  todostack, ranks_bsize = self._envelope_mkwrapper_todostack()
714
881
  # Generate the binding stuff
715
- bindingstack, bindingnode = self._envelope_mkwrapper_bindingstack(ranks_bsize)
882
+ bindingstack, bindingnode = self._envelope_mkwrapper_bindingstack(
883
+ ranks_bsize
884
+ )
716
885
  # Print binding details
717
- logger.debug('Vortex Envelope Mechanism is used' +
718
- (' & vortex binding is on.' if bindingstack else '.'))
719
- env_info_head = '{:5s} {:24s} {:4s}'.format('#rank', 'binary_name', '#OMP')
720
- env_info_fmt = '{:5d} {:24s} {:4s}'
886
+ logger.debug(
887
+ "Vortex Envelope Mechanism is used"
888
+ + (" & vortex binding is on." if bindingstack else ".")
889
+ )
890
+ env_info_head = "{:5s} {:24s} {:4s}".format(
891
+ "#rank", "binary_name", "#OMP"
892
+ )
893
+ env_info_fmt = "{:5d} {:24s} {:4s}"
721
894
  if bindingstack:
722
- env_info_head += ' {:5s} {:s}'.format('#node', 'bindings_list')
723
- env_info_fmt2 = ' {:5d} {:s}'
895
+ env_info_head += " {:5s} {:s}".format("#node", "bindings_list")
896
+ env_info_fmt2 = " {:5d} {:s}"
724
897
  binding_str = [env_info_head]
725
898
  for i_rank in sorted(todostack):
726
- entry_str = env_info_fmt.format(i_rank,
727
- self.system.path.basename(todostack[i_rank][0])[:24],
728
- str(todostack[i_rank][2]))
899
+ entry_str = env_info_fmt.format(
900
+ i_rank,
901
+ self.system.path.basename(todostack[i_rank][0])[:24],
902
+ str(todostack[i_rank][2]),
903
+ )
729
904
  if bindingstack:
730
- entry_str += env_info_fmt2.format(bindingnode[i_rank],
731
- ','.join([str(c)
732
- for c in sorted(bindingstack[i_rank])]))
905
+ entry_str += env_info_fmt2.format(
906
+ bindingnode[i_rank],
907
+ ",".join([str(c) for c in sorted(bindingstack[i_rank])]),
908
+ )
733
909
  binding_str.append(entry_str)
734
- logger.debug('Here are the envelope details:\n%s', '\n'.join(binding_str))
910
+ logger.debug(
911
+ "Here are the envelope details:\n%s", "\n".join(binding_str)
912
+ )
735
913
  # Create the launchwrapper
736
- wtpl = config.load_template(self.ticket,
737
- self._envelope_wrapper_tpl,
738
- encoding='utf-8')
739
- with open(self._envelope_wrapper_name, 'w', encoding='utf-8') as fhw:
914
+ with importlib.resources.path(
915
+ "vortex.algo.mpitools_templates",
916
+ self._envelope_wrapper_tpl,
917
+ ) as tplpath:
918
+ wtpl = config.load_template(tplpath, encoding="utf-8")
919
+ with open(self._envelope_wrapper_name, "w", encoding="utf-8") as fhw:
740
920
  fhw.write(
741
- wtpl.substitute(**self._envelope_mkwrapper_tplsubs(todostack,
742
- bindingstack))
921
+ wtpl.substitute(
922
+ **self._envelope_mkwrapper_tplsubs(todostack, bindingstack)
923
+ )
743
924
  )
744
925
  self.system.xperm(self._envelope_wrapper_name, force=True)
745
926
  return self._envelope_wrapper_name
@@ -754,7 +935,9 @@ class MpiTool(footprints.FootprintBase):
754
935
  for effective, e_bit in enumerate(self.envelope):
755
936
  if effective > 0 and self.binsep:
756
937
  cmdl.append(self.binsep)
757
- e_options = self._hook_binary_mpiopts(e_bit, e_bit.expanded_options())
938
+ e_options = self._hook_binary_mpiopts(
939
+ e_bit, e_bit.expanded_options()
940
+ )
758
941
  for k in sorted(e_options.keys()):
759
942
  if k in self.optmap:
760
943
  cmdl.append(self.optprefix + str(self.optmap[k]))
@@ -773,7 +956,9 @@ class MpiTool(footprints.FootprintBase):
773
956
 
774
957
  def mkcmdline(self):
775
958
  """Builds the MPI command line."""
776
- cmdl = [self.launcher, ]
959
+ cmdl = [
960
+ self.launcher,
961
+ ]
777
962
  for k, instances in sorted(self._reshaped_mpiopts().items()):
778
963
  for instance in instances:
779
964
  cmdl.append(self.optprefix + str(k))
@@ -789,17 +974,23 @@ class MpiTool(footprints.FootprintBase):
789
974
  """post-execution cleaning."""
790
975
  if self.mpiwrapstd:
791
976
  # Deal with standard output/error files
792
- for outf in sorted(self.system.glob('vwrap_stdeo.*')):
977
+ for outf in sorted(self.system.glob("vwrap_stdeo.*")):
793
978
  rank = int(outf[12:])
794
- with open(outf,
795
- encoding=locale.getlocale()[1] or 'ascii',
796
- errors='replace') as sfh:
797
- for (i, l) in enumerate(sfh):
979
+ with open(
980
+ outf,
981
+ encoding=locale.getlocale()[1] or "ascii",
982
+ errors="replace",
983
+ ) as sfh:
984
+ for i, l in enumerate(sfh):
798
985
  if i == 0:
799
- self.system.highlight('rank {:d}: stdout/err'.format(rank))
800
- print(l.rstrip('\n'))
986
+ self.system.highlight(
987
+ "rank {:d}: stdout/err".format(rank)
988
+ )
989
+ print(l.rstrip("\n"))
801
990
  self.system.remove(outf)
802
- if self.envelope and self.system.path.exists(self._envelope_wrapper_name):
991
+ if self.envelope and self.system.path.exists(
992
+ self._envelope_wrapper_name
993
+ ):
803
994
  self.system.remove(self._envelope_wrapper_name)
804
995
  if self.mpiwrapstd:
805
996
  self.system.remove(self._wrapstd_wrapper_name)
@@ -809,16 +1000,24 @@ class MpiTool(footprints.FootprintBase):
809
1000
 
810
1001
  def find_namelists(self, opts=None):
811
1002
  """Find any namelists candidates in actual context inputs."""
812
- namcandidates = [x.rh for x in
813
- self.context.sequence.effective_inputs(kind=('namelist', 'namelistfp'))]
814
- if opts is not None and 'loop' in opts:
1003
+ namcandidates = [
1004
+ x.rh
1005
+ for x in self.context.sequence.effective_inputs(
1006
+ kind=("namelist", "namelistfp")
1007
+ )
1008
+ ]
1009
+ if opts is not None and "loop" in opts:
815
1010
  namcandidates = [
816
- x for x in namcandidates
817
- if (hasattr(x.resource, 'term') and x.resource.term == opts['loop'])
1011
+ x
1012
+ for x in namcandidates
1013
+ if (
1014
+ hasattr(x.resource, "term")
1015
+ and x.resource.term == opts["loop"]
1016
+ )
818
1017
  ]
819
1018
  else:
820
- logger.info('No loop option in current parallel execution.')
821
- self.system.highlight('Namelist candidates')
1019
+ logger.info("No loop option in current parallel execution.")
1020
+ self.system.highlight("Namelist candidates")
822
1021
  for nam in namcandidates:
823
1022
  nam.quickview()
824
1023
  return namcandidates
@@ -831,18 +1030,30 @@ class MpiTool(footprints.FootprintBase):
831
1030
  """MPI information to be written in namelists."""
832
1031
  for namrh in self.find_namelists(opts):
833
1032
  namc = namrh.contents
834
- changed = self.setup_namelist_delta(namc, namrh.container.actualpath())
1033
+ changed = self.setup_namelist_delta(
1034
+ namc, namrh.container.actualpath()
1035
+ )
835
1036
  # Call the dedicated method en registered MPI binaries
836
1037
  for bin_obj in self.binaries:
837
- changed = bin_obj.setup_namelist_delta(namc, namrh.container.actualpath()) or changed
1038
+ changed = (
1039
+ bin_obj.setup_namelist_delta(
1040
+ namc, namrh.container.actualpath()
1041
+ )
1042
+ or changed
1043
+ )
838
1044
  if changed:
839
1045
  if namc.dumps_needs_update:
840
- logger.info('Rewritting the %s namelists file.', namrh.container.actualpath())
1046
+ logger.info(
1047
+ "Rewritting the %s namelists file.",
1048
+ namrh.container.actualpath(),
1049
+ )
841
1050
  namc.rewrite(namrh.container)
842
1051
 
843
1052
  def _logged_env_set(self, k, v):
844
1053
  """Set an environment variable *k* and emit a log message."""
845
- logger.info('Setting the "%s" environment variable to "%s"', k.upper(), v)
1054
+ logger.info(
1055
+ 'Setting the "%s" environment variable to "%s"', k.upper(), v
1056
+ )
846
1057
  self.env[k] = v
847
1058
 
848
1059
  def _logged_env_del(self, k):
@@ -867,8 +1078,11 @@ class MpiTool(footprints.FootprintBase):
867
1078
  try:
868
1079
  v = str(v).format(**envsub)
869
1080
  except KeyError:
870
- logger.warning("Substitution failed for the environment " +
871
- "variable %s. Ignoring it.", k)
1081
+ logger.warning(
1082
+ "Substitution failed for the environment "
1083
+ + "variable %s. Ignoring it.",
1084
+ k,
1085
+ )
872
1086
  else:
873
1087
  self._logged_env_set(k, v)
874
1088
  # Call the dedicated method en registered MPI binaries
@@ -885,56 +1099,60 @@ class MpiTool(footprints.FootprintBase):
885
1099
  class MpiBinaryDescription(footprints.FootprintBase):
886
1100
  """Root class for any :class:`MpiBinaryDescription` subclass."""
887
1101
 
888
- _collector = ('mpibinary',)
1102
+ _collector = ("mpibinary",)
889
1103
  _abstract = True
890
1104
  _footprint = dict(
891
- info = 'Holds information about a given MPI binary',
892
- attr = dict(
893
- kind = dict(
894
- info = "A free form description of the binary's type",
895
- values = ['basic', ],
1105
+ info="Holds information about a given MPI binary",
1106
+ attr=dict(
1107
+ kind=dict(
1108
+ info="A free form description of the binary's type",
1109
+ values=[
1110
+ "basic",
1111
+ ],
1112
+ ),
1113
+ nodes=dict(
1114
+ info="The number of nodes for this MPI binary",
1115
+ type=int,
1116
+ optional=True,
1117
+ access="rwx",
896
1118
  ),
897
- nodes = dict(
898
- info = "The number of nodes for this MPI binary",
899
- type = int,
900
- optional = True,
901
- access = 'rwx'
1119
+ tasks=dict(
1120
+ info="The number of tasks per node for this MPI binary",
1121
+ type=int,
1122
+ optional=True,
1123
+ access="rwx",
902
1124
  ),
903
- tasks = dict(
904
- info = "The number of tasks per node for this MPI binary",
905
- type = int,
906
- optional = True,
907
- access = 'rwx'
1125
+ openmp=dict(
1126
+ info="The number of threads per task for this MPI binary",
1127
+ type=int,
1128
+ optional=True,
1129
+ access="rwx",
908
1130
  ),
909
- openmp = dict(
910
- info = "The number of threads per task for this MPI binary",
911
- type = int,
912
- optional = True,
913
- access = 'rwx'
1131
+ ranks=dict(
1132
+ info="The number of MPI ranks to use (only when working in an envelope)",
1133
+ type=int,
1134
+ optional=True,
1135
+ access="rwx",
914
1136
  ),
915
- ranks = dict(
916
- info = "The number of MPI ranks to use (only when working in an envelope)",
917
- type = int,
918
- optional = True,
919
- access = 'rwx'
1137
+ allowbind=dict(
1138
+ info="Allow the MpiTool to bind this executable",
1139
+ type=bool,
1140
+ optional=True,
1141
+ default=True,
920
1142
  ),
921
- allowbind = dict(
922
- info = "Allow the MpiTool to bind this executable",
923
- type = bool,
924
- optional = True,
925
- default = True,
1143
+ basics=dict(
1144
+ type=footprints.FPList,
1145
+ optional=True,
1146
+ default=footprints.FPList(
1147
+ ["system", "env", "target", "context"]
1148
+ ),
926
1149
  ),
927
- basics = dict(
928
- type = footprints.FPList,
929
- optional = True,
930
- default = footprints.FPList(['system', 'env', 'target', 'context'])
931
- )
932
- )
1150
+ ),
933
1151
  )
934
1152
 
935
1153
  def __init__(self, *args, **kw):
936
1154
  """After parent initialization, set master and options to undefined."""
937
- logger.debug('Abstract mpi tool init %s', self.__class__)
1155
+ logger.debug("Abstract mpi tool init %s", self.__class__)
938
1156
  super().__init__(*args, **kw)
939
1157
  self._master = None
940
1158
  self._arguments = ()
@@ -944,16 +1162,18 @@ class MpiBinaryDescription(footprints.FootprintBase):
944
1162
  def __getattr__(self, key):
945
1163
  """Have a look to basics values provided by some proxy."""
946
1164
  if key in self.basics:
947
- return getattr(self, '_' + key)
1165
+ return getattr(self, "_" + key)
948
1166
  else:
949
- raise AttributeError('Attribute [%s] is not a basic mpitool attribute' % key)
1167
+ raise AttributeError(
1168
+ "Attribute [%s] is not a basic mpitool attribute" % key
1169
+ )
950
1170
 
951
1171
  def import_basics(self, obj, attrs=None):
952
1172
  """Import some current values such as system, env, target and context from provided ``obj``."""
953
1173
  if attrs is None:
954
1174
  attrs = self.basics
955
1175
  for k in [x for x in attrs if x in self.basics and hasattr(obj, x)]:
956
- setattr(self, '_' + k, getattr(obj, k))
1176
+ setattr(self, "_" + k, getattr(obj, k))
957
1177
 
958
1178
  def _get_options(self):
959
1179
  """Retrieve the current set of MPI options."""
@@ -967,25 +1187,25 @@ class MpiBinaryDescription(footprints.FootprintBase):
967
1187
  if value is None:
968
1188
  value = dict()
969
1189
  if self.ranks is not None:
970
- self._options['np'] = self.ranks
1190
+ self._options["np"] = self.ranks
971
1191
  if self.nodes is not None or self.tasks is not None:
972
- raise ValueError('Incompatible options provided.')
1192
+ raise ValueError("Incompatible options provided.")
973
1193
  else:
974
1194
  if self.nodes is not None:
975
- self._options['nn'] = self.nodes
1195
+ self._options["nn"] = self.nodes
976
1196
  if self.tasks is not None:
977
- self._options['nnp'] = self.tasks
1197
+ self._options["nnp"] = self.tasks
978
1198
  if self.openmp is not None:
979
- self._options['openmp'] = self.openmp
1199
+ self._options["openmp"] = self.openmp
980
1200
  for k, v in value.items():
981
- self._options[k.lstrip('-').lower()] = v
1201
+ self._options[k.lstrip("-").lower()] = v
982
1202
 
983
1203
  options = property(_get_options, _set_options)
984
1204
 
985
1205
  def expanded_options(self):
986
1206
  """The MPI options actually used by the :class:`MpiTool` object to generate the command line."""
987
1207
  options = self.options.copy()
988
- options.setdefault('np', self.nprocs)
1208
+ options.setdefault("np", self.nprocs)
989
1209
  return options
990
1210
 
991
1211
  def _get_group(self):
@@ -1001,12 +1221,12 @@ class MpiBinaryDescription(footprints.FootprintBase):
1001
1221
  @property
1002
1222
  def nprocs(self):
1003
1223
  """Figure out what is the effective total number of tasks."""
1004
- if 'np' in self.options:
1005
- nbproc = int(self.options['np'])
1006
- elif 'nnp' in self.options and 'nn' in self.options:
1007
- nbproc = int(self.options.get('nnp')) * int(self.options.get('nn'))
1224
+ if "np" in self.options:
1225
+ nbproc = int(self.options["np"])
1226
+ elif "nnp" in self.options and "nn" in self.options:
1227
+ nbproc = int(self.options.get("nnp")) * int(self.options.get("nn"))
1008
1228
  else:
1009
- raise MpiException('Impossible to compute nprocs.')
1229
+ raise MpiException("Impossible to compute nprocs.")
1010
1230
  return nbproc
1011
1231
 
1012
1232
  def _get_master(self):
@@ -1030,7 +1250,7 @@ class MpiBinaryDescription(footprints.FootprintBase):
1030
1250
  elif isinstance(args, collections.abc.Iterable):
1031
1251
  self._arguments = [str(a) for a in args]
1032
1252
  else:
1033
- raise ValueError('Improper *args* argument provided.')
1253
+ raise ValueError("Improper *args* argument provided.")
1034
1254
 
1035
1255
  arguments = property(_get_arguments, _set_arguments)
1036
1256
 
@@ -1051,9 +1271,11 @@ class MpiEnvelopeBit(MpiBinaryDescription):
1051
1271
  """Set NPROC and NBPROC in namelists given the MPI distribution."""
1052
1272
 
1053
1273
  _footprint = dict(
1054
- attr = dict(
1055
- kind = dict(
1056
- values = ['basicenvelopebit', ],
1274
+ attr=dict(
1275
+ kind=dict(
1276
+ values=[
1277
+ "basicenvelopebit",
1278
+ ],
1057
1279
  ),
1058
1280
  )
1059
1281
  )
@@ -1061,10 +1283,10 @@ class MpiEnvelopeBit(MpiBinaryDescription):
1061
1283
 
1062
1284
  class MpiBinary(MpiBinaryDescription):
1063
1285
  _footprint = dict(
1064
- attr = dict(
1286
+ attr=dict(
1065
1287
  distribution=dict(
1066
1288
  info="Describes how the various nodes are distributed accross nodes",
1067
- values=['continuous', 'roundrobin'],
1289
+ values=["continuous", "roundrobin"],
1068
1290
  optional=True,
1069
1291
  ),
1070
1292
  )
@@ -1075,9 +1297,11 @@ class MpiBinaryBasic(MpiBinary):
1075
1297
  """Set NPROC and NBPROC in namelists given the MPI distribution."""
1076
1298
 
1077
1299
  _footprint = dict(
1078
- attr = dict(
1079
- kind = dict(
1080
- values = ['basicsingle', ],
1300
+ attr=dict(
1301
+ kind=dict(
1302
+ values=[
1303
+ "basicsingle",
1304
+ ],
1081
1305
  ),
1082
1306
  )
1083
1307
  )
@@ -1090,10 +1314,12 @@ class MpiBinaryBasic(MpiBinary):
1090
1314
  for nam_block in namcontents.values():
1091
1315
  nam_macros.update(nam_block.macros())
1092
1316
  # Look for relevant once
1093
- nprocs_macros = ('NPROC', 'NBPROC', 'NTASKS')
1317
+ nprocs_macros = ("NPROC", "NBPROC", "NTASKS")
1094
1318
  if any([n in nam_macros for n in nprocs_macros]):
1095
1319
  for n in nprocs_macros:
1096
- logger.info('Setup macro %s=%s in %s', n, self.nprocs, namlocal)
1320
+ logger.info(
1321
+ "Setup macro %s=%s in %s", n, self.nprocs, namlocal
1322
+ )
1097
1323
  namcontents.setmacro(n, self.nprocs)
1098
1324
  namw = True
1099
1325
  return namw
@@ -1103,16 +1329,18 @@ class MpiBinaryIOServer(MpiBinary):
1103
1329
  """Standard binary description for IO Server binaries."""
1104
1330
 
1105
1331
  _footprint = dict(
1106
- attr = dict(
1107
- kind = dict(
1108
- values = ['ioserv', ],
1332
+ attr=dict(
1333
+ kind=dict(
1334
+ values=[
1335
+ "ioserv",
1336
+ ],
1109
1337
  ),
1110
1338
  )
1111
1339
  )
1112
1340
 
1113
1341
  def __init__(self, *args, **kw):
1114
1342
  """After parent initialization, set launcher value."""
1115
- logger.debug('Abstract mpi tool init %s', self.__class__)
1343
+ logger.debug("Abstract mpi tool init %s", self.__class__)
1116
1344
  super().__init__(*args, **kw)
1117
1345
  thisenv = env.current()
1118
1346
  if self.ranks is None:
@@ -1136,28 +1364,22 @@ class MpiRun(MpiTool):
1136
1364
  """Standard MPI launcher on most systems: `mpirun`."""
1137
1365
 
1138
1366
  _footprint = dict(
1139
- attr = dict(
1140
- sysname = dict(
1141
- values = ['Linux', 'Darwin', 'UnitTestLinux']
1142
- ),
1143
- mpiname = dict(
1144
- values = ['mpirun', 'mpiperso', 'default'],
1145
- remap = dict(
1146
- default = 'mpirun'
1147
- ),
1367
+ attr=dict(
1368
+ sysname=dict(values=["Linux", "Darwin", "UnitTestLinux"]),
1369
+ mpiname=dict(
1370
+ values=["mpirun", "mpiperso", "default"],
1371
+ remap=dict(default="mpirun"),
1148
1372
  ),
1149
- optsep = dict(
1150
- default = '',
1373
+ optsep=dict(
1374
+ default="",
1151
1375
  ),
1152
- optprefix = dict(
1153
- default = '-',
1376
+ optprefix=dict(
1377
+ default="-",
1154
1378
  ),
1155
- optmap = dict(
1156
- default = footprints.FPDict(np='np', nnp='npernode')
1379
+ optmap=dict(default=footprints.FPDict(np="np", nnp="npernode")),
1380
+ binsep=dict(
1381
+ default=":",
1157
1382
  ),
1158
- binsep = dict(
1159
- default = ':',
1160
- )
1161
1383
  )
1162
1384
  )
1163
1385
 
@@ -1166,50 +1388,51 @@ class SRun(MpiTool):
1166
1388
  """SLURM's srun launcher."""
1167
1389
 
1168
1390
  _footprint = dict(
1169
- attr = dict(
1170
- sysname = dict(
1171
- values = ['Linux', 'UnitTestLinux']
1172
- ),
1173
- mpiname = dict(
1174
- values = ['srun', ],
1391
+ attr=dict(
1392
+ sysname=dict(values=["Linux", "UnitTestLinux"]),
1393
+ mpiname=dict(
1394
+ values=[
1395
+ "srun",
1396
+ ],
1175
1397
  ),
1176
- optsep = dict(
1177
- default = '',
1398
+ optsep=dict(
1399
+ default="",
1178
1400
  ),
1179
- optprefix = dict(
1180
- default = '--',
1401
+ optprefix=dict(
1402
+ default="--",
1181
1403
  ),
1182
- optmap = dict(
1183
- default = footprints.FPDict(nn='nodes', nnp='ntasks-per-node', np='ntasks')
1184
- ),
1185
- slurmversion = dict(
1186
- type = int,
1187
- optional = True
1404
+ optmap=dict(
1405
+ default=footprints.FPDict(
1406
+ nn="nodes", nnp="ntasks-per-node", np="ntasks"
1407
+ )
1188
1408
  ),
1189
- mpiwrapstd = dict(
1190
- default = True,
1409
+ slurmversion=dict(type=int, optional=True),
1410
+ mpiwrapstd=dict(
1411
+ default=True,
1191
1412
  ),
1192
- bindingmethod = dict(
1193
- info = 'How to bind the MPI processes',
1194
- values = ['native', 'vortex', ],
1195
- access = 'rwx',
1196
- optional = True,
1197
- doc_visibility = footprints.doc.visibility.ADVANCED,
1198
- doc_zorder = -90,
1413
+ bindingmethod=dict(
1414
+ info="How to bind the MPI processes",
1415
+ values=[
1416
+ "native",
1417
+ "vortex",
1418
+ ],
1419
+ access="rwx",
1420
+ optional=True,
1421
+ doc_visibility=footprints.doc.visibility.ADVANCED,
1422
+ doc_zorder=-90,
1199
1423
  ),
1200
1424
  )
1201
1425
  )
1202
1426
 
1203
- _envelope_nodelist_name = './global_envelope_nodelist'
1204
- _envelope_rank_var = 'SLURM_PROCID'
1427
+ _envelope_nodelist_name = "./global_envelope_nodelist"
1428
+ _envelope_rank_var = "SLURM_PROCID"
1205
1429
  _supports_manual_ranks_mapping = True
1206
1430
 
1207
1431
  @property
1208
1432
  def _actual_slurmversion(self):
1209
1433
  """Return the slurm major version number."""
1210
- slurmversion = (
1211
- self.slurmversion or
1212
- from_config(section="mpitool", key="slurmversion")
1434
+ slurmversion = self.slurmversion or from_config(
1435
+ section="mpitool", key="slurmversion"
1213
1436
  )
1214
1437
  if not slurmversion:
1215
1438
  raise ValueError("No slurm version specified")
@@ -1217,65 +1440,94 @@ class SRun(MpiTool):
1217
1440
 
1218
1441
  def _set_binaries_hack(self, binaries):
1219
1442
  """Set the list of :class:`MpiBinaryDescription` objects associated with this instance."""
1220
- if not self.envelope and len([binary for binary in binaries if binary.expanded_options()]) > 1:
1443
+ if (
1444
+ not self.envelope
1445
+ and len(
1446
+ [binary for binary in binaries if binary.expanded_options()]
1447
+ )
1448
+ > 1
1449
+ ):
1221
1450
  self._set_envelope_from_binaries()
1222
1451
 
1223
1452
  def _valid_envelope(self, value):
1224
1453
  """Tweak the envelope ddescription values."""
1225
1454
  for e in value:
1226
- if not ('nn' in e and 'nnp' in e):
1227
- raise MpiException("Srun needs a nn/nnp specification to build the envelope.")
1455
+ if not ("nn" in e and "nnp" in e):
1456
+ raise MpiException(
1457
+ "Srun needs a nn/nnp specification to build the envelope."
1458
+ )
1228
1459
 
1229
1460
  def _set_envelope(self, value):
1230
1461
  """Set the envelope description."""
1231
1462
  super()._set_envelope(value)
1232
- if len(self._envelope) > 1 and self.bindingmethod not in (None, 'vortex'):
1463
+ if len(self._envelope) > 1 and self.bindingmethod not in (
1464
+ None,
1465
+ "vortex",
1466
+ ):
1233
1467
  logger.warning("Resetting the binding method to 'Vortex'.")
1234
- self.bindingmethod = 'vortex'
1468
+ self.bindingmethod = "vortex"
1235
1469
 
1236
1470
  envelope = property(MpiTool._get_envelope, _set_envelope)
1237
1471
 
1238
1472
  def _set_binaries_envelope_hack(self, binaries):
1239
1473
  """Tweak the envelope after binaries were setup."""
1240
- if self.bindingmethod not in (None, 'vortex'):
1241
- openmps = {b.options.get('openmp', None) for b in binaries}
1474
+ if self.bindingmethod not in (None, "vortex"):
1475
+ openmps = {b.options.get("openmp", None) for b in binaries}
1242
1476
  if len(openmps) > 1:
1243
- logger.warning("Resetting the binding method to 'Vortex' because " +
1244
- "the number of threads is not uniform.")
1245
- self.bindingmethod = 'vortex'
1477
+ logger.warning(
1478
+ "Resetting the binding method to 'Vortex' because "
1479
+ + "the number of threads is not uniform."
1480
+ )
1481
+ self.bindingmethod = "vortex"
1246
1482
 
1247
1483
  @property
1248
1484
  def _cpubind_opt(self):
1249
- return self.optprefix + ('cpu_bind' if self._actual_slurmversion < 18
1250
- else 'cpu-bind')
1485
+ return self.optprefix + (
1486
+ "cpu_bind" if self._actual_slurmversion < 18 else "cpu-bind"
1487
+ )
1251
1488
 
1252
1489
  def _build_cpumask(self, cmdl, what, bsize):
1253
1490
  """Add a --cpu-bind option if needed."""
1254
1491
  cmdl.append(self._cpubind_opt)
1255
- if self.bindingmethod == 'native':
1492
+ if self.bindingmethod == "native":
1256
1493
  assert len(what) == 1, "Only one item is allowed."
1257
1494
  if what[0].allowbind:
1258
- ids = self.system.cpus_ids_per_blocks(blocksize=bsize,
1259
- topology=self.mpibind_topology,
1260
- hexmask=True)
1495
+ ids = self.system.cpus_ids_per_blocks(
1496
+ blocksize=bsize,
1497
+ topology=self.mpibind_topology,
1498
+ hexmask=True,
1499
+ )
1261
1500
  if not ids:
1262
- raise MpiException('Unable to detect the CPU layout with topology: {:s}'
1263
- .format(self.mpibind_topology, ))
1264
- masklist = [m for _, m in zip(range(what[0].options['nnp']),
1265
- itertools.cycle(ids))]
1266
- cmdl.append('mask_cpu:' + ','.join(masklist))
1501
+ raise MpiException(
1502
+ "Unable to detect the CPU layout with topology: {:s}".format(
1503
+ self.mpibind_topology,
1504
+ )
1505
+ )
1506
+ masklist = [
1507
+ m
1508
+ for _, m in zip(
1509
+ range(what[0].options["nnp"]), itertools.cycle(ids)
1510
+ )
1511
+ ]
1512
+ cmdl.append("mask_cpu:" + ",".join(masklist))
1267
1513
  else:
1268
- cmdl.append('none')
1514
+ cmdl.append("none")
1269
1515
  else:
1270
- cmdl.append('none')
1516
+ cmdl.append("none")
1271
1517
 
1272
1518
  def _simple_mkcmdline(self, cmdl):
1273
1519
  """Builds the MPI command line when no envelope is used.
1274
1520
 
1275
1521
  :param list[str] cmdl: the command line as a list
1276
1522
  """
1277
- target_bins = [binary for binary in self.binaries if len(binary.expanded_options())]
1278
- self._build_cpumask(cmdl, target_bins, target_bins[0].options.get('openmp', 1))
1523
+ target_bins = [
1524
+ binary
1525
+ for binary in self.binaries
1526
+ if len(binary.expanded_options())
1527
+ ]
1528
+ self._build_cpumask(
1529
+ cmdl, target_bins, target_bins[0].options.get("openmp", 1)
1530
+ )
1279
1531
  super()._simple_mkcmdline(cmdl)
1280
1532
 
1281
1533
  def _envelope_mkcmdline(self, cmdl):
@@ -1284,9 +1536,12 @@ class SRun(MpiTool):
1284
1536
  :param list[str] cmdl: the command line as a list
1285
1537
  """
1286
1538
  # Simple case, only one envelope description
1287
- has_bin_groups = not all([b.group is None for b in self.binaries])
1288
- openmps = {b.options.get('openmp', 1) for b in self.binaries}
1289
- if len(self.envelope) == 1 and not self._complex_ranks_mapping and len(openmps) == 1:
1539
+ openmps = {b.options.get("openmp", 1) for b in self.binaries}
1540
+ if (
1541
+ len(self.envelope) == 1
1542
+ and not self._complex_ranks_mapping
1543
+ and len(openmps) == 1
1544
+ ):
1290
1545
  self._build_cpumask(cmdl, self.envelope, openmps.pop())
1291
1546
  super()._envelope_mkcmdline(cmdl)
1292
1547
  # Multiple entries... use the nodelist stuff :-(
@@ -1295,15 +1550,24 @@ class SRun(MpiTool):
1295
1550
  base_nodelist = []
1296
1551
  totalnodes = 0
1297
1552
  totaltasks = 0
1298
- availnodes = itertools.cycle(xlist_strings(self.env.SLURM_NODELIST
1299
- if self._actual_slurmversion < 18
1300
- else self.env.SLURM_JOB_NODELIST))
1553
+ availnodes = itertools.cycle(
1554
+ xlist_strings(
1555
+ self.env.SLURM_NODELIST
1556
+ if self._actual_slurmversion < 18
1557
+ else self.env.SLURM_JOB_NODELIST
1558
+ )
1559
+ )
1301
1560
  for e_bit in self.envelope:
1302
1561
  totaltasks += e_bit.nprocs
1303
- for _ in range(e_bit.options['nn']):
1562
+ for _ in range(e_bit.options["nn"]):
1304
1563
  availnode = next(availnodes)
1305
- logger.debug('Node #%5d is: %s', totalnodes, availnode)
1306
- base_nodelist.extend([availnode, ] * e_bit.options['nnp'])
1564
+ logger.debug("Node #%5d is: %s", totalnodes, availnode)
1565
+ base_nodelist.extend(
1566
+ [
1567
+ availnode,
1568
+ ]
1569
+ * e_bit.options["nnp"]
1570
+ )
1307
1571
  totalnodes += 1
1308
1572
  # Re-order the nodelist based on the binary groups
1309
1573
  nodelist = list()
@@ -1313,20 +1577,20 @@ class SRun(MpiTool):
1313
1577
  else:
1314
1578
  nodelist.append(base_nodelist[i_rank])
1315
1579
  # Write it to the nodefile
1316
- with open(self._envelope_nodelist_name, 'w') as fhnl:
1580
+ with open(self._envelope_nodelist_name, "w") as fhnl:
1317
1581
  fhnl.write("\n".join(nodelist))
1318
1582
  # Generate wrappers
1319
1583
  self._envelope_mkwrapper(cmdl)
1320
1584
  wrapstd = self._wrapstd_mkwrapper()
1321
1585
  # Update the command line
1322
- cmdl.append(self.optprefix + 'nodelist')
1586
+ cmdl.append(self.optprefix + "nodelist")
1323
1587
  cmdl.append(self._envelope_nodelist_name)
1324
- cmdl.append(self.optprefix + 'ntasks')
1588
+ cmdl.append(self.optprefix + "ntasks")
1325
1589
  cmdl.append(str(totaltasks))
1326
- cmdl.append(self.optprefix + 'distribution')
1327
- cmdl.append('arbitrary')
1590
+ cmdl.append(self.optprefix + "distribution")
1591
+ cmdl.append("arbitrary")
1328
1592
  cmdl.append(self._cpubind_opt)
1329
- cmdl.append('none')
1593
+ cmdl.append("none")
1330
1594
  if wrapstd:
1331
1595
  cmdl.append(wrapstd)
1332
1596
  cmdl.append(e_bit.master)
@@ -1346,70 +1610,82 @@ class SRun(MpiTool):
1346
1610
  if not shp.exists(self.launcher):
1347
1611
  actlauncher = self.system.which(actlauncher)
1348
1612
  if not actlauncher:
1349
- logger.error('The SRun launcher could not be found.')
1613
+ logger.error("The SRun launcher could not be found.")
1350
1614
  return sdict
1351
- sdict['srunpath'] = actlauncher
1615
+ sdict["srunpath"] = actlauncher
1352
1616
  # Detect the path to the PMI library
1353
- pmilib = shp.normpath(shp.join(shp.dirname(actlauncher),
1354
- '..', 'lib64', 'libpmi.so'))
1617
+ pmilib = shp.normpath(
1618
+ shp.join(shp.dirname(actlauncher), "..", "lib64", "libpmi.so")
1619
+ )
1355
1620
  if not shp.exists(pmilib):
1356
- pmilib = shp.normpath(shp.join(shp.dirname(actlauncher),
1357
- '..', 'lib', 'libpmi.so'))
1621
+ pmilib = shp.normpath(
1622
+ shp.join(shp.dirname(actlauncher), "..", "lib", "libpmi.so")
1623
+ )
1358
1624
  if not shp.exists(pmilib):
1359
- logger.error('Could not find a PMI library')
1625
+ logger.error("Could not find a PMI library")
1360
1626
  return sdict
1361
- sdict['pmilib'] = pmilib
1627
+ sdict["pmilib"] = pmilib
1362
1628
  return sdict
1363
1629
 
1364
1630
  def setup_environment(self, opts):
1365
1631
  """Tweak the environment with some srun specific settings."""
1366
1632
  super().setup_environment(opts)
1367
- if (self._complex_ranks_mapping and
1368
- self._mpilib_identification() and
1369
- self._mpilib_identification()[3] == 'intelmpi'):
1370
- logger.info('(Sadly) with IntelMPI, I_MPI_SLURM_EXT=0 is needed when a complex arbitrary' +
1371
- 'ranks distribution is used. Exporting it !')
1372
- self.env['I_MPI_SLURM_EXT'] = 0
1633
+ if (
1634
+ self._complex_ranks_mapping
1635
+ and self._mpilib_identification()
1636
+ and self._mpilib_identification()[3] == "intelmpi"
1637
+ ):
1638
+ logger.info(
1639
+ "(Sadly) with IntelMPI, I_MPI_SLURM_EXT=0 is needed when a complex arbitrary"
1640
+ + "ranks distribution is used. Exporting it !"
1641
+ )
1642
+ self.env["I_MPI_SLURM_EXT"] = 0
1373
1643
  if len(self.binaries) == 1 and not self.envelope:
1374
- omp = self.binaries[0].options.get('openmp', None)
1644
+ omp = self.binaries[0].options.get("openmp", None)
1375
1645
  if omp is not None:
1376
- self._logged_env_set('OMP_NUM_THREADS', omp)
1377
- if self.bindingmethod == 'native' and 'OMP_PROC_BIND' not in self.env:
1378
- self._logged_env_set('OMP_PROC_BIND', 'true')
1646
+ self._logged_env_set("OMP_NUM_THREADS", omp)
1647
+ if self.bindingmethod == "native" and "OMP_PROC_BIND" not in self.env:
1648
+ self._logged_env_set("OMP_PROC_BIND", "true")
1379
1649
  # cleaning unwanted environment stuff
1380
1650
  unwanted = set()
1381
1651
  for k in self.env:
1382
- if k.startswith('SLURM_'):
1652
+ if k.startswith("SLURM_"):
1383
1653
  k = k[6:]
1384
- if (k in ('NTASKS', 'NPROCS') or
1385
- re.match('N?TASKS_PER_', k) or
1386
- re.match('N?CPUS_PER_', k)):
1654
+ if (
1655
+ k in ("NTASKS", "NPROCS")
1656
+ or re.match("N?TASKS_PER_", k)
1657
+ or re.match("N?CPUS_PER_", k)
1658
+ ):
1387
1659
  unwanted.add(k)
1388
1660
  for k in unwanted:
1389
- self.env.delvar('SLURM_{:s}'.format(k))
1661
+ self.env.delvar("SLURM_{:s}".format(k))
1390
1662
 
1391
1663
 
1392
1664
  class SRunDDT(SRun):
1393
1665
  """SLURM's srun launcher with ARM's DDT."""
1394
1666
 
1395
1667
  _footprint = dict(
1396
- attr = dict(
1397
- mpiname = dict(
1398
- values = ['srun-ddt', ],
1668
+ attr=dict(
1669
+ mpiname=dict(
1670
+ values=[
1671
+ "srun-ddt",
1672
+ ],
1399
1673
  ),
1400
1674
  )
1401
1675
  )
1402
1676
 
1403
- _conf_suffix = '-ddt'
1677
+ _conf_suffix = "-ddt"
1404
1678
 
1405
1679
  def mkcmdline(self):
1406
1680
  """Add the DDT prefix command to the command line"""
1407
1681
  cmdl = super().mkcmdline()
1408
1682
  armtool = ArmForgeTool(self.ticket)
1409
- for extra_c in reversed(armtool.ddt_prefix_cmd(
1410
- sources=self.sources,
1411
- workdir=self.system.path.dirname(self.binaries[0].master)
1412
- )):
1683
+ for extra_c in reversed(
1684
+ armtool.ddt_prefix_cmd(
1685
+ sources=self.sources,
1686
+ workdir=self.system.path.dirname(self.binaries[0].master),
1687
+ )
1688
+ ):
1413
1689
  cmdl.insert(0, extra_c)
1414
1690
  return cmdl
1415
1691
 
@@ -1418,45 +1694,48 @@ class OmpiMpiRun(MpiTool):
1418
1694
  """OpenMPI's mpirun launcher."""
1419
1695
 
1420
1696
  _footprint = dict(
1421
- attr = dict(
1422
- sysname = dict(
1423
- values = ['Linux', 'UnitTestLinux']
1424
- ),
1425
- mpiname = dict(
1426
- values = ['openmpi', ],
1697
+ attr=dict(
1698
+ sysname=dict(values=["Linux", "UnitTestLinux"]),
1699
+ mpiname=dict(
1700
+ values=[
1701
+ "openmpi",
1702
+ ],
1427
1703
  ),
1428
- optsep = dict(
1429
- default = '',
1704
+ optsep=dict(
1705
+ default="",
1430
1706
  ),
1431
- optprefix = dict(
1432
- default = '-',
1707
+ optprefix=dict(
1708
+ default="-",
1433
1709
  ),
1434
- optmap = dict(
1435
- default = footprints.FPDict(np='np', nnp='npernode', xopenmp='x')
1710
+ optmap=dict(
1711
+ default=footprints.FPDict(np="np", nnp="npernode", xopenmp="x")
1436
1712
  ),
1437
- binsep = dict(
1438
- default = ':',
1713
+ binsep=dict(
1714
+ default=":",
1439
1715
  ),
1440
- mpiwrapstd = dict(
1441
- default = True,
1716
+ mpiwrapstd=dict(
1717
+ default=True,
1442
1718
  ),
1443
- bindingmethod = dict(
1444
- info = 'How to bind the MPI processes',
1445
- values = ['native', 'vortex', ],
1446
- optional = True,
1447
- doc_visibility = footprints.doc.visibility.ADVANCED,
1448
- doc_zorder = -90,
1719
+ bindingmethod=dict(
1720
+ info="How to bind the MPI processes",
1721
+ values=[
1722
+ "native",
1723
+ "vortex",
1724
+ ],
1725
+ optional=True,
1726
+ doc_visibility=footprints.doc.visibility.ADVANCED,
1727
+ doc_zorder=-90,
1449
1728
  ),
1450
- preexistingenv = dict(
1451
- optional = True,
1452
- type = bool,
1453
- default = False,
1729
+ preexistingenv=dict(
1730
+ optional=True,
1731
+ type=bool,
1732
+ default=False,
1454
1733
  ),
1455
1734
  )
1456
1735
  )
1457
1736
 
1458
- _envelope_rankfile_name = './global_envelope_rankfile'
1459
- _envelope_rank_var = 'OMPI_COMM_WORLD_RANK'
1737
+ _envelope_rankfile_name = "./global_envelope_rankfile"
1738
+ _envelope_rank_var = "OMPI_COMM_WORLD_RANK"
1460
1739
  _supports_manual_ranks_mapping = True
1461
1740
 
1462
1741
  def _get_launcher(self):
@@ -1466,27 +1745,29 @@ class OmpiMpiRun(MpiTool):
1466
1745
  else:
1467
1746
  mpi_data = self._mpilib_data()
1468
1747
  if mpi_data:
1469
- return self.system.path.join(mpi_data[1], 'mpirun')
1748
+ return self.system.path.join(mpi_data[1], "mpirun")
1470
1749
  else:
1471
1750
  return self._launcher
1472
1751
 
1473
1752
  launcher = property(_get_launcher, MpiTool._set_launcher)
1474
1753
 
1475
1754
  def _set_binaries_hack(self, binaries):
1476
- if not self.envelope and self.bindingmethod == 'native':
1755
+ if not self.envelope and self.bindingmethod == "native":
1477
1756
  self._set_envelope_from_binaries()
1478
1757
 
1479
1758
  def _valid_envelope(self, value):
1480
1759
  """Tweak the envelope description values."""
1481
1760
  for e in value:
1482
- if not ('nn' in e and 'nnp' in e):
1483
- raise MpiException("OpenMPI/mpirun needs a nn/nnp specification " +
1484
- "to build the envelope.")
1761
+ if not ("nn" in e and "nnp" in e):
1762
+ raise MpiException(
1763
+ "OpenMPI/mpirun needs a nn/nnp specification "
1764
+ + "to build the envelope."
1765
+ )
1485
1766
 
1486
1767
  def _hook_binary_mpiopts(self, binary, options):
1487
- openmp = options.pop('openmp', None)
1768
+ openmp = options.pop("openmp", None)
1488
1769
  if openmp is not None:
1489
- options['xopenmp'] = 'OMP_NUM_THREADS={:d}'.format(openmp)
1770
+ options["xopenmp"] = "OMP_NUM_THREADS={:d}".format(openmp)
1490
1771
  return options
1491
1772
 
1492
1773
  def _simple_mkcmdline(self, cmdl):
@@ -1495,7 +1776,9 @@ class OmpiMpiRun(MpiTool):
1495
1776
  :param list[str] cmdl: the command line as a list
1496
1777
  """
1497
1778
  if self.bindingmethod is not None:
1498
- raise RuntimeError('If bindingmethod is set, an enveloppe should allways be used.')
1779
+ raise RuntimeError(
1780
+ "If bindingmethod is set, an enveloppe should allways be used."
1781
+ )
1499
1782
  super()._simple_mkcmdline(cmdl)
1500
1783
 
1501
1784
  def _create_rankfile(self, rankslist, nodeslist, slotslist):
@@ -1503,9 +1786,9 @@ class OmpiMpiRun(MpiTool):
1503
1786
 
1504
1787
  def _dump_slot_string(slot_strings, s_start, s_end):
1505
1788
  if s_start == s_end:
1506
- slot_strings.append('{:d}'.format(s_start))
1789
+ slot_strings.append("{:d}".format(s_start))
1507
1790
  else:
1508
- slot_strings.append('{:d}-{:d}'.format(s_start, s_end))
1791
+ slot_strings.append("{:d}-{:d}".format(s_start, s_end))
1509
1792
 
1510
1793
  for rank, node, slot in zip(rankslist, nodeslist, slotslist):
1511
1794
  slot_strings = list()
@@ -1520,20 +1803,26 @@ class OmpiMpiRun(MpiTool):
1520
1803
  s_end = s_start = s
1521
1804
  _dump_slot_string(slot_strings, s_start, s_end)
1522
1805
  rf_strings.append(
1523
- 'rank {:d}={:s} slot={:s}'.format(rank,
1524
- node,
1525
- ','.join(slot_strings))
1806
+ "rank {:d}={:s} slot={:s}".format(
1807
+ rank, node, ",".join(slot_strings)
1808
+ )
1526
1809
  )
1527
- logger.info('self.preexistingenv = {}'.format(self.preexistingenv))
1528
- if self.preexistingenv and self.system.path.exists(self._envelope_rankfile_name):
1529
- logger.info('envelope file found in the directory')
1810
+ logger.info("self.preexistingenv = {}".format(self.preexistingenv))
1811
+ if self.preexistingenv and self.system.path.exists(
1812
+ self._envelope_rankfile_name
1813
+ ):
1814
+ logger.info("envelope file found in the directory")
1530
1815
  else:
1531
1816
  if self.preexistingenv:
1532
- logger.info('preexistingenv set to true, but no envelope file found')
1533
- logger.info('Using vortex computed one')
1534
- logger.debug('Here is the rankfile content:\n%s', '\n'.join(rf_strings))
1535
- with open(self._envelope_rankfile_name, mode='w') as tmp_rf:
1536
- tmp_rf.write('\n'.join(rf_strings))
1817
+ logger.info(
1818
+ "preexistingenv set to true, but no envelope file found"
1819
+ )
1820
+ logger.info("Using vortex computed one")
1821
+ logger.debug(
1822
+ "Here is the rankfile content:\n%s", "\n".join(rf_strings)
1823
+ )
1824
+ with open(self._envelope_rankfile_name, mode="w") as tmp_rf:
1825
+ tmp_rf.write("\n".join(rf_strings))
1537
1826
  return self._envelope_rankfile_name
1538
1827
 
1539
1828
  def _envelope_nodelist(self):
@@ -1541,10 +1830,14 @@ class OmpiMpiRun(MpiTool):
1541
1830
  base_nodelist = []
1542
1831
  totalnodes = 0
1543
1832
  for e_bit in self.envelope:
1544
- for i_node in range(e_bit.options['nn']):
1545
- logger.debug('Node #%5d is: +n%d', i_node, totalnodes)
1546
- base_nodelist.extend(['+n{:d}'.format(totalnodes), ] *
1547
- e_bit.options['nnp'])
1833
+ for i_node in range(e_bit.options["nn"]):
1834
+ logger.debug("Node #%5d is: +n%d", i_node, totalnodes)
1835
+ base_nodelist.extend(
1836
+ [
1837
+ "+n{:d}".format(totalnodes),
1838
+ ]
1839
+ * e_bit.options["nnp"]
1840
+ )
1548
1841
  totalnodes += 1
1549
1842
  return base_nodelist
1550
1843
 
@@ -1553,12 +1846,14 @@ class OmpiMpiRun(MpiTool):
1553
1846
 
1554
1847
  :param list[str] args: the command line as a list
1555
1848
  """
1556
- cmdl.append(self.optprefix + 'oversubscribe')
1557
- if self.bindingmethod in (None, 'native'):
1849
+ cmdl.append(self.optprefix + "oversubscribe")
1850
+ if self.bindingmethod in (None, "native"):
1558
1851
  # Generate the dictionary that associate rank numbers and programs
1559
1852
  todostack, ranks_bsize = self._envelope_mkwrapper_todostack()
1560
1853
  # Generate the binding stuff
1561
- bindingstack, _ = self._envelope_mkwrapper_bindingstack(ranks_bsize)
1854
+ bindingstack, _ = self._envelope_mkwrapper_bindingstack(
1855
+ ranks_bsize
1856
+ )
1562
1857
  # Generate a relative nodelist
1563
1858
  base_nodelist = self._envelope_nodelist()
1564
1859
  # Generate the rankfile
@@ -1567,21 +1862,23 @@ class OmpiMpiRun(MpiTool):
1567
1862
  if bindingstack:
1568
1863
  slots = [bindingstack[r] for r in ranks]
1569
1864
  else:
1570
- slots = [sorted(self.system.cpus_info.cpus.keys()), ] * len(ranks)
1865
+ slots = [
1866
+ sorted(self.system.cpus_info.cpus.keys()),
1867
+ ] * len(ranks)
1571
1868
  rfile = self._create_rankfile(ranks, nodes, slots)
1572
1869
  # Add the rankfile on the command line
1573
- cmdl.append(self.optprefix + 'rankfile')
1870
+ cmdl.append(self.optprefix + "rankfile")
1574
1871
  cmdl.append(rfile)
1575
1872
  # Add the "usual" call to binaries and setup OMP_NUM_THREADS values
1576
1873
  wrapstd = self._wrapstd_mkwrapper()
1577
1874
  for i_bin, a_bin in enumerate(self.binaries):
1578
1875
  if i_bin > 0:
1579
1876
  cmdl.append(self.binsep)
1580
- openmp = a_bin.options.get('openmp', None)
1877
+ openmp = a_bin.options.get("openmp", None)
1581
1878
  if openmp:
1582
- cmdl.append(self.optprefix + 'x')
1583
- cmdl.append('OMP_NUM_THREADS={!s}'.format(openmp))
1584
- cmdl.append(self.optprefix + 'np')
1879
+ cmdl.append(self.optprefix + "x")
1880
+ cmdl.append("OMP_NUM_THREADS={!s}".format(openmp))
1881
+ cmdl.append(self.optprefix + "np")
1585
1882
  cmdl.append(str(a_bin.nprocs))
1586
1883
  if wrapstd:
1587
1884
  cmdl.append(wrapstd)
@@ -1591,17 +1888,21 @@ class OmpiMpiRun(MpiTool):
1591
1888
  # Generate a host file but let vortex deal with the rest...
1592
1889
  base_nodelist = self._envelope_nodelist()
1593
1890
  ranks = list(range(len(base_nodelist)))
1594
- rfile = self._create_rankfile(ranks,
1595
- [base_nodelist[self._ranks_mapping[r]] for r in ranks],
1596
- [sorted(self.system.cpus_info.cpus.keys()), ]
1597
- * len(base_nodelist))
1891
+ rfile = self._create_rankfile(
1892
+ ranks,
1893
+ [base_nodelist[self._ranks_mapping[r]] for r in ranks],
1894
+ [
1895
+ sorted(self.system.cpus_info.cpus.keys()),
1896
+ ]
1897
+ * len(base_nodelist),
1898
+ )
1598
1899
  # Generate wrappers
1599
1900
  self._envelope_mkwrapper(cmdl)
1600
1901
  wrapstd = self._wrapstd_mkwrapper()
1601
1902
  # Update the command line
1602
- cmdl.append(self.optprefix + 'rankfile')
1903
+ cmdl.append(self.optprefix + "rankfile")
1603
1904
  cmdl.append(rfile)
1604
- cmdl.append(self.optprefix + 'np')
1905
+ cmdl.append(self.optprefix + "np")
1605
1906
  cmdl.append(str(len(base_nodelist)))
1606
1907
  if wrapstd:
1607
1908
  cmdl.append(wrapstd)
@@ -1616,33 +1917,37 @@ class OmpiMpiRun(MpiTool):
1616
1917
  def setup_environment(self, opts):
1617
1918
  """Tweak the environment with some srun specific settings."""
1618
1919
  super().setup_environment(opts)
1619
- if self.bindingmethod == 'native' and 'OMP_PROC_BIND' not in self.env:
1620
- self._logged_env_set('OMP_PROC_BIND', 'true')
1920
+ if self.bindingmethod == "native" and "OMP_PROC_BIND" not in self.env:
1921
+ self._logged_env_set("OMP_PROC_BIND", "true")
1621
1922
  for libpath in self._mpilib_identification()[2]:
1622
1923
  logger.info('Adding "%s" to LD_LIBRARY_PATH', libpath)
1623
- self.env.setgenericpath('LD_LIBRARY_PATH', libpath)
1924
+ self.env.setgenericpath("LD_LIBRARY_PATH", libpath)
1624
1925
 
1625
1926
 
1626
1927
  class OmpiMpiRunDDT(OmpiMpiRun):
1627
1928
  """SLURM's srun launcher with ARM's DDT."""
1628
1929
 
1629
1930
  _footprint = dict(
1630
- attr = dict(
1631
- mpiname = dict(
1632
- values = ['openmpi-ddt', ],
1931
+ attr=dict(
1932
+ mpiname=dict(
1933
+ values=[
1934
+ "openmpi-ddt",
1935
+ ],
1633
1936
  ),
1634
1937
  )
1635
1938
  )
1636
1939
 
1637
- _conf_suffix = '-ddt'
1940
+ _conf_suffix = "-ddt"
1638
1941
 
1639
1942
  def mkcmdline(self):
1640
1943
  """Add the DDT prefix command to the command line"""
1641
1944
  cmdl = super(OmpiMpiRun, self).mkcmdline()
1642
1945
  armtool = ArmForgeTool(self.ticket)
1643
- for extra_c in reversed(armtool.ddt_prefix_cmd(
1644
- sources=self.sources,
1645
- workdir=self.system.path.dirname(self.binaries[0].master)
1646
- )):
1946
+ for extra_c in reversed(
1947
+ armtool.ddt_prefix_cmd(
1948
+ sources=self.sources,
1949
+ workdir=self.system.path.dirname(self.binaries[0].master),
1950
+ )
1951
+ ):
1647
1952
  cmdl.insert(0, extra_c)
1648
1953
  return cmdl