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/nwp/algo/eps.py CHANGED
@@ -29,27 +29,27 @@ class Svect(IFSParallel):
29
29
  """Singular vectors computation."""
30
30
 
31
31
  _footprint = dict(
32
- info='Computation of the singular vectors.',
33
- attr = dict(
34
- kind = dict(
35
- values = ['svectors', 'svector', 'sv', 'svect', 'svarpe'],
36
- remap = dict(autoremap='first'),
32
+ info="Computation of the singular vectors.",
33
+ attr=dict(
34
+ kind=dict(
35
+ values=["svectors", "svector", "sv", "svect", "svarpe"],
36
+ remap=dict(autoremap="first"),
37
37
  ),
38
- conf = dict(
39
- type = int,
40
- optional = True,
41
- default = 601,
38
+ conf=dict(
39
+ type=int,
40
+ optional=True,
41
+ default=601,
42
42
  ),
43
- xpname = dict(
44
- optional = True,
45
- default = 'SVEC',
43
+ xpname=dict(
44
+ optional=True,
45
+ default="SVEC",
46
46
  ),
47
- )
47
+ ),
48
48
  )
49
49
 
50
50
  @property
51
51
  def realkind(self):
52
- return 'svector'
52
+ return "svector"
53
53
 
54
54
 
55
55
  class Combi(BlindRun, DrHookDecoMixin, EcGribDecoMixin):
@@ -59,7 +59,7 @@ class Combi(BlindRun, DrHookDecoMixin, EcGribDecoMixin):
59
59
 
60
60
  def execute(self, rh, opts):
61
61
  """Standard Combi execution."""
62
- namsec = self.setlink(initrole='Namelist', initkind='namelist')
62
+ namsec = self.setlink(initrole="Namelist", initkind="namelist")
63
63
  namsec[0].rh.container.cat()
64
64
  super().execute(rh, opts)
65
65
 
@@ -68,28 +68,44 @@ class Combi(BlindRun, DrHookDecoMixin, EcGribDecoMixin):
68
68
  raise NotImplementedError("Abstract property")
69
69
 
70
70
  def _addNmod(self, namrh, msg):
71
- namrh.contents['NAMMOD']['NMOD'] = self.nmod
71
+ namrh.contents["NAMMOD"]["NMOD"] = self.nmod
72
72
  logger.info("NMOD set to %d: %s.", self.nmod, msg)
73
73
 
74
74
  def _analysis_cp(self, nb, msg):
75
75
  # Copy the analysis
76
- initsec = self.setlink(initkind='analysis')
77
- radical = re.sub(r'^(.*?)\d+$', r'\1', initsec[0].rh.container.localpath())
76
+ initsec = self.setlink(initkind="analysis")
77
+ radical = re.sub(
78
+ r"^(.*?)\d+$", r"\1", initsec[0].rh.container.localpath()
79
+ )
78
80
  for num in footprints.util.rangex(1, nb):
79
- self.system.cp(initsec[0].rh.container.localpath(),
80
- radical + '{:03d}'.format(num),
81
- fmt=initsec[0].rh.container.actualfmt, intent=intent.INOUT)
81
+ self.system.cp(
82
+ initsec[0].rh.container.localpath(),
83
+ radical + "{:03d}".format(num),
84
+ fmt=initsec[0].rh.container.actualfmt,
85
+ intent=intent.INOUT,
86
+ )
82
87
  logger.info("Copy the analysis for the %d %s.", nb, msg)
83
88
 
84
89
  def _coeff_picking(self, kind, msg):
85
90
  # Pick up the coeff in the namelist
86
- for namsec in self.context.sequence.effective_inputs(kind='namelist'):
91
+ for namsec in self.context.sequence.effective_inputs(kind="namelist"):
87
92
  namsec.rh.reset_contents()
88
- if 'NAMCOEF' + kind.upper() in namsec.rh.contents:
89
- logger.info("Extract the " + msg + " coefficient from the updated namelist.")
90
- coeff = {'rcoef' + kind:
91
- float(namsec.rh.contents['NAMCOEF' + kind.upper()]['RCOEF' + kind.upper()])}
92
- self.system.json_dump(coeff, 'coeff' + kind + '.out', indent=4, cls=ShellEncoder)
93
+ if "NAMCOEF" + kind.upper() in namsec.rh.contents:
94
+ logger.info(
95
+ "Extract the "
96
+ + msg
97
+ + " coefficient from the updated namelist."
98
+ )
99
+ coeff = {
100
+ "rcoef" + kind: float(
101
+ namsec.rh.contents["NAMCOEF" + kind.upper()][
102
+ "RCOEF" + kind.upper()
103
+ ]
104
+ )
105
+ }
106
+ self.system.json_dump(
107
+ coeff, "coeff" + kind + ".out", indent=4, cls=ShellEncoder
108
+ )
93
109
 
94
110
 
95
111
  class CombiPert(Combi):
@@ -97,9 +113,9 @@ class CombiPert(Combi):
97
113
 
98
114
  _abstract = True
99
115
  _footprint = dict(
100
- attr = dict(
101
- nbpert = dict(
102
- type = int,
116
+ attr=dict(
117
+ nbpert=dict(
118
+ type=int,
103
119
  ),
104
120
  )
105
121
  )
@@ -109,15 +125,18 @@ class CombiPert(Combi):
109
125
  super().prepare(rh, opts)
110
126
 
111
127
  # Tweak the namelists
112
- for namsec in self.context.sequence.effective_inputs(role=re.compile('Namelist'),
113
- kind='namelist'):
114
- logger.info("Add the NBPERT coefficient to the NAMENS namelist entry")
115
- namsec.rh.contents['NAMENS']['NBPERT'] = self.nbpert
128
+ for namsec in self.context.sequence.effective_inputs(
129
+ role=re.compile("Namelist"), kind="namelist"
130
+ ):
131
+ logger.info(
132
+ "Add the NBPERT coefficient to the NAMENS namelist entry"
133
+ )
134
+ namsec.rh.contents["NAMENS"]["NBPERT"] = self.nbpert
116
135
  namsec.rh.save()
117
136
 
118
137
 
119
138
  #: Definition of a named tuple that holds informations on SV for a given zone
120
- _SvInfoTuple = collections.namedtuple('SvInfoTuple', ['available', 'expected'])
139
+ _SvInfoTuple = collections.namedtuple("SvInfoTuple", ["available", "expected"])
121
140
 
122
141
 
123
142
  class CombiSV(CombiPert):
@@ -125,10 +144,10 @@ class CombiSV(CombiPert):
125
144
 
126
145
  _abstract = True
127
146
  _footprint = dict(
128
- attr = dict(
129
- info_fname = dict(
130
- default = 'singular_vectors_info.json',
131
- optional = True,
147
+ attr=dict(
148
+ info_fname=dict(
149
+ default="singular_vectors_info.json",
150
+ optional=True,
132
151
  ),
133
152
  )
134
153
  )
@@ -140,76 +159,111 @@ class CombiSV(CombiPert):
140
159
  # Check the number of singular vectors and link them in succession
141
160
  nbVectTmp = collections.OrderedDict()
142
161
  totalVects = 0
143
- svec_sections = self.context.sequence.filtered_inputs(role='SingularVectors', kind='svector')
162
+ svec_sections = self.context.sequence.filtered_inputs(
163
+ role="SingularVectors", kind="svector"
164
+ )
144
165
  for svecsec in svec_sections:
145
- c_match = re.match(r'^([^+,.]+)[+,.][^+,.]+[+,.][^+,.]+(.*)$',
146
- svecsec.rh.container.localpath())
166
+ c_match = re.match(
167
+ r"^([^+,.]+)[+,.][^+,.]+[+,.][^+,.]+(.*)$",
168
+ svecsec.rh.container.localpath(),
169
+ )
147
170
  if c_match is None:
148
- logger.critical("The SV name is not formated correctly: %s",
149
- svecsec.rh.container.actualpath())
171
+ logger.critical(
172
+ "The SV name is not formated correctly: %s",
173
+ svecsec.rh.container.actualpath(),
174
+ )
150
175
  (radical, suffix) = c_match.groups()
151
176
  zone = svecsec.rh.resource.zone
152
177
  nbVectTmp.setdefault(zone, [0, 0])
153
178
  nbVectTmp[zone][1] += 1 # Expected
154
- if svecsec.stage == 'get':
179
+ if svecsec.stage == "get":
155
180
  totalVects += 1
156
181
  nbVectTmp[zone][0] += 1 # Available
157
- self.system.softlink(svecsec.rh.container.localpath(),
158
- radical + '{:03d}'.format(totalVects) + suffix)
182
+ self.system.softlink(
183
+ svecsec.rh.container.localpath(),
184
+ radical + "{:03d}".format(totalVects) + suffix,
185
+ )
159
186
  # Convert the temporary dictionary to a dictionary of tuples
160
187
  nbVect = collections.OrderedDict()
161
188
  for k, v in nbVectTmp.items():
162
189
  nbVect[k] = _SvInfoTuple(*v)
163
- logger.info("Number of vectors :\n" +
164
- '\n'.join(['- {0:8s}: {1.available:3d} ({1.expected:3d} expected).'.format(z, n)
165
- for z, n in nbVect.items()]))
190
+ logger.info(
191
+ "Number of vectors :\n"
192
+ + "\n".join(
193
+ [
194
+ "- {0:8s}: {1.available:3d} ({1.expected:3d} expected).".format(
195
+ z, n
196
+ )
197
+ for z, n in nbVect.items()
198
+ ]
199
+ )
200
+ )
166
201
  # Writing the singular vectors per areas in a json file
167
202
  self.system.json_dump(nbVect, self.info_fname)
168
203
 
169
204
  # Tweak the namelists
170
- namsecs = self.context.sequence.effective_inputs(role=re.compile('Namelist'), kind='namelist')
205
+ namsecs = self.context.sequence.effective_inputs(
206
+ role=re.compile("Namelist"), kind="namelist"
207
+ )
171
208
  for namsec in namsecs:
172
- namsec.rh.contents['NAMMOD']['LVS'] = True
173
- namsec.rh.contents['NAMMOD']['LANAP'] = False
174
- namsec.rh.contents['NAMMOD']['LBRED'] = False
209
+ namsec.rh.contents["NAMMOD"]["LVS"] = True
210
+ namsec.rh.contents["NAMMOD"]["LANAP"] = False
211
+ namsec.rh.contents["NAMMOD"]["LBRED"] = False
175
212
  logger.info("Added to NVSZONE namelist entry")
176
- namsec.rh.contents['NAMOPTI']['NVSZONE'] = [
213
+ namsec.rh.contents["NAMOPTI"]["NVSZONE"] = [
177
214
  v.available for v in nbVect.values() if v.available
178
215
  ] # Zones with 0 vectors are discarded
179
216
 
180
- nbVectNam = namsec.rh.contents['NAMENS']['NBVECT']
217
+ nbVectNam = namsec.rh.contents["NAMENS"]["NBVECT"]
181
218
  if int(nbVectNam) != totalVects:
182
- logger.warning("%s singular vectors expected but only %d accounted for.",
183
- nbVectNam, totalVects)
184
- logger.info("Update the total number of vectors in the NBVECT namelist entry")
185
- namsec.rh.contents['NAMENS']['NBVECT'] = totalVects
186
-
187
- actualZones = [k for k, v in nbVect.items() if v.available] # Zones with 0 vectors are discarded
219
+ logger.warning(
220
+ "%s singular vectors expected but only %d accounted for.",
221
+ nbVectNam,
222
+ totalVects,
223
+ )
224
+ logger.info(
225
+ "Update the total number of vectors in the NBVECT namelist entry"
226
+ )
227
+ namsec.rh.contents["NAMENS"]["NBVECT"] = totalVects
228
+
229
+ actualZones = [
230
+ k for k, v in nbVect.items() if v.available
231
+ ] # Zones with 0 vectors are discarded
188
232
  nbzone = len(actualZones)
189
- namsec.rh.contents['NAMOPTI']['NBZONE'] = nbzone
190
- namsec.rh.contents['NAMOPTI']['CNOMZONE'] = actualZones
191
- nbrc = len(namsec.rh.contents['NAMOPTI'].RC)
233
+ namsec.rh.contents["NAMOPTI"]["NBZONE"] = nbzone
234
+ namsec.rh.contents["NAMOPTI"]["CNOMZONE"] = actualZones
235
+ nbrc = len(namsec.rh.contents["NAMOPTI"].RC)
192
236
  if nbrc != nbzone:
193
- logger.critical("%d zones but NAMOPTI/RC has length %d" % (nbzone, nbrc))
194
- nbrl = len(namsec.rh.contents['NAMOPTI'].RL)
237
+ logger.critical(
238
+ "%d zones but NAMOPTI/RC has length %d" % (nbzone, nbrc)
239
+ )
240
+ nbrl = len(namsec.rh.contents["NAMOPTI"].RL)
195
241
  if nbrl != nbzone:
196
- logger.critical("%d zones but NAMOPTI/RL has length %d" % (nbzone, nbrl))
242
+ logger.critical(
243
+ "%d zones but NAMOPTI/RL has length %d" % (nbzone, nbrl)
244
+ )
197
245
 
198
246
  self._addNmod(namsec.rh, "combination of the SV")
199
247
  namsec.rh.save()
200
248
 
201
249
  # Copy the analysis to give all the perturbations a basis
202
- self._analysis_cp(self.nbpert, 'perturbations')
250
+ self._analysis_cp(self.nbpert, "perturbations")
203
251
 
204
252
 
205
253
  class CombiSVunit(CombiSV):
206
254
  """Combine the unit SV to create the raw perturbations by gaussian sampling."""
207
255
 
208
256
  _footprint = dict(
209
- attr = dict(
210
- kind = dict(
211
- values = ['sv2unitpert', 'init', 'combi_init', ],
212
- remap = dict(combi_init='init',),
257
+ attr=dict(
258
+ kind=dict(
259
+ values=[
260
+ "sv2unitpert",
261
+ "init",
262
+ "combi_init",
263
+ ],
264
+ remap=dict(
265
+ combi_init="init",
266
+ ),
213
267
  ),
214
268
  )
215
269
  )
@@ -226,10 +280,14 @@ class CombiSVnorm(CombiSV):
226
280
  """
227
281
 
228
282
  _footprint = dict(
229
- attr = dict(
230
- kind = dict(
231
- values = ['sv2normedpert', 'optim', 'combi_optim', ],
232
- remap = dict(autoremap='first'),
283
+ attr=dict(
284
+ kind=dict(
285
+ values=[
286
+ "sv2normedpert",
287
+ "optim",
288
+ "combi_optim",
289
+ ],
290
+ remap=dict(autoremap="first"),
233
291
  ),
234
292
  )
235
293
  )
@@ -237,7 +295,7 @@ class CombiSVnorm(CombiSV):
237
295
  def postfix(self, rh, opts):
238
296
  """Post processing cleaning."""
239
297
  # Pick up the coeff in the namelist
240
- self._coeff_picking('vs', 'SV')
298
+ self._coeff_picking("vs", "SV")
241
299
  super().postfix(rh, opts)
242
300
 
243
301
  @property
@@ -249,19 +307,23 @@ class CombiIC(Combi):
249
307
  """Combine the SV and AE or breeding perturbations to create the initial conditions."""
250
308
 
251
309
  _footprint = dict(
252
- attr = dict(
253
- kind = dict(
254
- values = ['pert2ic', 'sscales', 'combi_sscales', ],
255
- remap = dict(autoremap='first'),
310
+ attr=dict(
311
+ kind=dict(
312
+ values=[
313
+ "pert2ic",
314
+ "sscales",
315
+ "combi_sscales",
316
+ ],
317
+ remap=dict(autoremap="first"),
256
318
  ),
257
- nbic = dict(
258
- alias = ('nbruns',),
259
- type = int,
319
+ nbic=dict(
320
+ alias=("nbruns",),
321
+ type=int,
260
322
  ),
261
- nbpert = dict(
262
- type = int,
263
- optional = True,
264
- default = 0,
323
+ nbpert=dict(
324
+ type=int,
325
+ optional=True,
326
+ default=0,
265
327
  ),
266
328
  )
267
329
  )
@@ -275,80 +337,121 @@ class CombiIC(Combi):
275
337
  super().prepare(rh, opts)
276
338
 
277
339
  # Tweak the namelist
278
- namsec = self.setlink(initrole='Namelist', initkind='namelist')
279
- nammod = namsec[0].rh.contents['NAMMOD']
340
+ namsec = self.setlink(initrole="Namelist", initkind="namelist")
341
+ nammod = namsec[0].rh.contents["NAMMOD"]
280
342
 
281
343
  # The footprint's value is always preferred to the calculated one
282
344
  nbPert = self.nbpert
283
345
 
284
346
  # Dealing with singular vectors
285
- sv_sections = self.context.sequence.effective_inputs(role='CoeffSV')
286
- nammod['LVS'] = bool(sv_sections)
347
+ sv_sections = self.context.sequence.effective_inputs(role="CoeffSV")
348
+ nammod["LVS"] = bool(sv_sections)
287
349
  if sv_sections:
288
- logger.info("Add the SV coefficient to the NAMCOEFVS namelist entry.")
289
- namcoefvs = namsec[0].rh.contents.newblock('NAMCOEFVS')
290
- namcoefvs['RCOEFVS'] = sv_sections[0].rh.contents['rcoefvs']
350
+ logger.info(
351
+ "Add the SV coefficient to the NAMCOEFVS namelist entry."
352
+ )
353
+ namcoefvs = namsec[0].rh.contents.newblock("NAMCOEFVS")
354
+ namcoefvs["RCOEFVS"] = sv_sections[0].rh.contents["rcoefvs"]
291
355
  # The mean value may be present among the SV inputs: remove it
292
- svsecs = [sec for sec in self.context.sequence.effective_inputs(role='SVPerturbedState') or
293
- [sec for sec in self.context.sequence.effective_inputs(role='PerturbedState')
294
- if 'ICHR' in sec.rh.container.filename] if sec.rh.resource.number]
356
+ svsecs = [
357
+ sec
358
+ for sec in self.context.sequence.effective_inputs(
359
+ role="SVPerturbedState"
360
+ )
361
+ or [
362
+ sec
363
+ for sec in self.context.sequence.effective_inputs(
364
+ role="PerturbedState"
365
+ )
366
+ if "ICHR" in sec.rh.container.filename
367
+ ]
368
+ if sec.rh.resource.number
369
+ ]
295
370
  nbPert = nbPert or len(svsecs)
296
371
 
297
372
  # Dealing with breeding method's inputs
298
- bd_sections = self.context.sequence.effective_inputs(role='CoeffBreeding')
299
- nammod['LBRED'] = bool(bd_sections)
373
+ bd_sections = self.context.sequence.effective_inputs(
374
+ role="CoeffBreeding"
375
+ )
376
+ nammod["LBRED"] = bool(bd_sections)
300
377
  if bd_sections:
301
- logger.info("Add the breeding coefficient to the NAMCOEFBM namelist entry.")
302
- namcoefbm = namsec[0].rh.contents.newblock('NAMCOEFBM')
303
- namcoefbm['RCOEFBM'] = bd_sections[0].rh.contents['rcoefbm']
378
+ logger.info(
379
+ "Add the breeding coefficient to the NAMCOEFBM namelist entry."
380
+ )
381
+ namcoefbm = namsec[0].rh.contents.newblock("NAMCOEFBM")
382
+ namcoefbm["RCOEFBM"] = bd_sections[0].rh.contents["rcoefbm"]
304
383
  nbBd = len(
305
- self.context.sequence.effective_inputs(role='BreedingPerturbedState')
384
+ self.context.sequence.effective_inputs(
385
+ role="BreedingPerturbedState"
386
+ )
306
387
  or [
307
388
  sec
308
- for sec in self.context.sequence.effective_inputs(role='PerturbedState')
309
- if 'BMHR' in sec.rh.container.filename
389
+ for sec in self.context.sequence.effective_inputs(
390
+ role="PerturbedState"
391
+ )
392
+ if "BMHR" in sec.rh.container.filename
310
393
  ]
311
394
  )
312
395
  # symmetric perturbations except if analysis: one more file
313
396
  # or zero if one control ic (hypothesis: odd nbic)
314
- nbPert = nbPert or (nbBd - 1 if nbBd == self.nbic + 1 or
315
- (nbBd == self.nbic and self.nbic % 2 != 0)
316
- else self.nbic // 2)
397
+ nbPert = nbPert or (
398
+ nbBd - 1
399
+ if nbBd == self.nbic + 1
400
+ or (nbBd == self.nbic and self.nbic % 2 != 0)
401
+ else self.nbic // 2
402
+ )
317
403
 
318
404
  # Dealing with initial conditions from the assimilation ensemble
319
405
  # the mean value may be present among the AE inputs: remove it
320
- aesecs = [sec for sec in self.context.sequence.effective_inputs(
321
- role=('AEPerturbedState', 'ModelState')) if sec.rh.resource.number]
322
- nammod['LANAP'] = bool(aesecs)
406
+ aesecs = [
407
+ sec
408
+ for sec in self.context.sequence.effective_inputs(
409
+ role=("AEPerturbedState", "ModelState")
410
+ )
411
+ if sec.rh.resource.number
412
+ ]
413
+ nammod["LANAP"] = bool(aesecs)
323
414
  nbAe = len(aesecs)
324
415
  nbPert = nbPert or nbAe
325
416
  # If less AE members (but nor too less) than ic to build
326
417
  if nbAe < nbPert <= 2 * nbAe:
327
- logger.info("%d AE perturbations needed, %d AE members available: the first ones are duplicated.",
328
- nbPert, nbAe)
329
- prefix = aesecs[0].rh.container.filename.split('_')[0]
418
+ logger.info(
419
+ "%d AE perturbations needed, %d AE members available: the first ones are duplicated.",
420
+ nbPert,
421
+ nbAe,
422
+ )
423
+ prefix = aesecs[0].rh.container.filename.split("_")[0]
330
424
  for num in range(nbAe, nbPert):
331
- self.system.softlink(aesecs[num - nbAe].rh.container.filename,
332
- prefix + '_{:03d}'.format(num + 1))
333
-
334
- logger.info("NAMMOD namelist summary: LANAP=%s, LVS=%s, LBRED=%s.",
335
- *[nammod[k] for k in ('LANAP', 'LVS', 'LBRED')])
336
- logger.info("Add the NBPERT=%d coefficient to the NAMENS namelist entry.", nbPert)
337
- namsec[0].rh.contents['NAMENS']['NBPERT'] = nbPert
425
+ self.system.softlink(
426
+ aesecs[num - nbAe].rh.container.filename,
427
+ prefix + "_{:03d}".format(num + 1),
428
+ )
429
+
430
+ logger.info(
431
+ "NAMMOD namelist summary: LANAP=%s, LVS=%s, LBRED=%s.",
432
+ *[nammod[k] for k in ("LANAP", "LVS", "LBRED")],
433
+ )
434
+ logger.info(
435
+ "Add the NBPERT=%d coefficient to the NAMENS namelist entry.",
436
+ nbPert,
437
+ )
438
+ namsec[0].rh.contents["NAMENS"]["NBPERT"] = nbPert
338
439
 
339
440
  # symmectric perturbations ?
340
441
  if nbPert < self.nbic - 1:
341
- namsec[0].rh.contents['NAMENS']['LMIRROR'] = True
442
+ namsec[0].rh.contents["NAMENS"]["LMIRROR"] = True
342
443
  logger.info("Add LMIRROR=.TRUE. to the NAMENS namelist entry.")
343
- elif nbPert != 1: # 1 pert, 2 ic is possible without mirror adding the control
344
- namsec[0].rh.contents['NAMENS']['LMIRROR'] = False
444
+ elif (
445
+ nbPert != 1
446
+ ): # 1 pert, 2 ic is possible without mirror adding the control
447
+ namsec[0].rh.contents["NAMENS"]["LMIRROR"] = False
345
448
  logger.info("Add LMIRROR=.FALSE. to the NAMENS namelist entry.")
346
449
 
347
450
  self._addNmod(namsec[0].rh, "final combination of the perturbations")
348
451
  namsec[0].rh.save()
349
452
 
350
453
  # Copy the analysis to give all the members a basis
351
- self._analysis_cp(self.nbic - 1, 'perturbed states')
454
+ self._analysis_cp(self.nbic - 1, "perturbed states")
352
455
 
353
456
 
354
457
  class CombiBreeding(CombiPert):
@@ -358,10 +461,14 @@ class CombiBreeding(CombiPert):
358
461
  """
359
462
 
360
463
  _footprint = dict(
361
- attr = dict(
362
- kind = dict(
363
- values = ['fc2bredpert', 'breeding', 'combi_breeding', ],
364
- remap = dict(autoremap='first'),
464
+ attr=dict(
465
+ kind=dict(
466
+ values=[
467
+ "fc2bredpert",
468
+ "breeding",
469
+ "combi_breeding",
470
+ ],
471
+ remap=dict(autoremap="first"),
365
472
  ),
366
473
  )
367
474
  )
@@ -375,25 +482,31 @@ class CombiBreeding(CombiPert):
375
482
  super().prepare(rh, opts)
376
483
 
377
484
  # Consistent naming with the Fortran execution
378
- hst_sections = self.context.sequence.effective_inputs(kind=('pert', 'historic'))
485
+ hst_sections = self.context.sequence.effective_inputs(
486
+ kind=("pert", "historic")
487
+ )
379
488
  for num, hst in enumerate(hst_sections):
380
- self.system.softlink(hst.rh.container.localpath(),
381
- re.sub(r'^(.*?)\d+$', r'\1', hst.rh.container.localpath()) +
382
- '{:03d}.grb'.format(num + 1))
489
+ self.system.softlink(
490
+ hst.rh.container.localpath(),
491
+ re.sub(r"^(.*?)\d+$", r"\1", hst.rh.container.localpath())
492
+ + "{:03d}.grb".format(num + 1),
493
+ )
383
494
  logger.info("Rename the %d grib files consecutively.", num)
384
495
 
385
496
  # Tweak the namelist
386
- namsec = self.setlink(initrole='Namelist', initkind='namelist')
387
- namsec[0].rh.contents['NAMMOD']['LBRED'] = True
388
- namsec[0].rh.contents['NAMMOD']['LANAP'] = False
389
- namsec[0].rh.contents['NAMMOD']['LVS'] = False
390
- self._addNmod(namsec[0].rh, "compute the coefficient of the bred modes")
497
+ namsec = self.setlink(initrole="Namelist", initkind="namelist")
498
+ namsec[0].rh.contents["NAMMOD"]["LBRED"] = True
499
+ namsec[0].rh.contents["NAMMOD"]["LANAP"] = False
500
+ namsec[0].rh.contents["NAMMOD"]["LVS"] = False
501
+ self._addNmod(
502
+ namsec[0].rh, "compute the coefficient of the bred modes"
503
+ )
391
504
  namsec[0].rh.save()
392
505
 
393
506
  def postfix(self, rh, opts):
394
507
  """Post processing cleaning."""
395
508
  # Pick up the coeff in the namelist
396
- self._coeff_picking('bm', 'breeding')
509
+ self._coeff_picking("bm", "breeding")
397
510
  super().postfix(rh, opts)
398
511
 
399
512
 
@@ -404,13 +517,16 @@ class SurfCombiIC(BlindRun):
404
517
  """
405
518
 
406
519
  _footprint = dict(
407
- attr = dict(
408
- kind = dict(
409
- values = ['surf_pert2ic', 'surf2ic', ],
410
- remap = dict(autoremap='first'),
520
+ attr=dict(
521
+ kind=dict(
522
+ values=[
523
+ "surf_pert2ic",
524
+ "surf2ic",
525
+ ],
526
+ remap=dict(autoremap="first"),
411
527
  ),
412
- member = dict(
413
- type = int,
528
+ member=dict(
529
+ type=int,
414
530
  ),
415
531
  )
416
532
  )
@@ -419,15 +535,17 @@ class SurfCombiIC(BlindRun):
419
535
  """Set some variables according to target definition."""
420
536
  super().prepare(rh, opts)
421
537
 
422
- icsec = self.setlink(initrole=('SurfaceAnalysis', 'SurfaceInitialCondition'),
423
- initkind='ic')
538
+ icsec = self.setlink(
539
+ initrole=("SurfaceAnalysis", "SurfaceInitialCondition"),
540
+ initkind="ic",
541
+ )
424
542
  actualdate = icsec[0].rh.resource.date
425
543
  seed = int(actualdate.ymdh) + (actualdate.hour + 1) * (self.member + 1)
426
544
 
427
545
  # Tweak the namelist
428
- namsec = self.setlink(initrole='Namelist', initkind='namelist')
546
+ namsec = self.setlink(initrole="Namelist", initkind="namelist")
429
547
  logger.info("ISEED added to NAMSFC namelist entry: %d", seed)
430
- namsec[0].rh.contents['NAMSFC']['ISEED'] = seed
548
+ namsec[0].rh.contents["NAMSFC"]["ISEED"] = seed
431
549
  namsec[0].rh.save()
432
550
 
433
551
 
@@ -435,27 +553,30 @@ class Clustering(BlindRun, EcGribDecoMixin):
435
553
  """Select by clustering a sample of members among the whole set."""
436
554
 
437
555
  _footprint = dict(
438
- attr = dict(
439
- kind = dict(
440
- values = ['clustering', 'clust', ],
441
- remap = dict(autoremap='first'),
556
+ attr=dict(
557
+ kind=dict(
558
+ values=[
559
+ "clustering",
560
+ "clust",
561
+ ],
562
+ remap=dict(autoremap="first"),
442
563
  ),
443
- fileoutput = dict(
444
- optional = True,
445
- default = '_griblist',
564
+ fileoutput=dict(
565
+ optional=True,
566
+ default="_griblist",
446
567
  ),
447
- nbclust = dict(
448
- type = int,
568
+ nbclust=dict(
569
+ type=int,
449
570
  ),
450
- nbmembers = dict(
451
- type = int,
452
- optional = True,
453
- access = 'rwx',
571
+ nbmembers=dict(
572
+ type=int,
573
+ optional=True,
574
+ access="rwx",
454
575
  ),
455
- gribfilter_tasks = dict(
456
- type = int,
457
- optional = True,
458
- default = 8,
576
+ gribfilter_tasks=dict(
577
+ type=int,
578
+ optional=True,
579
+ default=8,
459
580
  ),
460
581
  )
461
582
  )
@@ -464,34 +585,49 @@ class Clustering(BlindRun, EcGribDecoMixin):
464
585
  """Set some variables according to target definition."""
465
586
  super().prepare(rh, opts)
466
587
 
467
- grib_sections = self.context.sequence.effective_inputs(role='ModelState',
468
- kind='gridpoint')
469
- avail_json = self.context.sequence.effective_inputs(role='AvailableMembers',
470
- kind='mbpopulation')
588
+ grib_sections = self.context.sequence.effective_inputs(
589
+ role="ModelState", kind="gridpoint"
590
+ )
591
+ avail_json = self.context.sequence.effective_inputs(
592
+ role="AvailableMembers", kind="mbpopulation"
593
+ )
471
594
 
472
595
  # If no population file is here, just do a sort on the file list,
473
596
  # otherwise use the population list
474
597
  if avail_json:
475
- population = avail_json[0].rh.contents.data['population']
598
+ population = avail_json[0].rh.contents.data["population"]
476
599
  self.nbmembers = len(population)
477
600
  file_list = list()
478
601
  terms_set = set()
479
602
  for elt in population:
480
603
  sublist_ids = list()
481
- for (i, grib) in enumerate(grib_sections):
604
+ for i, grib in enumerate(grib_sections):
482
605
  # If the grib file matches, let's go
483
- if all([grib.rh.wide_key_lookup(key, exports=True) == value
484
- for (key, value) in elt.items()]):
606
+ if all(
607
+ [
608
+ grib.rh.wide_key_lookup(key, exports=True) == value
609
+ for (key, value) in elt.items()
610
+ ]
611
+ ):
485
612
  sublist_ids.append(i)
486
613
  # Stack the gribs in file_list
487
- file_list.extend(sorted([str(grib_sections[i].rh.container.localpath())
488
- for i in sublist_ids]))
489
- terms_set.update([grib_sections[i].rh.resource.term for i in sublist_ids])
614
+ file_list.extend(
615
+ sorted(
616
+ [
617
+ str(grib_sections[i].rh.container.localpath())
618
+ for i in sublist_ids
619
+ ]
620
+ )
621
+ )
622
+ terms_set.update(
623
+ [grib_sections[i].rh.resource.term for i in sublist_ids]
624
+ )
490
625
  for i in reversed(sublist_ids):
491
626
  del grib_sections[i]
492
627
  else:
493
- file_list = sorted([str(grib.rh.container.localpath())
494
- for grib in grib_sections])
628
+ file_list = sorted(
629
+ [str(grib.rh.container.localpath()) for grib in grib_sections]
630
+ )
495
631
  terms_set = {grib.rh.resource.term for grib in grib_sections}
496
632
 
497
633
  # determine what terms are available to the clustering algorithm
@@ -501,86 +637,120 @@ class Clustering(BlindRun, EcGribDecoMixin):
501
637
  cluststep = delta.pop().hour
502
638
  else:
503
639
  cluststep = -999
504
- logger.error('Terms are not evenly spaced. What should we do ?')
505
- logger.error('Terms=' + str(terms) + 'delta=' + str(delta))
506
- logger.error('Continuing with little hope and cluststep = %d', cluststep)
640
+ logger.error("Terms are not evenly spaced. What should we do ?")
641
+ logger.error("Terms=" + str(terms) + "delta=" + str(delta))
642
+ logger.error(
643
+ "Continuing with little hope and cluststep = %d", cluststep
644
+ )
507
645
  clustdeb = terms[0].hour
508
646
  clustfin = terms[-1].hour
509
- logger.info('clustering deb=%d fin=%d step=%d', clustdeb, clustfin, cluststep)
647
+ logger.info(
648
+ "clustering deb=%d fin=%d step=%d", clustdeb, clustfin, cluststep
649
+ )
510
650
 
511
651
  # Deal with xGribs
512
- file_list_cat = [f + '.concatenated' for f in file_list]
513
- parallel_grib_filter(self.context, file_list, file_list_cat,
514
- cat=True, nthreads=self.gribfilter_tasks)
652
+ file_list_cat = [f + ".concatenated" for f in file_list]
653
+ parallel_grib_filter(
654
+ self.context,
655
+ file_list,
656
+ file_list_cat,
657
+ cat=True,
658
+ nthreads=self.gribfilter_tasks,
659
+ )
515
660
 
516
661
  if self.nbmembers is None or self.nbmembers > self.nbclust:
517
-
518
662
  # Tweak the namelist
519
- namsec = self.setlink(initrole='Namelist', initkind='namelist')
520
- logger.info("NBRCLUST added to NAMCLUST namelist entry: %d", self.nbclust)
521
- namsec[0].rh.contents['NAMCLUST']['NBRCLUST'] = self.nbclust
663
+ namsec = self.setlink(initrole="Namelist", initkind="namelist")
664
+ logger.info(
665
+ "NBRCLUST added to NAMCLUST namelist entry: %d", self.nbclust
666
+ )
667
+ namsec[0].rh.contents["NAMCLUST"]["NBRCLUST"] = self.nbclust
522
668
  if self.nbmembers is not None:
523
- logger.info("NBRMB added to NAMCLUST namelist entry: %d", self.nbmembers)
524
- namsec[0].rh.contents['NAMCLUST']['NBRMB'] = self.nbmembers
525
- logger.info('Setting namelist macros ECHDEB=%d ECHFIN=%d ECHSTEP=%d',
526
- clustdeb, clustfin, cluststep)
527
- namsec[0].rh.contents.setmacro('ECHDEB', clustdeb)
528
- namsec[0].rh.contents.setmacro('ECHFIN', clustfin)
529
- namsec[0].rh.contents.setmacro('ECHSTEP', cluststep)
669
+ logger.info(
670
+ "NBRMB added to NAMCLUST namelist entry: %d",
671
+ self.nbmembers,
672
+ )
673
+ namsec[0].rh.contents["NAMCLUST"]["NBRMB"] = self.nbmembers
674
+ logger.info(
675
+ "Setting namelist macros ECHDEB=%d ECHFIN=%d ECHSTEP=%d",
676
+ clustdeb,
677
+ clustfin,
678
+ cluststep,
679
+ )
680
+ namsec[0].rh.contents.setmacro("ECHDEB", clustdeb)
681
+ namsec[0].rh.contents.setmacro("ECHFIN", clustfin)
682
+ namsec[0].rh.contents.setmacro("ECHSTEP", cluststep)
530
683
  namsec[0].rh.save()
531
684
  namsec[0].rh.container.cat()
532
685
 
533
- with open(self.fileoutput, 'w') as optFile:
534
- optFile.write('\n'.join(file_list_cat))
686
+ with open(self.fileoutput, "w") as optFile:
687
+ optFile.write("\n".join(file_list_cat))
535
688
 
536
689
  def execute(self, rh, opts):
537
690
  # If the number of members is big enough -> normal processing
538
691
  if self.nbmembers is None or self.nbmembers > self.nbclust:
539
- logger.info("Normal clustering run (%d members, %d clusters)",
540
- self.nbmembers, self.nbclust)
692
+ logger.info(
693
+ "Normal clustering run (%d members, %d clusters)",
694
+ self.nbmembers,
695
+ self.nbclust,
696
+ )
541
697
  super().execute(rh, opts)
542
698
  # if not, generate face outputs
543
699
  else:
544
- logger.info("Generating fake outputs with %d members", self.nbmembers)
545
- with open('ASCII_CLUST', 'w') as fdcl:
546
- fdcl.write("\n".join(['{0:3d} {1:3d} {0:3d}'.format(i, 1)
547
- for i in range(1, self.nbmembers + 1)]))
548
- with open('ASCII_RMCLUST', 'w') as fdrm:
549
- fdrm.write("\n".join([str(i) for i in range(1, self.nbmembers + 1)]))
550
- with open('ASCII_POPCLUST', 'w') as fdpop:
551
- fdpop.write("\n".join(['1'] * self.nbmembers))
700
+ logger.info(
701
+ "Generating fake outputs with %d members", self.nbmembers
702
+ )
703
+ with open("ASCII_CLUST", "w") as fdcl:
704
+ fdcl.write(
705
+ "\n".join(
706
+ [
707
+ "{0:3d} {1:3d} {0:3d}".format(i, 1)
708
+ for i in range(1, self.nbmembers + 1)
709
+ ]
710
+ )
711
+ )
712
+ with open("ASCII_RMCLUST", "w") as fdrm:
713
+ fdrm.write(
714
+ "\n".join([str(i) for i in range(1, self.nbmembers + 1)])
715
+ )
716
+ with open("ASCII_POPCLUST", "w") as fdpop:
717
+ fdpop.write("\n".join(["1"] * self.nbmembers))
552
718
 
553
719
  def postfix(self, rh, opts):
554
720
  """Create a JSON with all the clustering informations."""
555
- avail_json = self.context.sequence.effective_inputs(role='AvailableMembers',
556
- kind='mbpopulation')
721
+ avail_json = self.context.sequence.effective_inputs(
722
+ role="AvailableMembers", kind="mbpopulation"
723
+ )
557
724
  # If no population file is here, does nothing
558
725
  if avail_json:
559
726
  logger.info("Creating a JSON output...")
560
727
  # Read the clustering information
561
- if self.system.path.exists('ASCII_CLUST'):
728
+ if self.system.path.exists("ASCII_CLUST"):
562
729
  # New format for clustering outputs
563
- with open('ASCII_CLUST') as fdcl:
730
+ with open("ASCII_CLUST") as fdcl:
564
731
  cluster_members = list()
565
732
  cluster_sizes = list()
566
733
  for l in [l.split() for l in fdcl.readlines()]:
567
734
  cluster_members.append(int(l[0]))
568
735
  cluster_sizes.append(int(l[1]))
569
736
  else:
570
- with open('ASCII_RMCLUST') as fdrm:
737
+ with open("ASCII_RMCLUST") as fdrm:
571
738
  cluster_members = [int(m) for m in fdrm.readlines()]
572
- with open('ASCII_POPCLUST') as fdpop:
739
+ with open("ASCII_POPCLUST") as fdpop:
573
740
  cluster_sizes = [int(s) for s in fdpop.readlines()]
574
741
  # Update the population JSON
575
742
  mycontent = copy.deepcopy(avail_json[0].rh.contents)
576
- mycontent.data['resource_kind'] = 'mbsample'
577
- mycontent.data['drawing'] = list()
743
+ mycontent.data["resource_kind"] = "mbsample"
744
+ mycontent.data["drawing"] = list()
578
745
  for member_no, cluster_size in zip(cluster_members, cluster_sizes):
579
- mycontent.data['drawing'].append(copy.copy(mycontent.data['population'][member_no - 1]))
580
- mycontent.data['drawing'][-1]['cluster_size'] = cluster_size
746
+ mycontent.data["drawing"].append(
747
+ copy.copy(mycontent.data["population"][member_no - 1])
748
+ )
749
+ mycontent.data["drawing"][-1]["cluster_size"] = cluster_size
581
750
  # Create a clustering output file
582
- new_container = footprints.proxy.container(filename='clustering_output.json',
583
- actualfmt='json')
751
+ new_container = footprints.proxy.container(
752
+ filename="clustering_output.json", actualfmt="json"
753
+ )
584
754
  mycontent.rewrite(new_container)
585
755
 
586
756
  super().postfix(rh, opts)
@@ -590,13 +760,15 @@ class Addpearp(BlindRun):
590
760
  """Add the selected PEARP perturbations to the deterministic AROME initial conditions."""
591
761
 
592
762
  _footprint = dict(
593
- attr = dict(
594
- kind = dict(
595
- values = ['addpearp', ],
596
- remap = dict(autoremap='first'),
763
+ attr=dict(
764
+ kind=dict(
765
+ values=[
766
+ "addpearp",
767
+ ],
768
+ remap=dict(autoremap="first"),
597
769
  ),
598
- nbpert = dict(
599
- type = int,
770
+ nbpert=dict(
771
+ type=int,
600
772
  ),
601
773
  )
602
774
  )
@@ -606,8 +778,8 @@ class Addpearp(BlindRun):
606
778
  super().prepare(rh, opts)
607
779
 
608
780
  # Tweak the namelist
609
- namsec = self.setlink(initrole='Namelist', initkind='namelist')
781
+ namsec = self.setlink(initrole="Namelist", initkind="namelist")
610
782
  logger.info("NBE added to NAMIC namelist entry: %d", self.nbpert)
611
- namsec[0].rh.contents['NAMIC']['NBPERT'] = self.nbpert
783
+ namsec[0].rh.contents["NAMIC"]["NBPERT"] = self.nbpert
612
784
  namsec[0].rh.save()
613
785
  namsec[0].rh.container.cat()