vortex-nwp 2.0.0b1__py3-none-any.whl → 2.0.0b2__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 (139) hide show
  1. vortex/__init__.py +59 -45
  2. vortex/algo/__init__.py +3 -2
  3. vortex/algo/components.py +940 -614
  4. vortex/algo/mpitools.py +802 -497
  5. vortex/algo/serversynctools.py +34 -33
  6. vortex/config.py +19 -22
  7. vortex/data/__init__.py +9 -3
  8. vortex/data/abstractstores.py +593 -655
  9. vortex/data/containers.py +217 -162
  10. vortex/data/contents.py +65 -39
  11. vortex/data/executables.py +93 -102
  12. vortex/data/flow.py +40 -34
  13. vortex/data/geometries.py +228 -132
  14. vortex/data/handlers.py +428 -225
  15. vortex/data/outflow.py +15 -15
  16. vortex/data/providers.py +185 -163
  17. vortex/data/resources.py +48 -42
  18. vortex/data/stores.py +544 -413
  19. vortex/gloves.py +114 -87
  20. vortex/layout/__init__.py +1 -8
  21. vortex/layout/contexts.py +150 -84
  22. vortex/layout/dataflow.py +353 -202
  23. vortex/layout/monitor.py +264 -128
  24. vortex/nwp/__init__.py +5 -2
  25. vortex/nwp/algo/__init__.py +14 -5
  26. vortex/nwp/algo/assim.py +205 -151
  27. vortex/nwp/algo/clim.py +683 -517
  28. vortex/nwp/algo/coupling.py +447 -225
  29. vortex/nwp/algo/eda.py +437 -229
  30. vortex/nwp/algo/eps.py +403 -231
  31. vortex/nwp/algo/forecasts.py +420 -271
  32. vortex/nwp/algo/fpserver.py +683 -307
  33. vortex/nwp/algo/ifsnaming.py +205 -145
  34. vortex/nwp/algo/ifsroot.py +210 -122
  35. vortex/nwp/algo/monitoring.py +132 -76
  36. vortex/nwp/algo/mpitools.py +321 -191
  37. vortex/nwp/algo/odbtools.py +617 -353
  38. vortex/nwp/algo/oopsroot.py +449 -273
  39. vortex/nwp/algo/oopstests.py +90 -56
  40. vortex/nwp/algo/request.py +287 -206
  41. vortex/nwp/algo/stdpost.py +878 -522
  42. vortex/nwp/data/__init__.py +22 -4
  43. vortex/nwp/data/assim.py +125 -137
  44. vortex/nwp/data/boundaries.py +121 -68
  45. vortex/nwp/data/climfiles.py +193 -211
  46. vortex/nwp/data/configfiles.py +73 -69
  47. vortex/nwp/data/consts.py +426 -401
  48. vortex/nwp/data/ctpini.py +59 -43
  49. vortex/nwp/data/diagnostics.py +94 -66
  50. vortex/nwp/data/eda.py +50 -51
  51. vortex/nwp/data/eps.py +195 -146
  52. vortex/nwp/data/executables.py +440 -434
  53. vortex/nwp/data/fields.py +63 -48
  54. vortex/nwp/data/gridfiles.py +183 -111
  55. vortex/nwp/data/logs.py +250 -217
  56. vortex/nwp/data/modelstates.py +180 -151
  57. vortex/nwp/data/monitoring.py +72 -99
  58. vortex/nwp/data/namelists.py +254 -202
  59. vortex/nwp/data/obs.py +400 -308
  60. vortex/nwp/data/oopsexec.py +22 -20
  61. vortex/nwp/data/providers.py +90 -65
  62. vortex/nwp/data/query.py +71 -82
  63. vortex/nwp/data/stores.py +49 -36
  64. vortex/nwp/data/surfex.py +136 -137
  65. vortex/nwp/syntax/__init__.py +1 -1
  66. vortex/nwp/syntax/stdattrs.py +173 -111
  67. vortex/nwp/tools/__init__.py +2 -2
  68. vortex/nwp/tools/addons.py +22 -17
  69. vortex/nwp/tools/agt.py +24 -12
  70. vortex/nwp/tools/bdap.py +16 -5
  71. vortex/nwp/tools/bdcp.py +4 -1
  72. vortex/nwp/tools/bdm.py +3 -0
  73. vortex/nwp/tools/bdmp.py +14 -9
  74. vortex/nwp/tools/conftools.py +728 -378
  75. vortex/nwp/tools/drhook.py +12 -8
  76. vortex/nwp/tools/grib.py +65 -39
  77. vortex/nwp/tools/gribdiff.py +22 -17
  78. vortex/nwp/tools/ifstools.py +82 -42
  79. vortex/nwp/tools/igastuff.py +167 -143
  80. vortex/nwp/tools/mars.py +14 -2
  81. vortex/nwp/tools/odb.py +234 -125
  82. vortex/nwp/tools/partitioning.py +61 -37
  83. vortex/nwp/tools/satrad.py +27 -12
  84. vortex/nwp/util/async.py +83 -55
  85. vortex/nwp/util/beacon.py +10 -10
  86. vortex/nwp/util/diffpygram.py +174 -86
  87. vortex/nwp/util/ens.py +144 -63
  88. vortex/nwp/util/hooks.py +30 -19
  89. vortex/nwp/util/taskdeco.py +28 -24
  90. vortex/nwp/util/usepygram.py +278 -172
  91. vortex/nwp/util/usetnt.py +31 -17
  92. vortex/sessions.py +72 -39
  93. vortex/syntax/__init__.py +1 -1
  94. vortex/syntax/stdattrs.py +410 -171
  95. vortex/syntax/stddeco.py +31 -22
  96. vortex/toolbox.py +327 -192
  97. vortex/tools/__init__.py +11 -2
  98. vortex/tools/actions.py +125 -59
  99. vortex/tools/addons.py +111 -92
  100. vortex/tools/arm.py +42 -22
  101. vortex/tools/compression.py +72 -69
  102. vortex/tools/date.py +11 -4
  103. vortex/tools/delayedactions.py +242 -132
  104. vortex/tools/env.py +75 -47
  105. vortex/tools/folder.py +342 -171
  106. vortex/tools/grib.py +311 -149
  107. vortex/tools/lfi.py +423 -216
  108. vortex/tools/listings.py +109 -40
  109. vortex/tools/names.py +218 -156
  110. vortex/tools/net.py +632 -298
  111. vortex/tools/parallelism.py +93 -61
  112. vortex/tools/prestaging.py +55 -31
  113. vortex/tools/schedulers.py +172 -105
  114. vortex/tools/services.py +402 -333
  115. vortex/tools/storage.py +293 -358
  116. vortex/tools/surfex.py +24 -24
  117. vortex/tools/systems.py +1211 -631
  118. vortex/tools/targets.py +156 -100
  119. vortex/util/__init__.py +1 -1
  120. vortex/util/config.py +377 -327
  121. vortex/util/empty.py +2 -2
  122. vortex/util/helpers.py +56 -24
  123. vortex/util/introspection.py +18 -12
  124. vortex/util/iosponge.py +8 -4
  125. vortex/util/roles.py +4 -6
  126. vortex/util/storefunctions.py +39 -13
  127. vortex/util/structs.py +3 -3
  128. vortex/util/worker.py +29 -17
  129. vortex_nwp-2.0.0b2.dist-info/METADATA +66 -0
  130. vortex_nwp-2.0.0b2.dist-info/RECORD +142 -0
  131. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/WHEEL +1 -1
  132. vortex/layout/appconf.py +0 -109
  133. vortex/layout/jobs.py +0 -1276
  134. vortex/layout/nodes.py +0 -1424
  135. vortex/layout/subjobs.py +0 -464
  136. vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
  137. vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
  138. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/LICENSE +0 -0
  139. {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.dist-info}/top_level.txt +0 -0
@@ -11,7 +11,11 @@ from bronx.fancies.dump import lightdump, fulldump
11
11
  from bronx.stdtypes.date import Date, Time, Period
12
12
  from bronx.compat.functools import cached_property
13
13
 
14
- from vortex.algo.components import AlgoComponentError, AlgoComponentDecoMixin, Parallel
14
+ from vortex.algo.components import (
15
+ AlgoComponentError,
16
+ AlgoComponentDecoMixin,
17
+ Parallel,
18
+ )
15
19
  from vortex.algo.components import algo_component_deco_mixin_autodoc
16
20
  from vortex.data import geometries
17
21
  from vortex.tools import grib
@@ -25,7 +29,7 @@ __all__ = []
25
29
  logger = footprints.loggers.getLogger(__name__)
26
30
 
27
31
 
28
- OOPSMemberInfos = namedtuple('OOPSMemberInfos', ('member', 'date'))
32
+ OOPSMemberInfos = namedtuple("OOPSMemberInfos", ("member", "date"))
29
33
 
30
34
 
31
35
  class EnsSizeAlgoComponentError(AlgoComponentError):
@@ -35,11 +39,17 @@ class EnsSizeAlgoComponentError(AlgoComponentError):
35
39
  self.nominal_ens_size = nominal_ens_size
36
40
  self.actual_ens_size = actual_ens_size
37
41
  self.min_ens_size = min_ens_size
38
- super().__init__('{:d} found ({:d} required)'.format(actual_ens_size, min_ens_size))
42
+ super().__init__(
43
+ "{:d} found ({:d} required)".format(actual_ens_size, min_ens_size)
44
+ )
39
45
 
40
46
  def __reduce__(self):
41
47
  red = list(super().__reduce__())
42
- red[1] = (self.nominal_ens_size, self.actual_ens_size, self.min_ens_size)
48
+ red[1] = (
49
+ self.nominal_ens_size,
50
+ self.actual_ens_size,
51
+ self.min_ens_size,
52
+ )
43
53
  return tuple(red)
44
54
 
45
55
 
@@ -47,17 +57,17 @@ class EnsSizeAlgoComponentError(AlgoComponentError):
47
57
  class OOPSMemberDecoMixin(AlgoComponentDecoMixin):
48
58
  """Add a member footprints' attribute and use it in the configuration files."""
49
59
 
50
- _MIXIN_EXTRA_FOOTPRINTS = (algo_member, )
60
+ _MIXIN_EXTRA_FOOTPRINTS = (algo_member,)
51
61
 
52
62
  def _algo_member_deco_setup(self, rh, opts): # @UnusedVariable
53
63
  """Update the configuration files."""
54
64
  if self.member is not None:
55
- self._generic_config_subs['member'] = self.member
65
+ self._generic_config_subs["member"] = self.member
56
66
  for namrh in self.updatable_namelists:
57
- namrh.contents.setmacro('MEMBER', self.member)
58
- namrh.contents.setmacro('PERTURB', self.member)
67
+ namrh.contents.setmacro("MEMBER", self.member)
68
+ namrh.contents.setmacro("PERTURB", self.member)
59
69
 
60
- _MIXIN_PREPARE_HOOKS = (_algo_member_deco_setup, )
70
+ _MIXIN_PREPARE_HOOKS = (_algo_member_deco_setup,)
61
71
 
62
72
 
63
73
  @algo_component_deco_mixin_autodoc
@@ -73,42 +83,49 @@ class OOPSMembersTermsDetectDecoMixin(AlgoComponentDecoMixin):
73
83
  :note: Effective terms are considered (i.e term - (current_date - resource_date))
74
84
  """
75
85
 
76
- _membersdetect_roles = tuple(p + r
77
- for p in ('', 'Ensemble')
78
- for r in ('ModelState',
79
- 'Guess',
80
- 'InitialCondition',
81
- 'Background',
82
- 'SurfaceModelState',
83
- 'SurfaceGuess',
84
- 'SurfaceInitialCondition',
85
- 'SurfaceBackground',)
86
- )
87
-
88
- _MIXIN_EXTRA_FOOTPRINTS = (footprints.Footprint(
89
- info="Abstract mbdetect footprint",
90
- attr=dict(
91
- ens_minsize=dict(
92
- info="For a multi-member algocomponent, the minimum of the ensemble.",
93
- optional=True,
94
- type=int
95
- ),
96
- ens_failure_conf_objects=dict(
97
- info="For a multi-member algocomponent, alternative config file when the ensemble is too small.",
98
- optional=True,
99
- ),
100
- strict_mbdetect=dict(
101
- info="Performs a strict members/terms detection",
102
- type=bool,
103
- optional=True,
104
- default=True,
105
- doc_zorder=-60,
106
- )
86
+ _membersdetect_roles = tuple(
87
+ p + r
88
+ for p in ("", "Ensemble")
89
+ for r in (
90
+ "ModelState",
91
+ "Guess",
92
+ "InitialCondition",
93
+ "Background",
94
+ "SurfaceModelState",
95
+ "SurfaceGuess",
96
+ "SurfaceInitialCondition",
97
+ "SurfaceBackground",
107
98
  )
108
- ),)
99
+ )
100
+
101
+ _MIXIN_EXTRA_FOOTPRINTS = (
102
+ footprints.Footprint(
103
+ info="Abstract mbdetect footprint",
104
+ attr=dict(
105
+ ens_minsize=dict(
106
+ info="For a multi-member algocomponent, the minimum of the ensemble.",
107
+ optional=True,
108
+ type=int,
109
+ ),
110
+ ens_failure_conf_objects=dict(
111
+ info="For a multi-member algocomponent, alternative config file when the ensemble is too small.",
112
+ optional=True,
113
+ ),
114
+ strict_mbdetect=dict(
115
+ info="Performs a strict members/terms detection",
116
+ type=bool,
117
+ optional=True,
118
+ default=True,
119
+ doc_zorder=-60,
120
+ ),
121
+ ),
122
+ ),
123
+ )
109
124
 
110
125
  @staticmethod
111
- def _stateless_members_detect(smap, basedate, section_check_cb, ensminsize=None, utest=False):
126
+ def _stateless_members_detect(
127
+ smap, basedate, section_check_cb, ensminsize=None, utest=False
128
+ ):
112
129
  """
113
130
  This method does not really need to be static but this way it allows for
114
131
  unit-testing (see ``tests.tests_algo.test_oopspara.py``).
@@ -124,15 +141,22 @@ class OOPSMembersTermsDetectDecoMixin(AlgoComponentDecoMixin):
124
141
  for arole, srole in smap.items():
125
142
  # Gather data
126
143
  for s in srole:
127
- minfo = OOPSMemberInfos(getattr(s.rh.provider, 'member', None),
128
- getattr(s.rh.resource, 'date', None))
129
- allmembers[arole][minfo][getattr(s.rh.resource, 'term', None)].add(s)
144
+ minfo = OOPSMemberInfos(
145
+ getattr(s.rh.provider, "member", None),
146
+ getattr(s.rh.resource, "date", None),
147
+ )
148
+ allmembers[arole][minfo][
149
+ getattr(s.rh.resource, "term", None)
150
+ ].add(s)
130
151
  # Sanity checks and filtering
131
152
  role_members_info = set(allmembers[arole].keys())
132
153
  if None in {a_member.member for a_member in role_members_info}:
133
154
  # Ignore sections when some sections have no members defined
134
155
  if len(role_members_info) > 1:
135
- logger.warning('Role: %s. Only some sections have a member number.', arole)
156
+ logger.warning(
157
+ "Role: %s. Only some sections have a member number.",
158
+ arole,
159
+ )
136
160
  role_members_info = set()
137
161
  if len(role_members_info) > 1:
138
162
  if not members:
@@ -140,7 +164,9 @@ class OOPSMembersTermsDetectDecoMixin(AlgoComponentDecoMixin):
140
164
  else:
141
165
  # Consistency check on members numbering
142
166
  if members != role_members_info:
143
- raise AlgoComponentError('Inconsistent members numbering')
167
+ raise AlgoComponentError(
168
+ "Inconsistent members numbering"
169
+ )
144
170
  else:
145
171
  # If there is only one member, ignore it: it's not really an ensemble!
146
172
  del allmembers[arole]
@@ -155,14 +181,19 @@ class OOPSMembersTermsDetectDecoMixin(AlgoComponentDecoMixin):
155
181
  # Be verbose...
156
182
  if lagged:
157
183
  for a_date, a_mset in members_by_date.items():
158
- logger.info('Members detected from date=%s: %s',
159
- a_date, ','.join(sorted(str(m) for m in a_mset)))
184
+ logger.info(
185
+ "Members detected from date=%s: %s",
186
+ a_date,
187
+ ",".join(sorted(str(m) for m in a_mset)),
188
+ )
160
189
  else:
161
- logger.info('Members detected: %s',
162
- ','.join(sorted(str(m[0]) for m in members)))
163
- logger.info('Total number of detected members: %d', len(members))
190
+ logger.info(
191
+ "Members detected: %s",
192
+ ",".join(sorted(str(m[0]) for m in members)),
193
+ )
194
+ logger.info("Total number of detected members: %d", len(members))
164
195
  r_members = sorted(allmembers.keys())
165
- logger.info('Members roles: %s', ','.join(r_members))
196
+ logger.info("Members roles: %s", ",".join(r_members))
166
197
 
167
198
  # Look for effective terms
168
199
  alleffterms = dict()
@@ -171,24 +202,30 @@ class OOPSMembersTermsDetectDecoMixin(AlgoComponentDecoMixin):
171
202
  for minfo, mterms in srole.items():
172
203
  effterms = set()
173
204
  for term in mterms.keys():
174
- effterms.add(term - (basedate - minfo.date)
175
- if term is not None and minfo.date is not None
176
- else None)
205
+ effterms.add(
206
+ term - (basedate - minfo.date)
207
+ if term is not None and minfo.date is not None
208
+ else None
209
+ )
177
210
  # Intra-role consistency
178
211
  if first_effterms is None:
179
212
  first_effterms = effterms
180
213
  else:
181
214
  # Consistency check on members numbering
182
215
  if effterms != first_effterms:
183
- raise AlgoComponentError('Inconsistent effective terms between members sets (role={:s})'
184
- .format(arole))
216
+ raise AlgoComponentError(
217
+ "Inconsistent effective terms between members sets (role={:s})".format(
218
+ arole
219
+ )
220
+ )
185
221
  # If there are more than one term, consider it
186
222
  if len(first_effterms) > 1:
187
223
  # Check that there is no None in the way
188
224
  if None in first_effterms:
189
225
  raise AlgoComponentError(
190
- 'For a given role, all of the resources or none of then should have a term (role={:s})'
191
- .format(arole)
226
+ "For a given role, all of the resources or none of then should have a term (role={:s})".format(
227
+ arole
228
+ )
192
229
  )
193
230
  # Remove Nones
194
231
  first_effterms = {e for e in first_effterms if e is not None}
@@ -200,19 +237,34 @@ class OOPSMembersTermsDetectDecoMixin(AlgoComponentDecoMixin):
200
237
  l_effterms = []
201
238
  if alleffterms:
202
239
  # Hard check only when multiple effetive terms are found
203
- multieffterms = {r: ets for r, ets in alleffterms.items() if len(ets) > 1}
240
+ multieffterms = {
241
+ r: ets for r, ets in alleffterms.items() if len(ets) > 1
242
+ }
204
243
  if multieffterms:
205
- if sum(1 for _ in itertools.groupby(multieffterms.values())) > 1:
206
- raise AlgoComponentError('Inconsistent effective terms between relevant roles')
244
+ if (
245
+ sum(1 for _ in itertools.groupby(multieffterms.values()))
246
+ > 1
247
+ ):
248
+ raise AlgoComponentError(
249
+ "Inconsistent effective terms between relevant roles"
250
+ )
207
251
  r_effterms = sorted(multieffterms.keys())
208
252
  _, l_effterms = multieffterms.popitem()
209
253
  else:
210
- if sum(1 for _ in itertools.groupby(alleffterms.values())) == 1:
254
+ if (
255
+ sum(1 for _ in itertools.groupby(alleffterms.values()))
256
+ == 1
257
+ ):
211
258
  r_effterms = sorted(alleffterms.keys())
212
259
  _, l_effterms = alleffterms.popitem()
213
260
  l_effterms = sorted(l_effterms)
214
- logger.info('Effective terms detected: %s', ','.join([str(t) for t in effterms]))
215
- logger.info('Terms roles: %s', ','.join(sorted(alleffterms.keys())))
261
+ logger.info(
262
+ "Effective terms detected: %s",
263
+ ",".join([str(t) for t in effterms]),
264
+ )
265
+ logger.info(
266
+ "Terms roles: %s", ",".join(sorted(alleffterms.keys()))
267
+ )
216
268
 
217
269
  # Theoretical ensemble size
218
270
  nominal_ens_size = len(members)
@@ -222,55 +274,87 @@ class OOPSMembersTermsDetectDecoMixin(AlgoComponentDecoMixin):
222
274
  # Look for missing resources in the various relevant roles
223
275
  broken = list()
224
276
  for arole in allmembers.keys():
225
- broken.extend([(s, arole)
226
- for t, slist in allmembers[arole][mb].items()
227
- for s in slist
228
- if not section_check_cb(s)])
277
+ broken.extend(
278
+ [
279
+ (s, arole)
280
+ for t, slist in allmembers[arole][mb].items()
281
+ for s in slist
282
+ if not section_check_cb(s)
283
+ ]
284
+ )
229
285
  for s, arole in broken:
230
286
  if not utest:
231
- logger.warning('Missing items: %s (role: %s).',
232
- s.rh.container.localpath(), arole)
287
+ logger.warning(
288
+ "Missing items: %s (role: %s).",
289
+ s.rh.container.localpath(),
290
+ arole,
291
+ )
233
292
  if broken:
234
- logger.warning('Throwing away member: %s', mb)
293
+ logger.warning("Throwing away member: %s", mb)
235
294
  else:
236
295
  eff_members.add(mb)
237
296
  # Sanity checks depending on ensminsize
238
297
  if ensminsize is None and len(eff_members) != nominal_ens_size:
239
- raise EnsSizeAlgoComponentError(nominal_ens_size, len(eff_members), nominal_ens_size)
298
+ raise EnsSizeAlgoComponentError(
299
+ nominal_ens_size, len(eff_members), nominal_ens_size
300
+ )
240
301
  elif ensminsize is not None and len(eff_members) < ensminsize:
241
- raise EnsSizeAlgoComponentError(nominal_ens_size, len(eff_members), ensminsize)
302
+ raise EnsSizeAlgoComponentError(
303
+ nominal_ens_size, len(eff_members), ensminsize
304
+ )
242
305
 
243
306
  members = eff_members
244
307
 
245
308
  l_members = [m.member for m in sorted(members)]
246
309
  l_members_d = [m.date for m in sorted(members)]
247
- l_members_o = [None if m.date is None else (basedate - m.date)
248
- for m in sorted(members)]
249
-
250
- return (l_members, l_members_d, l_members_o, l_effterms,
251
- lagged, nominal_ens_size, r_members, r_effterms)
310
+ l_members_o = [
311
+ None if m.date is None else (basedate - m.date)
312
+ for m in sorted(members)
313
+ ]
314
+
315
+ return (
316
+ l_members,
317
+ l_members_d,
318
+ l_members_o,
319
+ l_effterms,
320
+ lagged,
321
+ nominal_ens_size,
322
+ r_members,
323
+ r_effterms,
324
+ )
252
325
 
253
326
  def members_detect(self):
254
327
  """Detect the members/terms list and update the substitution dictionary."""
255
- sectionsmap = {r: self.context.sequence.filtered_inputs(role=r, no_alternates=True)
256
- for r in self._membersdetect_roles}
328
+ sectionsmap = {
329
+ r: self.context.sequence.filtered_inputs(
330
+ role=r, no_alternates=True
331
+ )
332
+ for r in self._membersdetect_roles
333
+ }
257
334
  try:
258
- (self._ens_members_num,
259
- self._ens_members_date,
260
- self._ens_members_offset,
261
- self._ens_effterms,
262
- self._ens_is_lagged,
263
- self._ens_nominal_size,
264
- _, _) = self._stateless_members_detect(sectionsmap,
265
- self.date,
266
- self.context.sequence.is_somehow_viable,
267
- self.ens_minsize)
335
+ (
336
+ self._ens_members_num,
337
+ self._ens_members_date,
338
+ self._ens_members_offset,
339
+ self._ens_effterms,
340
+ self._ens_is_lagged,
341
+ self._ens_nominal_size,
342
+ _,
343
+ _,
344
+ ) = self._stateless_members_detect(
345
+ sectionsmap,
346
+ self.date,
347
+ self.context.sequence.is_somehow_viable,
348
+ self.ens_minsize,
349
+ )
268
350
  except EnsSizeAlgoComponentError as e:
269
351
  if self.strict_mbdetect and self.ens_failure_conf_objects is None:
270
352
  raise
271
353
  else:
272
354
  logger.warning("Members detection failed: %s", str(e))
273
- logger.info("'strict_mbdetect' is False... going on with empty lists.")
355
+ logger.info(
356
+ "'strict_mbdetect' is False... going on with empty lists."
357
+ )
274
358
  self._ens_members_num = []
275
359
  self._ens_members_date = []
276
360
  self._ens_members_offset = []
@@ -281,12 +365,17 @@ class OOPSMembersTermsDetectDecoMixin(AlgoComponentDecoMixin):
281
365
  # Find the new configuration object
282
366
  main_conf = None
283
367
  for sconf, ssub in self._individual_config_subs.items():
284
- if getattr(sconf.rh.resource, 'objects', '') == self.ens_failure_conf_objects:
368
+ if (
369
+ getattr(sconf.rh.resource, "objects", "")
370
+ == self.ens_failure_conf_objects
371
+ ):
285
372
  main_conf = sconf
286
373
  main_conf_sub = ssub
287
374
  break
288
375
  if main_conf is None:
289
- raise AlgoComponentError('Alternative configuration file was not found')
376
+ raise AlgoComponentError(
377
+ "Alternative configuration file was not found"
378
+ )
290
379
  # Update the config ordered dictionary
291
380
  del self._individual_config_subs[main_conf]
292
381
  new_individual_config_subs = OrderedDict()
@@ -294,31 +383,40 @@ class OOPSMembersTermsDetectDecoMixin(AlgoComponentDecoMixin):
294
383
  for sconf, ssub in self._individual_config_subs.items():
295
384
  new_individual_config_subs[sconf] = ssub
296
385
  self._individual_config_subs = new_individual_config_subs
297
- logger.info("Using an alternative configuration file (objects=%s, role=%s)",
298
- self.ens_failure_conf_objects, main_conf.role)
386
+ logger.info(
387
+ "Using an alternative configuration file (objects=%s, role=%s)",
388
+ self.ens_failure_conf_objects,
389
+ main_conf.role,
390
+ )
299
391
 
300
- self._generic_config_subs['ens_members_num'] = self._ens_members_num
301
- self._generic_config_subs['ens_members_date'] = self._ens_members_date
302
- self._generic_config_subs['ens_members_offset'] = self._ens_members_offset
303
- self._generic_config_subs['ens_is_lagged'] = self._ens_is_lagged
392
+ self._generic_config_subs["ens_members_num"] = self._ens_members_num
393
+ self._generic_config_subs["ens_members_date"] = self._ens_members_date
394
+ self._generic_config_subs["ens_members_offset"] = (
395
+ self._ens_members_offset
396
+ )
397
+ self._generic_config_subs["ens_is_lagged"] = self._ens_is_lagged
304
398
  # Legacy:
305
- self._generic_config_subs['members'] = self._ens_members_num
399
+ self._generic_config_subs["members"] = self._ens_members_num
306
400
  # Namelist stuff
307
401
  for namrh in self.updatable_namelists:
308
- namrh.contents.setmacro('ENS_MEMBERS', len(self._ens_members_num))
402
+ namrh.contents.setmacro("ENS_MEMBERS", len(self._ens_members_num))
309
403
  if self._ens_members_num:
310
- namrh.contents.setmacro('ENS_AUTO_NSTRIN', len(self._ens_members_num))
404
+ namrh.contents.setmacro(
405
+ "ENS_AUTO_NSTRIN", len(self._ens_members_num)
406
+ )
311
407
  else:
312
- namrh.contents.setmacro('ENS_AUTO_NSTRIN', self._ens_nominal_size)
313
- self._generic_config_subs['ens_effterms'] = self._ens_effterms
408
+ namrh.contents.setmacro(
409
+ "ENS_AUTO_NSTRIN", self._ens_nominal_size
410
+ )
411
+ self._generic_config_subs["ens_effterms"] = self._ens_effterms
314
412
  # Legacy:
315
- self._generic_config_subs['effterms'] = self._ens_effterms
413
+ self._generic_config_subs["effterms"] = self._ens_effterms
316
414
 
317
415
  def _membersd_setup(self, rh, opts): # @UnusedVariable
318
416
  """Set up the members/terms detection."""
319
417
  self.members_detect()
320
418
 
321
- _MIXIN_PREPARE_HOOKS = (_membersd_setup, )
419
+ _MIXIN_PREPARE_HOOKS = (_membersd_setup,)
322
420
 
323
421
 
324
422
  @algo_component_deco_mixin_autodoc
@@ -331,137 +429,171 @@ class OOPSMembersTermsDecoMixin(AlgoComponentDecoMixin):
331
429
  the configuration file substitutions dictionary ``_generic_config_subs``.
332
430
  """
333
431
 
334
- _MIXIN_EXTRA_FOOTPRINTS = (oops_members_terms_lists, )
432
+ _MIXIN_EXTRA_FOOTPRINTS = (oops_members_terms_lists,)
335
433
 
336
434
  def _membersterms_deco_setup(self, rh, opts): # @UnusedVariable
337
435
  """Setup the configuration file."""
338
- actualmembers = [m if isinstance(m, int) else int(m)
339
- for m in self.members]
340
- actualterms = [t if isinstance(t, Time) else Time(t)
341
- for t in self.terms]
342
- self._generic_config_subs['members'] = actualmembers
343
- self._generic_config_subs['effterms'] = actualterms
436
+ actualmembers = [
437
+ m if isinstance(m, int) else int(m) for m in self.members
438
+ ]
439
+ actualterms = [
440
+ t if isinstance(t, Time) else Time(t) for t in self.terms
441
+ ]
442
+ self._generic_config_subs["members"] = actualmembers
443
+ self._generic_config_subs["effterms"] = actualterms
344
444
 
345
- _MIXIN_PREPARE_HOOKS = (_membersterms_deco_setup, )
445
+ _MIXIN_PREPARE_HOOKS = (_membersterms_deco_setup,)
346
446
 
347
447
 
348
448
  @algo_component_deco_mixin_autodoc
349
449
  class OOPSTimestepDecoMixin(AlgoComponentDecoMixin):
350
450
  """Add a timsestep attribute and handle substitutions."""
351
451
 
352
- _MIXIN_EXTRA_FOOTPRINTS = (footprints.Footprint(
353
- info="Abstract timestep footprint",
354
- attr=dict(
355
- timestep=dict(
356
- info="A possible model timestep (in seconds).",
357
- optional=True,
358
- type=float
452
+ _MIXIN_EXTRA_FOOTPRINTS = (
453
+ footprints.Footprint(
454
+ info="Abstract timestep footprint",
455
+ attr=dict(
456
+ timestep=dict(
457
+ info="A possible model timestep (in seconds).",
458
+ optional=True,
459
+ type=float,
460
+ ),
359
461
  ),
360
462
  ),
361
- ),)
463
+ )
362
464
 
363
465
  def _timestep_deco_setup(self, rh, opts): # @UnusedVariable
364
466
  """Set up the timestep in config and namelists."""
365
467
  if self.timestep is not None:
366
- self._generic_config_subs['timestep'] = Period(seconds=self.timestep)
468
+ self._generic_config_subs["timestep"] = Period(
469
+ seconds=self.timestep
470
+ )
367
471
  logger.info("Set macro TIMESTEP=%f in namelists.", self.timestep)
368
472
  for namrh in self.updatable_namelists:
369
- namrh.contents.setmacro('TIMESTEP', self.timestep)
473
+ namrh.contents.setmacro("TIMESTEP", self.timestep)
370
474
 
371
- _MIXIN_PREPARE_HOOKS = (_timestep_deco_setup, )
475
+ _MIXIN_PREPARE_HOOKS = (_timestep_deco_setup,)
372
476
 
373
477
 
374
478
  @algo_component_deco_mixin_autodoc
375
479
  class OOPSIncrementalDecoMixin(AlgoComponentDecoMixin):
376
480
  """Add incremental attributes and handle substitutions."""
377
481
 
378
- _MIXIN_EXTRA_FOOTPRINTS = (footprints.Footprint(
379
- info="Abstract incremental_* footprint",
380
- attr=dict(
381
- incremental_tsteps=dict(
382
- info="Timestep for each of the outer loop iteration (in seconds).",
383
- optional=True,
384
- type=footprints.FPList,
385
- default=footprints.FPList(),
386
- ),
387
- incremental_niters=dict(
388
- info="Inner loop size for each of the outer loop iteration.",
389
- optional=True,
390
- type=footprints.FPList,
391
- default=footprints.FPList(),
392
- ),
393
- incremental_geos=dict(
394
- info="Geometry for each of the outer loop iteration.",
395
- optional=True,
396
- type=footprints.FPList,
397
- default=footprints.FPList(),
482
+ _MIXIN_EXTRA_FOOTPRINTS = (
483
+ footprints.Footprint(
484
+ info="Abstract incremental_* footprint",
485
+ attr=dict(
486
+ incremental_tsteps=dict(
487
+ info="Timestep for each of the outer loop iteration (in seconds).",
488
+ optional=True,
489
+ type=footprints.FPList,
490
+ default=footprints.FPList(),
491
+ ),
492
+ incremental_niters=dict(
493
+ info="Inner loop size for each of the outer loop iteration.",
494
+ optional=True,
495
+ type=footprints.FPList,
496
+ default=footprints.FPList(),
497
+ ),
498
+ incremental_geos=dict(
499
+ info="Geometry for each of the outer loop iteration.",
500
+ optional=True,
501
+ type=footprints.FPList,
502
+ default=footprints.FPList(),
503
+ ),
398
504
  ),
399
505
  ),
400
- ),)
506
+ )
401
507
 
402
508
  def _incremental_deco_setup(self, rh, opts): # @UnusedVariable
403
509
  """Set up the incremental DA settings in config and namelists."""
404
510
  if self.incremental_tsteps or self.incremental_niters:
405
- sizes = {len(t) for t in [self.incremental_tsteps,
406
- self.incremental_niters,
407
- self.incremental_geos] if t}
511
+ sizes = {
512
+ len(t)
513
+ for t in [
514
+ self.incremental_tsteps,
515
+ self.incremental_niters,
516
+ self.incremental_geos,
517
+ ]
518
+ if t
519
+ }
408
520
  if len(sizes) != 1:
409
- raise ValueError('Inconsistent sizes between incr_tsteps and incr_niters')
521
+ raise ValueError(
522
+ "Inconsistent sizes between incr_tsteps and incr_niters"
523
+ )
410
524
  actual_tsteps = [float(t) for t in (self.incremental_tsteps or ())]
411
525
  actual_tsteps_p = [Period(seconds=t) for t in actual_tsteps]
412
526
  actual_niters = [int(t) for t in (self.incremental_niters or ())]
413
- actual_geos = [g if isinstance(g, geometries.Geometry) else geometries.get(tag=g)
414
- for g in (self.incremental_geos or ())]
527
+ actual_geos = [
528
+ g
529
+ if isinstance(g, geometries.Geometry)
530
+ else geometries.get(tag=g)
531
+ for g in (self.incremental_geos or ())
532
+ ]
415
533
  if actual_tsteps:
416
- self._generic_config_subs['incremental_tsteps'] = actual_tsteps_p
534
+ self._generic_config_subs["incremental_tsteps"] = (
535
+ actual_tsteps_p
536
+ )
417
537
  for upd_i, tstep in enumerate(actual_tsteps, start=1):
418
- logger.info("Set macro UPD%d_TIMESTEP=%f macro in namelists.",
419
- upd_i, tstep)
538
+ logger.info(
539
+ "Set macro UPD%d_TIMESTEP=%f macro in namelists.",
540
+ upd_i,
541
+ tstep,
542
+ )
420
543
  for namrh in self.updatable_namelists:
421
- namrh.contents.setmacro('UPD{:d}_TIMESTEP'.format(upd_i), tstep)
544
+ namrh.contents.setmacro(
545
+ "UPD{:d}_TIMESTEP".format(upd_i), tstep
546
+ )
422
547
  if actual_niters:
423
- self._generic_config_subs['incremental_niters'] = actual_niters
548
+ self._generic_config_subs["incremental_niters"] = actual_niters
424
549
  for upd_i, niter in enumerate(actual_niters, start=1):
425
- logger.info("Set macro UPD%d_NITER=%d macro in namelists.",
426
- upd_i, niter)
550
+ logger.info(
551
+ "Set macro UPD%d_NITER=%d macro in namelists.",
552
+ upd_i,
553
+ niter,
554
+ )
427
555
  for namrh in self.updatable_namelists:
428
- namrh.contents.setmacro('UPD{:d}_NITER'.format(upd_i), niter)
556
+ namrh.contents.setmacro(
557
+ "UPD{:d}_NITER".format(upd_i), niter
558
+ )
429
559
  if actual_geos:
430
- self._generic_config_subs['incremental_geos'] = actual_geos
560
+ self._generic_config_subs["incremental_geos"] = actual_geos
431
561
 
432
562
  _MIXIN_PREPARE_HOOKS = (_incremental_deco_setup,)
433
563
 
434
564
 
435
- class OOPSParallel(Parallel,
436
- drhook.DrHookDecoMixin,
437
- grib.EcGribDecoMixin,
438
- satrad.SatRadDecoMixin):
565
+ class OOPSParallel(
566
+ Parallel,
567
+ drhook.DrHookDecoMixin,
568
+ grib.EcGribDecoMixin,
569
+ satrad.SatRadDecoMixin,
570
+ ):
439
571
  """Abstract AlgoComponent for any OOPS run."""
440
572
 
441
573
  _abstract = True
442
574
  _footprint = dict(
443
- info = "Any OOPS Run (abstract).",
444
- attr = dict(
445
- kind = dict(
446
- values = ['oorun'],
575
+ info="Any OOPS Run (abstract).",
576
+ attr=dict(
577
+ kind=dict(
578
+ values=["oorun"],
447
579
  ),
448
- date = dict(
449
- info = 'The current run date.',
450
- access = 'rwx',
451
- type = Date,
452
- doc_zorder = -50,
580
+ date=dict(
581
+ info="The current run date.",
582
+ access="rwx",
583
+ type=Date,
584
+ doc_zorder=-50,
453
585
  ),
454
- config_subs = dict(
455
- info = "Substitutions to be performed in the config file (before run)",
456
- optional = True,
457
- type = footprints.FPDict,
458
- default = footprints.FPDict(),
459
- doc_zorder = -60,
586
+ config_subs=dict(
587
+ info="Substitutions to be performed in the config file (before run)",
588
+ optional=True,
589
+ type=footprints.FPDict,
590
+ default=footprints.FPDict(),
591
+ doc_zorder=-60,
460
592
  ),
461
593
  binarysingle=dict(
462
- default = 'basicnwp',
594
+ default="basicnwp",
463
595
  ),
464
- )
596
+ ),
465
597
  )
466
598
 
467
599
  def __init__(self, *kargs, **kwargs):
@@ -480,11 +612,11 @@ class OOPSParallel(Parallel,
480
612
  def valid_executable(self, rh):
481
613
  """Be sure that the specified executable has a cycle attribute."""
482
614
  valid = super().valid_executable(rh)
483
- if hasattr(rh.resource, 'cycle'):
615
+ if hasattr(rh.resource, "cycle"):
484
616
  self._oops_cycle = rh.resource.cycle
485
617
  return valid
486
618
  else:
487
- logger.error('The binary < %s > has no cycle attribute', repr(rh))
619
+ logger.error("The binary < %s > has no cycle attribute", repr(rh))
488
620
  return False
489
621
 
490
622
  def prepare(self, rh, opts):
@@ -508,24 +640,31 @@ class OOPSParallel(Parallel,
508
640
  """Prepare options for the binary's command line."""
509
641
  mconfig = list(self._individual_config_subs.keys())[0]
510
642
  configfile = mconfig.rh.container.localpath()
511
- options = {'configfile': configfile}
643
+ options = {"configfile": configfile}
512
644
  return options
513
645
 
514
646
  @cached_property
515
647
  def updatable_namelists(self):
516
- return [s.rh for s in self.context.sequence.effective_inputs(role='Namelist')]
648
+ return [
649
+ s.rh
650
+ for s in self.context.sequence.effective_inputs(role="Namelist")
651
+ ]
517
652
 
518
653
  def set_config_rendering(self):
519
654
  """
520
655
  Look into effective inputs for configuration files and register them for
521
656
  a later rendering using bronx' templating system.
522
657
  """
523
- mconfig = self.context.sequence.effective_inputs(role='MainConfig')
524
- gconfig = self.context.sequence.effective_inputs(role='Config')
658
+ mconfig = self.context.sequence.effective_inputs(role="MainConfig")
659
+ gconfig = self.context.sequence.effective_inputs(role="Config")
525
660
  if len(mconfig) > 1:
526
- raise AlgoComponentError("Only one Main Config section may be provided.")
661
+ raise AlgoComponentError(
662
+ "Only one Main Config section may be provided."
663
+ )
527
664
  if len(mconfig) == 0 and len(gconfig) != 1:
528
- raise AlgoComponentError("Please provide a Main Config section or a unique Config section.")
665
+ raise AlgoComponentError(
666
+ "Please provide a Main Config section or a unique Config section."
667
+ )
529
668
  if len(mconfig) == 1:
530
669
  gconfig.insert(0, mconfig[0])
531
670
  self._individual_config_subs = {sconf: dict() for sconf in gconfig}
@@ -534,40 +673,53 @@ class OOPSParallel(Parallel,
534
673
  """Render registered configuration files using the bronx' templating system."""
535
674
  l_first = True
536
675
  for sconf, sdict in self._individual_config_subs.items():
537
- self.system.subtitle('Configuration file rendering for: {:s}'
538
- .format(sconf.rh.container.localpath()))
676
+ self.system.subtitle(
677
+ "Configuration file rendering for: {:s}".format(
678
+ sconf.rh.container.localpath()
679
+ )
680
+ )
539
681
  l_subs = dict(now=self.date, date=self.date)
540
682
  l_subs.update(self._generic_config_subs)
541
683
  l_subs.update(sdict)
542
684
  l_subs.update(self.config_subs)
543
685
  if l_subs != self._last_l_subs.get(sconf, dict()):
544
- if not hasattr(sconf.rh.contents, 'bronx_tpl_render'):
545
- logger.error('The < %s > content object has no "bronx_tpl_render" method. Skipping it.',
546
- repr(sconf.rh.contents))
686
+ if not hasattr(sconf.rh.contents, "bronx_tpl_render"):
687
+ logger.error(
688
+ 'The < %s > content object has no "bronx_tpl_render" method. Skipping it.',
689
+ repr(sconf.rh.contents),
690
+ )
547
691
  continue
548
692
  try:
549
- sconf.rh.contents.bronx_tpl_render(** l_subs)
693
+ sconf.rh.contents.bronx_tpl_render(**l_subs)
550
694
  except Exception:
551
- logger.error('The config file rendering failed. The substitution dict was: \n%s',
552
- lightdump(l_subs))
695
+ logger.error(
696
+ "The config file rendering failed. The substitution dict was: \n%s",
697
+ lightdump(l_subs),
698
+ )
553
699
  raise
554
700
  self._last_l_subs[sconf] = l_subs
555
701
  if l_first:
556
702
  print(fulldump(sconf.rh.contents.data))
557
703
  sconf.rh.save()
558
704
  else:
559
- logger.info("It's not necessary to update the file (no changes).")
705
+ logger.info(
706
+ "It's not necessary to update the file (no changes)."
707
+ )
560
708
  l_first = False
561
709
 
562
710
  def do_namelist_rendering(self):
563
- todo = [r for r in self.updatable_namelists if r.contents.dumps_needs_update]
564
- self.system.subtitle('Updating namelists')
711
+ todo = [
712
+ r
713
+ for r in self.updatable_namelists
714
+ if r.contents.dumps_needs_update
715
+ ]
716
+ self.system.subtitle("Updating namelists")
565
717
  if todo:
566
718
  for namrh in todo:
567
- logger.info('Rewriting %s.', namrh.container.localpath())
719
+ logger.info("Rewriting %s.", namrh.container.localpath())
568
720
  namrh.save()
569
721
  else:
570
- logger.info('None of the namelists need to be rewritten.')
722
+ logger.info("None of the namelists need to be rewritten.")
571
723
 
572
724
  def boost_defaults(self):
573
725
  """Set defaults for BOOST environment variables.
@@ -576,14 +728,14 @@ class OOPSParallel(Parallel,
576
728
  depends on the code's cycle number.
577
729
  """
578
730
  defaults = {
579
- IfsCycle('cy1'): {
580
- 'BOOST_TEST_CATCH_SYSTEM_ERRORS': 'no',
581
- 'BOOST_TEST_DETECT_FP_EXCEPTIONS': 'no',
582
- 'BOOST_TEST_LOG_FORMAT': 'XML',
583
- 'BOOST_TEST_LOG_LEVEL': 'message',
584
- 'BOOST_TEST_OUTPUT_FORMAT': 'XML',
585
- 'BOOST_TEST_REPORT_FORMAT': 'XML',
586
- 'BOOST_TEST_RESULT_CODE': 'yes'
731
+ IfsCycle("cy1"): {
732
+ "BOOST_TEST_CATCH_SYSTEM_ERRORS": "no",
733
+ "BOOST_TEST_DETECT_FP_EXCEPTIONS": "no",
734
+ "BOOST_TEST_LOG_FORMAT": "XML",
735
+ "BOOST_TEST_LOG_LEVEL": "message",
736
+ "BOOST_TEST_OUTPUT_FORMAT": "XML",
737
+ "BOOST_TEST_REPORT_FORMAT": "XML",
738
+ "BOOST_TEST_RESULT_CODE": "yes",
587
739
  }
588
740
  }
589
741
  cydefaults = None
@@ -591,9 +743,11 @@ class OOPSParallel(Parallel,
591
743
  if k < self.oops_cycle:
592
744
  cydefaults = defdict
593
745
  break
594
- self.algoassert(cydefaults is not None,
595
- 'BOOST defaults not found for cycle: {!s}'.format(self.oops_cycle))
596
- logger.info('Setting up BOOST defaults:%s', lightdump(cydefaults))
746
+ self.algoassert(
747
+ cydefaults is not None,
748
+ "BOOST defaults not found for cycle: {!s}".format(self.oops_cycle),
749
+ )
750
+ logger.info("Setting up BOOST defaults:%s", lightdump(cydefaults))
597
751
  self.env.default(**cydefaults)
598
752
 
599
753
  def eckit_defaults(self):
@@ -603,10 +757,12 @@ class OOPSParallel(Parallel,
603
757
  depends on the code's cycle number.
604
758
  """
605
759
  defaults = {
606
- IfsCycle('cy1'): {
607
- 'ECKIT_MPI_INIT_THREAD': ('MPI_THREAD_MULTIPLE'
608
- if int(self.env.get('OMP_NUM_THREADS', '1')) > 1
609
- else 'MPI_THREAD_SINGLE'),
760
+ IfsCycle("cy1"): {
761
+ "ECKIT_MPI_INIT_THREAD": (
762
+ "MPI_THREAD_MULTIPLE"
763
+ if int(self.env.get("OMP_NUM_THREADS", "1")) > 1
764
+ else "MPI_THREAD_SINGLE"
765
+ ),
610
766
  }
611
767
  }
612
768
  cydefaults = None
@@ -614,9 +770,11 @@ class OOPSParallel(Parallel,
614
770
  if k < self.oops_cycle:
615
771
  cydefaults = defdict
616
772
  break
617
- self.algoassert(cydefaults is not None,
618
- 'eckit defaults not found for cycle: {!s}'.format(self.oops_cycle))
619
- logger.info('Setting up eckit defaults:%s', lightdump(cydefaults))
773
+ self.algoassert(
774
+ cydefaults is not None,
775
+ "eckit defaults not found for cycle: {!s}".format(self.oops_cycle),
776
+ )
777
+ logger.info("Setting up eckit defaults:%s", lightdump(cydefaults))
620
778
  self.env.default(**cydefaults)
621
779
 
622
780
 
@@ -625,15 +783,15 @@ class OOPSODB(OOPSParallel, odb.OdbComponentDecoMixin):
625
783
 
626
784
  _abstract = True
627
785
  _footprint = dict(
628
- info = "OOPS ObsOperator Test run.",
629
- attr = dict(
630
- kind = dict(
631
- values = ['oorunodb'],
786
+ info="OOPS ObsOperator Test run.",
787
+ attr=dict(
788
+ kind=dict(
789
+ values=["oorunodb"],
632
790
  ),
633
- binarysingle = dict(
634
- default = 'basicnwpobsort',
791
+ binarysingle=dict(
792
+ default="basicnwpobsort",
635
793
  ),
636
- )
794
+ ),
637
795
  )
638
796
 
639
797
  #: If ``True``, an empty CCMA database will be created before the run and
@@ -648,18 +806,24 @@ class OOPSODB(OOPSParallel, odb.OdbComponentDecoMixin):
648
806
 
649
807
  # Looking for input observations
650
808
  allodb = self.lookupodb()
651
- allcma = [x for x in allodb if x.rh.resource.layout.lower() == self.virtualdb]
652
- if self.virtualdb.lower() == 'ccma':
653
- self.algoassert(len(allcma) == 1, 'A unique CCMA database is to be provided.')
654
- self.algoassert(not self._OOPSODB_CCMA_DIRECT,
655
- '_OOPSODB_CCMA_DIRECT needs to be False if virtualdb=ccma.')
809
+ allcma = [
810
+ x for x in allodb if x.rh.resource.layout.lower() == self.virtualdb
811
+ ]
812
+ if self.virtualdb.lower() == "ccma":
813
+ self.algoassert(
814
+ len(allcma) == 1, "A unique CCMA database is to be provided."
815
+ )
816
+ self.algoassert(
817
+ not self._OOPSODB_CCMA_DIRECT,
818
+ "_OOPSODB_CCMA_DIRECT needs to be False if virtualdb=ccma.",
819
+ )
656
820
  cma = allcma[0]
657
821
  cma_path = sh.path.abspath(cma.rh.container.localpath())
658
822
  else:
659
823
  cma_path = self.odb_merge_if_needed(allcma)
660
824
  if self._OOPSODB_CCMA_DIRECT:
661
- ccma_path = self.odb_create_db(layout='CCMA')
662
- self.odb.fix_db_path('CCMA', ccma_path)
825
+ ccma_path = self.odb_create_db(layout="CCMA")
826
+ self.odb.fix_db_path("CCMA", ccma_path)
663
827
 
664
828
  # Set ODB environment
665
829
  self.odb.fix_db_path(self.virtualdb, cma_path)
@@ -669,51 +833,61 @@ class OOPSODB(OOPSParallel, odb.OdbComponentDecoMixin):
669
833
  else:
670
834
  self.odb.ioassign_gather(cma_path)
671
835
 
672
- if self.virtualdb.lower() != 'ccma':
836
+ if self.virtualdb.lower() != "ccma":
673
837
  self.odb.create_poolmask(self.virtualdb, cma_path)
674
- self.odb.shuffle_setup(self.slots,
675
- mergedirect=True,
676
- ccmadirect=self._OOPSODB_CCMA_DIRECT)
838
+ self.odb.shuffle_setup(
839
+ self.slots,
840
+ mergedirect=True,
841
+ ccmadirect=self._OOPSODB_CCMA_DIRECT,
842
+ )
677
843
 
678
844
  # Fix the input databases intent
679
- self.odb_rw_or_overwrite_method(* allcma)
845
+ self.odb_rw_or_overwrite_method(*allcma)
680
846
 
681
847
  # Look for extras ODB raw
682
848
  self.odb_handle_raw_dbs()
683
849
 
684
850
  # Allow assimilation window / timeslots configuration
685
- self._generic_config_subs['window_length'] = self.slots.window
686
- self._generic_config_subs['window_lmargin'] = Period(- self.slots.start)
687
- self._generic_config_subs['window_rmargin'] = self.slots.window + self.slots.start
688
- self._generic_config_subs['timeslot_length'] = self.slots.chunk
689
- self._generic_config_subs['timeslot_centered'] = self.slots.center
690
- self._generic_config_subs['timeslot_centers'] = self.slots.as_centers_fromstart()
691
-
692
-
693
- class OOPSAnalysis(OOPSODB,
694
- OOPSTimestepDecoMixin,
695
- OOPSIncrementalDecoMixin,
696
- OOPSMemberDecoMixin,
697
- OOPSMembersTermsDetectDecoMixin):
851
+ self._generic_config_subs["window_length"] = self.slots.window
852
+ self._generic_config_subs["window_lmargin"] = Period(-self.slots.start)
853
+ self._generic_config_subs["window_rmargin"] = (
854
+ self.slots.window + self.slots.start
855
+ )
856
+ self._generic_config_subs["timeslot_length"] = self.slots.chunk
857
+ self._generic_config_subs["timeslot_centered"] = self.slots.center
858
+ self._generic_config_subs["timeslot_centers"] = (
859
+ self.slots.as_centers_fromstart()
860
+ )
861
+
862
+
863
+ class OOPSAnalysis(
864
+ OOPSODB,
865
+ OOPSTimestepDecoMixin,
866
+ OOPSIncrementalDecoMixin,
867
+ OOPSMemberDecoMixin,
868
+ OOPSMembersTermsDetectDecoMixin,
869
+ ):
698
870
  """Any kind of OOPS analysis (screening/thining step excluded)."""
699
871
 
700
872
  _footprint = dict(
701
- info = "OOPS minimisation.",
702
- attr = dict(
703
- kind = dict(
704
- values = ['ooanalysis', 'oominim'],
705
- remap = dict(autoremap='first'),
873
+ info="OOPS minimisation.",
874
+ attr=dict(
875
+ kind=dict(
876
+ values=["ooanalysis", "oominim"],
877
+ remap=dict(autoremap="first"),
706
878
  ),
707
- virtualdb = dict(
708
- default = 'ccma',
879
+ virtualdb=dict(
880
+ default="ccma",
709
881
  ),
710
882
  withscreening=dict(
711
- values = [False, ],
712
- type = bool,
713
- optional = True,
714
- default = False,
883
+ values=[
884
+ False,
885
+ ],
886
+ type=bool,
887
+ optional=True,
888
+ default=False,
715
889
  ),
716
- )
890
+ ),
717
891
  )
718
892
 
719
893
 
@@ -723,13 +897,15 @@ class OOPSAnalysisWithScreening(OOPSAnalysis):
723
897
  _OOPSODB_CCMA_DIRECT = True
724
898
 
725
899
  _footprint = dict(
726
- attr = dict(
727
- virtualdb = dict(
728
- default = 'ecma',
900
+ attr=dict(
901
+ virtualdb=dict(
902
+ default="ecma",
729
903
  ),
730
- withscreening = dict(
731
- values = [True, ],
732
- optional = False,
904
+ withscreening=dict(
905
+ values=[
906
+ True,
907
+ ],
908
+ optional=False,
733
909
  ),
734
910
  )
735
911
  )