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,799 @@
1
+ """
2
+ This module contains the definition of all the Geometry objects widely in
3
+ Vortex's resources description. Geometry objects rely on the
4
+ :class:`bronx.patterns.getbytag.GetByTag` class.
5
+
6
+ When this module is first imported, pre-defined geometries are automatically
7
+ created using:
8
+
9
+ * The ``geometries.ini`` file from the Vortex's distribution ``conf`` directory
10
+ * The ``geometries.ini`` file located in the user's configuration directory
11
+ (usually ``$HOME/.vortexrc``). (This file may be missing)
12
+
13
+ Additional Geometry objects can be manually created by the user provided that
14
+ the ``new=True`` argument is given to the desired class constructor (otherwise
15
+ an exception will be raised).
16
+
17
+ To retrieve and browse an already defined geometry, please use the module's
18
+ interface methods, :func:`get`, :func:`keys`, :func:`values` and :func:`items`::
19
+
20
+ >>> from vortex.data import geometries
21
+ >>> print(geometries.get(tag="global798"))
22
+ <vortex.data.geometries.GaussGeometry | tag='global798' id='ARPEGE TL798 stretched-rotated geometry' tl=798 c=2.4>
23
+
24
+ It is also possible to retrieve an existing geometry using the :class:`Geometry`
25
+ class constructor::
26
+
27
+ >>> print(geometries.Geometry("global798"))
28
+ <vortex.data.geometries.GaussGeometry | tag='global798' id='ARPEGE TL798 stretched-rotated geometry' tl=798 c=2.4>
29
+
30
+ To build a new geometry, you need to pick the concrete geometry class that fits
31
+ your needs. Currently available concrete geometries are:
32
+
33
+ * :class:`GaussGeometry` (Global gaussian grid)
34
+ * :class:`ProjectedGeometry` (Any grid defined by a geographical projection, e.g. lambert, ...)
35
+ * :class:`LonlatGeometry` (That's pretty obvious)
36
+ * :class:`CurvlinearGeometry` (Curvlinear grid)
37
+
38
+ For example, let's build a new gaussian grid::
39
+
40
+ >>> geometries.GaussGeometry(tag='global2198', # The geometry's nickname #doctest: +ELLIPSIS
41
+ ... info='My own gaussian geometry',
42
+ ... truncation=2198, # The linear truncation
43
+ ... stretching=2.1, area='France', # 2.1 stretching over France
44
+ ... new=True) # Mandatory to create new geometries
45
+ <vortex.data.geometries.GaussGeometry (tag='global2198') object at 0x...>
46
+ >>> print(geometries.Geometry("global2198"))
47
+ <vortex.data.geometries.GaussGeometry | tag='global2198' id='My own gaussian geometry' tl=2198 c=2.1>
48
+
49
+ (From that moment on, the new geometry is available globally in Vortex)
50
+
51
+ Each geometry has its own attributes: please refers to each of the concrete
52
+ class documentation for more details.
53
+ """
54
+
55
+ import configparser
56
+ import importlib.resources
57
+ import re
58
+
59
+ from bronx.fancies import loggers
60
+ import bronx.patterns.getbytag
61
+
62
+ import footprints
63
+
64
+ from vortex.syntax.stddeco import namebuilding_insert, generic_pathname_insert
65
+
66
+ #: No automatic export
67
+ __all__ = []
68
+
69
+ logger = loggers.getLogger(__name__)
70
+
71
+
72
+ # Module Interface
73
+
74
+
75
+ def get(**kw):
76
+ """Return actual geometry object matching description.
77
+
78
+ :param str tag: The name of the wanted geometry
79
+ """
80
+ return Geometry(**kw)
81
+
82
+
83
+ def keys():
84
+ """Return the list of current names of geometries collected."""
85
+ return Geometry.tag_keys()
86
+
87
+
88
+ def values():
89
+ """Return the list of current values of geometries collected."""
90
+ return Geometry.tag_values()
91
+
92
+
93
+ def items():
94
+ """Return the items of the geometries table."""
95
+ return Geometry.tag_items()
96
+
97
+
98
+ def grep(**kw):
99
+ """Grep items that match the set of attributes given as named arguments."""
100
+ okmatch = list()
101
+ for item in Geometry.tag_values():
102
+ ok = True
103
+ for k, v in kw.items():
104
+ if not hasattr(item, k) or getattr(item, k) != v:
105
+ ok = False
106
+ break
107
+ if ok:
108
+ okmatch.append(item)
109
+ return okmatch
110
+
111
+
112
+ # Abstract geometry classes
113
+
114
+
115
+ class Geometry(bronx.patterns.getbytag.GetByTag):
116
+ """Abstract geometry."""
117
+
118
+ _tag_implicit_new = False
119
+
120
+ def __init__(self, **kw):
121
+ """
122
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
123
+ the first positional attribute is considered to be the tag name)
124
+ :param str info: A free description of the geometry
125
+
126
+ .. note:: This is an abstract class, do not instantiate.
127
+ """
128
+ self.info = "anonymous"
129
+ self.inifile = None
130
+ self.__dict__.update(kw)
131
+ self.kind = "abstract"
132
+ self._init_attributes = {k: v for k, v in kw.items() if v is not None}
133
+ logger.debug("Abstract Geometry init kw=%s", str(kw))
134
+
135
+ @classmethod
136
+ def _tag_implicit_new_error(cls, tag):
137
+ """Called whenever a tag does not exists and _tag_implicit_new = False."""
138
+ raise RuntimeError(
139
+ 'The "{:s}" {:s} object does not exist yet...'.format(
140
+ tag, cls.__name__
141
+ )
142
+ )
143
+
144
+ @classmethod
145
+ def tag_clean(self, tag):
146
+ """Geometries id tags are lower case."""
147
+ return tag.lower()
148
+
149
+ def __repr__(self):
150
+ """Nicer represenation for geometries."""
151
+ return "<{:s}.{:s} (tag='{:s}') object at {:#x}>".format(
152
+ self.__module__, self.__class__.__name__, self.tag, id(self)
153
+ )
154
+
155
+ def export_dict(self):
156
+ return self.tag
157
+
158
+ def doc_export(self):
159
+ """Relevant informations to print in the documentation."""
160
+ return "kind={:s}".format(self.kind)
161
+
162
+ def to_inifile(self):
163
+ """Format geometry to put in the inifile."""
164
+ self._init_attributes.update(kind=self.kind)
165
+ s = "[{}]\n".format(self.tag)
166
+ for k in sorted(self._init_attributes.keys()):
167
+ s += "{:10s} = {}\n".format(k, self._init_attributes[k])
168
+ return s
169
+
170
+
171
+ class VerticalGeometry(Geometry):
172
+ """Handle vertical geometry description (not used at the present time)."""
173
+
174
+ _tag_topcls = False
175
+
176
+ def __init__(self, **kw):
177
+ """
178
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
179
+ the first positional attribute is considered to be the tag name)
180
+ :param str info: A free description of the geometry
181
+
182
+ .. note:: This is an abstract class, do not instantiate.
183
+ """
184
+ super().__init__(**kw)
185
+ self.kind = "vertical"
186
+ logger.debug(
187
+ "Abstract Vertical Geometry init %s %s", str(self), str(kw)
188
+ )
189
+
190
+
191
+ class HorizontalGeometry(Geometry):
192
+ """Handle abstract horizontal geometry description."""
193
+
194
+ _tag_topcls = False
195
+
196
+ def __init__(self, **kw):
197
+ """
198
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
199
+ the first positional attribute is considered to be the tag name)
200
+ :param str info: A free description of the geometry
201
+ :param bool lam: Is it a limited area geometry (as opposed to global)
202
+
203
+ .. note:: This is an abstract class, do not instantiate.
204
+ """
205
+ desc = dict(
206
+ info="anonymous",
207
+ gridtype=None,
208
+ area=None,
209
+ nlon=None,
210
+ nlonmax=None,
211
+ nlat=None,
212
+ ni=None,
213
+ nj=None,
214
+ resolution=0.0,
215
+ expected_resolution=0.0,
216
+ runit=None,
217
+ truncation=None,
218
+ truncationtype=None,
219
+ stretching=None,
220
+ nmassif=None,
221
+ nposts=None,
222
+ lam=True,
223
+ lonmin=None,
224
+ latmin=None,
225
+ )
226
+ desc.update(kw)
227
+ super().__init__(**desc)
228
+ for k, v in self.__dict__.items():
229
+ if isinstance(v, str) and re.match("none", v, re.IGNORECASE):
230
+ self.__dict__[k] = None
231
+ if isinstance(v, str) and re.match("true", v, re.IGNORECASE):
232
+ self.__dict__[k] = True
233
+ if isinstance(v, str) and re.match("false", v, re.IGNORECASE):
234
+ self.__dict__[k] = False
235
+ for item in (
236
+ "nlon",
237
+ "nlat",
238
+ "ni",
239
+ "nj",
240
+ "nmassif",
241
+ "nposts",
242
+ "truncation",
243
+ ):
244
+ cv = getattr(self, item)
245
+ if cv is not None:
246
+ setattr(self, item, int(cv))
247
+ for item in ("stretching", "resolution", "lonmin", "latmin"):
248
+ cv = getattr(self, item)
249
+ if cv is not None:
250
+ setattr(self, item, float(cv))
251
+ self._check_attributes()
252
+ logger.debug("Abstract Horizontal Geometry init %s", str(self))
253
+
254
+ def _check_attributes(self):
255
+ if self.lam and (self.area is None):
256
+ raise AttributeError("Some mandatory arguments are missing")
257
+
258
+ @property
259
+ def gam(self):
260
+ """Is it a global geometry ?"""
261
+ return not self.lam
262
+
263
+ @property
264
+ def rnice(self):
265
+ """Returns a string with a nice representation of the resolution (if sensible)."""
266
+ if self.runit is not None:
267
+ if self.runit == "km":
268
+ res = "{:05.2f}".format(self.resolution)
269
+ elif self.runit in ("s", "min"):
270
+ res = "{:04.1f}".format(self.resolution)
271
+ else:
272
+ res = "{:06.3f}".format(self.resolution)
273
+ return re.sub(r"\.", self.runit, res, 1)
274
+ else:
275
+ return "Unknown Resolution"
276
+
277
+ @property
278
+ def rnice_u(self):
279
+ return self.rnice.upper()
280
+
281
+ @property
282
+ def gco_grid_def(self):
283
+ return (
284
+ "{0.area}" + ("_{0.rnice}" if self.runit is not None else "")
285
+ ).format(self)
286
+
287
+ def anonymous_info(self, *args): # @UnusedVariable
288
+ """Try to build a meaningful information from an anonymous geometry."""
289
+ return "{!s}, {:s}".format(self.area, self.rnice)
290
+
291
+ def __str__(self):
292
+ """Very short presentation."""
293
+ if self.info == "anonymous":
294
+ return "{:s}({:s})".format(
295
+ self.__class__.__name__, self.anonymous_info()
296
+ )
297
+ else:
298
+ return self.info
299
+
300
+ def idcard(self, indent=2):
301
+ """
302
+ Returns a multilines documentation string with a summary
303
+ of the valuable information contained by this geometry.
304
+ """
305
+ indent = " " * indent
306
+ # Basics...
307
+ card = "\n".join(
308
+ (
309
+ "{0}Geometry {1!r}",
310
+ "{0}{0}Info : {2:s}",
311
+ "{0}{0}LAM : {3!s}",
312
+ )
313
+ ).format(indent, self, self.info, self.lam)
314
+ # Optional infos
315
+ for attr in [
316
+ k
317
+ for k in (
318
+ "area",
319
+ "resolution",
320
+ "truncation",
321
+ "stretching",
322
+ "nlon",
323
+ "nlat",
324
+ "ni",
325
+ "nj",
326
+ "nmassif",
327
+ "nposts",
328
+ )
329
+ if getattr(self, k, False)
330
+ ]:
331
+ card += "\n{0}{0}{1:10s} : {2!s}".format(
332
+ indent, attr.title(), getattr(self, attr)
333
+ )
334
+ return card
335
+
336
+ def strheader(self):
337
+ """Return the beginning of the formatted print representation."""
338
+ header = "{:s}.{:s} | tag='{}' id='{:s}'".format(
339
+ self.__module__,
340
+ self.__class__.__name__,
341
+ self.tag,
342
+ self.info,
343
+ )
344
+ if self.lam:
345
+ header += " area='{:s}'".format(self.area)
346
+ return header
347
+
348
+ @property
349
+ def coordinates(self):
350
+ if any(
351
+ [
352
+ getattr(self, x) is None
353
+ for x in ("lonmin", "latmin", "nlat", "nlon", "resolution")
354
+ ]
355
+ ):
356
+ return dict()
357
+ coordinates = dict(lonmin=self.lonmin, latmin=self.latmin)
358
+ coordinates["latmax"] = self.latmin + self.resolution * (self.nlat - 1)
359
+ coordinates["lonmax"] = self.lonmin + self.resolution * (self.nlon - 1)
360
+ return coordinates
361
+
362
+
363
+ class SpectralAwareHorizontalGeometry(HorizontalGeometry):
364
+ """Additional check and support for the **truncationtype** attribute."""
365
+
366
+ _tag_topcls = False
367
+
368
+ def _check_attributes(self):
369
+ super()._check_attributes()
370
+ if self.truncationtype is None:
371
+ self.truncationtype = "linear"
372
+ if self.truncationtype not in ("linear", "quadratic", "cubic"):
373
+ raise ValueError(
374
+ "Improper value {:s} for *truncationtype*".format(
375
+ self.truncationtype
376
+ )
377
+ )
378
+
379
+ @property
380
+ def short_truncationtype(self):
381
+ return self.truncationtype[0]
382
+
383
+ @property
384
+ def xtruncationtype(self):
385
+ return dict(quadratic="quad").get(
386
+ self.truncationtype, self.truncationtype
387
+ )
388
+
389
+
390
+ # Combined geometry (not used at the present time)
391
+
392
+
393
+ class CombinedGeometry(Geometry):
394
+ """Combine horizontal and vertical geometry (not used at the present time)."""
395
+
396
+ _tag_topcls = False
397
+
398
+ def __init__(self, **kw):
399
+ """
400
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
401
+ the first positional attribute is considered to be the tag name)
402
+ :param str info: A free description of the geometry
403
+ :param HorizontalGeometry hgeo: An horizontal geometry
404
+ :param VerticalGeometry vgeo: A vertical geometry
405
+ """
406
+ self.hgeo = None
407
+ self.vgeo = None
408
+ super().__init__(**kw)
409
+ self.kind = "combined"
410
+ logger.debug("Combined Geometry init %s %s", str(self), str(kw))
411
+
412
+
413
+ # Concrete geometry classes
414
+
415
+
416
+ class GaussGeometry(SpectralAwareHorizontalGeometry):
417
+ """
418
+ Gaussian grid (stretched or not, rotated or not) associated with a spectral
419
+ represention.
420
+ """
421
+
422
+ _tag_topcls = False
423
+
424
+ def __init__(self, **kw):
425
+ """
426
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
427
+ the first positional attribute is considered to be the tag name)
428
+ :param str info: A free description of the geometry
429
+ :param int truncation: The linear truncation
430
+ :param float stretching: The stretching factor (1. for an unstretched grid)
431
+ :param str truncationtype: The method used to compute the spectral representation
432
+ (linear, quadratic or cubic).
433
+ :param str gridtype: The type of the gaussian grid (rgrid: the default
434
+ method used at Meteo-France until now, octahedral)
435
+ :param str area: The location of the pole of interest (when stretching > 1.)
436
+
437
+ .. note:: Gaussian grids are always global grids.
438
+ """
439
+ super().__init__(**kw)
440
+ self.kind = "gauss"
441
+ logger.debug("Gauss Geometry init %s", str(self))
442
+
443
+ def _check_attributes(self):
444
+ self.lam = False # Always false for gaussian grid
445
+ super()._check_attributes()
446
+ if self.truncation is None or self.stretching is None:
447
+ raise AttributeError("Some mandatory arguments are missing")
448
+ if self.gridtype is None:
449
+ self.gridtype = "rgrid"
450
+ if self.gridtype not in ("rgrid", "octahedral"):
451
+ raise ValueError(
452
+ "Improper value {:s} for *gridtype*".format(self.gridtype)
453
+ )
454
+
455
+ @property
456
+ def short_gridtype(self):
457
+ return self.gridtype[0] if self.gridtype != "rgrid" else ""
458
+
459
+ @property
460
+ def gco_grid_def(self):
461
+ geotag_f = "t"
462
+ if self.truncationtype != "linear" or self.gridtype != "rgrid":
463
+ geotag_f += "{0.short_truncationtype}"
464
+ geotag_f += "{0.short_gridtype}{0.truncation:d}"
465
+ return geotag_f.format(self)
466
+
467
+ def __str__(self):
468
+ """Standard formatted print representation."""
469
+ return "<{:s} t{:s}{:s}={:d} c={:g}>".format(
470
+ self.strheader(),
471
+ self.short_truncationtype,
472
+ self.short_gridtype,
473
+ self.truncation,
474
+ self.stretching,
475
+ )
476
+
477
+ def doc_export(self):
478
+ """Relevant informations to print in the documentation."""
479
+ if self.stretching > 1:
480
+ fmts = "kind={0:s}, t{1:s}{2:s}={3:d}, c={4:g} (pole of interest over {5!s})"
481
+ else:
482
+ fmts = "kind={0:s}, t{1:s}{2:s}={3:d}, c={4:g}"
483
+ return fmts.format(
484
+ self.kind,
485
+ self.short_truncationtype,
486
+ self.short_gridtype,
487
+ self.truncation,
488
+ self.stretching,
489
+ self.area,
490
+ )
491
+
492
+
493
+ class ProjectedGeometry(SpectralAwareHorizontalGeometry):
494
+ """Geometry defined by a geographical projection on a plane."""
495
+
496
+ _tag_topcls = False
497
+
498
+ def __init__(self, **kw):
499
+ """
500
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
501
+ the first positional attribute is considered to be the tag name)
502
+ :param str info: A free description of the geometry
503
+ :param bool lam: Is it a limited area grid (*True* by default)
504
+ :param int|float resolution: The grid's resolution
505
+ :param str runit: The unit of the resolution (km, ...) (km by default)
506
+ :param str area: The grid location (needed if **lam** is *True*)
507
+ """
508
+ kw.setdefault("runit", "km")
509
+ super().__init__(**kw)
510
+ self.kind = "projected"
511
+ logger.debug("Projected Geometry init %s", str(self))
512
+
513
+ def _check_attributes(self):
514
+ super()._check_attributes()
515
+ if self.resolution is None:
516
+ raise AttributeError("Some mandatory arguments are missing")
517
+
518
+ @property
519
+ def gco_grid_def(self):
520
+ geotag_f = (
521
+ "{0.area}_{0.rnice}"
522
+ if self.truncationtype == "linear"
523
+ else "{0.area}_{0.xtruncationtype}_{0.rnice}"
524
+ )
525
+ return geotag_f.format(self)
526
+
527
+ def __str__(self):
528
+ """Standard formatted print representation."""
529
+ return "<{:s} r='{:s}' {:s}>".format(
530
+ self.strheader(), self.rnice, self.truncationtype
531
+ )
532
+
533
+ def doc_export(self):
534
+ """Relevant informations to print in the documentation."""
535
+ if self.lam:
536
+ fmts = (
537
+ "kind={0:s}, r={1:s}, truncationtype={2:s}, limited-area={3:s}"
538
+ )
539
+ else:
540
+ fmts = "kind={0:s}, r={1:s}, truncationtype={2:s}, global"
541
+ return fmts.format(
542
+ self.kind, self.rnice, self.truncationtype, self.area
543
+ )
544
+
545
+
546
+ class LonlatGeometry(HorizontalGeometry):
547
+ """Geometry defined by a geographical projection on plane."""
548
+
549
+ _tag_topcls = False
550
+
551
+ def __init__(self, **kw):
552
+ """
553
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
554
+ the first positional attribute is considered to be the tag name)
555
+ :param str info: A free description of the geometry
556
+ :param bool lam: Is it a limited area grid (*True* by default)
557
+ :param int|float resolution: The grid's resolution
558
+ :param str runit: The unit of the resolution (deg, ...) (deg by default)
559
+ :param int nlon: The number of longitude points in the grid
560
+ :param int nlat: The number of latitude points in the grid
561
+ :param str area: The grid location (needed if **lam** is *True*)
562
+ """
563
+ kw.setdefault("runit", "dg")
564
+ super().__init__(**kw)
565
+ self.kind = "lonlat"
566
+ # TODO: coherence entre les coordonnees et nlon/nlat/resolution
567
+ logger.debug("Lon/Lat Geometry init %s", str(self))
568
+
569
+ def __str__(self):
570
+ """Standard formatted print representation."""
571
+ return "<{:s} r='{:s}'>".format(self.strheader(), self.rnice)
572
+
573
+ def doc_export(self):
574
+ """Relevant informations to print in the documentation."""
575
+ if self.lam:
576
+ fmts = "kind={0:s}, r={1:s}, limited-area={2:s}, nlon={3!s}, nlat={4!s}"
577
+ else:
578
+ fmts = "kind={0:s}, r={1:s}, global, nlon={3!s}, nlat={4!s}"
579
+ return fmts.format(
580
+ self.kind, self.rnice, self.area, self.nlon, self.nlat
581
+ )
582
+
583
+
584
+ class UnstructuredGeometry(HorizontalGeometry):
585
+ """Unstructured grid (curvlinear, finite-elements, ...)."""
586
+
587
+ _tag_topcls = False
588
+
589
+ def __init__(self, *args, **kw): # @UnusedVariable
590
+ """
591
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
592
+ the first positional attribute is considered to be the tag name)
593
+ :param str info: A free description of the geometry
594
+ :param bool lam: Is it a limited area geometry (as opposed to global)
595
+
596
+ .. note:: This is an abstract class, do not instantiate.
597
+ """
598
+ super().__init__(**kw)
599
+ self.kind = "unstructured"
600
+ logger.debug("Unstructured Geometry init %s", str(self))
601
+
602
+ def __str__(self):
603
+ """Standard formatted print representation."""
604
+ return "<{:s}>".format(self.strheader())
605
+
606
+ def doc_export(self):
607
+ """Relevant informations to print in the documentation."""
608
+ if self.area:
609
+ fmts = "kind={0:s}, area={1:s}"
610
+ else:
611
+ fmts = "kind={0:s}"
612
+ return fmts.format(self.kind, self.area)
613
+
614
+
615
+ class CurvlinearGeometry(UnstructuredGeometry):
616
+ """Curvlinear grid."""
617
+
618
+ _tag_topcls = False
619
+
620
+ def __init__(self, *args, **kw): # @UnusedVariable
621
+ """
622
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
623
+ the first positional attribute is considered to be the tag name)
624
+ :param str info: A free description of the geometry
625
+ :param bool lam: Is it a limited area grid (*True* by default)
626
+ :param int ni: The number of longitude points in the grid
627
+ :param int nj: The number of latitude points in the grid
628
+ :param str area: The grid location (needed if **lam** is *True*)
629
+ """
630
+ super().__init__(**kw)
631
+ self.kind = "curvlinear"
632
+
633
+ def _check_attributes(self):
634
+ super()._check_attributes()
635
+ if self.ni is None or self.nj is None:
636
+ raise AttributeError("Some mandatory arguments are missing")
637
+
638
+ def doc_export(self):
639
+ """Relevant informations to print in the documentation."""
640
+ if self.lam:
641
+ fmts = (
642
+ "kind={0:s}, r={1:s}, limited-area={2:s}, ni={3!s}, nj={4!s}"
643
+ )
644
+ else:
645
+ fmts = "kind={0:s}, r={1:s}, global, ni={3!s}, nj={4!s}"
646
+ return fmts.format(
647
+ self.kind, self.rnice, self.area, self.nlon, self.nlat
648
+ )
649
+
650
+
651
+ class RedgridGeometry(HorizontalGeometry):
652
+ """Spherical or LAM reduced grid (the number of longitude decreases toward the pole)."""
653
+
654
+ _tag_topcls = False
655
+
656
+ def __init__(self, **kw):
657
+ """
658
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
659
+ the first positional attribute is considered to be the tag
660
+ name)
661
+ :param str info: A free description of the geometry
662
+ :param int nlonmax: Maximum number of longitude points in the grid
663
+ :param int nlat: Number of latitude points in the grid
664
+ :param int expected_resolution: the real resolution for
665
+ ``longitudes = expected_resolution * cos(lat)``
666
+ :param str area: The grid location (needed if **lam** is *True*)
667
+ """
668
+ kw.setdefault("runit", "dg")
669
+ kw.setdefault("lam", False)
670
+ super().__init__(**kw)
671
+ self.kind = "redgrid"
672
+
673
+ def _check_attributes(self):
674
+ if (
675
+ self.nlonmax is None
676
+ or self.nlat is None
677
+ or self.resolution is None
678
+ ):
679
+ raise AttributeError("Some mandatory arguments are missing")
680
+ super()._check_attributes()
681
+ if self.lam is False:
682
+ self.area = "global"
683
+
684
+ def __str__(self):
685
+ """Standard formatted print representation."""
686
+ return "<{:s} area='{:s}' r='{:s}'>".format(
687
+ self.strheader(), self.area, self.rnice
688
+ )
689
+
690
+ def doc_export(self):
691
+ """Relevant informations to print in the documentation."""
692
+ fmts = "kind={0:s}, r={1:s}, area={2:s}, nlonmax={3!s}, nlat={4!s}"
693
+ return fmts.format(
694
+ self.kind, self.rnice, self.area, self.nlonmax, self.nlat
695
+ )
696
+
697
+
698
+ # Pre-defined footprint attribute for any HorizontalGeometry
699
+
700
+ #: Usual definition of the ``geometry`` attribute.
701
+ a_hgeometry = dict(
702
+ info="The resource's horizontal geometry.",
703
+ type=HorizontalGeometry,
704
+ )
705
+
706
+
707
+ def _add_geo2basename_info(cls):
708
+ """Decorator that adds a _geo2basename_info to a class."""
709
+
710
+ def _geo2basename_info(self, add_stretching=True):
711
+ """Return an array describing the geometry for the Vortex's name builder."""
712
+ if isinstance(self.geometry, GaussGeometry):
713
+ lgeo = [
714
+ {
715
+ "truncation": (
716
+ self.geometry.truncation,
717
+ self.geometry.short_truncationtype,
718
+ self.geometry.short_gridtype,
719
+ )
720
+ },
721
+ ]
722
+ if add_stretching:
723
+ lgeo.append({"stretching": self.geometry.stretching})
724
+ elif isinstance(self.geometry, (ProjectedGeometry, RedgridGeometry)):
725
+ lgeo = [self.geometry.area, self.geometry.rnice]
726
+ if (
727
+ self.geometry.truncationtype is not None
728
+ and self.geometry.truncationtype != "linear"
729
+ ):
730
+ lgeo.append(self.geometry.xtruncationtype)
731
+ else:
732
+ lgeo = self.geometry.area # Default: always defined
733
+ return lgeo
734
+
735
+ if not hasattr(cls, "_geo2basename_info"):
736
+ cls._geo2basename_info = _geo2basename_info
737
+ return cls
738
+
739
+
740
+ #: Abstract footprint definition of the ``geometry`` attribute.
741
+ hgeometry = footprints.Footprint(
742
+ info="Abstract Horizontal Geometry", attr=dict(geometry=a_hgeometry)
743
+ )
744
+
745
+ #: Abstract footprint definition of the ``geometry`` attribute with decorators
746
+ #: that alter the ``namebuilding_info`` method
747
+ hgeometry_deco = footprints.DecorativeFootprint(
748
+ hgeometry,
749
+ decorator=[
750
+ _add_geo2basename_info,
751
+ namebuilding_insert("geo", lambda self: self._geo2basename_info()),
752
+ generic_pathname_insert(
753
+ "geometry", lambda self: self.geometry, setdefault=True
754
+ ),
755
+ ],
756
+ )
757
+
758
+
759
+ # Load default geometries when the module is first imported
760
+
761
+
762
+ def load(inifile="@geometries.ini", refresh=False, verbose=True):
763
+ """Load a set of pre-defined geometries from a configuration file.
764
+
765
+ The class that will be instantiated depends on the "kind" keyword..
766
+ """
767
+ iniconf = configparser.ConfigParser()
768
+ with importlib.resources.open_text(
769
+ "vortex.data",
770
+ "geometries.ini",
771
+ ) as fh:
772
+ iniconf.read_file(fh)
773
+ for item in iniconf.sections():
774
+ gdesc = dict(iniconf.items(item))
775
+ gkind = gdesc.get("kind")
776
+ try:
777
+ thisclass = [
778
+ x
779
+ for x in Geometry.tag_classes()
780
+ if x.__name__.lower().startswith(gkind.lower())
781
+ ].pop()
782
+ except IndexError:
783
+ raise AttributeError(
784
+ "Kind={:s} is unknown (for geometry [{:s}])".format(
785
+ gkind, item
786
+ )
787
+ )
788
+ if verbose:
789
+ print("+ Load", item.ljust(16), "as", thisclass)
790
+ if refresh:
791
+ # Always recreate the Geometry...
792
+ thisclass(tag=item, new=True, **gdesc)
793
+ else:
794
+ # Only create new geometries
795
+ if item not in Geometry.tag_keys():
796
+ thisclass(tag=item, new=True, **gdesc)
797
+
798
+
799
+ load(verbose=False)