vortex-nwp 2.0.0b1__py3-none-any.whl → 2.0.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vortex/__init__.py +59 -45
- vortex/algo/__init__.py +3 -2
- vortex/algo/components.py +940 -614
- vortex/algo/mpitools.py +802 -497
- 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 +428 -225
- vortex/data/outflow.py +15 -15
- vortex/data/providers.py +185 -163
- vortex/data/resources.py +48 -42
- vortex/data/stores.py +544 -413
- 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 +420 -271
- vortex/nwp/algo/fpserver.py +683 -307
- vortex/nwp/algo/ifsnaming.py +205 -145
- vortex/nwp/algo/ifsroot.py +210 -122
- vortex/nwp/algo/monitoring.py +132 -76
- vortex/nwp/algo/mpitools.py +321 -191
- vortex/nwp/algo/odbtools.py +617 -353
- vortex/nwp/algo/oopsroot.py +449 -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 +125 -59
- 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 +311 -149
- vortex/tools/lfi.py +423 -216
- vortex/tools/listings.py +109 -40
- vortex/tools/names.py +218 -156
- vortex/tools/net.py +632 -298
- vortex/tools/parallelism.py +93 -61
- vortex/tools/prestaging.py +55 -31
- vortex/tools/schedulers.py +172 -105
- vortex/tools/services.py +402 -333
- vortex/tools/storage.py +293 -358
- vortex/tools/surfex.py +24 -24
- vortex/tools/systems.py +1211 -631
- vortex/tools/targets.py +156 -100
- vortex/util/__init__.py +1 -1
- vortex/util/config.py +377 -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.0.0b2.dist-info/METADATA +66 -0
- vortex_nwp-2.0.0b2.dist-info/RECORD +142 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.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.0.0b2.dist-info}/LICENSE +0 -0
- {vortex_nwp-2.0.0b1.dist-info → vortex_nwp-2.0.0b2.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,11 +612,11 @@ class OOPSParallel(Parallel,
|
|
|
480
612
|
def valid_executable(self, rh):
|
|
481
613
|
"""Be sure that the specified executable has a cycle attribute."""
|
|
482
614
|
valid = super().valid_executable(rh)
|
|
483
|
-
if hasattr(rh.resource,
|
|
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
|
|
|
490
622
|
def prepare(self, rh, opts):
|
|
@@ -508,24 +640,31 @@ class OOPSParallel(Parallel,
|
|
|
508
640
|
"""Prepare options for the binary's command line."""
|
|
509
641
|
mconfig = list(self._individual_config_subs.keys())[0]
|
|
510
642
|
configfile = mconfig.rh.container.localpath()
|
|
511
|
-
options = {
|
|
643
|
+
options = {"configfile": configfile}
|
|
512
644
|
return options
|
|
513
645
|
|
|
514
646
|
@cached_property
|
|
515
647
|
def updatable_namelists(self):
|
|
516
|
-
return [
|
|
648
|
+
return [
|
|
649
|
+
s.rh
|
|
650
|
+
for s in self.context.sequence.effective_inputs(role="Namelist")
|
|
651
|
+
]
|
|
517
652
|
|
|
518
653
|
def set_config_rendering(self):
|
|
519
654
|
"""
|
|
520
655
|
Look into effective inputs for configuration files and register them for
|
|
521
656
|
a later rendering using bronx' templating system.
|
|
522
657
|
"""
|
|
523
|
-
mconfig = self.context.sequence.effective_inputs(role=
|
|
524
|
-
gconfig = self.context.sequence.effective_inputs(role=
|
|
658
|
+
mconfig = self.context.sequence.effective_inputs(role="MainConfig")
|
|
659
|
+
gconfig = self.context.sequence.effective_inputs(role="Config")
|
|
525
660
|
if len(mconfig) > 1:
|
|
526
|
-
raise AlgoComponentError(
|
|
661
|
+
raise AlgoComponentError(
|
|
662
|
+
"Only one Main Config section may be provided."
|
|
663
|
+
)
|
|
527
664
|
if len(mconfig) == 0 and len(gconfig) != 1:
|
|
528
|
-
raise AlgoComponentError(
|
|
665
|
+
raise AlgoComponentError(
|
|
666
|
+
"Please provide a Main Config section or a unique Config section."
|
|
667
|
+
)
|
|
529
668
|
if len(mconfig) == 1:
|
|
530
669
|
gconfig.insert(0, mconfig[0])
|
|
531
670
|
self._individual_config_subs = {sconf: dict() for sconf in gconfig}
|
|
@@ -534,40 +673,53 @@ class OOPSParallel(Parallel,
|
|
|
534
673
|
"""Render registered configuration files using the bronx' templating system."""
|
|
535
674
|
l_first = True
|
|
536
675
|
for sconf, sdict in self._individual_config_subs.items():
|
|
537
|
-
self.system.subtitle(
|
|
538
|
-
|
|
676
|
+
self.system.subtitle(
|
|
677
|
+
"Configuration file rendering for: {:s}".format(
|
|
678
|
+
sconf.rh.container.localpath()
|
|
679
|
+
)
|
|
680
|
+
)
|
|
539
681
|
l_subs = dict(now=self.date, date=self.date)
|
|
540
682
|
l_subs.update(self._generic_config_subs)
|
|
541
683
|
l_subs.update(sdict)
|
|
542
684
|
l_subs.update(self.config_subs)
|
|
543
685
|
if l_subs != self._last_l_subs.get(sconf, dict()):
|
|
544
|
-
if not hasattr(sconf.rh.contents,
|
|
545
|
-
logger.error(
|
|
546
|
-
|
|
686
|
+
if not hasattr(sconf.rh.contents, "bronx_tpl_render"):
|
|
687
|
+
logger.error(
|
|
688
|
+
'The < %s > content object has no "bronx_tpl_render" method. Skipping it.',
|
|
689
|
+
repr(sconf.rh.contents),
|
|
690
|
+
)
|
|
547
691
|
continue
|
|
548
692
|
try:
|
|
549
|
-
sconf.rh.contents.bronx_tpl_render(**
|
|
693
|
+
sconf.rh.contents.bronx_tpl_render(**l_subs)
|
|
550
694
|
except Exception:
|
|
551
|
-
logger.error(
|
|
552
|
-
|
|
695
|
+
logger.error(
|
|
696
|
+
"The config file rendering failed. The substitution dict was: \n%s",
|
|
697
|
+
lightdump(l_subs),
|
|
698
|
+
)
|
|
553
699
|
raise
|
|
554
700
|
self._last_l_subs[sconf] = l_subs
|
|
555
701
|
if l_first:
|
|
556
702
|
print(fulldump(sconf.rh.contents.data))
|
|
557
703
|
sconf.rh.save()
|
|
558
704
|
else:
|
|
559
|
-
logger.info(
|
|
705
|
+
logger.info(
|
|
706
|
+
"It's not necessary to update the file (no changes)."
|
|
707
|
+
)
|
|
560
708
|
l_first = False
|
|
561
709
|
|
|
562
710
|
def do_namelist_rendering(self):
|
|
563
|
-
todo = [
|
|
564
|
-
|
|
711
|
+
todo = [
|
|
712
|
+
r
|
|
713
|
+
for r in self.updatable_namelists
|
|
714
|
+
if r.contents.dumps_needs_update
|
|
715
|
+
]
|
|
716
|
+
self.system.subtitle("Updating namelists")
|
|
565
717
|
if todo:
|
|
566
718
|
for namrh in todo:
|
|
567
|
-
logger.info(
|
|
719
|
+
logger.info("Rewriting %s.", namrh.container.localpath())
|
|
568
720
|
namrh.save()
|
|
569
721
|
else:
|
|
570
|
-
logger.info(
|
|
722
|
+
logger.info("None of the namelists need to be rewritten.")
|
|
571
723
|
|
|
572
724
|
def boost_defaults(self):
|
|
573
725
|
"""Set defaults for BOOST environment variables.
|
|
@@ -576,14 +728,14 @@ class OOPSParallel(Parallel,
|
|
|
576
728
|
depends on the code's cycle number.
|
|
577
729
|
"""
|
|
578
730
|
defaults = {
|
|
579
|
-
IfsCycle(
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
731
|
+
IfsCycle("cy1"): {
|
|
732
|
+
"BOOST_TEST_CATCH_SYSTEM_ERRORS": "no",
|
|
733
|
+
"BOOST_TEST_DETECT_FP_EXCEPTIONS": "no",
|
|
734
|
+
"BOOST_TEST_LOG_FORMAT": "XML",
|
|
735
|
+
"BOOST_TEST_LOG_LEVEL": "message",
|
|
736
|
+
"BOOST_TEST_OUTPUT_FORMAT": "XML",
|
|
737
|
+
"BOOST_TEST_REPORT_FORMAT": "XML",
|
|
738
|
+
"BOOST_TEST_RESULT_CODE": "yes",
|
|
587
739
|
}
|
|
588
740
|
}
|
|
589
741
|
cydefaults = None
|
|
@@ -591,9 +743,11 @@ class OOPSParallel(Parallel,
|
|
|
591
743
|
if k < self.oops_cycle:
|
|
592
744
|
cydefaults = defdict
|
|
593
745
|
break
|
|
594
|
-
self.algoassert(
|
|
595
|
-
|
|
596
|
-
|
|
746
|
+
self.algoassert(
|
|
747
|
+
cydefaults is not None,
|
|
748
|
+
"BOOST defaults not found for cycle: {!s}".format(self.oops_cycle),
|
|
749
|
+
)
|
|
750
|
+
logger.info("Setting up BOOST defaults:%s", lightdump(cydefaults))
|
|
597
751
|
self.env.default(**cydefaults)
|
|
598
752
|
|
|
599
753
|
def eckit_defaults(self):
|
|
@@ -603,10 +757,12 @@ class OOPSParallel(Parallel,
|
|
|
603
757
|
depends on the code's cycle number.
|
|
604
758
|
"""
|
|
605
759
|
defaults = {
|
|
606
|
-
IfsCycle(
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
760
|
+
IfsCycle("cy1"): {
|
|
761
|
+
"ECKIT_MPI_INIT_THREAD": (
|
|
762
|
+
"MPI_THREAD_MULTIPLE"
|
|
763
|
+
if int(self.env.get("OMP_NUM_THREADS", "1")) > 1
|
|
764
|
+
else "MPI_THREAD_SINGLE"
|
|
765
|
+
),
|
|
610
766
|
}
|
|
611
767
|
}
|
|
612
768
|
cydefaults = None
|
|
@@ -614,9 +770,11 @@ class OOPSParallel(Parallel,
|
|
|
614
770
|
if k < self.oops_cycle:
|
|
615
771
|
cydefaults = defdict
|
|
616
772
|
break
|
|
617
|
-
self.algoassert(
|
|
618
|
-
|
|
619
|
-
|
|
773
|
+
self.algoassert(
|
|
774
|
+
cydefaults is not None,
|
|
775
|
+
"eckit defaults not found for cycle: {!s}".format(self.oops_cycle),
|
|
776
|
+
)
|
|
777
|
+
logger.info("Setting up eckit defaults:%s", lightdump(cydefaults))
|
|
620
778
|
self.env.default(**cydefaults)
|
|
621
779
|
|
|
622
780
|
|
|
@@ -625,15 +783,15 @@ class OOPSODB(OOPSParallel, odb.OdbComponentDecoMixin):
|
|
|
625
783
|
|
|
626
784
|
_abstract = True
|
|
627
785
|
_footprint = dict(
|
|
628
|
-
info
|
|
629
|
-
attr
|
|
630
|
-
kind
|
|
631
|
-
values
|
|
786
|
+
info="OOPS ObsOperator Test run.",
|
|
787
|
+
attr=dict(
|
|
788
|
+
kind=dict(
|
|
789
|
+
values=["oorunodb"],
|
|
632
790
|
),
|
|
633
|
-
binarysingle
|
|
634
|
-
default
|
|
791
|
+
binarysingle=dict(
|
|
792
|
+
default="basicnwpobsort",
|
|
635
793
|
),
|
|
636
|
-
)
|
|
794
|
+
),
|
|
637
795
|
)
|
|
638
796
|
|
|
639
797
|
#: If ``True``, an empty CCMA database will be created before the run and
|
|
@@ -648,18 +806,24 @@ class OOPSODB(OOPSParallel, odb.OdbComponentDecoMixin):
|
|
|
648
806
|
|
|
649
807
|
# Looking for input observations
|
|
650
808
|
allodb = self.lookupodb()
|
|
651
|
-
allcma = [
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
809
|
+
allcma = [
|
|
810
|
+
x for x in allodb if x.rh.resource.layout.lower() == self.virtualdb
|
|
811
|
+
]
|
|
812
|
+
if self.virtualdb.lower() == "ccma":
|
|
813
|
+
self.algoassert(
|
|
814
|
+
len(allcma) == 1, "A unique CCMA database is to be provided."
|
|
815
|
+
)
|
|
816
|
+
self.algoassert(
|
|
817
|
+
not self._OOPSODB_CCMA_DIRECT,
|
|
818
|
+
"_OOPSODB_CCMA_DIRECT needs to be False if virtualdb=ccma.",
|
|
819
|
+
)
|
|
656
820
|
cma = allcma[0]
|
|
657
821
|
cma_path = sh.path.abspath(cma.rh.container.localpath())
|
|
658
822
|
else:
|
|
659
823
|
cma_path = self.odb_merge_if_needed(allcma)
|
|
660
824
|
if self._OOPSODB_CCMA_DIRECT:
|
|
661
|
-
ccma_path = self.odb_create_db(layout=
|
|
662
|
-
self.odb.fix_db_path(
|
|
825
|
+
ccma_path = self.odb_create_db(layout="CCMA")
|
|
826
|
+
self.odb.fix_db_path("CCMA", ccma_path)
|
|
663
827
|
|
|
664
828
|
# Set ODB environment
|
|
665
829
|
self.odb.fix_db_path(self.virtualdb, cma_path)
|
|
@@ -669,51 +833,61 @@ class OOPSODB(OOPSParallel, odb.OdbComponentDecoMixin):
|
|
|
669
833
|
else:
|
|
670
834
|
self.odb.ioassign_gather(cma_path)
|
|
671
835
|
|
|
672
|
-
if self.virtualdb.lower() !=
|
|
836
|
+
if self.virtualdb.lower() != "ccma":
|
|
673
837
|
self.odb.create_poolmask(self.virtualdb, cma_path)
|
|
674
|
-
self.odb.shuffle_setup(
|
|
675
|
-
|
|
676
|
-
|
|
838
|
+
self.odb.shuffle_setup(
|
|
839
|
+
self.slots,
|
|
840
|
+
mergedirect=True,
|
|
841
|
+
ccmadirect=self._OOPSODB_CCMA_DIRECT,
|
|
842
|
+
)
|
|
677
843
|
|
|
678
844
|
# Fix the input databases intent
|
|
679
|
-
self.odb_rw_or_overwrite_method(*
|
|
845
|
+
self.odb_rw_or_overwrite_method(*allcma)
|
|
680
846
|
|
|
681
847
|
# Look for extras ODB raw
|
|
682
848
|
self.odb_handle_raw_dbs()
|
|
683
849
|
|
|
684
850
|
# Allow assimilation window / timeslots configuration
|
|
685
|
-
self._generic_config_subs[
|
|
686
|
-
self._generic_config_subs[
|
|
687
|
-
self._generic_config_subs[
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
self._generic_config_subs[
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
851
|
+
self._generic_config_subs["window_length"] = self.slots.window
|
|
852
|
+
self._generic_config_subs["window_lmargin"] = Period(-self.slots.start)
|
|
853
|
+
self._generic_config_subs["window_rmargin"] = (
|
|
854
|
+
self.slots.window + self.slots.start
|
|
855
|
+
)
|
|
856
|
+
self._generic_config_subs["timeslot_length"] = self.slots.chunk
|
|
857
|
+
self._generic_config_subs["timeslot_centered"] = self.slots.center
|
|
858
|
+
self._generic_config_subs["timeslot_centers"] = (
|
|
859
|
+
self.slots.as_centers_fromstart()
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
class OOPSAnalysis(
|
|
864
|
+
OOPSODB,
|
|
865
|
+
OOPSTimestepDecoMixin,
|
|
866
|
+
OOPSIncrementalDecoMixin,
|
|
867
|
+
OOPSMemberDecoMixin,
|
|
868
|
+
OOPSMembersTermsDetectDecoMixin,
|
|
869
|
+
):
|
|
698
870
|
"""Any kind of OOPS analysis (screening/thining step excluded)."""
|
|
699
871
|
|
|
700
872
|
_footprint = dict(
|
|
701
|
-
info
|
|
702
|
-
attr
|
|
703
|
-
kind
|
|
704
|
-
values
|
|
705
|
-
remap
|
|
873
|
+
info="OOPS minimisation.",
|
|
874
|
+
attr=dict(
|
|
875
|
+
kind=dict(
|
|
876
|
+
values=["ooanalysis", "oominim"],
|
|
877
|
+
remap=dict(autoremap="first"),
|
|
706
878
|
),
|
|
707
|
-
virtualdb
|
|
708
|
-
default
|
|
879
|
+
virtualdb=dict(
|
|
880
|
+
default="ccma",
|
|
709
881
|
),
|
|
710
882
|
withscreening=dict(
|
|
711
|
-
values
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
883
|
+
values=[
|
|
884
|
+
False,
|
|
885
|
+
],
|
|
886
|
+
type=bool,
|
|
887
|
+
optional=True,
|
|
888
|
+
default=False,
|
|
715
889
|
),
|
|
716
|
-
)
|
|
890
|
+
),
|
|
717
891
|
)
|
|
718
892
|
|
|
719
893
|
|
|
@@ -723,13 +897,15 @@ class OOPSAnalysisWithScreening(OOPSAnalysis):
|
|
|
723
897
|
_OOPSODB_CCMA_DIRECT = True
|
|
724
898
|
|
|
725
899
|
_footprint = dict(
|
|
726
|
-
attr
|
|
727
|
-
virtualdb
|
|
728
|
-
default
|
|
900
|
+
attr=dict(
|
|
901
|
+
virtualdb=dict(
|
|
902
|
+
default="ecma",
|
|
729
903
|
),
|
|
730
|
-
withscreening
|
|
731
|
-
values
|
|
732
|
-
|
|
904
|
+
withscreening=dict(
|
|
905
|
+
values=[
|
|
906
|
+
True,
|
|
907
|
+
],
|
|
908
|
+
optional=False,
|
|
733
909
|
),
|
|
734
910
|
)
|
|
735
911
|
)
|