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/oopsroot.py
CHANGED
|
@@ -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
|
|
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(
|
|
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__(
|
|
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] = (
|
|
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[
|
|
65
|
+
self._generic_config_subs["member"] = self.member
|
|
56
66
|
for namrh in self.updatable_namelists:
|
|
57
|
-
namrh.contents.setmacro(
|
|
58
|
-
namrh.contents.setmacro(
|
|
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(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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(
|
|
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(
|
|
128
|
-
|
|
129
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
159
|
-
|
|
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(
|
|
162
|
-
|
|
163
|
-
|
|
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(
|
|
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(
|
|
175
|
-
|
|
176
|
-
|
|
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(
|
|
184
|
-
|
|
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
|
-
|
|
191
|
-
|
|
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 = {
|
|
240
|
+
multieffterms = {
|
|
241
|
+
r: ets for r, ets in alleffterms.items() if len(ets) > 1
|
|
242
|
+
}
|
|
204
243
|
if multieffterms:
|
|
205
|
-
if
|
|
206
|
-
|
|
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
|
|
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(
|
|
215
|
-
|
|
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(
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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(
|
|
232
|
-
|
|
287
|
+
logger.warning(
|
|
288
|
+
"Missing items: %s (role: %s).",
|
|
289
|
+
s.rh.container.localpath(),
|
|
290
|
+
arole,
|
|
291
|
+
)
|
|
233
292
|
if broken:
|
|
234
|
-
logger.warning(
|
|
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(
|
|
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(
|
|
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 = [
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
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 = {
|
|
256
|
-
|
|
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
|
-
(
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
298
|
-
|
|
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[
|
|
301
|
-
self._generic_config_subs[
|
|
302
|
-
self._generic_config_subs[
|
|
303
|
-
|
|
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[
|
|
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(
|
|
402
|
+
namrh.contents.setmacro("ENS_MEMBERS", len(self._ens_members_num))
|
|
309
403
|
if self._ens_members_num:
|
|
310
|
-
namrh.contents.setmacro(
|
|
404
|
+
namrh.contents.setmacro(
|
|
405
|
+
"ENS_AUTO_NSTRIN", len(self._ens_members_num)
|
|
406
|
+
)
|
|
311
407
|
else:
|
|
312
|
-
namrh.contents.setmacro(
|
|
313
|
-
|
|
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[
|
|
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 = [
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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 = (
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
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[
|
|
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(
|
|
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 = (
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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 = {
|
|
406
|
-
|
|
407
|
-
|
|
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(
|
|
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 = [
|
|
414
|
-
|
|
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[
|
|
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(
|
|
419
|
-
|
|
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(
|
|
544
|
+
namrh.contents.setmacro(
|
|
545
|
+
"UPD{:d}_TIMESTEP".format(upd_i), tstep
|
|
546
|
+
)
|
|
422
547
|
if actual_niters:
|
|
423
|
-
self._generic_config_subs[
|
|
548
|
+
self._generic_config_subs["incremental_niters"] = actual_niters
|
|
424
549
|
for upd_i, niter in enumerate(actual_niters, start=1):
|
|
425
|
-
logger.info(
|
|
426
|
-
|
|
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(
|
|
556
|
+
namrh.contents.setmacro(
|
|
557
|
+
"UPD{:d}_NITER".format(upd_i), niter
|
|
558
|
+
)
|
|
429
559
|
if actual_geos:
|
|
430
|
-
self._generic_config_subs[
|
|
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(
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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
|
|
444
|
-
attr
|
|
445
|
-
kind
|
|
446
|
-
values
|
|
575
|
+
info="Any OOPS Run (abstract).",
|
|
576
|
+
attr=dict(
|
|
577
|
+
kind=dict(
|
|
578
|
+
values=["oorun"],
|
|
447
579
|
),
|
|
448
|
-
date
|
|
449
|
-
info
|
|
450
|
-
access
|
|
451
|
-
type
|
|
452
|
-
doc_zorder
|
|
580
|
+
date=dict(
|
|
581
|
+
info="The current run date.",
|
|
582
|
+
access="rwx",
|
|
583
|
+
type=Date,
|
|
584
|
+
doc_zorder=-50,
|
|
453
585
|
),
|
|
454
|
-
config_subs
|
|
455
|
-
info
|
|
456
|
-
optional
|
|
457
|
-
type
|
|
458
|
-
default
|
|
459
|
-
doc_zorder
|
|
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
|
|
594
|
+
default="basicnwp",
|
|
463
595
|
),
|
|
464
|
-
)
|
|
596
|
+
),
|
|
465
597
|
)
|
|
466
598
|
|
|
467
599
|
def __init__(self, *kargs, **kwargs):
|
|
@@ -480,13 +612,18 @@ 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,
|
|
615
|
+
if hasattr(rh.resource, "cycle"):
|
|
484
616
|
self._oops_cycle = rh.resource.cycle
|
|
485
617
|
return valid
|
|
486
618
|
else:
|
|
487
|
-
logger.error(
|
|
619
|
+
logger.error("The binary < %s > has no cycle attribute", repr(rh))
|
|
488
620
|
return False
|
|
489
621
|
|
|
622
|
+
def _mpitool_attributes(self, opts):
|
|
623
|
+
conf_dict = super()._mpitool_attributes(opts)
|
|
624
|
+
conf_dict.update({"mplbased": True})
|
|
625
|
+
return conf_dict
|
|
626
|
+
|
|
490
627
|
def prepare(self, rh, opts):
|
|
491
628
|
"""Preliminary setups."""
|
|
492
629
|
super().prepare(rh, opts)
|
|
@@ -508,24 +645,31 @@ class OOPSParallel(Parallel,
|
|
|
508
645
|
"""Prepare options for the binary's command line."""
|
|
509
646
|
mconfig = list(self._individual_config_subs.keys())[0]
|
|
510
647
|
configfile = mconfig.rh.container.localpath()
|
|
511
|
-
options = {
|
|
648
|
+
options = {"configfile": configfile}
|
|
512
649
|
return options
|
|
513
650
|
|
|
514
651
|
@cached_property
|
|
515
652
|
def updatable_namelists(self):
|
|
516
|
-
return [
|
|
653
|
+
return [
|
|
654
|
+
s.rh
|
|
655
|
+
for s in self.context.sequence.effective_inputs(role="Namelist")
|
|
656
|
+
]
|
|
517
657
|
|
|
518
658
|
def set_config_rendering(self):
|
|
519
659
|
"""
|
|
520
660
|
Look into effective inputs for configuration files and register them for
|
|
521
661
|
a later rendering using bronx' templating system.
|
|
522
662
|
"""
|
|
523
|
-
mconfig = self.context.sequence.effective_inputs(role=
|
|
524
|
-
gconfig = self.context.sequence.effective_inputs(role=
|
|
663
|
+
mconfig = self.context.sequence.effective_inputs(role="MainConfig")
|
|
664
|
+
gconfig = self.context.sequence.effective_inputs(role="Config")
|
|
525
665
|
if len(mconfig) > 1:
|
|
526
|
-
raise AlgoComponentError(
|
|
666
|
+
raise AlgoComponentError(
|
|
667
|
+
"Only one Main Config section may be provided."
|
|
668
|
+
)
|
|
527
669
|
if len(mconfig) == 0 and len(gconfig) != 1:
|
|
528
|
-
raise AlgoComponentError(
|
|
670
|
+
raise AlgoComponentError(
|
|
671
|
+
"Please provide a Main Config section or a unique Config section."
|
|
672
|
+
)
|
|
529
673
|
if len(mconfig) == 1:
|
|
530
674
|
gconfig.insert(0, mconfig[0])
|
|
531
675
|
self._individual_config_subs = {sconf: dict() for sconf in gconfig}
|
|
@@ -534,40 +678,53 @@ class OOPSParallel(Parallel,
|
|
|
534
678
|
"""Render registered configuration files using the bronx' templating system."""
|
|
535
679
|
l_first = True
|
|
536
680
|
for sconf, sdict in self._individual_config_subs.items():
|
|
537
|
-
self.system.subtitle(
|
|
538
|
-
|
|
681
|
+
self.system.subtitle(
|
|
682
|
+
"Configuration file rendering for: {:s}".format(
|
|
683
|
+
sconf.rh.container.localpath()
|
|
684
|
+
)
|
|
685
|
+
)
|
|
539
686
|
l_subs = dict(now=self.date, date=self.date)
|
|
540
687
|
l_subs.update(self._generic_config_subs)
|
|
541
688
|
l_subs.update(sdict)
|
|
542
689
|
l_subs.update(self.config_subs)
|
|
543
690
|
if l_subs != self._last_l_subs.get(sconf, dict()):
|
|
544
|
-
if not hasattr(sconf.rh.contents,
|
|
545
|
-
logger.error(
|
|
546
|
-
|
|
691
|
+
if not hasattr(sconf.rh.contents, "bronx_tpl_render"):
|
|
692
|
+
logger.error(
|
|
693
|
+
'The < %s > content object has no "bronx_tpl_render" method. Skipping it.',
|
|
694
|
+
repr(sconf.rh.contents),
|
|
695
|
+
)
|
|
547
696
|
continue
|
|
548
697
|
try:
|
|
549
|
-
sconf.rh.contents.bronx_tpl_render(**
|
|
698
|
+
sconf.rh.contents.bronx_tpl_render(**l_subs)
|
|
550
699
|
except Exception:
|
|
551
|
-
logger.error(
|
|
552
|
-
|
|
700
|
+
logger.error(
|
|
701
|
+
"The config file rendering failed. The substitution dict was: \n%s",
|
|
702
|
+
lightdump(l_subs),
|
|
703
|
+
)
|
|
553
704
|
raise
|
|
554
705
|
self._last_l_subs[sconf] = l_subs
|
|
555
706
|
if l_first:
|
|
556
707
|
print(fulldump(sconf.rh.contents.data))
|
|
557
708
|
sconf.rh.save()
|
|
558
709
|
else:
|
|
559
|
-
logger.info(
|
|
710
|
+
logger.info(
|
|
711
|
+
"It's not necessary to update the file (no changes)."
|
|
712
|
+
)
|
|
560
713
|
l_first = False
|
|
561
714
|
|
|
562
715
|
def do_namelist_rendering(self):
|
|
563
|
-
todo = [
|
|
564
|
-
|
|
716
|
+
todo = [
|
|
717
|
+
r
|
|
718
|
+
for r in self.updatable_namelists
|
|
719
|
+
if r.contents.dumps_needs_update
|
|
720
|
+
]
|
|
721
|
+
self.system.subtitle("Updating namelists")
|
|
565
722
|
if todo:
|
|
566
723
|
for namrh in todo:
|
|
567
|
-
logger.info(
|
|
724
|
+
logger.info("Rewriting %s.", namrh.container.localpath())
|
|
568
725
|
namrh.save()
|
|
569
726
|
else:
|
|
570
|
-
logger.info(
|
|
727
|
+
logger.info("None of the namelists need to be rewritten.")
|
|
571
728
|
|
|
572
729
|
def boost_defaults(self):
|
|
573
730
|
"""Set defaults for BOOST environment variables.
|
|
@@ -576,14 +733,14 @@ class OOPSParallel(Parallel,
|
|
|
576
733
|
depends on the code's cycle number.
|
|
577
734
|
"""
|
|
578
735
|
defaults = {
|
|
579
|
-
IfsCycle(
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
736
|
+
IfsCycle("cy1"): {
|
|
737
|
+
"BOOST_TEST_CATCH_SYSTEM_ERRORS": "no",
|
|
738
|
+
"BOOST_TEST_DETECT_FP_EXCEPTIONS": "no",
|
|
739
|
+
"BOOST_TEST_LOG_FORMAT": "XML",
|
|
740
|
+
"BOOST_TEST_LOG_LEVEL": "message",
|
|
741
|
+
"BOOST_TEST_OUTPUT_FORMAT": "XML",
|
|
742
|
+
"BOOST_TEST_REPORT_FORMAT": "XML",
|
|
743
|
+
"BOOST_TEST_RESULT_CODE": "yes",
|
|
587
744
|
}
|
|
588
745
|
}
|
|
589
746
|
cydefaults = None
|
|
@@ -591,9 +748,11 @@ class OOPSParallel(Parallel,
|
|
|
591
748
|
if k < self.oops_cycle:
|
|
592
749
|
cydefaults = defdict
|
|
593
750
|
break
|
|
594
|
-
self.algoassert(
|
|
595
|
-
|
|
596
|
-
|
|
751
|
+
self.algoassert(
|
|
752
|
+
cydefaults is not None,
|
|
753
|
+
"BOOST defaults not found for cycle: {!s}".format(self.oops_cycle),
|
|
754
|
+
)
|
|
755
|
+
logger.info("Setting up BOOST defaults:%s", lightdump(cydefaults))
|
|
597
756
|
self.env.default(**cydefaults)
|
|
598
757
|
|
|
599
758
|
def eckit_defaults(self):
|
|
@@ -603,10 +762,12 @@ class OOPSParallel(Parallel,
|
|
|
603
762
|
depends on the code's cycle number.
|
|
604
763
|
"""
|
|
605
764
|
defaults = {
|
|
606
|
-
IfsCycle(
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
765
|
+
IfsCycle("cy1"): {
|
|
766
|
+
"ECKIT_MPI_INIT_THREAD": (
|
|
767
|
+
"MPI_THREAD_MULTIPLE"
|
|
768
|
+
if int(self.env.get("OMP_NUM_THREADS", "1")) > 1
|
|
769
|
+
else "MPI_THREAD_SINGLE"
|
|
770
|
+
),
|
|
610
771
|
}
|
|
611
772
|
}
|
|
612
773
|
cydefaults = None
|
|
@@ -614,9 +775,11 @@ class OOPSParallel(Parallel,
|
|
|
614
775
|
if k < self.oops_cycle:
|
|
615
776
|
cydefaults = defdict
|
|
616
777
|
break
|
|
617
|
-
self.algoassert(
|
|
618
|
-
|
|
619
|
-
|
|
778
|
+
self.algoassert(
|
|
779
|
+
cydefaults is not None,
|
|
780
|
+
"eckit defaults not found for cycle: {!s}".format(self.oops_cycle),
|
|
781
|
+
)
|
|
782
|
+
logger.info("Setting up eckit defaults:%s", lightdump(cydefaults))
|
|
620
783
|
self.env.default(**cydefaults)
|
|
621
784
|
|
|
622
785
|
|
|
@@ -625,15 +788,15 @@ class OOPSODB(OOPSParallel, odb.OdbComponentDecoMixin):
|
|
|
625
788
|
|
|
626
789
|
_abstract = True
|
|
627
790
|
_footprint = dict(
|
|
628
|
-
info
|
|
629
|
-
attr
|
|
630
|
-
kind
|
|
631
|
-
values
|
|
791
|
+
info="OOPS ObsOperator Test run.",
|
|
792
|
+
attr=dict(
|
|
793
|
+
kind=dict(
|
|
794
|
+
values=["oorunodb"],
|
|
632
795
|
),
|
|
633
|
-
binarysingle
|
|
634
|
-
default
|
|
796
|
+
binarysingle=dict(
|
|
797
|
+
default="basicnwpobsort",
|
|
635
798
|
),
|
|
636
|
-
)
|
|
799
|
+
),
|
|
637
800
|
)
|
|
638
801
|
|
|
639
802
|
#: If ``True``, an empty CCMA database will be created before the run and
|
|
@@ -648,18 +811,24 @@ class OOPSODB(OOPSParallel, odb.OdbComponentDecoMixin):
|
|
|
648
811
|
|
|
649
812
|
# Looking for input observations
|
|
650
813
|
allodb = self.lookupodb()
|
|
651
|
-
allcma = [
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
814
|
+
allcma = [
|
|
815
|
+
x for x in allodb if x.rh.resource.layout.lower() == self.virtualdb
|
|
816
|
+
]
|
|
817
|
+
if self.virtualdb.lower() == "ccma":
|
|
818
|
+
self.algoassert(
|
|
819
|
+
len(allcma) == 1, "A unique CCMA database is to be provided."
|
|
820
|
+
)
|
|
821
|
+
self.algoassert(
|
|
822
|
+
not self._OOPSODB_CCMA_DIRECT,
|
|
823
|
+
"_OOPSODB_CCMA_DIRECT needs to be False if virtualdb=ccma.",
|
|
824
|
+
)
|
|
656
825
|
cma = allcma[0]
|
|
657
826
|
cma_path = sh.path.abspath(cma.rh.container.localpath())
|
|
658
827
|
else:
|
|
659
828
|
cma_path = self.odb_merge_if_needed(allcma)
|
|
660
829
|
if self._OOPSODB_CCMA_DIRECT:
|
|
661
|
-
ccma_path = self.odb_create_db(layout=
|
|
662
|
-
self.odb.fix_db_path(
|
|
830
|
+
ccma_path = self.odb_create_db(layout="CCMA")
|
|
831
|
+
self.odb.fix_db_path("CCMA", ccma_path)
|
|
663
832
|
|
|
664
833
|
# Set ODB environment
|
|
665
834
|
self.odb.fix_db_path(self.virtualdb, cma_path)
|
|
@@ -669,51 +838,61 @@ class OOPSODB(OOPSParallel, odb.OdbComponentDecoMixin):
|
|
|
669
838
|
else:
|
|
670
839
|
self.odb.ioassign_gather(cma_path)
|
|
671
840
|
|
|
672
|
-
if self.virtualdb.lower() !=
|
|
841
|
+
if self.virtualdb.lower() != "ccma":
|
|
673
842
|
self.odb.create_poolmask(self.virtualdb, cma_path)
|
|
674
|
-
self.odb.shuffle_setup(
|
|
675
|
-
|
|
676
|
-
|
|
843
|
+
self.odb.shuffle_setup(
|
|
844
|
+
self.slots,
|
|
845
|
+
mergedirect=True,
|
|
846
|
+
ccmadirect=self._OOPSODB_CCMA_DIRECT,
|
|
847
|
+
)
|
|
677
848
|
|
|
678
849
|
# Fix the input databases intent
|
|
679
|
-
self.odb_rw_or_overwrite_method(*
|
|
850
|
+
self.odb_rw_or_overwrite_method(*allcma)
|
|
680
851
|
|
|
681
852
|
# Look for extras ODB raw
|
|
682
853
|
self.odb_handle_raw_dbs()
|
|
683
854
|
|
|
684
855
|
# Allow assimilation window / timeslots configuration
|
|
685
|
-
self._generic_config_subs[
|
|
686
|
-
self._generic_config_subs[
|
|
687
|
-
self._generic_config_subs[
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
self._generic_config_subs[
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
856
|
+
self._generic_config_subs["window_length"] = self.slots.window
|
|
857
|
+
self._generic_config_subs["window_lmargin"] = Period(-self.slots.start)
|
|
858
|
+
self._generic_config_subs["window_rmargin"] = (
|
|
859
|
+
self.slots.window + self.slots.start
|
|
860
|
+
)
|
|
861
|
+
self._generic_config_subs["timeslot_length"] = self.slots.chunk
|
|
862
|
+
self._generic_config_subs["timeslot_centered"] = self.slots.center
|
|
863
|
+
self._generic_config_subs["timeslot_centers"] = (
|
|
864
|
+
self.slots.as_centers_fromstart()
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
class OOPSAnalysis(
|
|
869
|
+
OOPSODB,
|
|
870
|
+
OOPSTimestepDecoMixin,
|
|
871
|
+
OOPSIncrementalDecoMixin,
|
|
872
|
+
OOPSMemberDecoMixin,
|
|
873
|
+
OOPSMembersTermsDetectDecoMixin,
|
|
874
|
+
):
|
|
698
875
|
"""Any kind of OOPS analysis (screening/thining step excluded)."""
|
|
699
876
|
|
|
700
877
|
_footprint = dict(
|
|
701
|
-
info
|
|
702
|
-
attr
|
|
703
|
-
kind
|
|
704
|
-
values
|
|
705
|
-
remap
|
|
878
|
+
info="OOPS minimisation.",
|
|
879
|
+
attr=dict(
|
|
880
|
+
kind=dict(
|
|
881
|
+
values=["ooanalysis", "oominim"],
|
|
882
|
+
remap=dict(autoremap="first"),
|
|
706
883
|
),
|
|
707
|
-
virtualdb
|
|
708
|
-
default
|
|
884
|
+
virtualdb=dict(
|
|
885
|
+
default="ccma",
|
|
709
886
|
),
|
|
710
887
|
withscreening=dict(
|
|
711
|
-
values
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
888
|
+
values=[
|
|
889
|
+
False,
|
|
890
|
+
],
|
|
891
|
+
type=bool,
|
|
892
|
+
optional=True,
|
|
893
|
+
default=False,
|
|
715
894
|
),
|
|
716
|
-
)
|
|
895
|
+
),
|
|
717
896
|
)
|
|
718
897
|
|
|
719
898
|
|
|
@@ -723,13 +902,15 @@ class OOPSAnalysisWithScreening(OOPSAnalysis):
|
|
|
723
902
|
_OOPSODB_CCMA_DIRECT = True
|
|
724
903
|
|
|
725
904
|
_footprint = dict(
|
|
726
|
-
attr
|
|
727
|
-
virtualdb
|
|
728
|
-
default
|
|
905
|
+
attr=dict(
|
|
906
|
+
virtualdb=dict(
|
|
907
|
+
default="ecma",
|
|
729
908
|
),
|
|
730
|
-
withscreening
|
|
731
|
-
values
|
|
732
|
-
|
|
909
|
+
withscreening=dict(
|
|
910
|
+
values=[
|
|
911
|
+
True,
|
|
912
|
+
],
|
|
913
|
+
optional=False,
|
|
733
914
|
),
|
|
734
915
|
)
|
|
735
916
|
)
|