vortex-nwp 2.0.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 +159 -0
- vortex/algo/__init__.py +13 -0
- vortex/algo/components.py +2462 -0
- vortex/algo/mpitools.py +1953 -0
- vortex/algo/mpitools_templates/__init__.py +1 -0
- vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +27 -0
- vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +29 -0
- vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +18 -0
- vortex/algo/serversynctools.py +171 -0
- vortex/config.py +112 -0
- vortex/data/__init__.py +19 -0
- vortex/data/abstractstores.py +1510 -0
- vortex/data/containers.py +835 -0
- vortex/data/contents.py +622 -0
- vortex/data/executables.py +275 -0
- vortex/data/flow.py +119 -0
- vortex/data/geometries.ini +2689 -0
- vortex/data/geometries.py +799 -0
- vortex/data/handlers.py +1230 -0
- vortex/data/outflow.py +67 -0
- vortex/data/providers.py +487 -0
- vortex/data/resources.py +207 -0
- vortex/data/stores.py +1390 -0
- vortex/data/sync_templates/__init__.py +0 -0
- vortex/gloves.py +309 -0
- vortex/layout/__init__.py +20 -0
- vortex/layout/contexts.py +577 -0
- vortex/layout/dataflow.py +1220 -0
- vortex/layout/monitor.py +969 -0
- vortex/nwp/__init__.py +14 -0
- vortex/nwp/algo/__init__.py +21 -0
- vortex/nwp/algo/assim.py +537 -0
- vortex/nwp/algo/clim.py +1086 -0
- vortex/nwp/algo/coupling.py +831 -0
- vortex/nwp/algo/eda.py +840 -0
- vortex/nwp/algo/eps.py +785 -0
- vortex/nwp/algo/forecasts.py +886 -0
- vortex/nwp/algo/fpserver.py +1303 -0
- vortex/nwp/algo/ifsnaming.py +463 -0
- vortex/nwp/algo/ifsroot.py +404 -0
- vortex/nwp/algo/monitoring.py +263 -0
- vortex/nwp/algo/mpitools.py +694 -0
- vortex/nwp/algo/odbtools.py +1258 -0
- vortex/nwp/algo/oopsroot.py +916 -0
- vortex/nwp/algo/oopstests.py +220 -0
- vortex/nwp/algo/request.py +660 -0
- vortex/nwp/algo/stdpost.py +1641 -0
- vortex/nwp/data/__init__.py +30 -0
- vortex/nwp/data/assim.py +380 -0
- vortex/nwp/data/boundaries.py +314 -0
- vortex/nwp/data/climfiles.py +521 -0
- vortex/nwp/data/configfiles.py +153 -0
- vortex/nwp/data/consts.py +954 -0
- vortex/nwp/data/ctpini.py +149 -0
- vortex/nwp/data/diagnostics.py +209 -0
- vortex/nwp/data/eda.py +147 -0
- vortex/nwp/data/eps.py +432 -0
- vortex/nwp/data/executables.py +1045 -0
- vortex/nwp/data/fields.py +111 -0
- vortex/nwp/data/gridfiles.py +380 -0
- vortex/nwp/data/logs.py +584 -0
- vortex/nwp/data/modelstates.py +363 -0
- vortex/nwp/data/monitoring.py +193 -0
- vortex/nwp/data/namelists.py +696 -0
- vortex/nwp/data/obs.py +840 -0
- vortex/nwp/data/oopsexec.py +74 -0
- vortex/nwp/data/providers.py +207 -0
- vortex/nwp/data/query.py +206 -0
- vortex/nwp/data/stores.py +160 -0
- vortex/nwp/data/surfex.py +337 -0
- vortex/nwp/syntax/__init__.py +9 -0
- vortex/nwp/syntax/stdattrs.py +437 -0
- vortex/nwp/tools/__init__.py +10 -0
- vortex/nwp/tools/addons.py +40 -0
- vortex/nwp/tools/agt.py +67 -0
- vortex/nwp/tools/bdap.py +59 -0
- vortex/nwp/tools/bdcp.py +41 -0
- vortex/nwp/tools/bdm.py +24 -0
- vortex/nwp/tools/bdmp.py +54 -0
- vortex/nwp/tools/conftools.py +1661 -0
- vortex/nwp/tools/drhook.py +66 -0
- vortex/nwp/tools/grib.py +294 -0
- vortex/nwp/tools/gribdiff.py +104 -0
- vortex/nwp/tools/ifstools.py +203 -0
- vortex/nwp/tools/igastuff.py +273 -0
- vortex/nwp/tools/mars.py +68 -0
- vortex/nwp/tools/odb.py +657 -0
- vortex/nwp/tools/partitioning.py +258 -0
- vortex/nwp/tools/satrad.py +71 -0
- vortex/nwp/util/__init__.py +6 -0
- vortex/nwp/util/async.py +212 -0
- vortex/nwp/util/beacon.py +40 -0
- vortex/nwp/util/diffpygram.py +447 -0
- vortex/nwp/util/ens.py +279 -0
- vortex/nwp/util/hooks.py +139 -0
- vortex/nwp/util/taskdeco.py +85 -0
- vortex/nwp/util/usepygram.py +697 -0
- vortex/nwp/util/usetnt.py +101 -0
- vortex/proxy.py +6 -0
- vortex/sessions.py +374 -0
- vortex/syntax/__init__.py +9 -0
- vortex/syntax/stdattrs.py +867 -0
- vortex/syntax/stddeco.py +185 -0
- vortex/toolbox.py +1117 -0
- vortex/tools/__init__.py +20 -0
- vortex/tools/actions.py +523 -0
- vortex/tools/addons.py +316 -0
- vortex/tools/arm.py +96 -0
- vortex/tools/compression.py +325 -0
- vortex/tools/date.py +27 -0
- vortex/tools/ddhpack.py +10 -0
- vortex/tools/delayedactions.py +782 -0
- vortex/tools/env.py +541 -0
- vortex/tools/folder.py +834 -0
- vortex/tools/grib.py +738 -0
- vortex/tools/lfi.py +953 -0
- vortex/tools/listings.py +423 -0
- vortex/tools/names.py +637 -0
- vortex/tools/net.py +2124 -0
- vortex/tools/odb.py +10 -0
- vortex/tools/parallelism.py +368 -0
- vortex/tools/prestaging.py +210 -0
- vortex/tools/rawfiles.py +10 -0
- vortex/tools/schedulers.py +480 -0
- vortex/tools/services.py +940 -0
- vortex/tools/storage.py +996 -0
- vortex/tools/surfex.py +61 -0
- vortex/tools/systems.py +3976 -0
- vortex/tools/targets.py +440 -0
- vortex/util/__init__.py +9 -0
- vortex/util/config.py +1122 -0
- vortex/util/empty.py +24 -0
- vortex/util/helpers.py +216 -0
- vortex/util/introspection.py +69 -0
- vortex/util/iosponge.py +80 -0
- vortex/util/roles.py +49 -0
- vortex/util/storefunctions.py +129 -0
- vortex/util/structs.py +26 -0
- vortex/util/worker.py +162 -0
- vortex_nwp-2.0.0.dist-info/METADATA +67 -0
- vortex_nwp-2.0.0.dist-info/RECORD +144 -0
- vortex_nwp-2.0.0.dist-info/WHEEL +5 -0
- vortex_nwp-2.0.0.dist-info/licenses/LICENSE +517 -0
- vortex_nwp-2.0.0.dist-info/top_level.txt +1 -0
vortex/nwp/tools/odb.py
ADDED
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Common interest classes to help setup the ODB software environment.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from bronx.fancies import loggers
|
|
8
|
+
from bronx.stdtypes import date as bdate
|
|
9
|
+
import footprints
|
|
10
|
+
|
|
11
|
+
from vortex.algo.components import (
|
|
12
|
+
AlgoComponentDecoMixin,
|
|
13
|
+
AlgoComponentError,
|
|
14
|
+
algo_component_deco_mixin_autodoc,
|
|
15
|
+
)
|
|
16
|
+
from vortex import config
|
|
17
|
+
from vortex.layout.dataflow import intent
|
|
18
|
+
|
|
19
|
+
from ..syntax.stdattrs import ArpIfsSimplifiedCycle
|
|
20
|
+
|
|
21
|
+
#: No automatic export
|
|
22
|
+
__all__ = []
|
|
23
|
+
|
|
24
|
+
logger = loggers.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TimeSlots:
|
|
28
|
+
"""Handling of assimilation time slots."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self, nslot=7, start="-PT3H", window="PT6H", chunk=None, center=True
|
|
32
|
+
):
|
|
33
|
+
if isinstance(nslot, str):
|
|
34
|
+
info = [x.strip() for x in nslot.split("/")]
|
|
35
|
+
nslot = info[0]
|
|
36
|
+
if len(info) > 1:
|
|
37
|
+
start = info[1]
|
|
38
|
+
if len(info) > 2:
|
|
39
|
+
window = info[2]
|
|
40
|
+
if len(info) > 3:
|
|
41
|
+
if re.match("^regular", info[3]):
|
|
42
|
+
center = False
|
|
43
|
+
else:
|
|
44
|
+
chunk = info[3]
|
|
45
|
+
self.nslot = int(nslot)
|
|
46
|
+
self.center = center if self.nslot > 1 else False
|
|
47
|
+
self.start = bdate.Period(start)
|
|
48
|
+
self.window = bdate.Period(window)
|
|
49
|
+
if chunk is None:
|
|
50
|
+
cslot = self.nslot - 1 if self.center else self.nslot
|
|
51
|
+
chunk = (
|
|
52
|
+
"PT" + str((self.window.length // max(1, cslot)) // 60) + "M"
|
|
53
|
+
)
|
|
54
|
+
self.chunk = self.window if self.nslot < 2 else bdate.Period(chunk)
|
|
55
|
+
|
|
56
|
+
def __eq__(self, other):
|
|
57
|
+
if isinstance(other, str):
|
|
58
|
+
try:
|
|
59
|
+
other = TimeSlots(other)
|
|
60
|
+
except ValueError:
|
|
61
|
+
pass
|
|
62
|
+
return (
|
|
63
|
+
isinstance(other, TimeSlots)
|
|
64
|
+
and self.nslot == other.nslot
|
|
65
|
+
and self.center == other.center
|
|
66
|
+
and self.start == other.start
|
|
67
|
+
and self.window == other.window
|
|
68
|
+
and self.chunk == other.chunk
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def __str__(self):
|
|
72
|
+
chunky = self.chunk.isoformat() if self.center else "regular"
|
|
73
|
+
return "{0.nslot:d}/{1:s}/{2:s}/{3:s}".format(
|
|
74
|
+
self, self.start.isoformat(), self.window.isoformat(), chunky
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def __repr__(self, *args, **kwargs):
|
|
78
|
+
return super().__repr__()[:-1] + " | {!s}>".format(self)
|
|
79
|
+
|
|
80
|
+
def as_slots(self):
|
|
81
|
+
"""Return a list of slots in seconds."""
|
|
82
|
+
if self.center:
|
|
83
|
+
slots = [
|
|
84
|
+
self.chunk.length,
|
|
85
|
+
] * self.nslot
|
|
86
|
+
nb = self.window.length // self.chunk.length
|
|
87
|
+
if nb != self.nslot:
|
|
88
|
+
slots[0] = slots[-1] = self.chunk.length // 2
|
|
89
|
+
else:
|
|
90
|
+
islot = self.window.length // self.nslot
|
|
91
|
+
slots = [
|
|
92
|
+
islot,
|
|
93
|
+
] * self.nslot
|
|
94
|
+
return slots
|
|
95
|
+
|
|
96
|
+
def as_centers_fromstart(self):
|
|
97
|
+
"""Return time slots centers as a list of Period objects."""
|
|
98
|
+
slots = self.as_slots()
|
|
99
|
+
fromstart = []
|
|
100
|
+
acc = 0
|
|
101
|
+
for i in range(len(slots)):
|
|
102
|
+
fromstart.append(acc + slots[i] / 2)
|
|
103
|
+
acc += slots[i]
|
|
104
|
+
if self.center and (
|
|
105
|
+
self.window.length // self.chunk.length != self.nslot
|
|
106
|
+
):
|
|
107
|
+
fromstart[0] = 0
|
|
108
|
+
fromstart[-1] = self.window.length
|
|
109
|
+
return [bdate.Period(seconds=t) for t in fromstart]
|
|
110
|
+
|
|
111
|
+
def as_bounds(self, date):
|
|
112
|
+
"""Return time slots as a list of compact date values."""
|
|
113
|
+
date = bdate.Date(date)
|
|
114
|
+
boundlist = [
|
|
115
|
+
date + self.start,
|
|
116
|
+
]
|
|
117
|
+
for x in self.as_slots():
|
|
118
|
+
boundlist.append(boundlist[-1] + x)
|
|
119
|
+
boundlist = [x.compact() for x in boundlist]
|
|
120
|
+
return boundlist
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def leftmargin(self):
|
|
124
|
+
"""Return length in minutes from left margin of the window."""
|
|
125
|
+
return int(self.start.total_seconds()) // 60
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def rightmargin(self):
|
|
129
|
+
"""Return length in minutes from rigth margin of the window."""
|
|
130
|
+
return int((self.start + self.window).total_seconds()) // 60
|
|
131
|
+
|
|
132
|
+
def as_environment(self):
|
|
133
|
+
"""Return a dictionary of ready-to-export variables that describe the timeslots."""
|
|
134
|
+
thelen = (
|
|
135
|
+
self.chunk.length // 60 if self.center and self.nslot > 1 else 0
|
|
136
|
+
)
|
|
137
|
+
return dict(
|
|
138
|
+
BATOR_WINDOW_LEN=self.window.length // 60,
|
|
139
|
+
BATOR_WINDOW_SHIFT=int(self.start.total_seconds()) // 60,
|
|
140
|
+
BATOR_SLOT_LEN=thelen,
|
|
141
|
+
BATOR_CENTER_LEN=thelen,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def as_file(self, date, filename):
|
|
145
|
+
"""Fill the specified ``filename`` wih the current list of time slots at this ``date``."""
|
|
146
|
+
with open(filename, "w") as fd:
|
|
147
|
+
for x in self.as_bounds(date):
|
|
148
|
+
fd.write(str(x) + "\n")
|
|
149
|
+
nbx = fd.tell()
|
|
150
|
+
return nbx
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class OdbDriver:
|
|
154
|
+
"""A dedicated class for handling some ODB settings."""
|
|
155
|
+
|
|
156
|
+
def __init__(self, cycle, sh=None, env=None):
|
|
157
|
+
"""
|
|
158
|
+
A quite challenging initialisation since cycle, sh, env and target
|
|
159
|
+
should be provided...
|
|
160
|
+
"""
|
|
161
|
+
self.cycle = cycle
|
|
162
|
+
self.sh = sh
|
|
163
|
+
if self.sh is None:
|
|
164
|
+
logger.critical(
|
|
165
|
+
"%s created with a proper shell access [%s]",
|
|
166
|
+
self.__class__,
|
|
167
|
+
self,
|
|
168
|
+
)
|
|
169
|
+
self.env = env
|
|
170
|
+
if self.env is None:
|
|
171
|
+
logger.critical(
|
|
172
|
+
"%s created with a proper environment access [%s]",
|
|
173
|
+
self.__class__,
|
|
174
|
+
self,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def setup(self, date, npool=1, nslot=1, iomethod=1, layout="ecma"):
|
|
178
|
+
"""Setup given environment with default ODB env variables."""
|
|
179
|
+
|
|
180
|
+
(logger.info("ODB: generic setup called."),)
|
|
181
|
+
self.env.update(
|
|
182
|
+
ODB_CMA=layout.upper(),
|
|
183
|
+
ODB_IO_METHOD=iomethod,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
self.env.default(
|
|
187
|
+
ODB_DEBUG=0,
|
|
188
|
+
ODB_CTX_DEBUG=0,
|
|
189
|
+
ODB_REPRODUCIBLE_SEQNO=4,
|
|
190
|
+
ODB_STATIC_LINKING=1,
|
|
191
|
+
ODB_ANALYSIS_DATE=date.ymd,
|
|
192
|
+
ODB_ANALYSIS_TIME=date.hm + "00",
|
|
193
|
+
TO_ODB_ECMWF=0,
|
|
194
|
+
TO_ODB_SWAPOUT=0,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
if iomethod == 4:
|
|
198
|
+
self.env.default(
|
|
199
|
+
ODB_IO_GRPSIZE=npool,
|
|
200
|
+
ODB_IO_FILESIZE=128,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
if self.sh.path.exists("IOASSIGN"):
|
|
204
|
+
self.env.default(
|
|
205
|
+
IOASSIGN=self.sh.path.abspath("IOASSIGN"),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
def force_overwrite_method(self):
|
|
209
|
+
"""Force ODB_OVERWRITE_METHOD if necessary."""
|
|
210
|
+
if not int(self.env.get("ODB_OVERWRITE_METHOD", 0)):
|
|
211
|
+
logger.info(
|
|
212
|
+
"ODB: Some input ODB databases are read-only. Setting ODB_OVERWRITE_METHOD to 1."
|
|
213
|
+
)
|
|
214
|
+
self.env.ODB_OVERWRITE_METHOD = 1
|
|
215
|
+
|
|
216
|
+
def _process_layout_dbpath(self, layout, dbpath=None):
|
|
217
|
+
"""Normalise **layout** and **dbpath**."""
|
|
218
|
+
layout = layout.upper()
|
|
219
|
+
thispwd = self.sh.path.abspath(self.sh.getcwd())
|
|
220
|
+
if dbpath is None:
|
|
221
|
+
dbpath = self.sh.path.join(thispwd, layout)
|
|
222
|
+
return layout, dbpath, thispwd
|
|
223
|
+
|
|
224
|
+
def fix_db_path(self, layout, dbpath=None, env=None):
|
|
225
|
+
"""Setup the path to the **layout** database."""
|
|
226
|
+
if env is None:
|
|
227
|
+
env = self.env
|
|
228
|
+
layout, dbpath, _ = self._process_layout_dbpath(layout, dbpath)
|
|
229
|
+
logger.info("ODB: Fix %s path: %s", layout, dbpath)
|
|
230
|
+
env["ODB_SRCPATH_{:s}".format(layout)] = dbpath
|
|
231
|
+
env["ODB_DATAPATH_{:s}".format(layout)] = dbpath
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def _default_iocreate_path(self):
|
|
235
|
+
"""The location to the default create_ioassign utility."""
|
|
236
|
+
return self.sh.path.join(
|
|
237
|
+
config.from_config(section="nwp-tools", key="odb"),
|
|
238
|
+
config.get_from_config_w_default(
|
|
239
|
+
section="nwp-tools",
|
|
240
|
+
key="iocreate_cmd",
|
|
241
|
+
default="create_ioassign",
|
|
242
|
+
),
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def _default_iomerge_path(self):
|
|
247
|
+
"""The location to the default merge_ioassign utility."""
|
|
248
|
+
return self.sh.path.join(
|
|
249
|
+
config.from_config(section="nwp-tools", key="odb"),
|
|
250
|
+
config.get_from_config_w_default(
|
|
251
|
+
section="nwp-tools",
|
|
252
|
+
key="iomerge_cmd",
|
|
253
|
+
default="merge_ioassign",
|
|
254
|
+
),
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def ioassign_create(
|
|
258
|
+
self,
|
|
259
|
+
ioassign="ioassign.x",
|
|
260
|
+
npool=1,
|
|
261
|
+
layout="ecma",
|
|
262
|
+
dbpath=None,
|
|
263
|
+
iocreate_path=None,
|
|
264
|
+
):
|
|
265
|
+
"""Build IO-Assign table."""
|
|
266
|
+
layout, dbpath, _ = self._process_layout_dbpath(layout, dbpath)
|
|
267
|
+
if iocreate_path is None:
|
|
268
|
+
iocreate_path = self._default_iocreate_path
|
|
269
|
+
ioassign = self.sh.path.abspath(ioassign)
|
|
270
|
+
self.sh.xperm(ioassign, force=True)
|
|
271
|
+
self.sh.mkdir(dbpath)
|
|
272
|
+
with self.env.clone() as lenv:
|
|
273
|
+
lenv["ODB_IOASSIGN_BINARY"] = ioassign
|
|
274
|
+
self.fix_db_path(layout, dbpath, env=lenv)
|
|
275
|
+
self.sh.spawn(
|
|
276
|
+
[
|
|
277
|
+
iocreate_path,
|
|
278
|
+
"-d" + dbpath,
|
|
279
|
+
"-l" + layout,
|
|
280
|
+
"-n" + str(npool),
|
|
281
|
+
],
|
|
282
|
+
output=False,
|
|
283
|
+
)
|
|
284
|
+
return dbpath
|
|
285
|
+
|
|
286
|
+
def ioassign_merge(
|
|
287
|
+
self,
|
|
288
|
+
ioassign="ioassign.x",
|
|
289
|
+
layout="ecma",
|
|
290
|
+
odbnames=None,
|
|
291
|
+
dbpath=None,
|
|
292
|
+
iomerge_path=None,
|
|
293
|
+
iocreate_path=None,
|
|
294
|
+
):
|
|
295
|
+
"""Build IO-Assign table."""
|
|
296
|
+
layout, dbpath, thispwd = self._process_layout_dbpath(layout, dbpath)
|
|
297
|
+
if iomerge_path is None:
|
|
298
|
+
iomerge_path = self._default_iomerge_path
|
|
299
|
+
if iocreate_path is None:
|
|
300
|
+
iocreate_path = self._default_iocreate_path
|
|
301
|
+
iocmd = [iomerge_path]
|
|
302
|
+
ioassign = self.sh.path.abspath(ioassign)
|
|
303
|
+
self.sh.xperm(ioassign, force=True)
|
|
304
|
+
with self.sh.cdcontext(dbpath, create=True):
|
|
305
|
+
iocmd.extend(["-d", thispwd])
|
|
306
|
+
for dbname in odbnames:
|
|
307
|
+
iocmd.extend(["-t", dbname])
|
|
308
|
+
with self.env.clone() as lenv:
|
|
309
|
+
lenv["ODB_IOASSIGN_BINARY"] = ioassign
|
|
310
|
+
if "ODB_IOCREATE_COMMAND" not in lenv:
|
|
311
|
+
lenv["ODB_IOCREATE_COMMAND"] = iocreate_path
|
|
312
|
+
self.fix_db_path(layout, dbpath, env=lenv)
|
|
313
|
+
self.sh.spawn(iocmd, output=False)
|
|
314
|
+
return dbpath
|
|
315
|
+
|
|
316
|
+
def _ioassign_process(self, dbpaths, wmode):
|
|
317
|
+
with open("IOASSIGN", wmode) as fhgather:
|
|
318
|
+
for dbpath in dbpaths:
|
|
319
|
+
with open(self.sh.path.join(dbpath, "IOASSIGN")) as fhlay:
|
|
320
|
+
for line in fhlay:
|
|
321
|
+
fhgather.write(line)
|
|
322
|
+
|
|
323
|
+
def ioassign_gather(self, *dbpaths):
|
|
324
|
+
"""Gather IOASSIGN data from **dbpaths** databases and create a global IOASSIGN file."""
|
|
325
|
+
logger.info(
|
|
326
|
+
"ODB: creating a global IOASSIGN file from: %s",
|
|
327
|
+
",".join([self.sh.path.basename(db) for db in dbpaths]),
|
|
328
|
+
)
|
|
329
|
+
self._ioassign_process(dbpaths, "w")
|
|
330
|
+
|
|
331
|
+
def ioassign_append(self, *dbpaths):
|
|
332
|
+
"""Append IOASSIGN data from **dbpaths** databases into the global IOASSIGN file."""
|
|
333
|
+
logger.info(
|
|
334
|
+
"ODB: extending the IOASSIGN file with: %s",
|
|
335
|
+
",".join([self.sh.path.basename(db) for db in dbpaths]),
|
|
336
|
+
)
|
|
337
|
+
self._ioassign_process(dbpaths, "a")
|
|
338
|
+
|
|
339
|
+
def shuffle_setup(self, slots, mergedirect=False, ccmadirect=False):
|
|
340
|
+
"""Setup environment variables to control ODB shuffle behaviour.
|
|
341
|
+
|
|
342
|
+
:param bool mergedirect: Run the shuffle procedure on the input database.
|
|
343
|
+
:param bool ccmadirect: Create a CCMA database at the end of the run.
|
|
344
|
+
"""
|
|
345
|
+
logger.info(
|
|
346
|
+
"ODB: shuffle_setup: mergedirect=%s, ccmadirect=%s",
|
|
347
|
+
str(mergedirect),
|
|
348
|
+
str(ccmadirect),
|
|
349
|
+
)
|
|
350
|
+
if mergedirect or ccmadirect:
|
|
351
|
+
self.env.update(
|
|
352
|
+
ODB_CCMA_TSLOTS=slots.nslot,
|
|
353
|
+
)
|
|
354
|
+
self.env.default(
|
|
355
|
+
ODB_CCMA_LEFT_MARGIN=slots.leftmargin,
|
|
356
|
+
ODB_CCMA_RIGHT_MARGIN=slots.rightmargin,
|
|
357
|
+
)
|
|
358
|
+
if mergedirect:
|
|
359
|
+
self.env.default(ODB_MERGEODB_DIRECT=1)
|
|
360
|
+
if ccmadirect:
|
|
361
|
+
self.env.update(ODB_CCMA_CREATE_DIRECT=1)
|
|
362
|
+
|
|
363
|
+
def create_poolmask(self, layout, dbpath=None):
|
|
364
|
+
"""Request the poolmask file creation."""
|
|
365
|
+
layout, dbpath, _ = self._process_layout_dbpath(layout, dbpath)
|
|
366
|
+
logger.info(
|
|
367
|
+
"ODB: requesting poolmask file for: %s (layout=%s).",
|
|
368
|
+
dbpath,
|
|
369
|
+
layout,
|
|
370
|
+
)
|
|
371
|
+
self.env.update(
|
|
372
|
+
ODB_CCMA_CREATE_POOLMASK=1,
|
|
373
|
+
ODB_CCMA_POOLMASK_FILE=self.sh.path.join(
|
|
374
|
+
dbpath, layout + ".poolmask"
|
|
375
|
+
),
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
def change_layout(self, layout, layout_new, dbpath=None):
|
|
379
|
+
"""Make the appropriate renaming of files in ECMA to CCMA."""
|
|
380
|
+
layout, dbpath, _ = self._process_layout_dbpath(layout, dbpath)
|
|
381
|
+
layout_new = layout_new.upper()
|
|
382
|
+
logger.info(
|
|
383
|
+
"ODB: changing layout (%s -> %s) for %s.",
|
|
384
|
+
layout,
|
|
385
|
+
layout_new,
|
|
386
|
+
dbpath,
|
|
387
|
+
)
|
|
388
|
+
to_cleanup = set()
|
|
389
|
+
for f in self.sh.ls(dbpath):
|
|
390
|
+
if self.sh.path.islink(self.sh.path.join(dbpath, f)):
|
|
391
|
+
fullpath = self.sh.path.join(dbpath, f)
|
|
392
|
+
target = self.sh.readlink(fullpath)
|
|
393
|
+
self.sh.unlink(fullpath)
|
|
394
|
+
self.sh.symlink(
|
|
395
|
+
target.replace(layout, layout_new),
|
|
396
|
+
fullpath.replace(layout, layout_new),
|
|
397
|
+
)
|
|
398
|
+
continue
|
|
399
|
+
if f in [n.format(layout) for n in ("{:s}.dd", "{:s}.flags")]:
|
|
400
|
+
self.sh.mv(
|
|
401
|
+
self.sh.path.join(dbpath, f),
|
|
402
|
+
self.sh.path.join(dbpath, f.replace(layout, layout_new)),
|
|
403
|
+
)
|
|
404
|
+
if f in [
|
|
405
|
+
n.format(layout)
|
|
406
|
+
for n in (
|
|
407
|
+
"{:s}.iomap",
|
|
408
|
+
"{:s}.sch",
|
|
409
|
+
"{:s}.IOASSIGN",
|
|
410
|
+
"IOASSIGN.{:s}",
|
|
411
|
+
"IOASSIGN",
|
|
412
|
+
)
|
|
413
|
+
]:
|
|
414
|
+
tmp_target = self.sh.path.join(dbpath, f + ".tmp_new")
|
|
415
|
+
with open(self.sh.path.join(dbpath, f)) as inodb:
|
|
416
|
+
with open(tmp_target, "w") as outodb:
|
|
417
|
+
for line in inodb:
|
|
418
|
+
outodb.write(line.replace(layout, layout_new))
|
|
419
|
+
self.sh.mv(
|
|
420
|
+
tmp_target,
|
|
421
|
+
self.sh.path.join(dbpath, f.replace(layout, layout_new)),
|
|
422
|
+
)
|
|
423
|
+
if layout in f:
|
|
424
|
+
to_cleanup.add(self.sh.path.join(dbpath, f))
|
|
425
|
+
for f_name in to_cleanup:
|
|
426
|
+
self.sh.rm(f_name)
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
#: Footprint's attributes needed to ODB to setup properly
|
|
430
|
+
odbmix_attributes = footprints.Footprint(
|
|
431
|
+
info="Abstract ODB footprints' attributes.",
|
|
432
|
+
attr=dict(
|
|
433
|
+
npool=dict(
|
|
434
|
+
info="The number of pool(s) in the ODB database.",
|
|
435
|
+
type=int,
|
|
436
|
+
optional=True,
|
|
437
|
+
default=1,
|
|
438
|
+
),
|
|
439
|
+
iomethod=dict(
|
|
440
|
+
info="The io_method of the ODB database.",
|
|
441
|
+
type=int,
|
|
442
|
+
optional=True,
|
|
443
|
+
default=1,
|
|
444
|
+
doc_zorder=-50,
|
|
445
|
+
),
|
|
446
|
+
slots=dict(
|
|
447
|
+
info="The timeslots of the assimilation window.",
|
|
448
|
+
type=TimeSlots,
|
|
449
|
+
optional=True,
|
|
450
|
+
default=TimeSlots(7, chunk="PT1H"),
|
|
451
|
+
),
|
|
452
|
+
virtualdb=dict(
|
|
453
|
+
info="The type of the virtual ODB database.",
|
|
454
|
+
optional=True,
|
|
455
|
+
default="ecma",
|
|
456
|
+
access="rwx",
|
|
457
|
+
doc_visibility=footprints.doc.visibility.ADVANCED,
|
|
458
|
+
),
|
|
459
|
+
date=dict(
|
|
460
|
+
info="The current run date.",
|
|
461
|
+
optional=True,
|
|
462
|
+
access="rwx",
|
|
463
|
+
type=bdate.Date,
|
|
464
|
+
doc_zorder=-50,
|
|
465
|
+
),
|
|
466
|
+
ioassign=dict(
|
|
467
|
+
info="The path to the ioassign binary (needed for merge/create actions",
|
|
468
|
+
optional=True,
|
|
469
|
+
default="ioassign.x",
|
|
470
|
+
),
|
|
471
|
+
),
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
@algo_component_deco_mixin_autodoc
|
|
476
|
+
class OdbComponentDecoMixin(AlgoComponentDecoMixin):
|
|
477
|
+
"""Handle ODB settings in AlgoComponents.
|
|
478
|
+
|
|
479
|
+
This mixin class is intended to be used with AlgoComponent classes. It will
|
|
480
|
+
automatically add footprints' arguments related to ODB
|
|
481
|
+
(see :data:`odbmix_attributes`), set up generic ODB environment variables
|
|
482
|
+
(:meth:`_odbobj_setup`) and provides a :attr:`odb` property that gives
|
|
483
|
+
access to a properly initialised :class:`OdbDriver` object that can be used
|
|
484
|
+
directly in AlgoComponents.
|
|
485
|
+
|
|
486
|
+
In addition it provides directly some utility methods that can be called
|
|
487
|
+
manually if needed.
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
_MIXIN_EXTRA_FOOTPRINTS = [
|
|
491
|
+
odbmix_attributes,
|
|
492
|
+
]
|
|
493
|
+
|
|
494
|
+
def _odbobj_init(self, rh, opts): # @UnusedVariable
|
|
495
|
+
"""Setup the OdbDriver object."""
|
|
496
|
+
cycle = ArpIfsSimplifiedCycle("cy01")
|
|
497
|
+
if rh and hasattr(rh.resource, "cycle"):
|
|
498
|
+
cycle = rh.resource.cycle
|
|
499
|
+
self._odb = OdbDriver(
|
|
500
|
+
cycle=cycle,
|
|
501
|
+
sh=self.system,
|
|
502
|
+
env=self.env,
|
|
503
|
+
)
|
|
504
|
+
if self.system.path.exists(self.ioassign):
|
|
505
|
+
self._x_ioassign = self.system.path.abspath(self.ioassign)
|
|
506
|
+
else:
|
|
507
|
+
# Legacy...
|
|
508
|
+
self._x_ioassign = self.ioassign
|
|
509
|
+
|
|
510
|
+
def _odbobj_setup(self, rh, opts): # @UnusedVariable
|
|
511
|
+
"""Setup the ODB object."""
|
|
512
|
+
self.odb.setup(
|
|
513
|
+
layout=self.virtualdb,
|
|
514
|
+
date=self.date,
|
|
515
|
+
npool=self.npool,
|
|
516
|
+
nslot=self.slots.nslot,
|
|
517
|
+
iomethod=self.iomethod,
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
_MIXIN_PREPARE_PREHOOKS = (_odbobj_init,)
|
|
521
|
+
_MIXIN_PREPARE_HOOKS = (_odbobj_setup,)
|
|
522
|
+
|
|
523
|
+
@property
|
|
524
|
+
def odb(self):
|
|
525
|
+
"""Access to a properly initialised :class:`OdbDriver` object."""
|
|
526
|
+
if not hasattr(self, "_odb"):
|
|
527
|
+
raise RuntimeError("Uninitialised *odb* object.")
|
|
528
|
+
return self._odb
|
|
529
|
+
|
|
530
|
+
def lookupodb(self, fatal=True):
|
|
531
|
+
"""Return a list of effective input resources which are odb observations."""
|
|
532
|
+
allodb = [
|
|
533
|
+
x
|
|
534
|
+
for x in self.context.sequence.effective_inputs(
|
|
535
|
+
kind="observations"
|
|
536
|
+
)
|
|
537
|
+
if x.rh.container.actualfmt == "odb"
|
|
538
|
+
]
|
|
539
|
+
allodb.sort(key=lambda s: s.rh.resource.part)
|
|
540
|
+
if not allodb and fatal:
|
|
541
|
+
logger.critical("Missing ODB input data for %s", self.fullname())
|
|
542
|
+
raise ValueError("Missing ODB input data")
|
|
543
|
+
return allodb
|
|
544
|
+
|
|
545
|
+
def odb_date_and_layout_from_sections(self, odbsections):
|
|
546
|
+
"""
|
|
547
|
+
Look into the **odsections** section list in order to find the current
|
|
548
|
+
run date and ODB database layout.
|
|
549
|
+
"""
|
|
550
|
+
alllayouts = {s.rh.resource.layout for s in odbsections}
|
|
551
|
+
alldates = {s.rh.resource.date for s in odbsections}
|
|
552
|
+
if len(alllayouts) != 1:
|
|
553
|
+
raise AlgoComponentError("Inconsistent ODB layouts")
|
|
554
|
+
if len(alldates) != 1:
|
|
555
|
+
raise AlgoComponentError("Inconsistent ODB dates")
|
|
556
|
+
self.virtualdb = alllayouts.pop()
|
|
557
|
+
self.date = alldates.pop()
|
|
558
|
+
logger.info(
|
|
559
|
+
"ODB: Detected from ODB database(s). self.date=%s, self.virtualdb=%s.",
|
|
560
|
+
self.date.stdvortex,
|
|
561
|
+
self.virtualdb,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
def _odb_find_ioassign_script(self, purpose):
|
|
565
|
+
"""Look for ioassign script of *purpose" attribute, return path."""
|
|
566
|
+
scripts = [
|
|
567
|
+
x.rh.container.abspath
|
|
568
|
+
for x in self.context.sequence.effective_inputs(
|
|
569
|
+
kind="ioassign_script"
|
|
570
|
+
)
|
|
571
|
+
if x.rh.resource.purpose == purpose
|
|
572
|
+
]
|
|
573
|
+
if len(scripts) > 1:
|
|
574
|
+
raise AlgoComponentError(
|
|
575
|
+
"More than one purpose={} ioassign_script found in resources."
|
|
576
|
+
)
|
|
577
|
+
elif len(scripts) == 1:
|
|
578
|
+
self.system.xperm(scripts[0], force=True)
|
|
579
|
+
return scripts[0]
|
|
580
|
+
else:
|
|
581
|
+
return None
|
|
582
|
+
|
|
583
|
+
def odb_merge_if_needed(self, odbsections, subdir="."):
|
|
584
|
+
"""
|
|
585
|
+
If multiple ODB databases are listed in the **odsections** section list,
|
|
586
|
+
start an ODB merge.
|
|
587
|
+
|
|
588
|
+
:return: The path to the ODB database (the virtual database if a merge is
|
|
589
|
+
performed).
|
|
590
|
+
"""
|
|
591
|
+
if len(odbsections) > 1 or self.virtualdb.lower() == "ecma":
|
|
592
|
+
logger.info("ODB: merge for: %s.", self.virtualdb)
|
|
593
|
+
iomerge_p = self._odb_find_ioassign_script("merge")
|
|
594
|
+
iocreate_p = self._odb_find_ioassign_script("create")
|
|
595
|
+
with self.system.cdcontext(subdir):
|
|
596
|
+
virtualdb_path = self.odb.ioassign_merge(
|
|
597
|
+
layout=self.virtualdb,
|
|
598
|
+
ioassign=self._x_ioassign,
|
|
599
|
+
odbnames=[x.rh.resource.part for x in odbsections],
|
|
600
|
+
iomerge_path=iomerge_p,
|
|
601
|
+
iocreate_path=iocreate_p,
|
|
602
|
+
)
|
|
603
|
+
else:
|
|
604
|
+
virtualdb_path = self.system.path.abspath(
|
|
605
|
+
odbsections[0].rh.container.localpath()
|
|
606
|
+
)
|
|
607
|
+
return virtualdb_path
|
|
608
|
+
|
|
609
|
+
def odb_create_db(self, layout, dbpath=None):
|
|
610
|
+
"""Create a new empty ODB database.
|
|
611
|
+
|
|
612
|
+
:param str layout: The new database layout
|
|
613
|
+
:param str dbpath: The path to the new database (current_dir/layout if omitted)
|
|
614
|
+
:return: The path to the new ODB database.
|
|
615
|
+
"""
|
|
616
|
+
dbout = self.odb.ioassign_create(
|
|
617
|
+
layout=layout,
|
|
618
|
+
npool=self.npool,
|
|
619
|
+
ioassign=self._x_ioassign,
|
|
620
|
+
dbpath=dbpath,
|
|
621
|
+
iocreate_path=self._odb_find_ioassign_script("create"),
|
|
622
|
+
)
|
|
623
|
+
logger.info("ODB: database created: %s (layout=%s).", dbout, layout)
|
|
624
|
+
return dbout
|
|
625
|
+
|
|
626
|
+
def odb_handle_raw_dbs(self):
|
|
627
|
+
"""Look for extras ODB raw databases and fix the environment accordingly."""
|
|
628
|
+
odbraw = [
|
|
629
|
+
x.rh
|
|
630
|
+
for x in self.context.sequence.effective_inputs(kind="odbraw")
|
|
631
|
+
if x.rh.container.actualfmt == "odb"
|
|
632
|
+
]
|
|
633
|
+
if not odbraw:
|
|
634
|
+
logger.error("No ODB raw databases found.")
|
|
635
|
+
else:
|
|
636
|
+
for rraw in odbraw:
|
|
637
|
+
rawpath = rraw.container.localpath()
|
|
638
|
+
self.odb.fix_db_path(rraw.resource.layout, rawpath)
|
|
639
|
+
for badlink in [
|
|
640
|
+
bl
|
|
641
|
+
for bl in self.system.glob(
|
|
642
|
+
self.system.path.join(rawpath, "*.h")
|
|
643
|
+
)
|
|
644
|
+
if self.system.path.islink(bl)
|
|
645
|
+
and not self.system.path.exists(bl)
|
|
646
|
+
]:
|
|
647
|
+
self.system.unlink(badlink)
|
|
648
|
+
self.odb.ioassign_append(
|
|
649
|
+
*[rraw.container.localpath() for rraw in odbraw]
|
|
650
|
+
)
|
|
651
|
+
return odbraw
|
|
652
|
+
|
|
653
|
+
def odb_rw_or_overwrite_method(self, *dbsections):
|
|
654
|
+
"""Are the input databases fetch with intent=inout ?"""
|
|
655
|
+
needs_work = [s for s in dbsections if s.intent == intent.IN]
|
|
656
|
+
if needs_work:
|
|
657
|
+
self.odb.force_overwrite_method()
|