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
|
@@ -0,0 +1,1258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AlgoComponents to work with Observational DataBases.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
import copy
|
|
7
|
+
import re
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
from bronx.fancies import loggers
|
|
11
|
+
from bronx.stdtypes.date import utcnow
|
|
12
|
+
from bronx.stdtypes.dictionaries import Foo
|
|
13
|
+
from bronx.system.memory import convert_bytes_in_unit
|
|
14
|
+
import footprints
|
|
15
|
+
from taylorism import Boss
|
|
16
|
+
|
|
17
|
+
from vortex.tools.systems import ExecutionError
|
|
18
|
+
|
|
19
|
+
from vortex.algo.components import Parallel, ParaBlindRun
|
|
20
|
+
from vortex.tools.parallelism import VortexWorkerBlindRun
|
|
21
|
+
|
|
22
|
+
from ..syntax.stdattrs import arpifs_cycle
|
|
23
|
+
|
|
24
|
+
from ..data.obs import ObsMapContent, ObsMapItem, ObsRefContent, ObsRefItem
|
|
25
|
+
from ..tools import odb, drhook
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
#: No automatic export
|
|
29
|
+
__all__ = []
|
|
30
|
+
|
|
31
|
+
logger = loggers.getLogger(__name__)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Raw2OdbExecutionError(ExecutionError):
|
|
35
|
+
def __init__(self, odb_database):
|
|
36
|
+
self.odb_database = odb_database
|
|
37
|
+
super().__init__("Raw2odb execution failed.")
|
|
38
|
+
|
|
39
|
+
def __str__(self):
|
|
40
|
+
return "Error while running bator for ODB database < {:s} >".format(
|
|
41
|
+
self.odb_database
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Bateur(VortexWorkerBlindRun):
|
|
46
|
+
"""
|
|
47
|
+
Worker for parallel BATOR run. It returns in its report a synthesis about
|
|
48
|
+
actual run-time and consumed memory (versus predictions).
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
_footprint = [
|
|
52
|
+
arpifs_cycle,
|
|
53
|
+
dict(
|
|
54
|
+
info="Bateur: launches a single bator execution in a parallel context",
|
|
55
|
+
attr=dict(
|
|
56
|
+
base=dict(
|
|
57
|
+
info="name of the odb database to process",
|
|
58
|
+
),
|
|
59
|
+
workdir=dict(
|
|
60
|
+
info="working directory of the run",
|
|
61
|
+
),
|
|
62
|
+
inputsize=dict(
|
|
63
|
+
info="input files total size in bytes",
|
|
64
|
+
type=int,
|
|
65
|
+
default=0,
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
),
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def memory_in_bytes(self):
|
|
73
|
+
return self.memory * 1024 * 1024
|
|
74
|
+
|
|
75
|
+
def vortex_task(self, **kwargs):
|
|
76
|
+
odb_drv = odb.OdbDriver(self.cycle, self.system, self.system.env)
|
|
77
|
+
self.system.cd("wkdir_" + self.base)
|
|
78
|
+
|
|
79
|
+
dbpath = self.system.path.join(self.workdir, "ECMA." + self.base)
|
|
80
|
+
listpath = self.system.path.join(self.workdir, "listing." + self.base)
|
|
81
|
+
|
|
82
|
+
odb_drv.fix_db_path("ecma", dbpath)
|
|
83
|
+
|
|
84
|
+
real_time = -time.time()
|
|
85
|
+
start_time = utcnow().isoformat()
|
|
86
|
+
rdict = dict(rc=True)
|
|
87
|
+
try:
|
|
88
|
+
self.local_spawn(listpath)
|
|
89
|
+
except ExecutionError:
|
|
90
|
+
rdict["rc"] = Raw2OdbExecutionError(self.base)
|
|
91
|
+
real_time += time.time()
|
|
92
|
+
|
|
93
|
+
if self.system.memory_info is not None:
|
|
94
|
+
realMem = self.system.memory_info.children_maxRSS("B")
|
|
95
|
+
memRatio = (
|
|
96
|
+
(realMem / float(self.memory_in_bytes))
|
|
97
|
+
if self.memory_in_bytes > 0
|
|
98
|
+
else None
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
realMem = None
|
|
102
|
+
memRatio = None
|
|
103
|
+
|
|
104
|
+
rdict["synthesis"] = dict(
|
|
105
|
+
base=self.base,
|
|
106
|
+
inputsize=self.inputsize,
|
|
107
|
+
mem_expected=self.memory_in_bytes,
|
|
108
|
+
mem_real=realMem,
|
|
109
|
+
mem_ratio=memRatio,
|
|
110
|
+
time_expected=self.expected_time,
|
|
111
|
+
time_start=start_time,
|
|
112
|
+
time_real=real_time,
|
|
113
|
+
time_ratio=(
|
|
114
|
+
real_time / float(self.expected_time)
|
|
115
|
+
if self.expected_time > 0
|
|
116
|
+
else None
|
|
117
|
+
),
|
|
118
|
+
sched_id=self.scheduler_ticket,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Save a copy of io assign map in the new database
|
|
122
|
+
if self.system.path.isdir(dbpath):
|
|
123
|
+
self.system.cp(
|
|
124
|
+
self.system.path.join(
|
|
125
|
+
self.workdir, "odb_db_template", "IOASSIGN"
|
|
126
|
+
),
|
|
127
|
+
self.system.path.join(dbpath, "IOASSIGN"),
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
logger.warning("ODB database not created: " + self.base)
|
|
131
|
+
|
|
132
|
+
return rdict
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class Raw2ODBparallel(
|
|
136
|
+
ParaBlindRun, odb.OdbComponentDecoMixin, drhook.DrHookDecoMixin
|
|
137
|
+
):
|
|
138
|
+
"""Convert raw observations files to ODB using taylorism."""
|
|
139
|
+
|
|
140
|
+
_footprint = dict(
|
|
141
|
+
attr=dict(
|
|
142
|
+
kind=dict(
|
|
143
|
+
values=["raw2odb", "bufr2odb", "obsoul2odb"],
|
|
144
|
+
remap=dict(
|
|
145
|
+
bufr2odb="raw2odb",
|
|
146
|
+
obsoul2odb="raw2odb",
|
|
147
|
+
),
|
|
148
|
+
),
|
|
149
|
+
engine=dict(
|
|
150
|
+
values=[
|
|
151
|
+
"blind",
|
|
152
|
+
"parallel",
|
|
153
|
+
] # parallel -> for backward compatibility
|
|
154
|
+
),
|
|
155
|
+
ioassign=dict(
|
|
156
|
+
optional=False,
|
|
157
|
+
),
|
|
158
|
+
lamflag=dict(
|
|
159
|
+
info="Activate LAMFLAG (i.e work for Limited Area Model)",
|
|
160
|
+
type=bool,
|
|
161
|
+
optional=True,
|
|
162
|
+
default=False,
|
|
163
|
+
),
|
|
164
|
+
ontime=dict(
|
|
165
|
+
info="Check observation's resources date vs own data attribute.",
|
|
166
|
+
type=bool,
|
|
167
|
+
optional=True,
|
|
168
|
+
default=True,
|
|
169
|
+
),
|
|
170
|
+
mapall=dict(
|
|
171
|
+
info="All observation files must be accounted for in an obsmap file. ",
|
|
172
|
+
type=bool,
|
|
173
|
+
optional=True,
|
|
174
|
+
default=False,
|
|
175
|
+
),
|
|
176
|
+
maponly=dict(
|
|
177
|
+
info=(
|
|
178
|
+
"Work only with observation files listed in the obsmap files. "
|
|
179
|
+
+ "(if False, obsmap entries may be automatically generated)."
|
|
180
|
+
),
|
|
181
|
+
type=bool,
|
|
182
|
+
optional=True,
|
|
183
|
+
default=False,
|
|
184
|
+
),
|
|
185
|
+
member=dict(
|
|
186
|
+
info=(
|
|
187
|
+
"The current member's number "
|
|
188
|
+
+ "(may be omitted in deterministic configurations)."
|
|
189
|
+
),
|
|
190
|
+
optional=True,
|
|
191
|
+
type=int,
|
|
192
|
+
),
|
|
193
|
+
dataid=dict(
|
|
194
|
+
info=(
|
|
195
|
+
"The ODB databases created by Bator contain an identifier "
|
|
196
|
+
+ "that is specified as a command-line argument. This "
|
|
197
|
+
+ "switch tweaks the way the command-line argument is "
|
|
198
|
+
+ "generated."
|
|
199
|
+
),
|
|
200
|
+
values=["empty", "hh"],
|
|
201
|
+
default="hh",
|
|
202
|
+
optional=True,
|
|
203
|
+
),
|
|
204
|
+
ntasks=dict(
|
|
205
|
+
info=(
|
|
206
|
+
"The maximum number of allowed concurrent task for "
|
|
207
|
+
"parallel execution."
|
|
208
|
+
),
|
|
209
|
+
default=1,
|
|
210
|
+
optional=True,
|
|
211
|
+
),
|
|
212
|
+
maxmemory=dict(
|
|
213
|
+
info="The maximum amount of usable memory (in GiB)",
|
|
214
|
+
type=int,
|
|
215
|
+
optional=True,
|
|
216
|
+
),
|
|
217
|
+
parallel_const=dict(
|
|
218
|
+
info=(
|
|
219
|
+
"Constant that are used to predict execution time and "
|
|
220
|
+
+ "memory consumption for a given ODB database."
|
|
221
|
+
),
|
|
222
|
+
type=footprints.FPDict,
|
|
223
|
+
optional=True,
|
|
224
|
+
),
|
|
225
|
+
)
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
_donot_link_roles = [
|
|
229
|
+
"Observations",
|
|
230
|
+
"Obsmap",
|
|
231
|
+
"IOPoll",
|
|
232
|
+
"LFIScripts",
|
|
233
|
+
"LFITOOLS",
|
|
234
|
+
"Binary",
|
|
235
|
+
"Bator",
|
|
236
|
+
"Batodb",
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
def __init__(self, *kargs, **kwargs):
|
|
240
|
+
super().__init__(*kargs, **kwargs)
|
|
241
|
+
self.para_synthesis = dict()
|
|
242
|
+
self.obspack = dict()
|
|
243
|
+
self.obsmapout = list()
|
|
244
|
+
self._effective_maxmem = None
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def effective_maxmem(self):
|
|
248
|
+
"""Return the maximum amount of usable memory (in MiB)."""
|
|
249
|
+
if self._effective_maxmem is None:
|
|
250
|
+
if self.maxmemory:
|
|
251
|
+
self._effective_maxmem = (
|
|
252
|
+
self.maxmemory * 1024
|
|
253
|
+
) # maxmemory in GB
|
|
254
|
+
else:
|
|
255
|
+
sys_maxmem = self.system.memory_info.system_RAM("MiB")
|
|
256
|
+
# System memory minus 20% or minus 4GB
|
|
257
|
+
self._effective_maxmem = max(
|
|
258
|
+
sys_maxmem * 0.8, sys_maxmem - 4 * 1024
|
|
259
|
+
)
|
|
260
|
+
return self._effective_maxmem
|
|
261
|
+
|
|
262
|
+
def input_obs(self):
|
|
263
|
+
"""Find out which are the usable observations."""
|
|
264
|
+
obsall = [
|
|
265
|
+
x
|
|
266
|
+
for x in self.context.sequence.effective_inputs(
|
|
267
|
+
kind="observations"
|
|
268
|
+
)
|
|
269
|
+
]
|
|
270
|
+
obsall.sort(key=lambda s: s.rh.resource.part)
|
|
271
|
+
|
|
272
|
+
# Looking for valid raw observations
|
|
273
|
+
sizemin = self.env.VORTEX_OBS_SIZEMIN or 80
|
|
274
|
+
obsok = list()
|
|
275
|
+
for secobs in obsall:
|
|
276
|
+
rhobs = secobs.rh
|
|
277
|
+
if rhobs.resource.nativefmt == "odb":
|
|
278
|
+
logger.warning(
|
|
279
|
+
"Observations set [%s] is ODB ready", rhobs.resource.part
|
|
280
|
+
)
|
|
281
|
+
continue
|
|
282
|
+
if rhobs.container.totalsize < sizemin:
|
|
283
|
+
logger.warning(
|
|
284
|
+
"Observations set [%s] is far too small: %d",
|
|
285
|
+
rhobs.resource.part,
|
|
286
|
+
rhobs.container.totalsize,
|
|
287
|
+
)
|
|
288
|
+
else:
|
|
289
|
+
logger.info(
|
|
290
|
+
"Observations set [%s] has size: %d",
|
|
291
|
+
rhobs.resource.part,
|
|
292
|
+
int(rhobs.container.totalsize),
|
|
293
|
+
)
|
|
294
|
+
obsok.append(Foo(rh=rhobs, refdata=list(), mapped=False))
|
|
295
|
+
|
|
296
|
+
# Check the observations dates
|
|
297
|
+
for obs in [obs for obs in obsok if obs.rh.resource.date != self.date]:
|
|
298
|
+
logger.warning(
|
|
299
|
+
"Observation [%s] %s [time mismatch: %s / %s]",
|
|
300
|
+
"discarded" if self.ontime else "is questionable",
|
|
301
|
+
obs.rh.resource.part,
|
|
302
|
+
obs.rh.resource.date.isoformat(),
|
|
303
|
+
self.date.isoformat(),
|
|
304
|
+
)
|
|
305
|
+
if self.ontime:
|
|
306
|
+
obsok = [obs for obs in obsok if obs.rh.resource.date == self.date]
|
|
307
|
+
|
|
308
|
+
return obsok
|
|
309
|
+
|
|
310
|
+
def _retrieve_refdatainfo(self, obslist):
|
|
311
|
+
"""Look for refdata resources and link their content with the obslist."""
|
|
312
|
+
refmap = dict()
|
|
313
|
+
refall = list(self.context.sequence.effective_inputs(kind="refdata"))
|
|
314
|
+
for rdata in refall:
|
|
315
|
+
logger.info("Inspect refdata " + rdata.rh.container.localpath())
|
|
316
|
+
self.system.subtitle(rdata.role)
|
|
317
|
+
rdata.rh.container.cat()
|
|
318
|
+
for item in rdata.rh.contents:
|
|
319
|
+
refmap[(item.fmt.lower(), item.data, item.instr)] = (
|
|
320
|
+
rdata.rh,
|
|
321
|
+
item,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Build actual refdata
|
|
325
|
+
for obs in obslist:
|
|
326
|
+
thispart = obs.rh.resource.part
|
|
327
|
+
thisfmt = obs.rh.container.actualfmt.lower()
|
|
328
|
+
logger.info(
|
|
329
|
+
" ".join(
|
|
330
|
+
("Building information for [", thisfmt, "/", thispart, "]")
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Gather equivalent refdata lines
|
|
335
|
+
if not self.system.path.exists("norefdata." + thispart) and (
|
|
336
|
+
not self.env.VORTEX_OBSDB_NOREF
|
|
337
|
+
or not re.search(
|
|
338
|
+
thispart, self.env.VORTEX_OBSDB_NOREF, re.IGNORECASE
|
|
339
|
+
)
|
|
340
|
+
):
|
|
341
|
+
for k, v in refmap.items():
|
|
342
|
+
x_fmt, x_data = k[:2]
|
|
343
|
+
if x_fmt == thisfmt and x_data == thispart:
|
|
344
|
+
rdata, item = v
|
|
345
|
+
obs.refdata.append(rdata.contents.formatted_data(item))
|
|
346
|
+
return refmap, refall
|
|
347
|
+
|
|
348
|
+
def _map_refdatainfo(self, refmap, refall, imap, thismap):
|
|
349
|
+
"""Associate obsmap entries with refdata entries."""
|
|
350
|
+
thiskey = (imap.fmt.lower(), imap.data, imap.instr)
|
|
351
|
+
if thiskey in refmap:
|
|
352
|
+
rdata, item = refmap[thiskey]
|
|
353
|
+
thismap.refdata.append(rdata.contents.formatted_data(item))
|
|
354
|
+
else:
|
|
355
|
+
logger.warning(
|
|
356
|
+
"Creating automatic refdata entry for " + str(thiskey)
|
|
357
|
+
)
|
|
358
|
+
item = ObsRefItem(
|
|
359
|
+
imap.data, imap.fmt, imap.instr, self.date.ymd, self.date.hh
|
|
360
|
+
)
|
|
361
|
+
if refall:
|
|
362
|
+
thismap.refdata.append(
|
|
363
|
+
refall[0].rh.contents.formatted_data(item)
|
|
364
|
+
)
|
|
365
|
+
else:
|
|
366
|
+
logger.error("No default for formatting data %s", item)
|
|
367
|
+
thismap.refdata.append(ObsRefContent.formatted_data(item))
|
|
368
|
+
|
|
369
|
+
@staticmethod
|
|
370
|
+
def _new_obspack_item():
|
|
371
|
+
"""Create a now entry in obspack."""
|
|
372
|
+
return Foo(
|
|
373
|
+
mapping=list(), standalone=False, refdata=list(), obsfile=dict()
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
def prepare(self, rh, opts):
|
|
377
|
+
"""Get a look at raw observations input files."""
|
|
378
|
+
|
|
379
|
+
sh = self.system
|
|
380
|
+
cycle = rh.resource.cycle
|
|
381
|
+
|
|
382
|
+
# First create the proper IO assign table for any of the resulting ECMA databases
|
|
383
|
+
self.odb_create_db("ECMA", "odb_db_template")
|
|
384
|
+
self.env.IOASSIGN = sh.path.abspath(
|
|
385
|
+
sh.path.join("odb_db_template", "IOASSIGN")
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
# Looking for input observations
|
|
389
|
+
obsok = self.input_obs()
|
|
390
|
+
|
|
391
|
+
# Building refdata map for direct access to (fmt, data, instr) entries
|
|
392
|
+
if cycle < "cy42_op1":
|
|
393
|
+
# Refdata information is not needed anymore with cy42_op1
|
|
394
|
+
refmap, refall = self._retrieve_refdatainfo(obsok)
|
|
395
|
+
|
|
396
|
+
# Looking for obs maps
|
|
397
|
+
mapitems = list()
|
|
398
|
+
for omsec in self.context.sequence.effective_inputs(kind="obsmap"):
|
|
399
|
+
logger.info(
|
|
400
|
+
" ".join(
|
|
401
|
+
(
|
|
402
|
+
"Gathering information from map",
|
|
403
|
+
omsec.rh.container.localpath(),
|
|
404
|
+
)
|
|
405
|
+
)
|
|
406
|
+
)
|
|
407
|
+
sh.subtitle(omsec.role)
|
|
408
|
+
omsec.rh.container.cat()
|
|
409
|
+
mapitems.extend(omsec.rh.contents)
|
|
410
|
+
|
|
411
|
+
self.obspack = defaultdict(self._new_obspack_item) # Reset the obspack
|
|
412
|
+
for imap in mapitems:
|
|
413
|
+
# Match observation files and obsmap entries + Various checks
|
|
414
|
+
logger.info("Inspect " + str(imap))
|
|
415
|
+
candidates = [
|
|
416
|
+
obs
|
|
417
|
+
for obs in obsok
|
|
418
|
+
if (
|
|
419
|
+
obs.rh.resource.part == imap.data
|
|
420
|
+
and obs.rh.container.actualfmt.lower() == imap.fmt.lower()
|
|
421
|
+
)
|
|
422
|
+
]
|
|
423
|
+
if not candidates:
|
|
424
|
+
errmsg = (
|
|
425
|
+
"No input obsfile could match [data:{:s}/fmt:{:s}]".format(
|
|
426
|
+
imap.data, imap.fmt
|
|
427
|
+
)
|
|
428
|
+
)
|
|
429
|
+
if self.mapall:
|
|
430
|
+
raise ValueError(errmsg)
|
|
431
|
+
else:
|
|
432
|
+
logger.warning(errmsg)
|
|
433
|
+
continue
|
|
434
|
+
candidates[-1].mapped = True
|
|
435
|
+
# Build the obspack entry
|
|
436
|
+
thismap = self.obspack[imap.odb]
|
|
437
|
+
thismap.mapping.append(imap)
|
|
438
|
+
thismap.obsfile[imap.fmt.upper() + "." + imap.data] = candidates[
|
|
439
|
+
-1
|
|
440
|
+
]
|
|
441
|
+
# Map refdata and obsmap entries
|
|
442
|
+
if cycle < "cy42_op1":
|
|
443
|
+
# Refdata information is not needed anymore with cy42_op1
|
|
444
|
+
self._map_refdatainfo(refmap, refall, imap, thismap)
|
|
445
|
+
|
|
446
|
+
# Deal with observations that are not described in the obsmap
|
|
447
|
+
for notmap in [obs for obs in obsok if not obs.mapped]:
|
|
448
|
+
thispart = notmap.rh.resource.part
|
|
449
|
+
logger.info("Inspect not mapped obs " + thispart)
|
|
450
|
+
if thispart not in self.obspack:
|
|
451
|
+
thisfmt = notmap.rh.container.actualfmt.upper()
|
|
452
|
+
thismsg = "standalone obs entry [data:{:s} / fmt:{:s}]".format(
|
|
453
|
+
thispart, thisfmt
|
|
454
|
+
)
|
|
455
|
+
if self.maponly:
|
|
456
|
+
logger.warning("Ignore " + thismsg)
|
|
457
|
+
else:
|
|
458
|
+
logger.warning("Active " + thismsg)
|
|
459
|
+
thismap = self.obspack[thispart]
|
|
460
|
+
thismap.standalone = thisfmt
|
|
461
|
+
thismap.mapping.append(
|
|
462
|
+
ObsMapItem(thispart, thispart, thisfmt, thispart)
|
|
463
|
+
)
|
|
464
|
+
thismap.refdata = notmap.refdata
|
|
465
|
+
thismap.obsfile[thisfmt.upper() + "." + thispart] = notmap
|
|
466
|
+
|
|
467
|
+
# Informations about timeslots
|
|
468
|
+
logger.info("The timeslot definition is: %s", str(self.slots))
|
|
469
|
+
if cycle < "cy42_op1":
|
|
470
|
+
# ficdate is not needed anymore with cy42_op1...
|
|
471
|
+
self.slots.as_file(self.date, "ficdate")
|
|
472
|
+
else:
|
|
473
|
+
# From cy42_op1 onward, we only need environment variables
|
|
474
|
+
for var, value in self.slots.as_environment().items():
|
|
475
|
+
logger.info("Setting env %s = %s", var, str(value))
|
|
476
|
+
self.env[var] = value
|
|
477
|
+
|
|
478
|
+
# Let ancestors handling most of the env setting
|
|
479
|
+
super().prepare(rh, opts)
|
|
480
|
+
self.env.update(
|
|
481
|
+
BATOR_NBPOOL=self.npool,
|
|
482
|
+
BATODB_NBPOOL=self.npool,
|
|
483
|
+
BATOR_NBSLOT=self.slots.nslot,
|
|
484
|
+
BATODB_NBSLOT=self.slots.nslot,
|
|
485
|
+
)
|
|
486
|
+
self.env.default(
|
|
487
|
+
TIME_INIT_YYYYMMDD=self.date.ymd,
|
|
488
|
+
TIME_INIT_HHMMSS=self.date.hm + "00",
|
|
489
|
+
)
|
|
490
|
+
if self.lamflag:
|
|
491
|
+
for lamvar in ("BATOR_LAMFLAG", "BATODB_LAMFLAG"):
|
|
492
|
+
logger.info("Setting env %s = %d", lamvar, 1)
|
|
493
|
+
self.env[lamvar] = 1
|
|
494
|
+
|
|
495
|
+
if self.member is not None:
|
|
496
|
+
for nam in self.context.sequence.effective_inputs(
|
|
497
|
+
kind=("namelist", "namelistfp")
|
|
498
|
+
):
|
|
499
|
+
nam.rh.contents.setmacro("MEMBER", self.member)
|
|
500
|
+
logger.info(
|
|
501
|
+
"Setup macro MEMBER=%s in %s",
|
|
502
|
+
self.member,
|
|
503
|
+
nam.rh.container.actualpath(),
|
|
504
|
+
)
|
|
505
|
+
if nam.rh.contents.dumps_needs_update:
|
|
506
|
+
nam.rh.save()
|
|
507
|
+
|
|
508
|
+
def spawn_command_options(self):
|
|
509
|
+
"""Any data useful to build the command line."""
|
|
510
|
+
opts_dict = super().spawn_command_options()
|
|
511
|
+
opts_dict["dataid"] = self.dataid
|
|
512
|
+
opts_dict["date"] = self.date
|
|
513
|
+
return opts_dict
|
|
514
|
+
|
|
515
|
+
def _default_pre_execute(self, rh, opts):
|
|
516
|
+
"""Change default initialisation to use LongerFirstScheduler"""
|
|
517
|
+
# Start the task scheduler
|
|
518
|
+
self._boss = Boss(
|
|
519
|
+
verbose=self.verbose,
|
|
520
|
+
scheduler=footprints.proxy.scheduler(
|
|
521
|
+
limit="threads+memory",
|
|
522
|
+
max_threads=self.ntasks,
|
|
523
|
+
max_memory=self.effective_maxmem,
|
|
524
|
+
),
|
|
525
|
+
)
|
|
526
|
+
self._boss.make_them_work()
|
|
527
|
+
|
|
528
|
+
def execute(self, rh, opts):
|
|
529
|
+
"""
|
|
530
|
+
For each base, a directory is created such that each worker works in his
|
|
531
|
+
directory. Symlinks are created into these working directories.
|
|
532
|
+
"""
|
|
533
|
+
|
|
534
|
+
sh = self.system
|
|
535
|
+
cycle = rh.resource.cycle
|
|
536
|
+
|
|
537
|
+
batnam = [
|
|
538
|
+
x.rh
|
|
539
|
+
for x in self.context.sequence.effective_inputs(
|
|
540
|
+
role="NamelistBatodb"
|
|
541
|
+
)
|
|
542
|
+
]
|
|
543
|
+
# Give a glance to the actual namelist
|
|
544
|
+
if batnam:
|
|
545
|
+
sh.subtitle("Namelist Raw2ODB")
|
|
546
|
+
batnam[0].container.cat()
|
|
547
|
+
|
|
548
|
+
self.obsmapout = list() # Reset the obsmapout
|
|
549
|
+
scheduler_instructions = defaultdict(list)
|
|
550
|
+
|
|
551
|
+
workdir = sh.pwd()
|
|
552
|
+
|
|
553
|
+
for odbset, thispack in self.obspack.items():
|
|
554
|
+
odbname = self.virtualdb.upper() + "." + odbset
|
|
555
|
+
sh.title("Cocooning ODB set: " + odbname)
|
|
556
|
+
with sh.cdcontext("wkdir_" + odbset, create=True):
|
|
557
|
+
for inpt in [
|
|
558
|
+
s
|
|
559
|
+
for s in self.context.sequence.inputs()
|
|
560
|
+
if s.stage == "get"
|
|
561
|
+
]:
|
|
562
|
+
if inpt.role not in self._donot_link_roles:
|
|
563
|
+
logger.info(
|
|
564
|
+
"creating softlink: %s -> %s",
|
|
565
|
+
inpt.rh.container.localpath(),
|
|
566
|
+
sh.path.join(
|
|
567
|
+
workdir, inpt.rh.container.localpath()
|
|
568
|
+
),
|
|
569
|
+
)
|
|
570
|
+
sh.softlink(
|
|
571
|
+
sh.path.join(
|
|
572
|
+
workdir, inpt.rh.container.localpath()
|
|
573
|
+
),
|
|
574
|
+
inpt.rh.container.localpath(),
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
if cycle < "cy42_op1":
|
|
578
|
+
# Special stuff for cy < 42
|
|
579
|
+
logger.info("creating softlink for ficdate.")
|
|
580
|
+
sh.softlink(sh.path.join(workdir, "ficdate"), "ficdate")
|
|
581
|
+
|
|
582
|
+
odb_input_size = 0
|
|
583
|
+
for obsname, obsinfo in thispack.obsfile.items():
|
|
584
|
+
logger.info(
|
|
585
|
+
"creating softlink: %s -> %s",
|
|
586
|
+
obsname,
|
|
587
|
+
sh.path.join(
|
|
588
|
+
workdir, obsinfo.rh.container.localpath()
|
|
589
|
+
),
|
|
590
|
+
)
|
|
591
|
+
sh.softlink(
|
|
592
|
+
sh.path.join(
|
|
593
|
+
workdir, obsinfo.rh.container.localpath()
|
|
594
|
+
),
|
|
595
|
+
obsname,
|
|
596
|
+
)
|
|
597
|
+
if thispack.standalone and cycle < "cy42_op1":
|
|
598
|
+
logger.info(
|
|
599
|
+
"creating softlink: %s -> %s",
|
|
600
|
+
thispack.standalone,
|
|
601
|
+
sh.path.join(
|
|
602
|
+
workdir, obsinfo.rh.container.localpath()
|
|
603
|
+
),
|
|
604
|
+
)
|
|
605
|
+
sh.softlink(
|
|
606
|
+
sh.path.join(
|
|
607
|
+
workdir, obsinfo.rh.container.localpath()
|
|
608
|
+
),
|
|
609
|
+
thispack.standalone,
|
|
610
|
+
)
|
|
611
|
+
|
|
612
|
+
odb_input_size += obsinfo.rh.container.totalsize
|
|
613
|
+
|
|
614
|
+
# Fill the actual refdata according to information gathered in prepare stage
|
|
615
|
+
if cycle < "cy42_op1":
|
|
616
|
+
if thispack.refdata:
|
|
617
|
+
with open("refdata", "w") as fd:
|
|
618
|
+
for rdentry in thispack.refdata:
|
|
619
|
+
fd.write(str(rdentry + "\n"))
|
|
620
|
+
sh.subtitle("Local refdata for: {:s}".format(odbname))
|
|
621
|
+
sh.cat("refdata", output=False)
|
|
622
|
+
# Drive bator with a batormap file (from cy42_op1 onward)
|
|
623
|
+
else:
|
|
624
|
+
with open("batormap", "w") as fd:
|
|
625
|
+
for mapentry in sorted(thispack.mapping):
|
|
626
|
+
fd.write(
|
|
627
|
+
str(
|
|
628
|
+
ObsMapContent.formatted_data(mapentry)
|
|
629
|
+
+ "\n"
|
|
630
|
+
)
|
|
631
|
+
)
|
|
632
|
+
sh.subtitle("Local batormap for: {:s}".format(odbname))
|
|
633
|
+
sh.cat("batormap", output=False)
|
|
634
|
+
|
|
635
|
+
self.obsmapout.extend(thispack.mapping)
|
|
636
|
+
|
|
637
|
+
# Compute the expected memory and time
|
|
638
|
+
if isinstance(self.parallel_const, dict):
|
|
639
|
+
pconst = self.parallel_const.get(
|
|
640
|
+
odbset,
|
|
641
|
+
self.parallel_const.get("default", (999999.0, 1.0)),
|
|
642
|
+
)
|
|
643
|
+
offsets = self.parallel_const.get(
|
|
644
|
+
"offset", (0.0, 0.0)
|
|
645
|
+
) # In MiB for the memory
|
|
646
|
+
else:
|
|
647
|
+
pconst = (999999.0, 1.0)
|
|
648
|
+
offsets = (0.0, 0.0)
|
|
649
|
+
bTime = (odb_input_size * pconst[1] / 1048576) + offsets[1]
|
|
650
|
+
bMemory = odb_input_size * pconst[0] + (
|
|
651
|
+
offsets[0] * 1024 * 1024
|
|
652
|
+
)
|
|
653
|
+
bMemory = bMemory / 1024.0 / 1024.0
|
|
654
|
+
if bMemory > self.effective_maxmem:
|
|
655
|
+
logger.info(
|
|
656
|
+
"For %s, the computed memory needs exceed the node limit.",
|
|
657
|
+
odbset,
|
|
658
|
+
)
|
|
659
|
+
logger.info(
|
|
660
|
+
"Memory requirement reseted to %d (originally %d.)",
|
|
661
|
+
int(self.effective_maxmem),
|
|
662
|
+
int(bMemory),
|
|
663
|
+
)
|
|
664
|
+
bMemory = self.effective_maxmem
|
|
665
|
+
scheduler_instructions["name"].append(
|
|
666
|
+
"ODB_database_{:s}".format(odbset)
|
|
667
|
+
)
|
|
668
|
+
scheduler_instructions["base"].append(odbset)
|
|
669
|
+
scheduler_instructions["memory"].append(bMemory)
|
|
670
|
+
scheduler_instructions["expected_time"].append(bTime)
|
|
671
|
+
scheduler_instructions["inputsize"].append(odb_input_size)
|
|
672
|
+
|
|
673
|
+
sh.title("Launching Bator using taylorism...")
|
|
674
|
+
self._default_pre_execute(rh, opts)
|
|
675
|
+
common_i = self._default_common_instructions(rh, opts)
|
|
676
|
+
# Update the common instructions
|
|
677
|
+
common_i.update(
|
|
678
|
+
dict(
|
|
679
|
+
workdir=workdir,
|
|
680
|
+
cycle=cycle,
|
|
681
|
+
)
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
self._add_instructions(common_i, scheduler_instructions)
|
|
685
|
+
|
|
686
|
+
post_opts = copy.copy(opts)
|
|
687
|
+
post_opts["synthesis"] = self.para_synthesis
|
|
688
|
+
self._default_post_execute(rh, post_opts)
|
|
689
|
+
|
|
690
|
+
def _default_rc_action(self, rh, opts, report, rc):
|
|
691
|
+
super()._default_rc_action(rh, opts, report, rc)
|
|
692
|
+
my_report = report["report"].get("synthesis", None)
|
|
693
|
+
if my_report:
|
|
694
|
+
opts["synthesis"][my_report.pop("base")] = my_report
|
|
695
|
+
|
|
696
|
+
def postfix(self, rh, opts):
|
|
697
|
+
"""Post conversion cleaning."""
|
|
698
|
+
sh = self.system
|
|
699
|
+
|
|
700
|
+
# Remove empty ECMA databases from the output obsmap
|
|
701
|
+
self.obsmapout = [
|
|
702
|
+
x
|
|
703
|
+
for x in self.obsmapout
|
|
704
|
+
if (
|
|
705
|
+
sh.path.isdir("ECMA." + x.odb)
|
|
706
|
+
and sh.path.isdir("ECMA." + x.odb + "/1")
|
|
707
|
+
)
|
|
708
|
+
]
|
|
709
|
+
|
|
710
|
+
# At least one non-empty database is needed...
|
|
711
|
+
self.algoassert(
|
|
712
|
+
self.obsmapout, "At least one non-empty ODB database is expected"
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
# Generate the output bator_map
|
|
716
|
+
with open("batodb_map.out", "w") as fd:
|
|
717
|
+
for x in sorted(self.obsmapout):
|
|
718
|
+
fd.write(str(ObsMapContent.formatted_data(x) + "\n"))
|
|
719
|
+
|
|
720
|
+
# Generate a global refdata (if cycle allows it and if possible)
|
|
721
|
+
if rh.resource.cycle < "cy42_op1":
|
|
722
|
+
rdrh_dict = {
|
|
723
|
+
y.rh.resource.part: y.rh
|
|
724
|
+
for y in self.context.sequence.effective_inputs(kind="refdata")
|
|
725
|
+
if y.rh.resource.part != "all"
|
|
726
|
+
}
|
|
727
|
+
with open("refdata_global", "w") as rdg:
|
|
728
|
+
for x in sorted(self.obsmapout):
|
|
729
|
+
if (
|
|
730
|
+
x.data in rdrh_dict
|
|
731
|
+
and sh.path.getsize(
|
|
732
|
+
rdrh_dict[x.data].container.localpath()
|
|
733
|
+
)
|
|
734
|
+
> 0
|
|
735
|
+
):
|
|
736
|
+
with open(
|
|
737
|
+
rdrh_dict[x.data].container.localpath()
|
|
738
|
+
) as rdl:
|
|
739
|
+
rdg.write(rdl.readline())
|
|
740
|
+
elif (
|
|
741
|
+
sh.path.exists("refdata." + x.data)
|
|
742
|
+
and sh.path.getsize("refdata." + x.data) > 0
|
|
743
|
+
):
|
|
744
|
+
with open("refdata." + x.data) as rdl:
|
|
745
|
+
rdg.write(rdl.readline())
|
|
746
|
+
else:
|
|
747
|
+
logger.info(
|
|
748
|
+
"Unable to create a global refdata entry for data="
|
|
749
|
+
+ x.data
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
sh.json_dump(self.para_synthesis, "parallel_exec_synthesis.json")
|
|
753
|
+
|
|
754
|
+
# Print the parallel execution summary
|
|
755
|
+
sh.subtitle("Here is the parallel execution synthesis: memory aspects")
|
|
756
|
+
header = "Database InputSize(MiB) PredMem(GiB) RealMem(GiB) Real/Pred Ratio"
|
|
757
|
+
rfmt = "{:8s} {:>15.0f} {:>12.1f} {:>12.1f} {:>15.2f}"
|
|
758
|
+
print(header)
|
|
759
|
+
for row in sorted(self.para_synthesis.keys()):
|
|
760
|
+
srep = self.para_synthesis[row]
|
|
761
|
+
print(
|
|
762
|
+
rfmt.format(
|
|
763
|
+
row,
|
|
764
|
+
convert_bytes_in_unit(srep["inputsize"], "MiB"),
|
|
765
|
+
convert_bytes_in_unit(srep["mem_expected"], "GiB"),
|
|
766
|
+
(
|
|
767
|
+
99.99
|
|
768
|
+
if srep["mem_real"] is None
|
|
769
|
+
else convert_bytes_in_unit(srep["mem_real"], "GiB")
|
|
770
|
+
),
|
|
771
|
+
(
|
|
772
|
+
99.99
|
|
773
|
+
if srep["mem_ratio"] is None
|
|
774
|
+
else srep["mem_ratio"]
|
|
775
|
+
),
|
|
776
|
+
)
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
sh.subtitle(
|
|
780
|
+
"Here is the parallel execution synthesis: elapsed time aspects"
|
|
781
|
+
)
|
|
782
|
+
header = (
|
|
783
|
+
"Database InputSize(MiB) PredTime(s) RealTime(s) Real/Pred Ratio"
|
|
784
|
+
)
|
|
785
|
+
rfmt = "{:8s} {:>15.0f} {:>11.1f} {:>11.1f} {:>15.2f}"
|
|
786
|
+
print(header)
|
|
787
|
+
for row in sorted(self.para_synthesis.keys()):
|
|
788
|
+
srep = self.para_synthesis[row]
|
|
789
|
+
print(
|
|
790
|
+
rfmt.format(
|
|
791
|
+
row,
|
|
792
|
+
convert_bytes_in_unit(srep["inputsize"], "MiB"),
|
|
793
|
+
srep["time_expected"],
|
|
794
|
+
srep["time_real"],
|
|
795
|
+
(
|
|
796
|
+
99.99
|
|
797
|
+
if srep["time_ratio"] is None
|
|
798
|
+
else srep["time_ratio"]
|
|
799
|
+
),
|
|
800
|
+
)
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
sh.subtitle("Here is the parallel execution synthesis: timeline")
|
|
804
|
+
header = "Database StartTime(UTC) PredMem(GiB) RealTime(s) ExecSlot"
|
|
805
|
+
rfmt = "{:8s} {:>40s} {:>11.1f} {:>12.1f} {:>8s}"
|
|
806
|
+
print(header)
|
|
807
|
+
for row, srep in sorted(
|
|
808
|
+
self.para_synthesis.items(), key=lambda x: x[1]["time_start"]
|
|
809
|
+
):
|
|
810
|
+
print(
|
|
811
|
+
rfmt.format(
|
|
812
|
+
row,
|
|
813
|
+
srep["time_start"],
|
|
814
|
+
convert_bytes_in_unit(srep["mem_expected"], "GiB"),
|
|
815
|
+
srep["time_real"],
|
|
816
|
+
str(srep["sched_id"]),
|
|
817
|
+
)
|
|
818
|
+
)
|
|
819
|
+
|
|
820
|
+
print(
|
|
821
|
+
"\nThe memory limit was set to: {:.1f} GiB".format(
|
|
822
|
+
self.effective_maxmem / 1024.0
|
|
823
|
+
)
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
super().postfix(rh, opts)
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
class OdbAverage(Parallel, odb.OdbComponentDecoMixin, drhook.DrHookDecoMixin):
|
|
830
|
+
"""TODO the father of this component is very much welcome."""
|
|
831
|
+
|
|
832
|
+
_footprint = dict(
|
|
833
|
+
attr=dict(
|
|
834
|
+
kind=dict(
|
|
835
|
+
values=["average"],
|
|
836
|
+
),
|
|
837
|
+
binarysingle=dict(
|
|
838
|
+
default="basicobsort",
|
|
839
|
+
),
|
|
840
|
+
ioassign=dict(),
|
|
841
|
+
outdb=dict(
|
|
842
|
+
optional=True,
|
|
843
|
+
default="ccma",
|
|
844
|
+
value=["ecma", "ccma"],
|
|
845
|
+
),
|
|
846
|
+
maskname=dict(
|
|
847
|
+
optional=True,
|
|
848
|
+
default="mask4x4.txt",
|
|
849
|
+
),
|
|
850
|
+
)
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
def _mpitool_attributes(self, opts):
|
|
854
|
+
conf_dict = super()._mpitool_attributes(opts)
|
|
855
|
+
conf_dict.update({"mplbased": True})
|
|
856
|
+
return conf_dict
|
|
857
|
+
|
|
858
|
+
def prepare(self, rh, opts):
|
|
859
|
+
"""Find any ODB candidate in input files."""
|
|
860
|
+
|
|
861
|
+
sh = self.system
|
|
862
|
+
|
|
863
|
+
# Looking for input observations
|
|
864
|
+
obsall = [
|
|
865
|
+
x
|
|
866
|
+
for x in self.lookupodb()
|
|
867
|
+
if x.rh.resource.layout.lower() == "ecma"
|
|
868
|
+
]
|
|
869
|
+
# One database at a time
|
|
870
|
+
if len(obsall) != 1:
|
|
871
|
+
raise ValueError("One and only one ECMA input should be here")
|
|
872
|
+
self.bingo = ecma = obsall[0]
|
|
873
|
+
|
|
874
|
+
# First create a fake CCMA
|
|
875
|
+
self.layout_new = self.outdb.upper()
|
|
876
|
+
ccma_path = self.odb_create_db(self.layout_new)
|
|
877
|
+
self.odb.fix_db_path(self.layout_new, ccma_path)
|
|
878
|
+
|
|
879
|
+
self.layout_in = ecma.rh.resource.layout.upper()
|
|
880
|
+
ecma_path = sh.path.abspath(ecma.rh.container.localpath())
|
|
881
|
+
self.odb.fix_db_path(self.layout_in, ecma_path)
|
|
882
|
+
|
|
883
|
+
self.odb.ioassign_gather(ecma_path, ccma_path)
|
|
884
|
+
|
|
885
|
+
ecma_pool = sh.path.join(ecma_path, "1")
|
|
886
|
+
if not sh.path.isdir(ecma_pool):
|
|
887
|
+
logger.error("The input ECMA base is empty")
|
|
888
|
+
self.abort("No ECMA input")
|
|
889
|
+
return
|
|
890
|
+
|
|
891
|
+
self.odb.create_poolmask(self.layout_new, ccma_path)
|
|
892
|
+
|
|
893
|
+
# Some extra settings
|
|
894
|
+
self.env.update(
|
|
895
|
+
TO_ODB_CANARI=0,
|
|
896
|
+
TO_ODB_REDUC=self.env.TO_ODB_REDUC or 1,
|
|
897
|
+
TO_ODB_SETACTIVE=1,
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
# Let ancesters handling most of the env setting
|
|
901
|
+
super().prepare(rh, opts)
|
|
902
|
+
|
|
903
|
+
def spawn_command_options(self):
|
|
904
|
+
"""Prepare command line options to binary."""
|
|
905
|
+
return dict(
|
|
906
|
+
dbin=self.layout_in,
|
|
907
|
+
dbout=self.layout_new,
|
|
908
|
+
npool=self.npool,
|
|
909
|
+
nslot=self.slots.nslot,
|
|
910
|
+
date=self.date,
|
|
911
|
+
masksize=4,
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
def execute(self, rh, opts):
|
|
915
|
+
"""To mask input."""
|
|
916
|
+
|
|
917
|
+
sh = self.system
|
|
918
|
+
|
|
919
|
+
mask = [
|
|
920
|
+
x.rh
|
|
921
|
+
for x in self.context.sequence.effective_inputs(kind="atmsmask")
|
|
922
|
+
]
|
|
923
|
+
if not mask:
|
|
924
|
+
raise ValueError("Could not find any MASK input")
|
|
925
|
+
|
|
926
|
+
# Have a look to mask file
|
|
927
|
+
if mask[0].container.localpath() != self.maskname:
|
|
928
|
+
sh.softlink(mask[0].container.localpath(), self.maskname)
|
|
929
|
+
|
|
930
|
+
sh.subtitle("Mask")
|
|
931
|
+
mask[0].container.cat()
|
|
932
|
+
|
|
933
|
+
# Standard execution
|
|
934
|
+
super().execute(rh, opts)
|
|
935
|
+
|
|
936
|
+
def postfix(self, rh, opts):
|
|
937
|
+
"""Post shuffle / average cleaning."""
|
|
938
|
+
sh = self.system
|
|
939
|
+
|
|
940
|
+
with sh.cdcontext(self.layout_new):
|
|
941
|
+
for ccma in sh.glob("{:s}.*".format(self.layout_new)):
|
|
942
|
+
slurp = sh.cat(ccma, outsplit=False).replace(
|
|
943
|
+
self.layout_new, self.layout_in
|
|
944
|
+
)
|
|
945
|
+
with open(
|
|
946
|
+
ccma.replace(self.layout_new, self.layout_in), "w"
|
|
947
|
+
) as fd:
|
|
948
|
+
fd.write(str(slurp))
|
|
949
|
+
sh.rm(ccma)
|
|
950
|
+
|
|
951
|
+
sh.mv(
|
|
952
|
+
self.layout_new, self.layout_in + "." + self.bingo.rh.resource.part
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
super().postfix(rh, opts)
|
|
956
|
+
|
|
957
|
+
|
|
958
|
+
class OdbCompress(Parallel, odb.OdbComponentDecoMixin, drhook.DrHookDecoMixin):
|
|
959
|
+
"""Take a screening ODB ECMA database and create the compressed CCMA database."""
|
|
960
|
+
|
|
961
|
+
_footprint = dict(
|
|
962
|
+
attr=dict(
|
|
963
|
+
kind=dict(
|
|
964
|
+
values=["odbcompress"],
|
|
965
|
+
),
|
|
966
|
+
ioassign=dict(),
|
|
967
|
+
)
|
|
968
|
+
)
|
|
969
|
+
|
|
970
|
+
def _mpitool_attributes(self, opts):
|
|
971
|
+
conf_dict = super()._mpitool_attributes(opts)
|
|
972
|
+
conf_dict.update({"mplbased": True})
|
|
973
|
+
return conf_dict
|
|
974
|
+
|
|
975
|
+
def prepare(self, rh, opts):
|
|
976
|
+
"""Find any ODB candidate in input files and fox ODB env accordingly."""
|
|
977
|
+
|
|
978
|
+
obsall = [
|
|
979
|
+
x
|
|
980
|
+
for x in self.lookupodb()
|
|
981
|
+
if x.rh.resource.layout.lower() == "ecma"
|
|
982
|
+
]
|
|
983
|
+
if len(obsall) > 1:
|
|
984
|
+
obsvirtual = [o for o in obsall if o.rh.resource.part == "virtual"]
|
|
985
|
+
if len(obsvirtual) != 1:
|
|
986
|
+
raise ValueError(
|
|
987
|
+
"One and only one virtual database must be provided"
|
|
988
|
+
)
|
|
989
|
+
ecma = obsvirtual[0]
|
|
990
|
+
elif len(obsall) == 1:
|
|
991
|
+
ecma = obsall[0]
|
|
992
|
+
else:
|
|
993
|
+
raise ValueError("No ECMA database provided")
|
|
994
|
+
|
|
995
|
+
# First create a fake CCMA
|
|
996
|
+
self.layout_new = "ccma"
|
|
997
|
+
ccma_path = self.odb_create_db(self.layout_new)
|
|
998
|
+
self.odb.fix_db_path(self.layout_new, ccma_path)
|
|
999
|
+
|
|
1000
|
+
self.layout_in = ecma.rh.resource.layout.upper()
|
|
1001
|
+
ecma_path = self.system.path.abspath(ecma.rh.container.localpath())
|
|
1002
|
+
self.odb.fix_db_path(self.layout_in, ecma_path)
|
|
1003
|
+
|
|
1004
|
+
self.odb.ioassign_gather(ecma_path, ccma_path)
|
|
1005
|
+
|
|
1006
|
+
self.odb.create_poolmask(self.layout_new, ccma_path)
|
|
1007
|
+
|
|
1008
|
+
self.odb_rw_or_overwrite_method(*obsall)
|
|
1009
|
+
|
|
1010
|
+
# Let ancesters handling most of the env setting
|
|
1011
|
+
super().prepare(rh, opts)
|
|
1012
|
+
|
|
1013
|
+
def spawn_command_options(self):
|
|
1014
|
+
"""Prepare command line options to binary."""
|
|
1015
|
+
return dict(
|
|
1016
|
+
dbin=self.layout_in,
|
|
1017
|
+
dbout=self.layout_new,
|
|
1018
|
+
npool=self.npool,
|
|
1019
|
+
nslot=self.slots.nslot,
|
|
1020
|
+
date=self.date,
|
|
1021
|
+
)
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
class OdbMatchup(Parallel, odb.OdbComponentDecoMixin, drhook.DrHookDecoMixin):
|
|
1025
|
+
"""Report some information from post-minim CCMA to post-screening ECMA base."""
|
|
1026
|
+
|
|
1027
|
+
_footprint = dict(
|
|
1028
|
+
attr=dict(
|
|
1029
|
+
kind=dict(
|
|
1030
|
+
values=["matchup"],
|
|
1031
|
+
),
|
|
1032
|
+
fcmalayout=dict(
|
|
1033
|
+
optional=True,
|
|
1034
|
+
value=["ecma", "ccma", "CCMA", "ECMA"],
|
|
1035
|
+
remap=dict(CCMA="ccma", ECMA="ecma"),
|
|
1036
|
+
),
|
|
1037
|
+
)
|
|
1038
|
+
)
|
|
1039
|
+
|
|
1040
|
+
def _mpitool_attributes(self, opts):
|
|
1041
|
+
conf_dict = super()._mpitool_attributes(opts)
|
|
1042
|
+
conf_dict.update({"mplbased": True})
|
|
1043
|
+
return conf_dict
|
|
1044
|
+
|
|
1045
|
+
def prepare(self, rh, opts):
|
|
1046
|
+
"""Find ODB candidates in input files."""
|
|
1047
|
+
|
|
1048
|
+
sh = self.system
|
|
1049
|
+
|
|
1050
|
+
# Looking for input observations
|
|
1051
|
+
obsscr_virtual = [
|
|
1052
|
+
x
|
|
1053
|
+
for x in self.lookupodb()
|
|
1054
|
+
if x.rh.resource.stage.startswith("screen")
|
|
1055
|
+
and x.rh.resource.part == "virtual"
|
|
1056
|
+
]
|
|
1057
|
+
obsscr_parts = [
|
|
1058
|
+
x
|
|
1059
|
+
for x in self.lookupodb()
|
|
1060
|
+
if x.rh.resource.stage.startswith("screen")
|
|
1061
|
+
and x.rh.resource.part != "virtual"
|
|
1062
|
+
]
|
|
1063
|
+
obscompressed = [
|
|
1064
|
+
x
|
|
1065
|
+
for x in self.lookupodb()
|
|
1066
|
+
if x.rh.resource.stage.startswith("min")
|
|
1067
|
+
or x.rh.resource.stage.startswith("traj")
|
|
1068
|
+
]
|
|
1069
|
+
|
|
1070
|
+
# One database at a time
|
|
1071
|
+
if not obsscr_virtual:
|
|
1072
|
+
raise ValueError("Could not find any ODB screening input")
|
|
1073
|
+
if not obscompressed:
|
|
1074
|
+
raise ValueError("Could not find any ODB minim input")
|
|
1075
|
+
|
|
1076
|
+
# Set actual layout and path
|
|
1077
|
+
ecma = obsscr_virtual.pop(0)
|
|
1078
|
+
ccma = obscompressed.pop(0)
|
|
1079
|
+
self.layout_screening = ecma.rh.resource.layout
|
|
1080
|
+
self.layout_compressed = ccma.rh.resource.layout
|
|
1081
|
+
self.layout_fcma = (
|
|
1082
|
+
self.layout_compressed
|
|
1083
|
+
if self.fcmalayout is None
|
|
1084
|
+
else self.fcmalayout
|
|
1085
|
+
)
|
|
1086
|
+
ecma_path = sh.path.abspath(ecma.rh.container.localpath())
|
|
1087
|
+
ccma_path = sh.path.abspath(ccma.rh.container.localpath())
|
|
1088
|
+
|
|
1089
|
+
self.odb.fix_db_path(self.layout_screening, ecma_path)
|
|
1090
|
+
self.odb.fix_db_path(self.layout_compressed, ccma_path)
|
|
1091
|
+
self.odb.ioassign_gather(ccma_path, ecma_path)
|
|
1092
|
+
|
|
1093
|
+
# Ok, but why ???
|
|
1094
|
+
sh.cp(
|
|
1095
|
+
sh.path.join(ecma_path, "ECMA.dd"),
|
|
1096
|
+
sh.path.join(ccma_path, "ECMA.dd"),
|
|
1097
|
+
)
|
|
1098
|
+
|
|
1099
|
+
# Let ancesters handling most of the env setting
|
|
1100
|
+
super().prepare(rh, opts)
|
|
1101
|
+
|
|
1102
|
+
# Fix the input database intent
|
|
1103
|
+
self.odb_rw_or_overwrite_method(ecma)
|
|
1104
|
+
self.odb_rw_or_overwrite_method(*obsscr_parts)
|
|
1105
|
+
|
|
1106
|
+
def spawn_command_options(self):
|
|
1107
|
+
"""Prepare command line options to binary."""
|
|
1108
|
+
return dict(
|
|
1109
|
+
dbin=self.layout_compressed,
|
|
1110
|
+
dbout=self.layout_screening,
|
|
1111
|
+
npool=self.npool,
|
|
1112
|
+
nslot=self.slots.nslot,
|
|
1113
|
+
date=self.date,
|
|
1114
|
+
fcma=self.layout_fcma,
|
|
1115
|
+
)
|
|
1116
|
+
|
|
1117
|
+
|
|
1118
|
+
class OdbReshuffle(
|
|
1119
|
+
Parallel, odb.OdbComponentDecoMixin, drhook.DrHookDecoMixin
|
|
1120
|
+
):
|
|
1121
|
+
"""Take a bunch of ECMA databases and create new ones with an updated number of pools."""
|
|
1122
|
+
|
|
1123
|
+
_footprint = dict(
|
|
1124
|
+
attr=dict(
|
|
1125
|
+
kind=dict(
|
|
1126
|
+
values=["reshuffle"],
|
|
1127
|
+
),
|
|
1128
|
+
)
|
|
1129
|
+
)
|
|
1130
|
+
|
|
1131
|
+
_OUT_DIRECTORY = "reshuffled"
|
|
1132
|
+
_BARE_OUT_LAYOUT = "ccma"
|
|
1133
|
+
|
|
1134
|
+
def _mpitool_attributes(self, opts):
|
|
1135
|
+
conf_dict = super()._mpitool_attributes(opts)
|
|
1136
|
+
conf_dict.update({"mplbased": True})
|
|
1137
|
+
return conf_dict
|
|
1138
|
+
|
|
1139
|
+
def prepare(self, rh, opts):
|
|
1140
|
+
"""Find ODB candidates in input files."""
|
|
1141
|
+
|
|
1142
|
+
# Looking for input observations
|
|
1143
|
+
obs_in_virtual = [
|
|
1144
|
+
x for x in self.lookupodb() if x.rh.resource.part == "virtual"
|
|
1145
|
+
]
|
|
1146
|
+
if obs_in_virtual:
|
|
1147
|
+
raise ValueError("Do not input a Virtual database")
|
|
1148
|
+
self.obs_in_parts = [
|
|
1149
|
+
x for x in self.lookupodb() if x.rh.resource.part != "virtual"
|
|
1150
|
+
]
|
|
1151
|
+
|
|
1152
|
+
# Find the input layout
|
|
1153
|
+
in_layout = {x.rh.resource.layout for x in self.obs_in_parts}
|
|
1154
|
+
if len(in_layout) != 1:
|
|
1155
|
+
raise ValueError(
|
|
1156
|
+
"Incoherent layout in input databases or no input databases"
|
|
1157
|
+
)
|
|
1158
|
+
self.layout_in = in_layout.pop()
|
|
1159
|
+
|
|
1160
|
+
# Some extra settings
|
|
1161
|
+
self.env.update(TO_ODB_FULL=1)
|
|
1162
|
+
|
|
1163
|
+
# prepare the ouputs' directory
|
|
1164
|
+
self.system.mkdir(self._OUT_DIRECTORY)
|
|
1165
|
+
|
|
1166
|
+
super().prepare(rh, opts)
|
|
1167
|
+
|
|
1168
|
+
def execute(self, rh, opts):
|
|
1169
|
+
"""Loop on available databases."""
|
|
1170
|
+
sh = self.system
|
|
1171
|
+
for a_db in self.obs_in_parts:
|
|
1172
|
+
sh.subtitle(
|
|
1173
|
+
"Dealing with {:s}".format(a_db.rh.container.localpath())
|
|
1174
|
+
)
|
|
1175
|
+
|
|
1176
|
+
ecma_path = sh.path.abspath(a_db.rh.container.localpath())
|
|
1177
|
+
ccma_path = sh.path.abspath(
|
|
1178
|
+
sh.path.join(
|
|
1179
|
+
self._OUT_DIRECTORY,
|
|
1180
|
+
".".join([self.layout_in.upper(), a_db.rh.resource.part]),
|
|
1181
|
+
)
|
|
1182
|
+
)
|
|
1183
|
+
self.odb_create_db(self._BARE_OUT_LAYOUT, dbpath=ccma_path)
|
|
1184
|
+
self.odb.fix_db_path(self.layout_in, ecma_path)
|
|
1185
|
+
self.odb.fix_db_path(self._BARE_OUT_LAYOUT, ccma_path)
|
|
1186
|
+
self.odb.ioassign_gather(ccma_path, ecma_path)
|
|
1187
|
+
|
|
1188
|
+
# Apparently te binary tries to write in the input databse,
|
|
1189
|
+
# no idea why but...
|
|
1190
|
+
self.odb_rw_or_overwrite_method(a_db)
|
|
1191
|
+
|
|
1192
|
+
super().execute(rh, opts)
|
|
1193
|
+
|
|
1194
|
+
# CCMA -> ECMA
|
|
1195
|
+
self.odb.change_layout(
|
|
1196
|
+
self._BARE_OUT_LAYOUT, self.layout_in, ccma_path
|
|
1197
|
+
)
|
|
1198
|
+
|
|
1199
|
+
def postfix(self, rh, opts):
|
|
1200
|
+
"""Create a virtual database for output data."""
|
|
1201
|
+
self.system.subtitle("Creating the virtual database")
|
|
1202
|
+
virtual_db = self.odb_merge_if_needed(
|
|
1203
|
+
self.obs_in_parts, subdir=self._OUT_DIRECTORY
|
|
1204
|
+
)
|
|
1205
|
+
logger.info(
|
|
1206
|
+
"The output virtual DB was created: %s",
|
|
1207
|
+
self.system.path.join(self._OUT_DIRECTORY, virtual_db),
|
|
1208
|
+
)
|
|
1209
|
+
|
|
1210
|
+
def spawn_command_options(self):
|
|
1211
|
+
"""Prepare command line options to binary."""
|
|
1212
|
+
return dict(
|
|
1213
|
+
dbin=self.layout_in,
|
|
1214
|
+
dbout=self._BARE_OUT_LAYOUT,
|
|
1215
|
+
npool=self.npool,
|
|
1216
|
+
)
|
|
1217
|
+
|
|
1218
|
+
|
|
1219
|
+
class FlagsCompute(
|
|
1220
|
+
Parallel, odb.OdbComponentDecoMixin, drhook.DrHookDecoMixin
|
|
1221
|
+
):
|
|
1222
|
+
"""Compute observations flags."""
|
|
1223
|
+
|
|
1224
|
+
_footprint = dict(
|
|
1225
|
+
info="Computation of observations flags.",
|
|
1226
|
+
attr=dict(
|
|
1227
|
+
kind=dict(
|
|
1228
|
+
values=["flagscomp"],
|
|
1229
|
+
),
|
|
1230
|
+
),
|
|
1231
|
+
)
|
|
1232
|
+
|
|
1233
|
+
def execute(self, rh, opts):
|
|
1234
|
+
"""Spawn the binary for each of the input databases."""
|
|
1235
|
+
# Look for the input databases
|
|
1236
|
+
input_databases = self.context.sequence.effective_inputs(
|
|
1237
|
+
role="ECMA",
|
|
1238
|
+
kind="observations",
|
|
1239
|
+
)
|
|
1240
|
+
# Check that there is at least one database
|
|
1241
|
+
if len(input_databases) < 1:
|
|
1242
|
+
raise AttributeError("No database in input. Stop.")
|
|
1243
|
+
|
|
1244
|
+
for input_database in input_databases:
|
|
1245
|
+
ecma = input_database.rh
|
|
1246
|
+
ecma_filename = ecma.container.filename
|
|
1247
|
+
# Environment variable to set DB path
|
|
1248
|
+
self.odb.fix_db_path(ecma.resource.layout, ecma.container.abspath)
|
|
1249
|
+
self.env.setvar("ODB_ECMA", ecma_filename)
|
|
1250
|
+
logger.info("Variable %s set to %s.", "ODB_ECMA", ecma_filename)
|
|
1251
|
+
# Path to the IOASSIGN file
|
|
1252
|
+
self.env.IOASSIGN = self.system.path.join(
|
|
1253
|
+
ecma.container.abspath, "IOASSIGN"
|
|
1254
|
+
)
|
|
1255
|
+
# Let ancesters handling most of the env setting
|
|
1256
|
+
super().execute(rh, opts)
|
|
1257
|
+
# Rename the output file according to the name of the part of the observations treated
|
|
1258
|
+
self.system.mv("BDM_CQ", "_".join(["BDM_CQ", ecma.resource.part]))
|