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,447 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Usage of the EPyGrAM package to compute diffs.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import collections
|
|
6
|
+
import copy
|
|
7
|
+
import functools
|
|
8
|
+
import hashlib
|
|
9
|
+
import io
|
|
10
|
+
import json
|
|
11
|
+
import operator
|
|
12
|
+
import pprint
|
|
13
|
+
|
|
14
|
+
import footprints
|
|
15
|
+
from vortex import sessions
|
|
16
|
+
from . import usepygram
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class HGeoDesc:
|
|
20
|
+
"""Holds Epygram's horizontal geometry data."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, epyfield):
|
|
23
|
+
"""
|
|
24
|
+
:param epyfied: An epygram fild object.
|
|
25
|
+
"""
|
|
26
|
+
geo = epyfield.geometry
|
|
27
|
+
self.grid = geo.grid
|
|
28
|
+
self.dimensions = geo.dimensions
|
|
29
|
+
self.name = geo.name
|
|
30
|
+
self.projection = (
|
|
31
|
+
None if not geo.projected_geometry else geo.projection
|
|
32
|
+
)
|
|
33
|
+
sio = io.StringIO()
|
|
34
|
+
geo.what(out=sio, vertical_geometry=False)
|
|
35
|
+
sio.seek(0)
|
|
36
|
+
self._what = sio.readlines()[3:]
|
|
37
|
+
|
|
38
|
+
def __eq__(self, other):
|
|
39
|
+
return (
|
|
40
|
+
(self.grid == other.grid)
|
|
41
|
+
and (self.dimensions == other.dimensions)
|
|
42
|
+
and (self.name == other.name)
|
|
43
|
+
and (self.projection == other.projection)
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def __str__(self):
|
|
47
|
+
return "".join(self._what)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class DataDesc:
|
|
51
|
+
"""Holds information about an Epygram's field data (basic stats + checksum)."""
|
|
52
|
+
|
|
53
|
+
def __init__(self, epyfield):
|
|
54
|
+
"""
|
|
55
|
+
:param epyfied: An epygram fild object.
|
|
56
|
+
"""
|
|
57
|
+
self.stats = epyfield.stats()
|
|
58
|
+
self.stats.pop("quadmean", None) # We do not want quadmean
|
|
59
|
+
s256 = hashlib.sha256()
|
|
60
|
+
s256.update(epyfield.data.tobytes())
|
|
61
|
+
self.checksum = s256.digest()
|
|
62
|
+
|
|
63
|
+
def __eq__(self, other):
|
|
64
|
+
return self.checksum == other.checksum
|
|
65
|
+
|
|
66
|
+
def __str__(self):
|
|
67
|
+
return ", ".join(
|
|
68
|
+
["{:s}={!s}".format(k, v) for k, v in self.stats.items()]
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class HGeoLibrary:
|
|
73
|
+
"""A collection/library of :class:`HGeoDesc` objects."""
|
|
74
|
+
|
|
75
|
+
def __init__(self):
|
|
76
|
+
self._geolist = list()
|
|
77
|
+
|
|
78
|
+
def register(self, hgeo_desc):
|
|
79
|
+
"""Check if an :class:`HGeoDesc` object is already in the library.
|
|
80
|
+
|
|
81
|
+
If the *hgeo_desc* object is not already in the library it is inserted.
|
|
82
|
+
In any case, the index of the *hgeo_desc* geometry within the library is
|
|
83
|
+
returned.
|
|
84
|
+
"""
|
|
85
|
+
found = (None, None)
|
|
86
|
+
for i, g in enumerate(self._geolist):
|
|
87
|
+
if hgeo_desc == g:
|
|
88
|
+
found = (i, g)
|
|
89
|
+
break
|
|
90
|
+
if found == (None, None):
|
|
91
|
+
found = (len(self._geolist), hgeo_desc)
|
|
92
|
+
self._geolist.append(hgeo_desc)
|
|
93
|
+
return found[0]
|
|
94
|
+
|
|
95
|
+
def __str__(self):
|
|
96
|
+
outstr = ""
|
|
97
|
+
for i, g in enumerate(self._geolist):
|
|
98
|
+
outstr += "HORIZONTAL GEOMETRY #{:d}\n\n".format(i)
|
|
99
|
+
outstr += str(g)
|
|
100
|
+
outstr += "\n"
|
|
101
|
+
return outstr
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class FieldDesc:
|
|
105
|
+
"""Holds various information about an Epygram field."""
|
|
106
|
+
|
|
107
|
+
def __init__(self, hgeoid, vgeo, datadesc, fid, valid):
|
|
108
|
+
self.hgeoid = hgeoid
|
|
109
|
+
self.vgeo = vgeo
|
|
110
|
+
self.datadesc = datadesc
|
|
111
|
+
self.fid = fid
|
|
112
|
+
self.valid = valid
|
|
113
|
+
|
|
114
|
+
def ranking(self, other):
|
|
115
|
+
"""
|
|
116
|
+
Compute the comparison score of the present field with respect to a
|
|
117
|
+
reference one (*other*).
|
|
118
|
+
"""
|
|
119
|
+
fidscore = functools.reduce(
|
|
120
|
+
operator.add,
|
|
121
|
+
[
|
|
122
|
+
int(self.fid[k] == other.fid[k])
|
|
123
|
+
for k in self.fid.keys()
|
|
124
|
+
if k in other.fid
|
|
125
|
+
],
|
|
126
|
+
)
|
|
127
|
+
fidscore = (
|
|
128
|
+
5.0 * float(fidscore) / float(max(len(self.fid), len(other.fid)))
|
|
129
|
+
)
|
|
130
|
+
return (
|
|
131
|
+
int(self.valid != other.valid) * -5.0
|
|
132
|
+
+ int(self.hgeoid != other.hgeoid) * -5.0
|
|
133
|
+
+ int(self.vgeo != other.vgeo) * -4.0
|
|
134
|
+
+ int(self.datadesc == other.datadesc) * 5.0
|
|
135
|
+
+ fidscore
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def ranking_summary(self, other):
|
|
139
|
+
"""Returns detailed comparison information (including the ranking)."""
|
|
140
|
+
return (
|
|
141
|
+
self.datadesc == other.datadesc,
|
|
142
|
+
self.valid == other.valid,
|
|
143
|
+
self.hgeoid == other.hgeoid and self.vgeo == other.vgeo,
|
|
144
|
+
self.ranking(other),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
def __str__(self):
|
|
148
|
+
out = "HGeo=#{:d} ; Validity={!s} ; metadata are:\n".format(
|
|
149
|
+
self.hgeoid, self.valid
|
|
150
|
+
)
|
|
151
|
+
out += pprint.pformat(self.fid) + "\n"
|
|
152
|
+
out += "Data: {!s}".format(self.datadesc)
|
|
153
|
+
return out
|
|
154
|
+
|
|
155
|
+
def prefixed_str(self, prefix):
|
|
156
|
+
"""A representation of this object prefixed with the *prefix* string."""
|
|
157
|
+
return "\n".join([prefix + l for l in str(self).split("\n")])
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class FieldBundle:
|
|
161
|
+
"""A collection of FieldDesc objects."""
|
|
162
|
+
|
|
163
|
+
def __init__(self, hgeolib):
|
|
164
|
+
self._hgeolib = hgeolib
|
|
165
|
+
self._fields = list()
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def fields(self):
|
|
169
|
+
"""The list of fields in the present collection."""
|
|
170
|
+
return self._fields
|
|
171
|
+
|
|
172
|
+
def _common_processing(self, fld, fid):
|
|
173
|
+
hgeo = HGeoDesc(fld)
|
|
174
|
+
vgeo = fld.geometry.vcoordinate
|
|
175
|
+
valid = fld.validity.get()
|
|
176
|
+
ddesc = DataDesc(fld)
|
|
177
|
+
hgeo_id = self._hgeolib.register(hgeo)
|
|
178
|
+
fid = copy.copy(fid)
|
|
179
|
+
fid["datebasis"] = fld.validity.getbasis()
|
|
180
|
+
fid["term"] = fld.validity.term()
|
|
181
|
+
fid["cumulativeduration"] = fld.validity.cumulativeduration()
|
|
182
|
+
return FieldDesc(hgeo_id, vgeo, ddesc, fid, valid)
|
|
183
|
+
|
|
184
|
+
@usepygram.epygram_checker.disabled_if_unavailable(version="1.0.0")
|
|
185
|
+
def read_grib(self, filename):
|
|
186
|
+
"""Read in a GRIB file."""
|
|
187
|
+
with usepygram.epy_env_prepare(sessions.current()):
|
|
188
|
+
gribdata = footprints.proxy.dataformat(
|
|
189
|
+
filename=filename, openmode="r", format="GRIB"
|
|
190
|
+
)
|
|
191
|
+
fld = gribdata.iter_fields(
|
|
192
|
+
get_info_as_json=("centre", "subCentre")
|
|
193
|
+
)
|
|
194
|
+
while fld:
|
|
195
|
+
fid = fld.fid.get("GRIB2", fld.fid.get("GRIB1"))
|
|
196
|
+
fid.update(json.loads(fld.comment))
|
|
197
|
+
self._fields.append(self._common_processing(fld, fid))
|
|
198
|
+
fld = gribdata.iter_fields(
|
|
199
|
+
get_info_as_json=("centre", "subCentre")
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class FieldBundles:
|
|
204
|
+
"""A collection of :class:`FieldBundle` objects."""
|
|
205
|
+
|
|
206
|
+
def __init__(self):
|
|
207
|
+
self._hgeolib = HGeoLibrary()
|
|
208
|
+
self._bundles = dict()
|
|
209
|
+
|
|
210
|
+
@property
|
|
211
|
+
def hgeo_library(self):
|
|
212
|
+
"""The :class:`HGeoLibrary` onject being used in this collection."""
|
|
213
|
+
return self._hgeolib
|
|
214
|
+
|
|
215
|
+
def new_bundle(self, name):
|
|
216
|
+
"""Create a new :class:`FieldBundle` object in this collection."""
|
|
217
|
+
fbd = FieldBundle(self._hgeolib)
|
|
218
|
+
self._bundles[name] = fbd
|
|
219
|
+
return fbd
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def bundles(self):
|
|
223
|
+
"""The dictionary of bundles in the present collection."""
|
|
224
|
+
return self._bundles
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class EpyGribDiff(FieldBundles):
|
|
228
|
+
"""A specialised version of :class:`FieldBundles` that deals with GRIB files."""
|
|
229
|
+
|
|
230
|
+
_FMT_COUNTER = "[{:04d}] "
|
|
231
|
+
_HEAD_COUNTER = " " * len(_FMT_COUNTER.format(0))
|
|
232
|
+
|
|
233
|
+
_FMT_SHORT = (
|
|
234
|
+
"#{n:>4d} id={id:16s} l={level:<6d} c={centre:<3d},{scentre:3d}"
|
|
235
|
+
)
|
|
236
|
+
_HEAD_SHORT = "Mess. ParamId/ShortN Level Centre,S "
|
|
237
|
+
|
|
238
|
+
_FMT_MIDDLE = " | {0:1s} {1:1s} {2:1s} {3:6s} | "
|
|
239
|
+
_HEAD_MIDDLE = " | {:1s} {:1s} {:1s} {:5s} | "
|
|
240
|
+
_ELTS_MIDDLE = ("data", "valid", "geo", "score")
|
|
241
|
+
|
|
242
|
+
_SPACER = (
|
|
243
|
+
_HEAD_COUNTER
|
|
244
|
+
+ "REF "
|
|
245
|
+
+ "-" * (len(_HEAD_SHORT) - 4)
|
|
246
|
+
+ " | ----- ----- | "
|
|
247
|
+
+ "NEW "
|
|
248
|
+
+ "-" * (len(_HEAD_SHORT) - 4)
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
_DETAILED_SUMARY = "Data: {0:1s} ; Validity Date: {1:1s} ; HGeometry: {2:s} ; Score: {3:6s}"
|
|
252
|
+
|
|
253
|
+
def __init__(self, ref, new):
|
|
254
|
+
"""
|
|
255
|
+
:param str ref: Path to the reference GRIB file
|
|
256
|
+
:param str new: Path to the new GRIB file
|
|
257
|
+
"""
|
|
258
|
+
super().__init__()
|
|
259
|
+
self._new = self.new_bundle("New")
|
|
260
|
+
self._new.read_grib(new)
|
|
261
|
+
self._ref = self.new_bundle("Ref")
|
|
262
|
+
self._ref.read_grib(ref)
|
|
263
|
+
|
|
264
|
+
def _compute_diff(self):
|
|
265
|
+
"""Explore all possible field combinations and find the closest match.
|
|
266
|
+
|
|
267
|
+
:return: tuple (newfield_id, list of matching reffield_ids, rankingscore,
|
|
268
|
+
list of ranking_summaries)
|
|
269
|
+
"""
|
|
270
|
+
found = set()
|
|
271
|
+
couples = list()
|
|
272
|
+
for i, field in enumerate(self._new.fields):
|
|
273
|
+
rscore = collections.defaultdict(list)
|
|
274
|
+
rsummary = collections.defaultdict(list)
|
|
275
|
+
for j, rfield in enumerate(self._ref.fields):
|
|
276
|
+
tsummary = field.ranking_summary(rfield)
|
|
277
|
+
rscore[tsummary[-1]].append(j)
|
|
278
|
+
rsummary[tsummary[-1]].append(tsummary)
|
|
279
|
+
highest = max(rscore.keys())
|
|
280
|
+
# If the score is >= 3 the fields are paired...
|
|
281
|
+
# Note: Their might be several field combinations with the same
|
|
282
|
+
# ranking score
|
|
283
|
+
if highest >= 3.0:
|
|
284
|
+
refs = rscore[highest]
|
|
285
|
+
couples.append((i, refs, highest, rsummary[highest]))
|
|
286
|
+
found.update(refs)
|
|
287
|
+
else:
|
|
288
|
+
couples.append((i, (), None, None))
|
|
289
|
+
missings = set(range(len(self._ref.fields))) - found
|
|
290
|
+
if missings:
|
|
291
|
+
couples.append((None, list(missings), None, None))
|
|
292
|
+
return couples
|
|
293
|
+
|
|
294
|
+
@classmethod
|
|
295
|
+
def _str_header(cls):
|
|
296
|
+
"""Returns the comparison table header."""
|
|
297
|
+
out = cls._SPACER + "\n"
|
|
298
|
+
e_len = max([len(e) for e in cls._ELTS_MIDDLE])
|
|
299
|
+
e_new = [
|
|
300
|
+
("{:>" + str(e_len) + "s}").format(e.upper())
|
|
301
|
+
for e in cls._ELTS_MIDDLE
|
|
302
|
+
]
|
|
303
|
+
if e_len > 1:
|
|
304
|
+
for i in range(e_len - 1):
|
|
305
|
+
out += (
|
|
306
|
+
cls._HEAD_COUNTER
|
|
307
|
+
+ " " * len(cls._HEAD_SHORT)
|
|
308
|
+
+ cls._HEAD_MIDDLE.format(*[e[i] for e in e_new])
|
|
309
|
+
+ " " * len(cls._HEAD_SHORT)
|
|
310
|
+
+ "\n"
|
|
311
|
+
)
|
|
312
|
+
out += (
|
|
313
|
+
(
|
|
314
|
+
cls._HEAD_COUNTER
|
|
315
|
+
+ cls._HEAD_SHORT
|
|
316
|
+
+ cls._HEAD_MIDDLE.format(*[e[-1] for e in e_new])
|
|
317
|
+
+ cls._HEAD_SHORT
|
|
318
|
+
+ "\n"
|
|
319
|
+
)
|
|
320
|
+
+ cls._SPACER
|
|
321
|
+
+ "\n"
|
|
322
|
+
)
|
|
323
|
+
return out
|
|
324
|
+
|
|
325
|
+
@classmethod
|
|
326
|
+
def _str_field_summary(cls, n, field):
|
|
327
|
+
"""Returns a string that summarise a field properties."""
|
|
328
|
+
if "paramId" in field.fid:
|
|
329
|
+
# GRIB1
|
|
330
|
+
sid = str(field.fid["paramId"]) + "/" + field.fid["shortName"]
|
|
331
|
+
else:
|
|
332
|
+
# GRIB2
|
|
333
|
+
sid = (
|
|
334
|
+
str(field.fid["parameterCategory"])
|
|
335
|
+
+ "-"
|
|
336
|
+
+ str(field.fid["parameterNumber"])
|
|
337
|
+
+ "/"
|
|
338
|
+
+ field.fid["shortName"]
|
|
339
|
+
)
|
|
340
|
+
if len(sid) > 16: # Truncate if the string is too long
|
|
341
|
+
sid = sid[:15] + "*"
|
|
342
|
+
return cls._FMT_SHORT.format(
|
|
343
|
+
n=n,
|
|
344
|
+
id=sid,
|
|
345
|
+
level=field.fid.get("level", -99),
|
|
346
|
+
centre=field.fid["centre"],
|
|
347
|
+
scentre=field.fid.get("subCentre", -99),
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
@classmethod
|
|
351
|
+
def _str_rsummary_format(cls, rsum, fmt):
|
|
352
|
+
"""Format the ranking_summary output."""
|
|
353
|
+
dmap = {True: "=", False: "!"}
|
|
354
|
+
return fmt.format(
|
|
355
|
+
dmap[rsum[0]],
|
|
356
|
+
dmap[rsum[1]],
|
|
357
|
+
dmap[rsum[2]],
|
|
358
|
+
"======" if rsum[3] == 10 else "{:6.2f}".format(rsum[3]),
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
@staticmethod
|
|
362
|
+
def _embedded_counter(c):
|
|
363
|
+
"""Return the formatted comparison counter."""
|
|
364
|
+
return "[{:04d}] ".format(c)
|
|
365
|
+
|
|
366
|
+
def format_diff(self, detailed=True):
|
|
367
|
+
"""Return a string that contains the comparison results.
|
|
368
|
+
|
|
369
|
+
:param bool detailed: If False, just returns the comparison table.
|
|
370
|
+
"""
|
|
371
|
+
out = ""
|
|
372
|
+
counter = 0
|
|
373
|
+
for couple in self._compute_diff():
|
|
374
|
+
if couple[0] is None:
|
|
375
|
+
for n in couple[1]:
|
|
376
|
+
counter += 1
|
|
377
|
+
out += self._embedded_counter(counter)
|
|
378
|
+
if detailed:
|
|
379
|
+
out += "Unmatched reference field\n"
|
|
380
|
+
out += (
|
|
381
|
+
self.bundles["Ref"]
|
|
382
|
+
.fields[n]
|
|
383
|
+
.prefixed_str(" REF| ")
|
|
384
|
+
+ "\n"
|
|
385
|
+
)
|
|
386
|
+
else:
|
|
387
|
+
out += self._str_field_summary(
|
|
388
|
+
n, self.bundles["Ref"].fields[n]
|
|
389
|
+
)
|
|
390
|
+
out += (
|
|
391
|
+
self._FMT_MIDDLE.format("?", "?", "?", " ?")
|
|
392
|
+
+ "\n"
|
|
393
|
+
)
|
|
394
|
+
else:
|
|
395
|
+
new = self.bundles["New"].fields[couple[0]]
|
|
396
|
+
if len(couple[1]):
|
|
397
|
+
for i, n in enumerate(couple[1]):
|
|
398
|
+
counter += 1
|
|
399
|
+
ref = self.bundles["Ref"].fields[n]
|
|
400
|
+
out += self._embedded_counter(counter)
|
|
401
|
+
if detailed:
|
|
402
|
+
out += (
|
|
403
|
+
self._str_rsummary_format(
|
|
404
|
+
couple[3][i], self._DETAILED_SUMARY
|
|
405
|
+
)
|
|
406
|
+
+ "\n"
|
|
407
|
+
)
|
|
408
|
+
out += (
|
|
409
|
+
ref.prefixed_str(" REF| ")
|
|
410
|
+
+ "\n vs\n"
|
|
411
|
+
+ new.prefixed_str(" NEW| ")
|
|
412
|
+
+ "\n"
|
|
413
|
+
)
|
|
414
|
+
else:
|
|
415
|
+
out += self._str_field_summary(n, ref)
|
|
416
|
+
out += self._str_rsummary_format(
|
|
417
|
+
couple[3][i], self._FMT_MIDDLE
|
|
418
|
+
)
|
|
419
|
+
out += (
|
|
420
|
+
self._str_field_summary(couple[0], new)
|
|
421
|
+
if i == 0
|
|
422
|
+
else " idem."
|
|
423
|
+
)
|
|
424
|
+
out += "\n"
|
|
425
|
+
else:
|
|
426
|
+
counter += 1
|
|
427
|
+
out += self._embedded_counter(counter)
|
|
428
|
+
if detailed:
|
|
429
|
+
out += "Unmatched new field \n"
|
|
430
|
+
out += (
|
|
431
|
+
self.bundles["New"]
|
|
432
|
+
.fields[couple[0]]
|
|
433
|
+
.prefixed_str(" NEW| ")
|
|
434
|
+
+ "\n"
|
|
435
|
+
)
|
|
436
|
+
else:
|
|
437
|
+
out += " " * len(self._HEAD_SHORT)
|
|
438
|
+
out += self._FMT_MIDDLE.format("?", "?", "?", " ?")
|
|
439
|
+
out += self._str_field_summary(couple[0], new) + "\n"
|
|
440
|
+
out += "\n" if detailed else ""
|
|
441
|
+
if detailed:
|
|
442
|
+
out += "LIST OF HORIZONTAL GEOMETRIES:\n\n"
|
|
443
|
+
out += str(self.hgeo_library)
|
|
444
|
+
return out
|
|
445
|
+
|
|
446
|
+
def __str__(self):
|
|
447
|
+
return self._str_header() + self.format_diff(detailed=False)
|