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.
Files changed (144) hide show
  1. vortex/__init__.py +159 -0
  2. vortex/algo/__init__.py +13 -0
  3. vortex/algo/components.py +2462 -0
  4. vortex/algo/mpitools.py +1953 -0
  5. vortex/algo/mpitools_templates/__init__.py +1 -0
  6. vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +27 -0
  7. vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +29 -0
  8. vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +18 -0
  9. vortex/algo/serversynctools.py +171 -0
  10. vortex/config.py +112 -0
  11. vortex/data/__init__.py +19 -0
  12. vortex/data/abstractstores.py +1510 -0
  13. vortex/data/containers.py +835 -0
  14. vortex/data/contents.py +622 -0
  15. vortex/data/executables.py +275 -0
  16. vortex/data/flow.py +119 -0
  17. vortex/data/geometries.ini +2689 -0
  18. vortex/data/geometries.py +799 -0
  19. vortex/data/handlers.py +1230 -0
  20. vortex/data/outflow.py +67 -0
  21. vortex/data/providers.py +487 -0
  22. vortex/data/resources.py +207 -0
  23. vortex/data/stores.py +1390 -0
  24. vortex/data/sync_templates/__init__.py +0 -0
  25. vortex/gloves.py +309 -0
  26. vortex/layout/__init__.py +20 -0
  27. vortex/layout/contexts.py +577 -0
  28. vortex/layout/dataflow.py +1220 -0
  29. vortex/layout/monitor.py +969 -0
  30. vortex/nwp/__init__.py +14 -0
  31. vortex/nwp/algo/__init__.py +21 -0
  32. vortex/nwp/algo/assim.py +537 -0
  33. vortex/nwp/algo/clim.py +1086 -0
  34. vortex/nwp/algo/coupling.py +831 -0
  35. vortex/nwp/algo/eda.py +840 -0
  36. vortex/nwp/algo/eps.py +785 -0
  37. vortex/nwp/algo/forecasts.py +886 -0
  38. vortex/nwp/algo/fpserver.py +1303 -0
  39. vortex/nwp/algo/ifsnaming.py +463 -0
  40. vortex/nwp/algo/ifsroot.py +404 -0
  41. vortex/nwp/algo/monitoring.py +263 -0
  42. vortex/nwp/algo/mpitools.py +694 -0
  43. vortex/nwp/algo/odbtools.py +1258 -0
  44. vortex/nwp/algo/oopsroot.py +916 -0
  45. vortex/nwp/algo/oopstests.py +220 -0
  46. vortex/nwp/algo/request.py +660 -0
  47. vortex/nwp/algo/stdpost.py +1641 -0
  48. vortex/nwp/data/__init__.py +30 -0
  49. vortex/nwp/data/assim.py +380 -0
  50. vortex/nwp/data/boundaries.py +314 -0
  51. vortex/nwp/data/climfiles.py +521 -0
  52. vortex/nwp/data/configfiles.py +153 -0
  53. vortex/nwp/data/consts.py +954 -0
  54. vortex/nwp/data/ctpini.py +149 -0
  55. vortex/nwp/data/diagnostics.py +209 -0
  56. vortex/nwp/data/eda.py +147 -0
  57. vortex/nwp/data/eps.py +432 -0
  58. vortex/nwp/data/executables.py +1045 -0
  59. vortex/nwp/data/fields.py +111 -0
  60. vortex/nwp/data/gridfiles.py +380 -0
  61. vortex/nwp/data/logs.py +584 -0
  62. vortex/nwp/data/modelstates.py +363 -0
  63. vortex/nwp/data/monitoring.py +193 -0
  64. vortex/nwp/data/namelists.py +696 -0
  65. vortex/nwp/data/obs.py +840 -0
  66. vortex/nwp/data/oopsexec.py +74 -0
  67. vortex/nwp/data/providers.py +207 -0
  68. vortex/nwp/data/query.py +206 -0
  69. vortex/nwp/data/stores.py +160 -0
  70. vortex/nwp/data/surfex.py +337 -0
  71. vortex/nwp/syntax/__init__.py +9 -0
  72. vortex/nwp/syntax/stdattrs.py +437 -0
  73. vortex/nwp/tools/__init__.py +10 -0
  74. vortex/nwp/tools/addons.py +40 -0
  75. vortex/nwp/tools/agt.py +67 -0
  76. vortex/nwp/tools/bdap.py +59 -0
  77. vortex/nwp/tools/bdcp.py +41 -0
  78. vortex/nwp/tools/bdm.py +24 -0
  79. vortex/nwp/tools/bdmp.py +54 -0
  80. vortex/nwp/tools/conftools.py +1661 -0
  81. vortex/nwp/tools/drhook.py +66 -0
  82. vortex/nwp/tools/grib.py +294 -0
  83. vortex/nwp/tools/gribdiff.py +104 -0
  84. vortex/nwp/tools/ifstools.py +203 -0
  85. vortex/nwp/tools/igastuff.py +273 -0
  86. vortex/nwp/tools/mars.py +68 -0
  87. vortex/nwp/tools/odb.py +657 -0
  88. vortex/nwp/tools/partitioning.py +258 -0
  89. vortex/nwp/tools/satrad.py +71 -0
  90. vortex/nwp/util/__init__.py +6 -0
  91. vortex/nwp/util/async.py +212 -0
  92. vortex/nwp/util/beacon.py +40 -0
  93. vortex/nwp/util/diffpygram.py +447 -0
  94. vortex/nwp/util/ens.py +279 -0
  95. vortex/nwp/util/hooks.py +139 -0
  96. vortex/nwp/util/taskdeco.py +85 -0
  97. vortex/nwp/util/usepygram.py +697 -0
  98. vortex/nwp/util/usetnt.py +101 -0
  99. vortex/proxy.py +6 -0
  100. vortex/sessions.py +374 -0
  101. vortex/syntax/__init__.py +9 -0
  102. vortex/syntax/stdattrs.py +867 -0
  103. vortex/syntax/stddeco.py +185 -0
  104. vortex/toolbox.py +1117 -0
  105. vortex/tools/__init__.py +20 -0
  106. vortex/tools/actions.py +523 -0
  107. vortex/tools/addons.py +316 -0
  108. vortex/tools/arm.py +96 -0
  109. vortex/tools/compression.py +325 -0
  110. vortex/tools/date.py +27 -0
  111. vortex/tools/ddhpack.py +10 -0
  112. vortex/tools/delayedactions.py +782 -0
  113. vortex/tools/env.py +541 -0
  114. vortex/tools/folder.py +834 -0
  115. vortex/tools/grib.py +738 -0
  116. vortex/tools/lfi.py +953 -0
  117. vortex/tools/listings.py +423 -0
  118. vortex/tools/names.py +637 -0
  119. vortex/tools/net.py +2124 -0
  120. vortex/tools/odb.py +10 -0
  121. vortex/tools/parallelism.py +368 -0
  122. vortex/tools/prestaging.py +210 -0
  123. vortex/tools/rawfiles.py +10 -0
  124. vortex/tools/schedulers.py +480 -0
  125. vortex/tools/services.py +940 -0
  126. vortex/tools/storage.py +996 -0
  127. vortex/tools/surfex.py +61 -0
  128. vortex/tools/systems.py +3976 -0
  129. vortex/tools/targets.py +440 -0
  130. vortex/util/__init__.py +9 -0
  131. vortex/util/config.py +1122 -0
  132. vortex/util/empty.py +24 -0
  133. vortex/util/helpers.py +216 -0
  134. vortex/util/introspection.py +69 -0
  135. vortex/util/iosponge.py +80 -0
  136. vortex/util/roles.py +49 -0
  137. vortex/util/storefunctions.py +129 -0
  138. vortex/util/structs.py +26 -0
  139. vortex/util/worker.py +162 -0
  140. vortex_nwp-2.0.0.dist-info/METADATA +67 -0
  141. vortex_nwp-2.0.0.dist-info/RECORD +144 -0
  142. vortex_nwp-2.0.0.dist-info/WHEEL +5 -0
  143. vortex_nwp-2.0.0.dist-info/licenses/LICENSE +517 -0
  144. vortex_nwp-2.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,423 @@
1
+ """Utility classes to read and compare IFS/Arpege listings."""
2
+
3
+ import copy
4
+ import re
5
+ from collections import OrderedDict, defaultdict, deque
6
+
7
+ import footprints
8
+ from arpifs_listings import cost_functions, jo_tables, listings, norms
9
+ from bronx.stdtypes.date import Date
10
+ from vortex.data.contents import FormatAdapterAbstractImplementation
11
+
12
+ from . import addons
13
+
14
+ #: No automatic export
15
+ __all__ = []
16
+
17
+
18
+ def use_in_shell(sh, **kw):
19
+ """Extend current shell with the arpifs_listings interface defined by optional arguments."""
20
+ kw["shell"] = sh
21
+ return footprints.proxy.addon(**kw)
22
+
23
+
24
+ class ArpIfsListingDiff_Result:
25
+ """Holds the detailed results of a listing comparison."""
26
+
27
+ def __init__(self, norms_eq, jos_eq, jos_diff):
28
+ self._norms_eq = norms_eq
29
+ self._jos_eq = jos_eq
30
+ self._jos_diff = jos_diff
31
+
32
+ def __str__(self):
33
+ return "{:s} | NormsOk={:b} JoTablesOk={:b}>".format(
34
+ repr(self).rstrip(">"),
35
+ all(self._norms_eq.values()),
36
+ all(self._jos_eq.values()),
37
+ )
38
+
39
+ def differences(self):
40
+ """Print a summary of the listing comparison."""
41
+ # print() # activation breaks test_arpifs_listings_integration.py
42
+ if self._norms_eq:
43
+ if all(self._norms_eq.values()):
44
+ print("Norms check succeeded for all steps.")
45
+ else:
46
+ print(
47
+ "Norms check succeeded for steps:\n {:s}".format(
48
+ "\n ".join(
49
+ [str(k) for k, v in self._norms_eq.items() if v]
50
+ )
51
+ )
52
+ )
53
+ print(
54
+ "Norms check FAILED for steps:\n {:s}".format(
55
+ "\n ".join(
56
+ [
57
+ str(k)
58
+ for k, v in self._norms_eq.items()
59
+ if not v
60
+ ]
61
+ )
62
+ )
63
+ )
64
+ else:
65
+ print("No norms found in the new listing or no matching norms.")
66
+ # print() # activation breaks test_arpifs_listings_integration.py
67
+ if self._jos_eq:
68
+ diffprinted = False
69
+ for k, v in self._jos_eq.items():
70
+ if v:
71
+ print("JoTable check succeeded for: {:s}".format(k))
72
+ else:
73
+ print("JoTable check FAILED for: {:s}".format(k))
74
+ if not diffprinted:
75
+ todo = self._jos_diff[k]
76
+ for otype_k, otype_v in todo.items():
77
+ for sensor_k, sensor_v in otype_v.items():
78
+ for var_k, var_v in sensor_v.items():
79
+ if var_k == "GLOBAL":
80
+ continue
81
+ print(
82
+ " > {:s} > {:s} > {:4s} : d_n={:<9d} d_jo={:f}".format(
83
+ otype_k,
84
+ sensor_k,
85
+ var_k,
86
+ var_v["n"]["diff"],
87
+ var_v["jo"]["diff"],
88
+ )
89
+ )
90
+ diffprinted = True
91
+ else:
92
+ print(
93
+ "No Jo-Tables were found or the number of Jo-Tables do not match."
94
+ )
95
+
96
+
97
+ class ArpIfsListingDiff_Status:
98
+ """Holds the status of a listing comparison."""
99
+
100
+ def __init__(self, norms_eq, jos_eq, jos_diff):
101
+ self._norms_ok = all(norms_eq.values())
102
+ self._jos_ok = all(jos_eq.values())
103
+ self._result = ArpIfsListingDiff_Result(norms_eq, jos_eq, jos_diff)
104
+
105
+ def __str__(self):
106
+ return "{:s} | rc={:b}>".format(repr(self).rstrip(">"), bool(self))
107
+
108
+ @property
109
+ def result(self):
110
+ """Return the detailed results of the comparison."""
111
+ return self._result
112
+
113
+ def __bool__(self):
114
+ return bool(self._norms_ok and self._jos_ok)
115
+
116
+
117
+ class ArpIfsListingsTool(addons.Addon):
118
+ """Interface to arpifs_listings (designed as a shell Addon)."""
119
+
120
+ _footprint = dict(
121
+ info="Default arpifs_listings interface",
122
+ attr=dict(
123
+ kind=dict(
124
+ values=["arpifs_listings"],
125
+ ),
126
+ ),
127
+ )
128
+
129
+ def arpifslist_diff(self, listing1, listing2):
130
+ """Difference between two Arpege/IFS listing files.
131
+
132
+ Only Spectral/Gridpoint norms and JO-tables are compared.
133
+
134
+ :param listing1: first file to compare
135
+ :param listing2: second file to compare
136
+ :rtype: :class:`ArpIfsListingDiff_Status`
137
+ """
138
+
139
+ with open(listing1) as fh1:
140
+ l1_slurp = [l.rstrip("\n") for l in fh1]
141
+ with open(listing2) as fh2:
142
+ l2_slurp = [l.rstrip("\n") for l in fh2]
143
+ l1_normset = norms.NormsSet(l1_slurp)
144
+ l2_normset = norms.NormsSet(l2_slurp)
145
+ l1_jos = jo_tables.JoTables(listing1, l1_slurp)
146
+ l2_jos = jo_tables.JoTables(listing2, l2_slurp)
147
+
148
+ # The reference listing may contain more norms compared to the second one
149
+ norms_eq = OrderedDict()
150
+ if len(l2_normset):
151
+ if not l2_normset.steps_equal(l1_normset):
152
+ l1_tdict = OrderedDict()
153
+ for n in l1_normset:
154
+ l1_tdict[n.format_step()] = n
155
+ l2_tdict = OrderedDict()
156
+ for n in l2_normset:
157
+ l2_tdict[n.format_step()] = n
158
+ ikeys = set(l1_tdict.keys()) & set(l2_tdict.keys())
159
+ for k in ikeys:
160
+ norms_eq[k] = l1_tdict[k] == l2_tdict[k]
161
+ else:
162
+ for i, n in enumerate(l2_normset):
163
+ k = n.format_step()
164
+ norms_eq[k] = n == l1_normset[i]
165
+
166
+ jos_eq = OrderedDict()
167
+ jos_diff = OrderedDict()
168
+ if len(l2_jos):
169
+ if not l1_jos == l2_jos:
170
+ # If the JoTables list is not consistent: do nothing
171
+ if list(l1_jos.keys()) == list(l2_jos.keys()):
172
+ for table1, table2 in zip(
173
+ l1_jos.values(), l2_jos.values()
174
+ ):
175
+ jos_eq[table1.name] = table1 == table2
176
+ if not jos_eq[table1.name]:
177
+ jos_diff[table1.name] = OrderedDict()
178
+ # We only save differences when deltaN or deltaJo != 0
179
+ for otype_k, otype_v in table2.compute_diff(
180
+ table1
181
+ ).items():
182
+ otype_tmp = OrderedDict()
183
+ for sensor_k, sensor_v in otype_v.items():
184
+ sensor_tmp = OrderedDict()
185
+ for k, v in sensor_v.items():
186
+ if (
187
+ v["n"]["diff"] != 0
188
+ or v["jo"]["diff"] != 0
189
+ ):
190
+ sensor_tmp[k] = v
191
+ if len(sensor_tmp):
192
+ otype_tmp[sensor_k] = sensor_tmp
193
+ if len(otype_tmp):
194
+ jos_diff[table1.name][otype_k] = otype_tmp
195
+ else:
196
+ for k in l1_jos.keys():
197
+ jos_eq[k] = True
198
+
199
+ return ArpIfsListingDiff_Status(norms_eq, jos_eq, jos_diff)
200
+
201
+
202
+ class ArpifsListingsFormatAdapter(FormatAdapterAbstractImplementation):
203
+ _footprint = dict(
204
+ attr=dict(
205
+ format=dict(
206
+ values=[
207
+ "ARPIFSLIST",
208
+ ],
209
+ ),
210
+ )
211
+ )
212
+
213
+ def __init__(self, *kargs, **kwargs):
214
+ super().__init__(*kargs, **kwargs)
215
+ self._lines = None
216
+ self._normset = None
217
+ self._jotables = None
218
+ self._costs = None
219
+ self._end_is_reached = None
220
+ if not self.fmtdelayedopen:
221
+ self.normset
222
+ self.jotables
223
+ self.costs
224
+ self.flush_lines()
225
+
226
+ @property
227
+ def lines(self):
228
+ """Return an array populated with the listing file lines."""
229
+ if self._lines is None:
230
+ with open(
231
+ self.filename,
232
+ self.openmode,
233
+ encoding="utf-8",
234
+ errors="replace",
235
+ ) as f:
236
+ self._lines = [
237
+ l.rstrip("\n") for l in f
238
+ ] # to remove trailing '\n'
239
+ return self._lines
240
+
241
+ def flush_lines(self):
242
+ """By defaults, listing lines are cached (that consumes memory). This method clear the cache."""
243
+ self._lines = None
244
+
245
+ @property
246
+ def end_is_reached(self):
247
+ """Return whether the end of CNT0 was reached."""
248
+ if self._end_is_reached is None:
249
+ self._end_is_reached = False
250
+ for line in self.lines:
251
+ if any(
252
+ [
253
+ p in line
254
+ for p in listings.OutputListing.patterns[
255
+ "end_is_reached"
256
+ ]
257
+ ]
258
+ ):
259
+ self._end_is_reached = True
260
+ break
261
+ return self._end_is_reached
262
+
263
+ @property
264
+ def normset(self):
265
+ """Return a :class:`arpifs_listings.norms.NormsSet` object."""
266
+ if self._normset is None:
267
+ self._normset = norms.NormsSet(self.lines)
268
+ if not self.fmtdelayedopen:
269
+ self.flush_lines()
270
+ return self._normset
271
+
272
+ @property
273
+ def jotables(self):
274
+ """Return a :class:`arpifs_listings.jo_tables.JoTables` object."""
275
+ if self._jotables is None:
276
+ self._jotables = jo_tables.JoTables(self.filename, self.lines)
277
+ if not self.fmtdelayedopen:
278
+ self.flush_lines()
279
+ return self._jotables
280
+
281
+ @property
282
+ def cost_functions(self):
283
+ """Return a :class:`arpifs_listings.jo_tables.JoTables` object."""
284
+ if self._costs is None:
285
+ self._costs = cost_functions.CostFunctions(
286
+ self.filename, self.lines
287
+ )
288
+ if not self.fmtdelayedopen:
289
+ self.flush_lines()
290
+ return self._costs
291
+
292
+ def __len__(self):
293
+ """The number of lines in the listing."""
294
+ return len(self.lines)
295
+
296
+
297
+ class ListBasedCutoffDispenser:
298
+ """
299
+ From a dictionary of cutoff times (probably read from an extraction listing,
300
+ see :class:`BdmBufrListingsFormatAdapter`), for a given *obstype*, find the
301
+ best suited cutoff time.
302
+
303
+ The __call__ method takes a unique *obstype* argument. It will return the
304
+ best suited cutoff time for this particular *obstype*. N.B: If no exact
305
+ match is found, the latest cutoff time will be used.
306
+ """
307
+
308
+ def __init__(self, cutoffs, fuse_per_obstype=False):
309
+ self._cutoffs = cutoffs
310
+ f_cutoffs = {}
311
+ for k, dates in cutoffs.items():
312
+ f_dates = [d for d in dates if d is not None]
313
+ if f_dates:
314
+ f_cutoffs[k] = f_dates
315
+ if f_cutoffs:
316
+ self._max_cutoff = max(
317
+ [max(dates) for dates in f_cutoffs.values()]
318
+ )
319
+ else:
320
+ self._max_cutoff = None
321
+ self._default_cutoffs = defaultdict(lambda: self._max_cutoff)
322
+ self._default_cutoffs.update(
323
+ {k: max(dates) for k, dates in f_cutoffs.items()}
324
+ )
325
+ self._fuse_per_obstype = fuse_per_obstype
326
+
327
+ @property
328
+ def max_cutoff(self):
329
+ """The latest cutoff time(accoss any available obstypes)."""
330
+ return self._max_cutoff
331
+
332
+ @property
333
+ def default_cutoffs(self):
334
+ """A dictionary of the latest cutoff time for each of the obstypes."""
335
+ return self._default_cutoffs
336
+
337
+ def __call__(self, obstype):
338
+ """Find the best suited cutoff time for *obstype*."""
339
+ obstype = obstype.lower()
340
+ if self._cutoffs.get(obstype, None) and not self._fuse_per_obstype:
341
+ item = self._cutoffs[obstype].popleft()
342
+ return item or self.default_cutoffs[obstype]
343
+ else:
344
+ return self.default_cutoffs[obstype]
345
+
346
+
347
+ class BdmBufrListingsFormatAdapter(FormatAdapterAbstractImplementation):
348
+ """Read the content of a BDM extraction output listing."""
349
+
350
+ _footprint = dict(
351
+ attr=dict(
352
+ format=dict(
353
+ values=[
354
+ "BDMBUFR_LISTING",
355
+ ],
356
+ ),
357
+ )
358
+ )
359
+
360
+ _RE_OBSTYPE_GRP = re.compile(
361
+ r"^.*tentative\s+(?:d')?extraction\s+pour\s+'?(?P<obstype>\w+)'?\b",
362
+ re.IGNORECASE,
363
+ )
364
+ _RE_OBSTYPE_CUT = re.compile(
365
+ r"^.*cutoff\s+pour\s+'?(?P<obstype>\w+)'?\s*:\s*(?P<datetime>\d+)\b",
366
+ re.IGNORECASE,
367
+ )
368
+
369
+ def __init__(self, *kargs, **kwargs):
370
+ super().__init__(*kargs, **kwargs)
371
+ self._lines = None
372
+ self._cutoffs = defaultdict(deque)
373
+ if not self.fmtdelayedopen:
374
+ self.lines
375
+
376
+ @property
377
+ def lines(self):
378
+ """Return an array populated with the listing file lines."""
379
+ if self._lines is None:
380
+ with open(
381
+ self.filename,
382
+ self.openmode,
383
+ encoding="utf-8",
384
+ errors="replace",
385
+ ) as f:
386
+ self._lines = [
387
+ l.rstrip("\n") for l in f
388
+ ] # to remove trailing '\n'
389
+ return self._lines
390
+
391
+ @property
392
+ def cutoffs(self):
393
+ """
394
+ A dictionary of cutoff times for all of the obstypes available in the
395
+ listing.
396
+ """
397
+ if not self._cutoffs:
398
+ cur_obstype = None
399
+ for line in self.lines:
400
+ l_match = self._RE_OBSTYPE_GRP.match(line)
401
+ if l_match:
402
+ if cur_obstype is not None:
403
+ self._cutoffs[cur_obstype].append(None)
404
+ cur_obstype = l_match.group("obstype").lower()
405
+ if cur_obstype:
406
+ l_match = self._RE_OBSTYPE_CUT.match(line)
407
+ if (
408
+ l_match
409
+ and l_match.group("obstype").lower() == cur_obstype
410
+ ):
411
+ self._cutoffs[cur_obstype].append(
412
+ Date(l_match.group("datetime"))
413
+ )
414
+ cur_obstype = None
415
+ if cur_obstype is not None:
416
+ self._cutoffs[cur_obstype].append(None)
417
+ return self._cutoffs
418
+
419
+ def cutoffs_dispenser(self, fuse_per_obstype=False):
420
+ """Return a new :class:`CutoffDispenser` object."""
421
+ return ListBasedCutoffDispenser(
422
+ copy.deepcopy(self.cutoffs), fuse_per_obstype=fuse_per_obstype
423
+ )