vortex-nwp 2.0.0b1__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 (146) hide show
  1. vortex/__init__.py +135 -0
  2. vortex/algo/__init__.py +12 -0
  3. vortex/algo/components.py +2136 -0
  4. vortex/algo/mpitools.py +1648 -0
  5. vortex/algo/mpitools_templates/envelope_wrapper_default.tpl +27 -0
  6. vortex/algo/mpitools_templates/envelope_wrapper_mpiauto.tpl +29 -0
  7. vortex/algo/mpitools_templates/wrapstd_wrapper_default.tpl +18 -0
  8. vortex/algo/serversynctools.py +170 -0
  9. vortex/config.py +115 -0
  10. vortex/data/__init__.py +13 -0
  11. vortex/data/abstractstores.py +1572 -0
  12. vortex/data/containers.py +780 -0
  13. vortex/data/contents.py +596 -0
  14. vortex/data/executables.py +284 -0
  15. vortex/data/flow.py +113 -0
  16. vortex/data/geometries.ini +2689 -0
  17. vortex/data/geometries.py +703 -0
  18. vortex/data/handlers.py +1021 -0
  19. vortex/data/outflow.py +67 -0
  20. vortex/data/providers.py +465 -0
  21. vortex/data/resources.py +201 -0
  22. vortex/data/stores.py +1271 -0
  23. vortex/gloves.py +282 -0
  24. vortex/layout/__init__.py +27 -0
  25. vortex/layout/appconf.py +109 -0
  26. vortex/layout/contexts.py +511 -0
  27. vortex/layout/dataflow.py +1069 -0
  28. vortex/layout/jobs.py +1276 -0
  29. vortex/layout/monitor.py +833 -0
  30. vortex/layout/nodes.py +1424 -0
  31. vortex/layout/subjobs.py +464 -0
  32. vortex/nwp/__init__.py +11 -0
  33. vortex/nwp/algo/__init__.py +12 -0
  34. vortex/nwp/algo/assim.py +483 -0
  35. vortex/nwp/algo/clim.py +920 -0
  36. vortex/nwp/algo/coupling.py +609 -0
  37. vortex/nwp/algo/eda.py +632 -0
  38. vortex/nwp/algo/eps.py +613 -0
  39. vortex/nwp/algo/forecasts.py +745 -0
  40. vortex/nwp/algo/fpserver.py +927 -0
  41. vortex/nwp/algo/ifsnaming.py +403 -0
  42. vortex/nwp/algo/ifsroot.py +311 -0
  43. vortex/nwp/algo/monitoring.py +202 -0
  44. vortex/nwp/algo/mpitools.py +554 -0
  45. vortex/nwp/algo/odbtools.py +974 -0
  46. vortex/nwp/algo/oopsroot.py +735 -0
  47. vortex/nwp/algo/oopstests.py +186 -0
  48. vortex/nwp/algo/request.py +579 -0
  49. vortex/nwp/algo/stdpost.py +1285 -0
  50. vortex/nwp/data/__init__.py +12 -0
  51. vortex/nwp/data/assim.py +392 -0
  52. vortex/nwp/data/boundaries.py +261 -0
  53. vortex/nwp/data/climfiles.py +539 -0
  54. vortex/nwp/data/configfiles.py +149 -0
  55. vortex/nwp/data/consts.py +929 -0
  56. vortex/nwp/data/ctpini.py +133 -0
  57. vortex/nwp/data/diagnostics.py +181 -0
  58. vortex/nwp/data/eda.py +148 -0
  59. vortex/nwp/data/eps.py +383 -0
  60. vortex/nwp/data/executables.py +1039 -0
  61. vortex/nwp/data/fields.py +96 -0
  62. vortex/nwp/data/gridfiles.py +308 -0
  63. vortex/nwp/data/logs.py +551 -0
  64. vortex/nwp/data/modelstates.py +334 -0
  65. vortex/nwp/data/monitoring.py +220 -0
  66. vortex/nwp/data/namelists.py +644 -0
  67. vortex/nwp/data/obs.py +748 -0
  68. vortex/nwp/data/oopsexec.py +72 -0
  69. vortex/nwp/data/providers.py +182 -0
  70. vortex/nwp/data/query.py +217 -0
  71. vortex/nwp/data/stores.py +147 -0
  72. vortex/nwp/data/surfex.py +338 -0
  73. vortex/nwp/syntax/__init__.py +9 -0
  74. vortex/nwp/syntax/stdattrs.py +375 -0
  75. vortex/nwp/tools/__init__.py +10 -0
  76. vortex/nwp/tools/addons.py +35 -0
  77. vortex/nwp/tools/agt.py +55 -0
  78. vortex/nwp/tools/bdap.py +48 -0
  79. vortex/nwp/tools/bdcp.py +38 -0
  80. vortex/nwp/tools/bdm.py +21 -0
  81. vortex/nwp/tools/bdmp.py +49 -0
  82. vortex/nwp/tools/conftools.py +1311 -0
  83. vortex/nwp/tools/drhook.py +62 -0
  84. vortex/nwp/tools/grib.py +268 -0
  85. vortex/nwp/tools/gribdiff.py +99 -0
  86. vortex/nwp/tools/ifstools.py +163 -0
  87. vortex/nwp/tools/igastuff.py +249 -0
  88. vortex/nwp/tools/mars.py +56 -0
  89. vortex/nwp/tools/odb.py +548 -0
  90. vortex/nwp/tools/partitioning.py +234 -0
  91. vortex/nwp/tools/satrad.py +56 -0
  92. vortex/nwp/util/__init__.py +6 -0
  93. vortex/nwp/util/async.py +184 -0
  94. vortex/nwp/util/beacon.py +40 -0
  95. vortex/nwp/util/diffpygram.py +359 -0
  96. vortex/nwp/util/ens.py +198 -0
  97. vortex/nwp/util/hooks.py +128 -0
  98. vortex/nwp/util/taskdeco.py +81 -0
  99. vortex/nwp/util/usepygram.py +591 -0
  100. vortex/nwp/util/usetnt.py +87 -0
  101. vortex/proxy.py +6 -0
  102. vortex/sessions.py +341 -0
  103. vortex/syntax/__init__.py +9 -0
  104. vortex/syntax/stdattrs.py +628 -0
  105. vortex/syntax/stddeco.py +176 -0
  106. vortex/toolbox.py +982 -0
  107. vortex/tools/__init__.py +11 -0
  108. vortex/tools/actions.py +457 -0
  109. vortex/tools/addons.py +297 -0
  110. vortex/tools/arm.py +76 -0
  111. vortex/tools/compression.py +322 -0
  112. vortex/tools/date.py +20 -0
  113. vortex/tools/ddhpack.py +10 -0
  114. vortex/tools/delayedactions.py +672 -0
  115. vortex/tools/env.py +513 -0
  116. vortex/tools/folder.py +663 -0
  117. vortex/tools/grib.py +559 -0
  118. vortex/tools/lfi.py +746 -0
  119. vortex/tools/listings.py +354 -0
  120. vortex/tools/names.py +575 -0
  121. vortex/tools/net.py +1790 -0
  122. vortex/tools/odb.py +10 -0
  123. vortex/tools/parallelism.py +336 -0
  124. vortex/tools/prestaging.py +186 -0
  125. vortex/tools/rawfiles.py +10 -0
  126. vortex/tools/schedulers.py +413 -0
  127. vortex/tools/services.py +871 -0
  128. vortex/tools/storage.py +1061 -0
  129. vortex/tools/surfex.py +61 -0
  130. vortex/tools/systems.py +3396 -0
  131. vortex/tools/targets.py +384 -0
  132. vortex/util/__init__.py +9 -0
  133. vortex/util/config.py +1071 -0
  134. vortex/util/empty.py +24 -0
  135. vortex/util/helpers.py +184 -0
  136. vortex/util/introspection.py +63 -0
  137. vortex/util/iosponge.py +76 -0
  138. vortex/util/roles.py +51 -0
  139. vortex/util/storefunctions.py +103 -0
  140. vortex/util/structs.py +26 -0
  141. vortex/util/worker.py +150 -0
  142. vortex_nwp-2.0.0b1.dist-info/LICENSE +517 -0
  143. vortex_nwp-2.0.0b1.dist-info/METADATA +50 -0
  144. vortex_nwp-2.0.0b1.dist-info/RECORD +146 -0
  145. vortex_nwp-2.0.0b1.dist-info/WHEEL +5 -0
  146. vortex_nwp-2.0.0b1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,703 @@
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
+ from vortex.util.config import GenericConfigParser
66
+
67
+ #: No automatic export
68
+ __all__ = []
69
+
70
+ logger = loggers.getLogger(__name__)
71
+
72
+
73
+ # Module Interface
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
+ class Geometry(bronx.patterns.getbytag.GetByTag):
115
+ """Abstract geometry."""
116
+
117
+ _tag_implicit_new = False
118
+
119
+ def __init__(self, **kw):
120
+ """
121
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
122
+ the first positional attribute is considered to be the tag name)
123
+ :param str info: A free description of the geometry
124
+
125
+ .. note:: This is an abstract class, do not instantiate.
126
+ """
127
+ self.info = 'anonymous'
128
+ self.inifile = None
129
+ self.__dict__.update(kw)
130
+ self.kind = 'abstract'
131
+ self._init_attributes = {k: v for k, v in kw.items() if v is not None}
132
+ logger.debug('Abstract Geometry init kw=%s', str(kw))
133
+
134
+ @classmethod
135
+ def _tag_implicit_new_error(cls, tag):
136
+ """Called whenever a tag does not exists and _tag_implicit_new = False."""
137
+ raise RuntimeError('The "{:s}" {:s} object does not exist yet...'.
138
+ format(tag, cls.__name__))
139
+
140
+ @classmethod
141
+ def tag_clean(self, tag):
142
+ """Geometries id tags are lower case."""
143
+ return tag.lower()
144
+
145
+ def __repr__(self):
146
+ """Nicer represenation for geometries."""
147
+ return '<{:s}.{:s} (tag=\'{:s}\') object at {:#x}>'.format(
148
+ self.__module__, self.__class__.__name__, self.tag, id(self)
149
+ )
150
+
151
+ def export_dict(self):
152
+ return self.tag
153
+
154
+ def doc_export(self):
155
+ """Relevant informations to print in the documentation."""
156
+ return 'kind={:s}'.format(self.kind)
157
+
158
+ def to_inifile(self):
159
+ """Format geometry to put in the inifile."""
160
+ self._init_attributes.update(kind=self.kind)
161
+ s = '[{}]\n'.format(self.tag)
162
+ for k in sorted(self._init_attributes.keys()):
163
+ s += '{:10s} = {}\n'.format(k, self._init_attributes[k])
164
+ return s
165
+
166
+
167
+ class VerticalGeometry(Geometry):
168
+ """Handle vertical geometry description (not used at the present time)."""
169
+
170
+ _tag_topcls = False
171
+
172
+ def __init__(self, **kw):
173
+ """
174
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
175
+ the first positional attribute is considered to be the tag name)
176
+ :param str info: A free description of the geometry
177
+
178
+ .. note:: This is an abstract class, do not instantiate.
179
+ """
180
+ super().__init__(**kw)
181
+ self.kind = 'vertical'
182
+ logger.debug('Abstract Vertical Geometry init %s %s', str(self), str(kw))
183
+
184
+
185
+ class HorizontalGeometry(Geometry):
186
+ """Handle abstract horizontal geometry description."""
187
+
188
+ _tag_topcls = False
189
+
190
+ def __init__(self, **kw):
191
+ """
192
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
193
+ the first positional attribute is considered to be the tag name)
194
+ :param str info: A free description of the geometry
195
+ :param bool lam: Is it a limited area geometry (as opposed to global)
196
+
197
+ .. note:: This is an abstract class, do not instantiate.
198
+ """
199
+ desc = dict(
200
+ info='anonymous',
201
+ gridtype=None,
202
+ area=None,
203
+ nlon=None,
204
+ nlonmax=None,
205
+ nlat=None,
206
+ ni=None,
207
+ nj=None,
208
+ resolution=0.,
209
+ expected_resolution=0.,
210
+ runit=None,
211
+ truncation=None,
212
+ truncationtype=None,
213
+ stretching=None,
214
+ nmassif=None,
215
+ nposts=None,
216
+ lam=True,
217
+ lonmin=None,
218
+ latmin=None,
219
+ )
220
+ desc.update(kw)
221
+ super().__init__(**desc)
222
+ for k, v in self.__dict__.items():
223
+ if isinstance(v, str) and re.match('none', v, re.IGNORECASE):
224
+ self.__dict__[k] = None
225
+ if isinstance(v, str) and re.match('true', v, re.IGNORECASE):
226
+ self.__dict__[k] = True
227
+ if isinstance(v, str) and re.match('false', v, re.IGNORECASE):
228
+ self.__dict__[k] = False
229
+ for item in ('nlon', 'nlat', 'ni', 'nj', 'nmassif', 'nposts', 'truncation'):
230
+ cv = getattr(self, item)
231
+ if cv is not None:
232
+ setattr(self, item, int(cv))
233
+ for item in ('stretching', 'resolution', 'lonmin', 'latmin'):
234
+ cv = getattr(self, item)
235
+ if cv is not None:
236
+ setattr(self, item, float(cv))
237
+ self._check_attributes()
238
+ logger.debug('Abstract Horizontal Geometry init %s', str(self))
239
+
240
+ def _check_attributes(self):
241
+ if self.lam and (self.area is None):
242
+ raise AttributeError("Some mandatory arguments are missing")
243
+
244
+ @property
245
+ def gam(self):
246
+ """Is it a global geometry ?"""
247
+ return not self.lam
248
+
249
+ @property
250
+ def rnice(self):
251
+ """Returns a string with a nice representation of the resolution (if sensible)."""
252
+ if self.runit is not None:
253
+ if self.runit == 'km':
254
+ res = '{:05.2f}'.format(self.resolution)
255
+ elif self.runit in ('s', 'min'):
256
+ res = '{:04.1f}'.format(self.resolution)
257
+ else:
258
+ res = '{:06.3f}'.format(self.resolution)
259
+ return re.sub(r'\.', self.runit, res, 1)
260
+ else:
261
+ return 'Unknown Resolution'
262
+
263
+ @property
264
+ def rnice_u(self):
265
+ return self.rnice.upper()
266
+
267
+ @property
268
+ def gco_grid_def(self):
269
+ return ('{0.area}' + ('_{0.rnice}' if self.runit is not None else '')).format(self)
270
+
271
+ def anonymous_info(self, *args): # @UnusedVariable
272
+ """Try to build a meaningful information from an anonymous geometry."""
273
+ return '{!s}, {:s}'.format(self.area, self.rnice)
274
+
275
+ def __str__(self):
276
+ """Very short presentation."""
277
+ if self.info == 'anonymous':
278
+ return '{:s}({:s})'.format(self.__class__.__name__, self.anonymous_info())
279
+ else:
280
+ return self.info
281
+
282
+ def idcard(self, indent=2):
283
+ """
284
+ Returns a multilines documentation string with a summary
285
+ of the valuable information contained by this geometry.
286
+ """
287
+ indent = ' ' * indent
288
+ # Basics...
289
+ card = "\n".join((
290
+ '{0}Geometry {1!r}',
291
+ '{0}{0}Info : {2:s}',
292
+ '{0}{0}LAM : {3!s}',
293
+ )).format(indent, self, self.info, self.lam)
294
+ # Optional infos
295
+ for attr in [k for k in ('area', 'resolution', 'truncation',
296
+ 'stretching', 'nlon', 'nlat', 'ni', 'nj',
297
+ 'nmassif', 'nposts')
298
+ if getattr(self, k, False)]:
299
+ card += "\n{0}{0}{1:10s} : {2!s}".format(indent, attr.title(),
300
+ getattr(self, attr))
301
+ return card
302
+
303
+ def strheader(self):
304
+ """Return the beginning of the formatted print representation."""
305
+ header = '{:s}.{:s} | tag=\'{}\' id=\'{:s}\''.format(
306
+ self.__module__,
307
+ self.__class__.__name__,
308
+ self.tag,
309
+ self.info,
310
+ )
311
+ if self.lam:
312
+ header += ' area=\'{:s}\''.format(self.area)
313
+ return header
314
+
315
+ @property
316
+ def coordinates(self):
317
+ if any([getattr(self, x) is None for x in ('lonmin', 'latmin', 'nlat', 'nlon', 'resolution')]):
318
+ return dict()
319
+ coordinates = dict(lonmin=self.lonmin, latmin=self.latmin)
320
+ coordinates['latmax'] = self.latmin + self.resolution * (self.nlat - 1)
321
+ coordinates['lonmax'] = self.lonmin + self.resolution * (self.nlon - 1)
322
+ return coordinates
323
+
324
+
325
+ class SpectralAwareHorizontalGeometry(HorizontalGeometry):
326
+ """Additional check and support for the **truncationtype** attribute."""
327
+
328
+ _tag_topcls = False
329
+
330
+ def _check_attributes(self):
331
+ super()._check_attributes()
332
+ if self.truncationtype is None:
333
+ self.truncationtype = 'linear'
334
+ if self.truncationtype not in ('linear', 'quadratic', 'cubic'):
335
+ raise ValueError("Improper value {:s} for *truncationtype*"
336
+ .format(self.truncationtype))
337
+
338
+ @property
339
+ def short_truncationtype(self):
340
+ return self.truncationtype[0]
341
+
342
+ @property
343
+ def xtruncationtype(self):
344
+ return dict(quadratic='quad').get(self.truncationtype, self.truncationtype)
345
+
346
+
347
+ # Combined geometry (not used at the present time)
348
+
349
+ class CombinedGeometry(Geometry):
350
+ """Combine horizontal and vertical geometry (not used at the present time)."""
351
+
352
+ _tag_topcls = False
353
+
354
+ def __init__(self, **kw):
355
+ """
356
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
357
+ the first positional attribute is considered to be the tag name)
358
+ :param str info: A free description of the geometry
359
+ :param HorizontalGeometry hgeo: An horizontal geometry
360
+ :param VerticalGeometry vgeo: A vertical geometry
361
+ """
362
+ self.hgeo = None
363
+ self.vgeo = None
364
+ super().__init__(**kw)
365
+ self.kind = 'combined'
366
+ logger.debug('Combined Geometry init %s %s', str(self), str(kw))
367
+
368
+
369
+ # Concrete geometry classes
370
+
371
+ class GaussGeometry(SpectralAwareHorizontalGeometry):
372
+ """
373
+ Gaussian grid (stretched or not, rotated or not) associated with a spectral
374
+ represention.
375
+ """
376
+
377
+ _tag_topcls = False
378
+
379
+ def __init__(self, **kw):
380
+ """
381
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
382
+ the first positional attribute is considered to be the tag name)
383
+ :param str info: A free description of the geometry
384
+ :param int truncation: The linear truncation
385
+ :param float stretching: The stretching factor (1. for an unstretched grid)
386
+ :param str truncationtype: The method used to compute the spectral representation
387
+ (linear, quadratic or cubic).
388
+ :param str gridtype: The type of the gaussian grid (rgrid: the default
389
+ method used at Meteo-France until now, octahedral)
390
+ :param str area: The location of the pole of interest (when stretching > 1.)
391
+
392
+ .. note:: Gaussian grids are always global grids.
393
+ """
394
+ super().__init__(**kw)
395
+ self.kind = 'gauss'
396
+ logger.debug('Gauss Geometry init %s', str(self))
397
+
398
+ def _check_attributes(self):
399
+ self.lam = False # Always false for gaussian grid
400
+ super()._check_attributes()
401
+ if self.truncation is None or self.stretching is None:
402
+ raise AttributeError("Some mandatory arguments are missing")
403
+ if self.gridtype is None:
404
+ self.gridtype = 'rgrid'
405
+ if self.gridtype not in ('rgrid', 'octahedral'):
406
+ raise ValueError("Improper value {:s} for *gridtype*"
407
+ .format(self.gridtype))
408
+
409
+ @property
410
+ def short_gridtype(self):
411
+ return self.gridtype[0] if self.gridtype != 'rgrid' else ''
412
+
413
+ @property
414
+ def gco_grid_def(self):
415
+ geotag_f = 't'
416
+ if self.truncationtype != 'linear' or self.gridtype != 'rgrid':
417
+ geotag_f += '{0.short_truncationtype}'
418
+ geotag_f += '{0.short_gridtype}{0.truncation:d}'
419
+ return geotag_f.format(self)
420
+
421
+ def __str__(self):
422
+ """Standard formatted print representation."""
423
+ return '<{:s} t{:s}{:s}={:d} c={:g}>'.format(self.strheader(),
424
+ self.short_truncationtype,
425
+ self.short_gridtype,
426
+ self.truncation,
427
+ self.stretching)
428
+
429
+ def doc_export(self):
430
+ """Relevant informations to print in the documentation."""
431
+ if self.stretching > 1:
432
+ fmts = 'kind={0:s}, t{1:s}{2:s}={3:d}, c={4:g} (pole of interest over {5!s})'
433
+ else:
434
+ fmts = 'kind={0:s}, t{1:s}{2:s}={3:d}, c={4:g}'
435
+ return fmts.format(self.kind, self.short_truncationtype, self.short_gridtype,
436
+ self.truncation, self.stretching, self.area)
437
+
438
+
439
+ class ProjectedGeometry(SpectralAwareHorizontalGeometry):
440
+ """Geometry defined by a geographical projection on a plane."""
441
+
442
+ _tag_topcls = False
443
+
444
+ def __init__(self, **kw):
445
+ """
446
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
447
+ the first positional attribute is considered to be the tag name)
448
+ :param str info: A free description of the geometry
449
+ :param bool lam: Is it a limited area grid (*True* by default)
450
+ :param int|float resolution: The grid's resolution
451
+ :param str runit: The unit of the resolution (km, ...) (km by default)
452
+ :param str area: The grid location (needed if **lam** is *True*)
453
+ """
454
+ kw.setdefault('runit', 'km')
455
+ super().__init__(**kw)
456
+ self.kind = 'projected'
457
+ logger.debug('Projected Geometry init %s', str(self))
458
+
459
+ def _check_attributes(self):
460
+ super()._check_attributes()
461
+ if self.resolution is None:
462
+ raise AttributeError("Some mandatory arguments are missing")
463
+
464
+ @property
465
+ def gco_grid_def(self):
466
+ geotag_f = ('{0.area}_{0.rnice}' if self.truncationtype == 'linear' else
467
+ '{0.area}_{0.xtruncationtype}_{0.rnice}')
468
+ return geotag_f.format(self)
469
+
470
+ def __str__(self):
471
+ """Standard formatted print representation."""
472
+ return '<{:s} r=\'{:s}\' {:s}>'.format(self.strheader(),
473
+ self.rnice,
474
+ self.truncationtype)
475
+
476
+ def doc_export(self):
477
+ """Relevant informations to print in the documentation."""
478
+ if self.lam:
479
+ fmts = 'kind={0:s}, r={1:s}, truncationtype={2:s}, limited-area={3:s}'
480
+ else:
481
+ fmts = 'kind={0:s}, r={1:s}, truncationtype={2:s}, global'
482
+ return fmts.format(self.kind, self.rnice, self.truncationtype, self.area)
483
+
484
+
485
+ class LonlatGeometry(HorizontalGeometry):
486
+ """Geometry defined by a geographical projection on plane."""
487
+
488
+ _tag_topcls = False
489
+
490
+ def __init__(self, **kw):
491
+ """
492
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
493
+ the first positional attribute is considered to be the tag name)
494
+ :param str info: A free description of the geometry
495
+ :param bool lam: Is it a limited area grid (*True* by default)
496
+ :param int|float resolution: The grid's resolution
497
+ :param str runit: The unit of the resolution (deg, ...) (deg by default)
498
+ :param int nlon: The number of longitude points in the grid
499
+ :param int nlat: The number of latitude points in the grid
500
+ :param str area: The grid location (needed if **lam** is *True*)
501
+ """
502
+ kw.setdefault('runit', 'dg')
503
+ super().__init__(**kw)
504
+ self.kind = 'lonlat'
505
+ # TODO: coherence entre les coordonnees et nlon/nlat/resolution
506
+ logger.debug('Lon/Lat Geometry init %s', str(self))
507
+
508
+ def __str__(self):
509
+ """Standard formatted print representation."""
510
+ return '<{:s} r=\'{:s}\'>'.format(self.strheader(), self.rnice)
511
+
512
+ def doc_export(self):
513
+ """Relevant informations to print in the documentation."""
514
+ if self.lam:
515
+ fmts = 'kind={0:s}, r={1:s}, limited-area={2:s}, nlon={3!s}, nlat={4!s}'
516
+ else:
517
+ fmts = 'kind={0:s}, r={1:s}, global, nlon={3!s}, nlat={4!s}'
518
+ return fmts.format(self.kind, self.rnice, self.area, self.nlon, self.nlat)
519
+
520
+
521
+ class UnstructuredGeometry(HorizontalGeometry):
522
+ """Unstructured grid (curvlinear, finite-elements, ...)."""
523
+
524
+ _tag_topcls = False
525
+
526
+ def __init__(self, *args, **kw): # @UnusedVariable
527
+ """
528
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
529
+ the first positional attribute is considered to be the tag name)
530
+ :param str info: A free description of the geometry
531
+ :param bool lam: Is it a limited area geometry (as opposed to global)
532
+
533
+ .. note:: This is an abstract class, do not instantiate.
534
+ """
535
+ super().__init__(**kw)
536
+ self.kind = 'unstructured'
537
+ logger.debug('Unstructured Geometry init %s', str(self))
538
+
539
+ def __str__(self):
540
+ """Standard formatted print representation."""
541
+ return '<{:s}>'.format(self.strheader())
542
+
543
+ def doc_export(self):
544
+ """Relevant informations to print in the documentation."""
545
+ if self.area:
546
+ fmts = 'kind={0:s}, area={1:s}'
547
+ else:
548
+ fmts = 'kind={0:s}'
549
+ return fmts.format(self.kind, self.area)
550
+
551
+
552
+ class CurvlinearGeometry(UnstructuredGeometry):
553
+ """Curvlinear grid."""
554
+
555
+ _tag_topcls = False
556
+
557
+ def __init__(self, *args, **kw): # @UnusedVariable
558
+ """
559
+ :param str tag: The geometry's name (if no **tag** attributes is provided,
560
+ the first positional attribute is considered to be the tag name)
561
+ :param str info: A free description of the geometry
562
+ :param bool lam: Is it a limited area grid (*True* by default)
563
+ :param int ni: The number of longitude points in the grid
564
+ :param int nj: The number of latitude points in the grid
565
+ :param str area: The grid location (needed if **lam** is *True*)
566
+ """
567
+ super().__init__(**kw)
568
+ self.kind = 'curvlinear'
569
+
570
+ def _check_attributes(self):
571
+ super()._check_attributes()
572
+ if self.ni is None or self.nj is None:
573
+ raise AttributeError("Some mandatory arguments are missing")
574
+
575
+ def doc_export(self):
576
+ """Relevant informations to print in the documentation."""
577
+ if self.lam:
578
+ fmts = 'kind={0:s}, r={1:s}, limited-area={2:s}, ni={3!s}, nj={4!s}'
579
+ else:
580
+ fmts = 'kind={0:s}, r={1:s}, global, ni={3!s}, nj={4!s}'
581
+ return fmts.format(self.kind, self.rnice, self.area, self.nlon, self.nlat)
582
+
583
+
584
+ class RedgridGeometry(HorizontalGeometry):
585
+ """Spherical or LAM reduced grid (the number of longitude decreases toward the pole)."""
586
+
587
+ _tag_topcls = False
588
+
589
+ def __init__(self, **kw):
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
593
+ name)
594
+ :param str info: A free description of the geometry
595
+ :param int nlonmax: Maximum number of longitude points in the grid
596
+ :param int nlat: Number of latitude points in the grid
597
+ :param int expected_resolution: the real resolution for
598
+ ``longitudes = expected_resolution * cos(lat)``
599
+ :param str area: The grid location (needed if **lam** is *True*)
600
+ """
601
+ kw.setdefault('runit', 'dg')
602
+ kw.setdefault('lam', False)
603
+ super().__init__(**kw)
604
+ self.kind = 'redgrid'
605
+
606
+ def _check_attributes(self):
607
+ if self.nlonmax is None or self.nlat is None or self.resolution is None:
608
+ raise AttributeError("Some mandatory arguments are missing")
609
+ super()._check_attributes()
610
+ if self.lam is False:
611
+ self.area = 'global'
612
+
613
+ def __str__(self):
614
+ """Standard formatted print representation."""
615
+ return '<{:s} area=\'{:s}\' r=\'{:s}\'>'.format(self.strheader(),
616
+ self.area,
617
+ self.rnice)
618
+
619
+ def doc_export(self):
620
+ """Relevant informations to print in the documentation."""
621
+ fmts = 'kind={0:s}, r={1:s}, area={2:s}, nlonmax={3!s}, nlat={4!s}'
622
+ return fmts.format(self.kind, self.rnice, self.area, self.nlonmax, self.nlat)
623
+
624
+
625
+ # Pre-defined footprint attribute for any HorizontalGeometry
626
+
627
+ #: Usual definition of the ``geometry`` attribute.
628
+ a_hgeometry = dict(
629
+ info="The resource's horizontal geometry.",
630
+ type=HorizontalGeometry,
631
+ )
632
+
633
+
634
+ def _add_geo2basename_info(cls):
635
+ """Decorator that adds a _geo2basename_info to a class."""
636
+
637
+ def _geo2basename_info(self, add_stretching=True):
638
+ """Return an array describing the geometry for the Vortex's name builder."""
639
+ if isinstance(self.geometry, GaussGeometry):
640
+ lgeo = [{'truncation': (self.geometry.truncation,
641
+ self.geometry.short_truncationtype,
642
+ self.geometry.short_gridtype)}, ]
643
+ if add_stretching:
644
+ lgeo.append({'stretching': self.geometry.stretching})
645
+ elif isinstance(self.geometry, (ProjectedGeometry, RedgridGeometry)):
646
+ lgeo = [self.geometry.area, self.geometry.rnice]
647
+ if (self.geometry.truncationtype is not None and
648
+ self.geometry.truncationtype != 'linear'):
649
+ lgeo.append(self.geometry.xtruncationtype)
650
+ else:
651
+ lgeo = self.geometry.area # Default: always defined
652
+ return lgeo
653
+
654
+ if not hasattr(cls, '_geo2basename_info'):
655
+ cls._geo2basename_info = _geo2basename_info
656
+ return cls
657
+
658
+
659
+ #: Abstract footprint definition of the ``geometry`` attribute.
660
+ hgeometry = footprints.Footprint(info='Abstract Horizontal Geometry',
661
+ attr=dict(geometry=a_hgeometry))
662
+
663
+ #: Abstract footprint definition of the ``geometry`` attribute with decorators
664
+ #: that alter the ``namebuilding_info`` method
665
+ hgeometry_deco = footprints.DecorativeFootprint(
666
+ hgeometry,
667
+ decorator=[_add_geo2basename_info,
668
+ namebuilding_insert('geo', lambda self: self._geo2basename_info()),
669
+ generic_pathname_insert('geometry', lambda self: self.geometry, setdefault=True)])
670
+
671
+
672
+ # Load default geometries when the module is first imported
673
+
674
+ def load(inifile='@geometries.ini', refresh=False, verbose=True):
675
+ """Load a set of pre-defined geometries from a configuration file.
676
+
677
+ The class that will be instantiated depends on the "kind" keyword..
678
+ """
679
+ iniconf = configparser.ConfigParser()
680
+ with importlib.resources.open_text(
681
+ "vortex.data", "geometries.ini",
682
+ ) as fh:
683
+ iniconf.read_file(fh)
684
+ for item in iniconf.sections():
685
+ gdesc = dict(iniconf.items(item))
686
+ gkind = gdesc.get('kind')
687
+ try:
688
+ thisclass = [x for x in Geometry.tag_classes()
689
+ if x.__name__.lower().startswith(gkind.lower())].pop()
690
+ except IndexError:
691
+ raise AttributeError('Kind={:s} is unknown (for geometry [{:s}])'.format(gkind, item))
692
+ if verbose:
693
+ print('+ Load', item.ljust(16), 'as', thisclass)
694
+ if refresh:
695
+ # Always recreate the Geometry...
696
+ thisclass(tag=item, new=True, **gdesc)
697
+ else:
698
+ # Only create new geometries
699
+ if item not in Geometry.tag_keys():
700
+ thisclass(tag=item, new=True, **gdesc)
701
+
702
+
703
+ load(verbose=False)