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.
- vortex/__init__.py +75 -47
- vortex/algo/__init__.py +3 -2
- vortex/algo/components.py +944 -618
- vortex/algo/mpitools.py +802 -497
- vortex/algo/mpitools_templates/__init__.py +1 -0
- vortex/algo/serversynctools.py +34 -33
- vortex/config.py +19 -22
- vortex/data/__init__.py +9 -3
- vortex/data/abstractstores.py +593 -655
- vortex/data/containers.py +217 -162
- vortex/data/contents.py +65 -39
- vortex/data/executables.py +93 -102
- vortex/data/flow.py +40 -34
- vortex/data/geometries.py +228 -132
- vortex/data/handlers.py +436 -227
- vortex/data/outflow.py +15 -15
- vortex/data/providers.py +185 -163
- vortex/data/resources.py +48 -42
- vortex/data/stores.py +540 -417
- vortex/data/sync_templates/__init__.py +0 -0
- vortex/gloves.py +114 -87
- vortex/layout/__init__.py +1 -8
- vortex/layout/contexts.py +150 -84
- vortex/layout/dataflow.py +353 -202
- vortex/layout/monitor.py +264 -128
- vortex/nwp/__init__.py +5 -2
- vortex/nwp/algo/__init__.py +14 -5
- vortex/nwp/algo/assim.py +205 -151
- vortex/nwp/algo/clim.py +683 -517
- vortex/nwp/algo/coupling.py +447 -225
- vortex/nwp/algo/eda.py +437 -229
- vortex/nwp/algo/eps.py +403 -231
- vortex/nwp/algo/forecasts.py +416 -275
- vortex/nwp/algo/fpserver.py +683 -307
- vortex/nwp/algo/ifsnaming.py +205 -145
- vortex/nwp/algo/ifsroot.py +215 -122
- vortex/nwp/algo/monitoring.py +137 -76
- vortex/nwp/algo/mpitools.py +330 -190
- vortex/nwp/algo/odbtools.py +637 -353
- vortex/nwp/algo/oopsroot.py +454 -273
- vortex/nwp/algo/oopstests.py +90 -56
- vortex/nwp/algo/request.py +287 -206
- vortex/nwp/algo/stdpost.py +878 -522
- vortex/nwp/data/__init__.py +22 -4
- vortex/nwp/data/assim.py +125 -137
- vortex/nwp/data/boundaries.py +121 -68
- vortex/nwp/data/climfiles.py +193 -211
- vortex/nwp/data/configfiles.py +73 -69
- vortex/nwp/data/consts.py +426 -401
- vortex/nwp/data/ctpini.py +59 -43
- vortex/nwp/data/diagnostics.py +94 -66
- vortex/nwp/data/eda.py +50 -51
- vortex/nwp/data/eps.py +195 -146
- vortex/nwp/data/executables.py +440 -434
- vortex/nwp/data/fields.py +63 -48
- vortex/nwp/data/gridfiles.py +183 -111
- vortex/nwp/data/logs.py +250 -217
- vortex/nwp/data/modelstates.py +180 -151
- vortex/nwp/data/monitoring.py +72 -99
- vortex/nwp/data/namelists.py +254 -202
- vortex/nwp/data/obs.py +400 -308
- vortex/nwp/data/oopsexec.py +22 -20
- vortex/nwp/data/providers.py +90 -65
- vortex/nwp/data/query.py +71 -82
- vortex/nwp/data/stores.py +49 -36
- vortex/nwp/data/surfex.py +136 -137
- vortex/nwp/syntax/__init__.py +1 -1
- vortex/nwp/syntax/stdattrs.py +173 -111
- vortex/nwp/tools/__init__.py +2 -2
- vortex/nwp/tools/addons.py +22 -17
- vortex/nwp/tools/agt.py +24 -12
- vortex/nwp/tools/bdap.py +16 -5
- vortex/nwp/tools/bdcp.py +4 -1
- vortex/nwp/tools/bdm.py +3 -0
- vortex/nwp/tools/bdmp.py +14 -9
- vortex/nwp/tools/conftools.py +728 -378
- vortex/nwp/tools/drhook.py +12 -8
- vortex/nwp/tools/grib.py +65 -39
- vortex/nwp/tools/gribdiff.py +22 -17
- vortex/nwp/tools/ifstools.py +82 -42
- vortex/nwp/tools/igastuff.py +167 -143
- vortex/nwp/tools/mars.py +14 -2
- vortex/nwp/tools/odb.py +234 -125
- vortex/nwp/tools/partitioning.py +61 -37
- vortex/nwp/tools/satrad.py +27 -12
- vortex/nwp/util/async.py +83 -55
- vortex/nwp/util/beacon.py +10 -10
- vortex/nwp/util/diffpygram.py +174 -86
- vortex/nwp/util/ens.py +144 -63
- vortex/nwp/util/hooks.py +30 -19
- vortex/nwp/util/taskdeco.py +28 -24
- vortex/nwp/util/usepygram.py +278 -172
- vortex/nwp/util/usetnt.py +31 -17
- vortex/sessions.py +72 -39
- vortex/syntax/__init__.py +1 -1
- vortex/syntax/stdattrs.py +410 -171
- vortex/syntax/stddeco.py +31 -22
- vortex/toolbox.py +327 -192
- vortex/tools/__init__.py +11 -2
- vortex/tools/actions.py +110 -121
- vortex/tools/addons.py +111 -92
- vortex/tools/arm.py +42 -22
- vortex/tools/compression.py +72 -69
- vortex/tools/date.py +11 -4
- vortex/tools/delayedactions.py +242 -132
- vortex/tools/env.py +75 -47
- vortex/tools/folder.py +342 -171
- vortex/tools/grib.py +341 -162
- vortex/tools/lfi.py +423 -216
- vortex/tools/listings.py +109 -40
- vortex/tools/names.py +218 -156
- vortex/tools/net.py +655 -299
- vortex/tools/parallelism.py +93 -61
- vortex/tools/prestaging.py +55 -31
- vortex/tools/schedulers.py +172 -105
- vortex/tools/services.py +403 -334
- vortex/tools/storage.py +293 -358
- vortex/tools/surfex.py +24 -24
- vortex/tools/systems.py +1234 -643
- vortex/tools/targets.py +156 -100
- vortex/util/__init__.py +1 -1
- vortex/util/config.py +378 -327
- vortex/util/empty.py +2 -2
- vortex/util/helpers.py +56 -24
- vortex/util/introspection.py +18 -12
- vortex/util/iosponge.py +8 -4
- vortex/util/roles.py +4 -6
- vortex/util/storefunctions.py +39 -13
- vortex/util/structs.py +3 -3
- vortex/util/worker.py +29 -17
- vortex_nwp-2.1.0.dist-info/METADATA +67 -0
- vortex_nwp-2.1.0.dist-info/RECORD +144 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/WHEEL +1 -1
- vortex/layout/appconf.py +0 -109
- vortex/layout/jobs.py +0 -1276
- vortex/layout/nodes.py +0 -1424
- vortex/layout/subjobs.py +0 -464
- vortex_nwp-2.0.0b1.dist-info/METADATA +0 -50
- vortex_nwp-2.0.0b1.dist-info/RECORD +0 -146
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info/licenses}/LICENSE +0 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.1.0.dist-info}/top_level.txt +0 -0
vortex/nwp/algo/eda.py
CHANGED
|
@@ -24,27 +24,30 @@ class IFSEdaAbstractAlgo(IFSParallel):
|
|
|
24
24
|
|
|
25
25
|
_abstract = True
|
|
26
26
|
_footprint = dict(
|
|
27
|
-
info=
|
|
27
|
+
info="Base class for any EDA related task",
|
|
28
28
|
attr=dict(
|
|
29
|
-
inputnaming
|
|
30
|
-
info
|
|
31
|
-
optional
|
|
32
|
-
doc_visibility
|
|
29
|
+
inputnaming=dict(
|
|
30
|
+
info="Prescribe your own naming template for input files.",
|
|
31
|
+
optional=True,
|
|
32
|
+
doc_visibility=footprints.doc.visibility.ADVANCED,
|
|
33
33
|
),
|
|
34
|
-
)
|
|
34
|
+
),
|
|
35
35
|
)
|
|
36
36
|
|
|
37
37
|
def naming_convention(self, kind, rh, actualfmt=None, **kwargs):
|
|
38
38
|
"""Take into account the *inputnaming* attribute."""
|
|
39
|
-
if kind ==
|
|
40
|
-
return super().naming_convention(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
if kind == "edainput":
|
|
40
|
+
return super().naming_convention(
|
|
41
|
+
kind,
|
|
42
|
+
rh,
|
|
43
|
+
actualfmt=actualfmt,
|
|
44
|
+
namingformat=self.inputnaming,
|
|
45
|
+
**kwargs,
|
|
46
|
+
)
|
|
44
47
|
else:
|
|
45
|
-
return super().naming_convention(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
return super().naming_convention(
|
|
49
|
+
kind, rh, actualfmt=actualfmt, **kwargs
|
|
50
|
+
)
|
|
48
51
|
|
|
49
52
|
|
|
50
53
|
class IFSEdaEnsembleAbstractAlgo(IFSEdaAbstractAlgo):
|
|
@@ -55,24 +58,24 @@ class IFSEdaEnsembleAbstractAlgo(IFSEdaAbstractAlgo):
|
|
|
55
58
|
to deal with missing members).
|
|
56
59
|
"""
|
|
57
60
|
|
|
58
|
-
_INPUTS_ROLE =
|
|
61
|
+
_INPUTS_ROLE = "ModelState"
|
|
59
62
|
|
|
60
63
|
_abstract = True
|
|
61
64
|
_footprint = dict(
|
|
62
|
-
info=
|
|
65
|
+
info="Base class for any EDA related task",
|
|
63
66
|
attr=dict(
|
|
64
|
-
nbmember
|
|
65
|
-
info
|
|
66
|
-
type
|
|
67
|
-
optional
|
|
67
|
+
nbmember=dict(
|
|
68
|
+
info="The number of members to deal will (auto-detected if omitted)",
|
|
69
|
+
type=int,
|
|
70
|
+
optional=True,
|
|
68
71
|
),
|
|
69
|
-
nbmin
|
|
70
|
-
info
|
|
71
|
-
type
|
|
72
|
-
optional
|
|
73
|
-
default
|
|
74
|
-
)
|
|
75
|
-
)
|
|
72
|
+
nbmin=dict(
|
|
73
|
+
info="The minimum number of input members that is mandatory to go one",
|
|
74
|
+
type=int,
|
|
75
|
+
optional=True,
|
|
76
|
+
default=2,
|
|
77
|
+
),
|
|
78
|
+
),
|
|
76
79
|
)
|
|
77
80
|
|
|
78
81
|
def __init__(self, *kargs, **kwargs):
|
|
@@ -97,56 +100,80 @@ class IFSEdaEnsembleAbstractAlgo(IFSEdaAbstractAlgo):
|
|
|
97
100
|
@staticmethod
|
|
98
101
|
def _members_sorting_key(s):
|
|
99
102
|
"""Return the sorting key for the **s** section."""
|
|
100
|
-
member = getattr(s.rh.provider,
|
|
101
|
-
member = -
|
|
102
|
-
block = getattr(s.rh.provider,
|
|
103
|
-
filename = getattr(s.rh.container,
|
|
103
|
+
member = getattr(s.rh.provider, "member", None)
|
|
104
|
+
member = -math.inf if member is None else member
|
|
105
|
+
block = getattr(s.rh.provider, "block", "")
|
|
106
|
+
filename = getattr(s.rh.container, "basename", "")
|
|
104
107
|
return member, block, filename
|
|
105
108
|
|
|
106
109
|
def _members_effective_inputs(self):
|
|
107
110
|
"""The list of effective sections representing input data."""
|
|
108
|
-
return sorted(
|
|
109
|
-
|
|
111
|
+
return sorted(
|
|
112
|
+
self.context.sequence.effective_inputs(role=self._INPUTS_ROLE),
|
|
113
|
+
key=self._members_sorting_key,
|
|
114
|
+
)
|
|
110
115
|
|
|
111
116
|
def _members_all_inputs(self):
|
|
112
117
|
"""The list of sections representing input data."""
|
|
113
|
-
return sorted(
|
|
114
|
-
|
|
118
|
+
return sorted(
|
|
119
|
+
self.context.sequence.filtered_inputs(role=self._INPUTS_ROLE),
|
|
120
|
+
key=self._members_sorting_key,
|
|
121
|
+
)
|
|
115
122
|
|
|
116
123
|
def _check_members_list_numbering(self, rh, mlist, mformats):
|
|
117
124
|
"""Check if, for the **mlist** members list, some renaming id needed."""
|
|
118
125
|
if len(set(mlist)) != len(mlist):
|
|
119
|
-
logger.warning(
|
|
126
|
+
logger.warning(
|
|
127
|
+
"Some members are duplicated. That's very strange..."
|
|
128
|
+
)
|
|
120
129
|
if self.nbmember is None:
|
|
121
|
-
self.algoassert(
|
|
130
|
+
self.algoassert(
|
|
131
|
+
len(mformats) <= 1, "Mixed formats are not allowed !"
|
|
132
|
+
)
|
|
122
133
|
elif self.nbmember and len(mformats) > 1:
|
|
123
|
-
logger.info(
|
|
134
|
+
logger.info(
|
|
135
|
+
"%s have mixed formats... please correct that.",
|
|
136
|
+
self._INPUTS_ROLE,
|
|
137
|
+
)
|
|
124
138
|
if mlist and self.nbmember is not None:
|
|
125
139
|
# Consistency check
|
|
126
140
|
if len(mlist) != self.nbmember:
|
|
127
|
-
logger.warning(
|
|
128
|
-
|
|
141
|
+
logger.warning(
|
|
142
|
+
"Discrepancy between *nbmember* and effective input files..."
|
|
143
|
+
+ " sticking with *nbmember*"
|
|
144
|
+
)
|
|
129
145
|
else:
|
|
130
146
|
logger.info("The input files member numbers checks out !")
|
|
131
147
|
return False # Ok, apparently the user knows what she/he is doing
|
|
132
148
|
elif self.nbmember and not mlist:
|
|
133
149
|
return False # Ok, apparently the user knows what she/he is doing
|
|
134
150
|
elif mlist and self.nbmember is None:
|
|
135
|
-
innc = self.naming_convention(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
151
|
+
innc = self.naming_convention(
|
|
152
|
+
kind="edainput",
|
|
153
|
+
variant=self.kind,
|
|
154
|
+
rh=rh,
|
|
155
|
+
totalnumber=self.actual_totalnumber,
|
|
156
|
+
actualfmt=mformats.pop(),
|
|
157
|
+
)
|
|
158
|
+
checkfiles = [
|
|
159
|
+
m
|
|
160
|
+
for m in range(1, len(mlist) + 1)
|
|
161
|
+
if self.system.path.exists(innc(number=m))
|
|
162
|
+
]
|
|
140
163
|
if len(checkfiles) == len(mlist):
|
|
141
164
|
logger.info("The input files numbering checks out !")
|
|
142
|
-
return
|
|
165
|
+
return (
|
|
166
|
+
False # Ok, apparently the user knows what she/he is doing
|
|
167
|
+
)
|
|
143
168
|
elif len(checkfiles) == 0:
|
|
144
169
|
return True
|
|
145
170
|
else:
|
|
146
|
-
raise AlgoComponentError(
|
|
147
|
-
|
|
171
|
+
raise AlgoComponentError(
|
|
172
|
+
"Members renumbering is needed but some "
|
|
173
|
+
+ "files are blocking the way !"
|
|
174
|
+
)
|
|
148
175
|
elif len(mlist) == 0 and self.nbmember is None:
|
|
149
|
-
raise AlgoComponentError(
|
|
176
|
+
raise AlgoComponentError("No input files where found !")
|
|
150
177
|
|
|
151
178
|
def modelstate_needs_renumbering(self, rh):
|
|
152
179
|
"""Check if, for the **mlist** members list, some renaming id needed."""
|
|
@@ -160,35 +187,46 @@ class IFSEdaEnsembleAbstractAlgo(IFSEdaAbstractAlgo):
|
|
|
160
187
|
def modelstate_renumbering(self, rh, mlist):
|
|
161
188
|
"""Actualy rename the effective inputs."""
|
|
162
189
|
eff_format = mlist[0].rh.container.actualfmt
|
|
163
|
-
innc = self.naming_convention(
|
|
164
|
-
|
|
165
|
-
|
|
190
|
+
innc = self.naming_convention(
|
|
191
|
+
kind="edainput",
|
|
192
|
+
variant=self.kind,
|
|
193
|
+
rh=rh,
|
|
194
|
+
totalnumber=self.actual_totalnumber,
|
|
195
|
+
actualfmt=eff_format,
|
|
196
|
+
)
|
|
166
197
|
for i, s in enumerate(mlist, start=1):
|
|
167
|
-
logger.info(
|
|
168
|
-
|
|
198
|
+
logger.info(
|
|
199
|
+
"Soft-Linking %s to %s",
|
|
200
|
+
s.rh.container.localpath(),
|
|
201
|
+
innc(number=i),
|
|
202
|
+
)
|
|
169
203
|
self.system.softlink(s.rh.container.localpath(), innc(number=i))
|
|
170
204
|
|
|
171
205
|
def prepare_namelist_delta(self, rh, namcontents, namlocal):
|
|
172
206
|
"""Update the namelists with EDA related macros."""
|
|
173
|
-
nam_updated = super(IFSEdaAbstractAlgo, self).prepare_namelist_delta(
|
|
174
|
-
|
|
175
|
-
|
|
207
|
+
nam_updated = super(IFSEdaAbstractAlgo, self).prepare_namelist_delta(
|
|
208
|
+
rh, namcontents, namlocal
|
|
209
|
+
)
|
|
176
210
|
if self.actual_nbe is not None:
|
|
177
|
-
self._set_nam_macro(namcontents, namlocal,
|
|
211
|
+
self._set_nam_macro(namcontents, namlocal, "NBE", self.actual_nbe)
|
|
178
212
|
nam_updated = True
|
|
179
213
|
return nam_updated
|
|
180
214
|
|
|
181
215
|
def prepare(self, rh, opts):
|
|
182
216
|
"""Check the input files and act on it."""
|
|
183
|
-
self.system.subtitle(
|
|
217
|
+
self.system.subtitle("Solving the input files nightmare...")
|
|
184
218
|
if self.modelstate_needs_renumbering(rh):
|
|
185
219
|
eff_sections = self._members_effective_inputs()
|
|
186
220
|
if len(eff_sections) < self.actual_nbmin:
|
|
187
|
-
raise AlgoComponentError(
|
|
188
|
-
|
|
189
|
-
|
|
221
|
+
raise AlgoComponentError(
|
|
222
|
+
"Not enough input files to continue..."
|
|
223
|
+
)
|
|
224
|
+
logger.info(
|
|
225
|
+
"Starting input files renumbering. %d effective members found",
|
|
226
|
+
len(eff_sections),
|
|
227
|
+
)
|
|
190
228
|
self.modelstate_renumbering(rh, eff_sections)
|
|
191
|
-
self.system.subtitle(
|
|
229
|
+
self.system.subtitle("Other IFS related settings")
|
|
192
230
|
super().prepare(rh, opts)
|
|
193
231
|
|
|
194
232
|
|
|
@@ -200,24 +238,24 @@ class IFSEdaLaggedEnsembleAbstractAlgo(IFSEdaEnsembleAbstractAlgo):
|
|
|
200
238
|
able to deal with missing members).
|
|
201
239
|
"""
|
|
202
240
|
|
|
203
|
-
_PADDING_ROLE =
|
|
241
|
+
_PADDING_ROLE = "PaddingModelState"
|
|
204
242
|
|
|
205
243
|
_abstract = True
|
|
206
244
|
_footprint = dict(
|
|
207
|
-
info=
|
|
245
|
+
info="Base class for any EDA related task",
|
|
208
246
|
attr=dict(
|
|
209
|
-
nblag
|
|
210
|
-
info
|
|
211
|
-
type
|
|
212
|
-
optional
|
|
247
|
+
nblag=dict(
|
|
248
|
+
info="The number of lagged dates (auto-detected if omitted)",
|
|
249
|
+
type=int,
|
|
250
|
+
optional=True,
|
|
213
251
|
),
|
|
214
|
-
padding
|
|
215
|
-
info
|
|
216
|
-
type
|
|
217
|
-
optional
|
|
218
|
-
default
|
|
219
|
-
)
|
|
220
|
-
)
|
|
252
|
+
padding=dict(
|
|
253
|
+
info="Fill the gaps with some kind of climatological data",
|
|
254
|
+
type=bool,
|
|
255
|
+
optional=True,
|
|
256
|
+
default=False,
|
|
257
|
+
),
|
|
258
|
+
),
|
|
221
259
|
)
|
|
222
260
|
|
|
223
261
|
def __init__(self, *kargs, **kwargs):
|
|
@@ -232,7 +270,11 @@ class IFSEdaLaggedEnsembleAbstractAlgo(IFSEdaEnsembleAbstractAlgo):
|
|
|
232
270
|
@property
|
|
233
271
|
def actual_totalnumber(self):
|
|
234
272
|
"""The total number of members."""
|
|
235
|
-
return
|
|
273
|
+
return (
|
|
274
|
+
self.actual_nbe * self.actual_nresx
|
|
275
|
+
if self.padding
|
|
276
|
+
else self.actual_nbe
|
|
277
|
+
)
|
|
236
278
|
|
|
237
279
|
@property
|
|
238
280
|
def actual_nbmin(self):
|
|
@@ -242,7 +284,7 @@ class IFSEdaLaggedEnsembleAbstractAlgo(IFSEdaEnsembleAbstractAlgo):
|
|
|
242
284
|
def _members_sorting_key(self, s):
|
|
243
285
|
"""Return the sorting key for the **s** section."""
|
|
244
286
|
stuple = list(super()._members_sorting_key(s))
|
|
245
|
-
rdate = getattr(s.rh.resource,
|
|
287
|
+
rdate = getattr(s.rh.resource, "date")
|
|
246
288
|
stuple.insert(0, rdate)
|
|
247
289
|
return tuple(stuple)
|
|
248
290
|
|
|
@@ -256,8 +298,10 @@ class IFSEdaLaggedEnsembleAbstractAlgo(IFSEdaEnsembleAbstractAlgo):
|
|
|
256
298
|
if all_dates and self.nblag is not None:
|
|
257
299
|
# Consistency check
|
|
258
300
|
if len(all_dates) != self.nblag:
|
|
259
|
-
logger.warning(
|
|
260
|
-
|
|
301
|
+
logger.warning(
|
|
302
|
+
"Discrepancy between *nblag* and input files..."
|
|
303
|
+
+ " sticking with *nblag*"
|
|
304
|
+
)
|
|
261
305
|
elif all_dates and self.nblag is None:
|
|
262
306
|
self._actual_nresx = len(all_dates)
|
|
263
307
|
|
|
@@ -266,18 +310,30 @@ class IFSEdaLaggedEnsembleAbstractAlgo(IFSEdaEnsembleAbstractAlgo):
|
|
|
266
310
|
# For each date, check the member's list
|
|
267
311
|
d_blocks = dict()
|
|
268
312
|
for a_date in all_dates:
|
|
269
|
-
d_sections = [
|
|
270
|
-
|
|
313
|
+
d_sections = [
|
|
314
|
+
sec
|
|
315
|
+
for sec in all_sections
|
|
316
|
+
if sec.rh.resource.date == a_date
|
|
317
|
+
]
|
|
318
|
+
d_members = sorted(
|
|
319
|
+
[sec.rh.provider.member for sec in d_sections]
|
|
320
|
+
)
|
|
271
321
|
d_formats = {sec.rh.container.actualfmt for sec in d_sections}
|
|
272
322
|
d_blocks[a_date] = (d_members, d_formats)
|
|
273
323
|
ref_date, (eff_members, eff_formats) = d_blocks.popitem()
|
|
274
324
|
for a_date, a_data in d_blocks.items():
|
|
275
|
-
self.algoassert(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
325
|
+
self.algoassert(
|
|
326
|
+
a_data[0] == eff_members,
|
|
327
|
+
"Inconsistent members list (date={!s} vs {!s}).".format(
|
|
328
|
+
a_date, ref_date
|
|
329
|
+
),
|
|
330
|
+
)
|
|
331
|
+
self.algoassert(
|
|
332
|
+
a_data[1] == eff_formats,
|
|
333
|
+
"Inconsistent formats list (date={!s} vs {!s}).".format(
|
|
334
|
+
a_date, ref_date
|
|
335
|
+
),
|
|
336
|
+
)
|
|
281
337
|
d_blocks[ref_date] = (eff_members, eff_formats)
|
|
282
338
|
if eff_members and self.nbmember is None:
|
|
283
339
|
# Here, NBE is the number of members for one date
|
|
@@ -288,8 +344,10 @@ class IFSEdaLaggedEnsembleAbstractAlgo(IFSEdaEnsembleAbstractAlgo):
|
|
|
288
344
|
for member in a_data[0]:
|
|
289
345
|
eff_members.append((a_date, member))
|
|
290
346
|
else:
|
|
291
|
-
eff_members = [
|
|
292
|
-
|
|
347
|
+
eff_members = [
|
|
348
|
+
(sec.rh.resource.date, sec.rh.provider.member)
|
|
349
|
+
for sec in eff_sections
|
|
350
|
+
]
|
|
293
351
|
eff_formats = {sec.rh.container.actualfmt for sec in eff_sections}
|
|
294
352
|
if eff_members and self.nbmember is None:
|
|
295
353
|
# Here, NBE is the number of members for all dates
|
|
@@ -301,37 +359,64 @@ class IFSEdaLaggedEnsembleAbstractAlgo(IFSEdaEnsembleAbstractAlgo):
|
|
|
301
359
|
"""Actualy rename the effective inputs."""
|
|
302
360
|
if self.padding:
|
|
303
361
|
eff_format = mlist[0].rh.container.actualfmt
|
|
304
|
-
innc = self.naming_convention(
|
|
305
|
-
|
|
306
|
-
|
|
362
|
+
innc = self.naming_convention(
|
|
363
|
+
kind="edainput",
|
|
364
|
+
variant=self.kind,
|
|
365
|
+
rh=rh,
|
|
366
|
+
totalnumber=self.actual_totalnumber,
|
|
367
|
+
actualfmt=eff_format,
|
|
368
|
+
)
|
|
307
369
|
all_sections = self._members_all_inputs()
|
|
308
|
-
paddingstuff = self.context.sequence.effective_inputs(
|
|
370
|
+
paddingstuff = self.context.sequence.effective_inputs(
|
|
371
|
+
role=self._PADDING_ROLE
|
|
372
|
+
)
|
|
309
373
|
for i, s in enumerate(all_sections, start=1):
|
|
310
|
-
if s.stage ==
|
|
311
|
-
logger.info(
|
|
312
|
-
|
|
313
|
-
|
|
374
|
+
if s.stage == "get" and s.rh.container.exists():
|
|
375
|
+
logger.info(
|
|
376
|
+
"Soft-Linking %s to %s",
|
|
377
|
+
s.rh.container.localpath(),
|
|
378
|
+
innc(number=i),
|
|
379
|
+
)
|
|
380
|
+
self.system.softlink(
|
|
381
|
+
s.rh.container.localpath(), innc(number=i)
|
|
382
|
+
)
|
|
314
383
|
else:
|
|
315
384
|
mypadding = None
|
|
316
385
|
for p in paddingstuff:
|
|
317
|
-
if
|
|
318
|
-
|
|
386
|
+
if (
|
|
387
|
+
getattr(
|
|
388
|
+
p.rh.resource,
|
|
389
|
+
"ipert",
|
|
390
|
+
getattr(p.rh.resource, "number", None),
|
|
391
|
+
)
|
|
392
|
+
== i
|
|
393
|
+
):
|
|
319
394
|
mypadding = p
|
|
320
395
|
break
|
|
321
396
|
else:
|
|
322
|
-
if (
|
|
323
|
-
|
|
397
|
+
if (
|
|
398
|
+
getattr(p.rh.resource, "date", None)
|
|
399
|
+
== s.rh.resource.date
|
|
400
|
+
and getattr(p.rh.provider, "member", None)
|
|
401
|
+
== s.rh.provider.member
|
|
402
|
+
):
|
|
324
403
|
mypadding = p
|
|
325
404
|
break
|
|
326
405
|
if mypadding is not None:
|
|
327
|
-
logger.warning(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
406
|
+
logger.warning(
|
|
407
|
+
"Soft-Linking Padding data %s to %s",
|
|
408
|
+
mypadding.rh.container.localpath(),
|
|
409
|
+
innc(number=i),
|
|
410
|
+
)
|
|
411
|
+
self.system.softlink(
|
|
412
|
+
mypadding.rh.container.localpath(), innc(number=i)
|
|
413
|
+
)
|
|
332
414
|
else:
|
|
333
|
-
raise AlgoComponentError(
|
|
334
|
-
|
|
415
|
+
raise AlgoComponentError(
|
|
416
|
+
"No padding data where found for i= {:d}: {!s}".format(
|
|
417
|
+
i, s
|
|
418
|
+
)
|
|
419
|
+
)
|
|
335
420
|
else:
|
|
336
421
|
super().modelstate_renumbering(rh, mlist)
|
|
337
422
|
|
|
@@ -339,7 +424,9 @@ class IFSEdaLaggedEnsembleAbstractAlgo(IFSEdaEnsembleAbstractAlgo):
|
|
|
339
424
|
"""Update the namelists with EDA related macros."""
|
|
340
425
|
nam_updated = super().prepare_namelist_delta(rh, namcontents, namlocal)
|
|
341
426
|
if self.actual_nresx is not None:
|
|
342
|
-
self._set_nam_macro(
|
|
427
|
+
self._set_nam_macro(
|
|
428
|
+
namcontents, namlocal, "NRESX", self.actual_nresx
|
|
429
|
+
)
|
|
343
430
|
nam_updated = True
|
|
344
431
|
return nam_updated
|
|
345
432
|
|
|
@@ -348,17 +435,17 @@ class IFSEdaFemars(IFSEdaAbstractAlgo):
|
|
|
348
435
|
"""Convert some FA file in ECMWF-GRIB files. PLEASE DO NOT USE !"""
|
|
349
436
|
|
|
350
437
|
_footprint = dict(
|
|
351
|
-
info=
|
|
438
|
+
info="Convert some FA file in ECMWF-GRIB files.",
|
|
352
439
|
attr=dict(
|
|
353
440
|
kind=dict(
|
|
354
|
-
values=[
|
|
441
|
+
values=["femars"],
|
|
355
442
|
),
|
|
356
|
-
rawfiles
|
|
357
|
-
type
|
|
358
|
-
optional
|
|
359
|
-
default
|
|
443
|
+
rawfiles=dict(
|
|
444
|
+
type=bool,
|
|
445
|
+
optional=True,
|
|
446
|
+
default=False,
|
|
360
447
|
),
|
|
361
|
-
)
|
|
448
|
+
),
|
|
362
449
|
)
|
|
363
450
|
|
|
364
451
|
def postfix(self, rh, opts):
|
|
@@ -366,31 +453,36 @@ class IFSEdaFemars(IFSEdaAbstractAlgo):
|
|
|
366
453
|
sh = self.system
|
|
367
454
|
# Gather rawfiles in folders
|
|
368
455
|
if self.rawfiles:
|
|
369
|
-
flist = sh.glob(
|
|
370
|
-
dest =
|
|
371
|
-
logger.info(
|
|
456
|
+
flist = sh.glob("tmprawfile_D000_L*")
|
|
457
|
+
dest = "rawfiles"
|
|
458
|
+
logger.info("Creating a rawfiles pack: %s", dest)
|
|
372
459
|
sh.mkdir(dest)
|
|
373
460
|
for fic in flist:
|
|
374
|
-
sh.mv(fic, dest, fmt=
|
|
461
|
+
sh.mv(fic, dest, fmt="grib")
|
|
375
462
|
super().postfix(rh, opts)
|
|
376
463
|
|
|
377
464
|
|
|
378
465
|
class IFSInflationLike(IFSEdaAbstractAlgo):
|
|
379
466
|
"""Apply the inflation scheme on a given modelstate."""
|
|
380
467
|
|
|
381
|
-
_RUNSTORE =
|
|
382
|
-
_USELESS_MATCH = re.compile(r
|
|
468
|
+
_RUNSTORE = "RUNOUT"
|
|
469
|
+
_USELESS_MATCH = re.compile(r"^(?P<target>\w+)\+term\d+:\d+$")
|
|
383
470
|
|
|
384
471
|
_footprint = dict(
|
|
385
|
-
info=
|
|
472
|
+
info="Operations around the background error covariance matrix",
|
|
386
473
|
attr=dict(
|
|
387
474
|
kind=dict(
|
|
388
|
-
values=[
|
|
475
|
+
values=[
|
|
476
|
+
"infl",
|
|
477
|
+
"pert",
|
|
478
|
+
],
|
|
389
479
|
),
|
|
390
480
|
conf=dict(
|
|
391
|
-
values=[
|
|
392
|
-
|
|
393
|
-
|
|
481
|
+
values=[
|
|
482
|
+
701,
|
|
483
|
+
]
|
|
484
|
+
),
|
|
485
|
+
),
|
|
394
486
|
)
|
|
395
487
|
|
|
396
488
|
def __init__(self, *kargs, **kwargs):
|
|
@@ -400,35 +492,57 @@ class IFSInflationLike(IFSEdaAbstractAlgo):
|
|
|
400
492
|
def _check_effective_terms(self, roles):
|
|
401
493
|
eff_terms = None
|
|
402
494
|
for role in roles:
|
|
403
|
-
eterm = {
|
|
404
|
-
|
|
495
|
+
eterm = {
|
|
496
|
+
sec.rh.resource.term
|
|
497
|
+
for sec in self.context.sequence.effective_inputs(role=role)
|
|
498
|
+
}
|
|
405
499
|
if eterm:
|
|
406
500
|
if eff_terms is None:
|
|
407
501
|
eff_terms = eterm
|
|
408
502
|
else:
|
|
409
503
|
if eff_terms != eterm:
|
|
410
|
-
raise AlgoComponentError(
|
|
504
|
+
raise AlgoComponentError(
|
|
505
|
+
"Inconsistencies between inputs effective terms."
|
|
506
|
+
)
|
|
411
507
|
return sorted(eff_terms)
|
|
412
508
|
|
|
413
|
-
def _link_stuff_in(
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
509
|
+
def _link_stuff_in(
|
|
510
|
+
self, role, actualterm, targetnc, targetintent="in", wastebasket=None
|
|
511
|
+
):
|
|
512
|
+
estuff = [
|
|
513
|
+
sec
|
|
514
|
+
for sec in self.context.sequence.effective_inputs(role=role)
|
|
515
|
+
if sec.rh.resource.term == actualterm
|
|
516
|
+
]
|
|
417
517
|
if len(estuff) > 1:
|
|
418
|
-
logger.warning(
|
|
518
|
+
logger.warning(
|
|
519
|
+
"Multiple %s for the same date ! Going on...", role
|
|
520
|
+
)
|
|
419
521
|
elif len(estuff) == 1:
|
|
420
522
|
# Detect the inputs format
|
|
421
523
|
actfmt = estuff[0].rh.container.actualfmt
|
|
422
524
|
nconv = self.naming_convention(actualfmt=actfmt, **targetnc)
|
|
423
525
|
targetname = nconv(**targetnc)
|
|
424
526
|
if self.system.path.exists(targetname):
|
|
425
|
-
logger.info(
|
|
426
|
-
|
|
527
|
+
logger.info(
|
|
528
|
+
"%s: %s already exists. Hopping for the best...",
|
|
529
|
+
role,
|
|
530
|
+
targetname,
|
|
531
|
+
)
|
|
427
532
|
else:
|
|
428
|
-
logger.info(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
533
|
+
logger.info(
|
|
534
|
+
"%s: copying (intent=%s) %s to %s",
|
|
535
|
+
role,
|
|
536
|
+
targetintent,
|
|
537
|
+
estuff[0].rh.container.localpath(),
|
|
538
|
+
targetname,
|
|
539
|
+
)
|
|
540
|
+
self.system.cp(
|
|
541
|
+
estuff[0].rh.container.localpath(),
|
|
542
|
+
targetname,
|
|
543
|
+
fmt=actfmt,
|
|
544
|
+
intent=targetintent,
|
|
545
|
+
)
|
|
432
546
|
if wastebasket is not None:
|
|
433
547
|
wastebasket.append((targetname, actfmt))
|
|
434
548
|
return nconv, targetname, estuff[0]
|
|
@@ -437,92 +551,152 @@ class IFSInflationLike(IFSEdaAbstractAlgo):
|
|
|
437
551
|
def execute(self, rh, opts):
|
|
438
552
|
"""Loop on the various terms provided."""
|
|
439
553
|
|
|
440
|
-
eff_terms = self._check_effective_terms(
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
fix_curclim = self.do_climfile_fixer(rh, convkind=
|
|
444
|
-
fix_clclim = self.do_climfile_fixer(rh, convkind=
|
|
554
|
+
eff_terms = self._check_effective_terms(
|
|
555
|
+
["ModelState", "EnsembleMean", "Guess"]
|
|
556
|
+
)
|
|
557
|
+
fix_curclim = self.do_climfile_fixer(rh, convkind="modelclim")
|
|
558
|
+
fix_clclim = self.do_climfile_fixer(rh, convkind="closest_modelclim")
|
|
445
559
|
|
|
446
560
|
if eff_terms:
|
|
447
561
|
for actualterm in eff_terms:
|
|
448
562
|
wastebasket = list()
|
|
449
|
-
self.system.title(
|
|
450
|
-
self.system.subtitle(
|
|
563
|
+
self.system.title("Loop on term {!s}".format(actualterm))
|
|
564
|
+
self.system.subtitle("Solving the input files nightmare...")
|
|
451
565
|
# Ensemble Mean ?
|
|
452
|
-
mean_number = 2 if self.model ==
|
|
453
|
-
targetnc = dict(
|
|
454
|
-
|
|
455
|
-
|
|
566
|
+
mean_number = 2 if self.model == "arome" else 0
|
|
567
|
+
targetnc = dict(
|
|
568
|
+
kind="edainput", variant="infl", rh=rh, number=mean_number
|
|
569
|
+
)
|
|
570
|
+
self._link_stuff_in(
|
|
571
|
+
"EnsembleMean",
|
|
572
|
+
actualterm,
|
|
573
|
+
targetnc,
|
|
574
|
+
wastebasket=wastebasket,
|
|
575
|
+
)
|
|
456
576
|
# Model State ?
|
|
457
|
-
targetnc = dict(
|
|
458
|
-
|
|
459
|
-
|
|
577
|
+
targetnc = dict(
|
|
578
|
+
kind="edainput", variant="infl", rh=rh, number=1
|
|
579
|
+
)
|
|
580
|
+
_, _, mstate = self._link_stuff_in(
|
|
581
|
+
"ModelState", actualterm, targetnc, wastebasket=wastebasket
|
|
582
|
+
)
|
|
460
583
|
# Control ?
|
|
461
|
-
control_number = 0 if self.model ==
|
|
462
|
-
targetnc = dict(
|
|
463
|
-
|
|
464
|
-
|
|
584
|
+
control_number = 0 if self.model == "arome" else 2
|
|
585
|
+
targetnc = dict(
|
|
586
|
+
kind="edainput",
|
|
587
|
+
variant="infl",
|
|
588
|
+
rh=rh,
|
|
589
|
+
number=control_number,
|
|
590
|
+
)
|
|
591
|
+
self._link_stuff_in(
|
|
592
|
+
"Control", actualterm, targetnc, wastebasket=wastebasket
|
|
593
|
+
)
|
|
465
594
|
# Guess ?
|
|
466
|
-
targetnc = dict(
|
|
467
|
-
|
|
468
|
-
|
|
595
|
+
targetnc = dict(
|
|
596
|
+
kind="edaoutput",
|
|
597
|
+
variant="infl",
|
|
598
|
+
rh=rh,
|
|
599
|
+
number=1,
|
|
600
|
+
term=Time(0),
|
|
601
|
+
)
|
|
602
|
+
outnc, _, _ = self._link_stuff_in(
|
|
603
|
+
"Guess", actualterm, targetnc, targetintent="inout"
|
|
604
|
+
)
|
|
469
605
|
if outnc is None:
|
|
470
|
-
outnc = self.naming_convention(
|
|
606
|
+
outnc = self.naming_convention(
|
|
607
|
+
kind="edaoutput", variant="infl", rh=rh
|
|
608
|
+
)
|
|
471
609
|
# Fix clim !
|
|
472
610
|
if fix_curclim and mstate:
|
|
473
611
|
month = Month((mstate.rh.resource.date + actualterm).ymdh)
|
|
474
|
-
self.climfile_fixer(
|
|
475
|
-
|
|
476
|
-
|
|
612
|
+
self.climfile_fixer(
|
|
613
|
+
rh=rh,
|
|
614
|
+
convkind="modelclim",
|
|
615
|
+
month=month,
|
|
616
|
+
inputrole=("GlobalClim", "InitialClim"),
|
|
617
|
+
inputkind="clim_model",
|
|
618
|
+
)
|
|
477
619
|
if fix_clclim and mstate:
|
|
478
|
-
closestmonth = Month(
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
620
|
+
closestmonth = Month(
|
|
621
|
+
(mstate.rh.resource.date + actualterm).ymdh
|
|
622
|
+
+ ":closest"
|
|
623
|
+
)
|
|
624
|
+
self.climfile_fixer(
|
|
625
|
+
rh=rh,
|
|
626
|
+
convkind="closest_modelclim",
|
|
627
|
+
month=closestmonth,
|
|
628
|
+
inputrole=("GlobalClim", "InitialClim"),
|
|
629
|
+
inputkind="clim_model",
|
|
630
|
+
)
|
|
482
631
|
# Deal with useless stuff... SADLY !
|
|
483
|
-
useless = [
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
632
|
+
useless = [
|
|
633
|
+
sec
|
|
634
|
+
for sec in self.context.sequence.effective_inputs(
|
|
635
|
+
role="Useless"
|
|
636
|
+
)
|
|
637
|
+
if (
|
|
638
|
+
sec.rh.resource.term == actualterm
|
|
639
|
+
and self._USELESS_MATCH.match(
|
|
640
|
+
sec.rh.container.localpath()
|
|
641
|
+
)
|
|
642
|
+
)
|
|
643
|
+
]
|
|
487
644
|
for a_useless in useless:
|
|
488
|
-
targetname = self._USELESS_MATCH.match(
|
|
645
|
+
targetname = self._USELESS_MATCH.match(
|
|
646
|
+
a_useless.rh.container.localpath()
|
|
647
|
+
).group("target")
|
|
489
648
|
if self.system.path.exists(targetname):
|
|
490
|
-
logger.warning(
|
|
491
|
-
|
|
649
|
+
logger.warning(
|
|
650
|
+
"Some useless stuff is already here: %s. I don't care...",
|
|
651
|
+
targetname,
|
|
652
|
+
)
|
|
492
653
|
else:
|
|
493
|
-
logger.info(
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
654
|
+
logger.info(
|
|
655
|
+
"Dealing with useless stuff: %s -> %s",
|
|
656
|
+
a_useless.rh.container.localpath(),
|
|
657
|
+
targetname,
|
|
658
|
+
)
|
|
659
|
+
self.system.cp(
|
|
660
|
+
a_useless.rh.container.localpath(),
|
|
661
|
+
targetname,
|
|
662
|
+
fmt=a_useless.rh.container.actualfmt,
|
|
663
|
+
intent="in",
|
|
664
|
+
)
|
|
665
|
+
wastebasket.append(
|
|
666
|
+
(targetname, a_useless.rh.container.actualfmt)
|
|
667
|
+
)
|
|
498
668
|
|
|
499
669
|
# Standard execution
|
|
500
670
|
super().execute(rh, opts)
|
|
501
671
|
|
|
502
672
|
# The concatenated listing
|
|
503
|
-
self.system.cat(
|
|
673
|
+
self.system.cat("NODE.001_01", output="NODE.all")
|
|
504
674
|
|
|
505
675
|
# prepares the next execution
|
|
506
676
|
if len(eff_terms) > 1:
|
|
507
677
|
self.system.mkdir(self._RUNSTORE)
|
|
508
678
|
# Freeze the current output
|
|
509
|
-
shelf_label = self.system.path.join(
|
|
510
|
-
|
|
679
|
+
shelf_label = self.system.path.join(
|
|
680
|
+
self._RUNSTORE, outnc(number=1, term=actualterm)
|
|
681
|
+
)
|
|
682
|
+
self.system.move(
|
|
683
|
+
outnc(number=1, term=Time(0)), shelf_label, fmt="fa"
|
|
684
|
+
)
|
|
511
685
|
self._outputs_shelf.append(shelf_label)
|
|
512
686
|
# Some cleaning
|
|
513
687
|
for afile in wastebasket:
|
|
514
688
|
self.system.remove(afile[0], fmt=afile[1])
|
|
515
|
-
self.system.rmall(
|
|
689
|
+
self.system.rmall("ncf927", "dirlst")
|
|
516
690
|
else:
|
|
517
691
|
# We should not be here but whatever... some task are poorly written !
|
|
518
692
|
super().execute(rh, opts)
|
|
519
693
|
|
|
520
694
|
def postfix(self, rh, opts):
|
|
521
695
|
"""Post-processing cleaning."""
|
|
522
|
-
self.system.title(
|
|
696
|
+
self.system.title("Finalising the execution...")
|
|
523
697
|
for afile in self._outputs_shelf:
|
|
524
698
|
logger.info("Output found: %s", self.system.path.basename(afile))
|
|
525
|
-
self.system.move(afile, self.system.path.basename(afile), fmt=
|
|
699
|
+
self.system.move(afile, self.system.path.basename(afile), fmt="fa")
|
|
526
700
|
super().postfix(rh, opts)
|
|
527
701
|
|
|
528
702
|
|
|
@@ -530,12 +704,14 @@ class IFSInflationFactor(IFSEdaEnsembleAbstractAlgo):
|
|
|
530
704
|
"""Compute an inflation factor based on individual members."""
|
|
531
705
|
|
|
532
706
|
_footprint = dict(
|
|
533
|
-
info=
|
|
707
|
+
info="Compute an inflation factor based on individual members",
|
|
534
708
|
attr=dict(
|
|
535
709
|
kind=dict(
|
|
536
|
-
values=[
|
|
710
|
+
values=[
|
|
711
|
+
"infl_factor",
|
|
712
|
+
],
|
|
537
713
|
),
|
|
538
|
-
)
|
|
714
|
+
),
|
|
539
715
|
)
|
|
540
716
|
|
|
541
717
|
|
|
@@ -546,15 +722,17 @@ class IFSInflationFactorLegacy(IFSInflationFactor):
|
|
|
546
722
|
"""
|
|
547
723
|
|
|
548
724
|
_footprint = dict(
|
|
549
|
-
info=
|
|
725
|
+
info="Compute an inflation factor based on individual members",
|
|
550
726
|
attr=dict(
|
|
551
727
|
kind=dict(
|
|
552
|
-
values=[
|
|
728
|
+
values=["infl", "pert"],
|
|
553
729
|
),
|
|
554
730
|
conf=dict(
|
|
555
|
-
outcast=[
|
|
556
|
-
|
|
557
|
-
|
|
731
|
+
outcast=[
|
|
732
|
+
701,
|
|
733
|
+
]
|
|
734
|
+
),
|
|
735
|
+
),
|
|
558
736
|
)
|
|
559
737
|
|
|
560
738
|
|
|
@@ -562,12 +740,14 @@ class IFSEnsembleMean(IFSEdaEnsembleAbstractAlgo):
|
|
|
562
740
|
"""Apply the inflation scheme on a given modelstate."""
|
|
563
741
|
|
|
564
742
|
_footprint = dict(
|
|
565
|
-
info=
|
|
743
|
+
info="Operations around the background error covariance matrix",
|
|
566
744
|
attr=dict(
|
|
567
745
|
kind=dict(
|
|
568
|
-
values=[
|
|
746
|
+
values=[
|
|
747
|
+
"mean",
|
|
748
|
+
],
|
|
569
749
|
),
|
|
570
|
-
)
|
|
750
|
+
),
|
|
571
751
|
)
|
|
572
752
|
|
|
573
753
|
|
|
@@ -575,20 +755,22 @@ class IFSCovB(IFSEdaLaggedEnsembleAbstractAlgo):
|
|
|
575
755
|
"""Operations around the background error covariance matrix."""
|
|
576
756
|
|
|
577
757
|
_footprint = dict(
|
|
578
|
-
info=
|
|
758
|
+
info="Operations around the background error covariance matrix",
|
|
579
759
|
attr=dict(
|
|
580
760
|
kind=dict(
|
|
581
|
-
values=[
|
|
761
|
+
values=[
|
|
762
|
+
"covb",
|
|
763
|
+
],
|
|
582
764
|
),
|
|
583
|
-
hybrid
|
|
584
|
-
type
|
|
585
|
-
optional
|
|
586
|
-
default
|
|
765
|
+
hybrid=dict(
|
|
766
|
+
type=bool,
|
|
767
|
+
optional=True,
|
|
768
|
+
default=False,
|
|
587
769
|
),
|
|
588
|
-
)
|
|
770
|
+
),
|
|
589
771
|
)
|
|
590
772
|
|
|
591
|
-
_HYBRID_CLIM_ROLE =
|
|
773
|
+
_HYBRID_CLIM_ROLE = "ClimatologicalModelState"
|
|
592
774
|
|
|
593
775
|
@property
|
|
594
776
|
def actual_totalnumber(self):
|
|
@@ -600,33 +782,59 @@ class IFSCovB(IFSEdaLaggedEnsembleAbstractAlgo):
|
|
|
600
782
|
"""Default pre-link for the initial condition file"""
|
|
601
783
|
super().prepare(rh, opts)
|
|
602
784
|
# Legacy...
|
|
603
|
-
for num, sec in enumerate(
|
|
604
|
-
|
|
605
|
-
|
|
785
|
+
for num, sec in enumerate(
|
|
786
|
+
sorted(
|
|
787
|
+
self.context.sequence.effective_inputs(role="Rawfiles"),
|
|
788
|
+
key=self._members_sorting_key,
|
|
789
|
+
),
|
|
790
|
+
start=1,
|
|
791
|
+
):
|
|
606
792
|
repname = sec.rh.container.localpath()
|
|
607
|
-
radical = repname.split(
|
|
793
|
+
radical = repname.split("_")[0] + "_D{:03d}_L{:s}"
|
|
608
794
|
for filename in self.system.listdir(repname):
|
|
609
|
-
level = re.search(r
|
|
795
|
+
level = re.search(r"_L(\d+)$", filename)
|
|
610
796
|
if level is not None:
|
|
611
|
-
self.system.softlink(
|
|
612
|
-
|
|
797
|
+
self.system.softlink(
|
|
798
|
+
self.system.path.join(repname, filename),
|
|
799
|
+
radical.format(num, level.group(1)),
|
|
800
|
+
)
|
|
613
801
|
# Legacy...
|
|
614
|
-
for num, sec in enumerate(
|
|
615
|
-
|
|
616
|
-
|
|
802
|
+
for num, sec in enumerate(
|
|
803
|
+
sorted(
|
|
804
|
+
self.context.sequence.effective_inputs(role="LaggedEnsemble"),
|
|
805
|
+
key=self._members_sorting_key,
|
|
806
|
+
),
|
|
807
|
+
start=1,
|
|
808
|
+
):
|
|
617
809
|
repname = sec.rh.container.localpath()
|
|
618
|
-
radical = repname.split(
|
|
810
|
+
radical = repname.split("_")[0] + "_{:03d}"
|
|
619
811
|
self.system.softlink(repname, radical.format(num))
|
|
620
812
|
# Requesting Hybrid computations ?
|
|
621
813
|
if self.hybrid:
|
|
622
|
-
hybstuff = self.context.sequence.effective_inputs(
|
|
814
|
+
hybstuff = self.context.sequence.effective_inputs(
|
|
815
|
+
role=self._HYBRID_CLIM_ROLE
|
|
816
|
+
)
|
|
623
817
|
hybformat = hybstuff[0].rh.container.actualfmt
|
|
624
|
-
totalnumber =
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
818
|
+
totalnumber = (
|
|
819
|
+
self.actual_nbe * self.actual_nresx
|
|
820
|
+
if self.padding
|
|
821
|
+
else self.actual_nbe
|
|
822
|
+
)
|
|
823
|
+
for i, tnum in enumerate(
|
|
824
|
+
range(totalnumber + 1, 2 * totalnumber + 1)
|
|
825
|
+
):
|
|
826
|
+
innc = self.naming_convention(
|
|
827
|
+
kind="edainput",
|
|
828
|
+
variant=self.kind,
|
|
829
|
+
totalnumber=self.actual_totalnumber,
|
|
830
|
+
rh=rh,
|
|
831
|
+
actualfmt=hybformat,
|
|
832
|
+
)
|
|
833
|
+
logger.info(
|
|
834
|
+
"Soft-Linking %s to %s",
|
|
835
|
+
hybstuff[i].rh.container.localpath(),
|
|
836
|
+
innc(number=tnum),
|
|
837
|
+
)
|
|
838
|
+
self.system.softlink(
|
|
839
|
+
hybstuff[i].rh.container.localpath(), innc(number=tnum)
|
|
840
|
+
)
|