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.
- vortex/__init__.py +135 -0
- vortex/algo/__init__.py +12 -0
- vortex/algo/components.py +2136 -0
- vortex/algo/mpitools.py +1648 -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 +170 -0
- vortex/config.py +115 -0
- vortex/data/__init__.py +13 -0
- vortex/data/abstractstores.py +1572 -0
- vortex/data/containers.py +780 -0
- vortex/data/contents.py +596 -0
- vortex/data/executables.py +284 -0
- vortex/data/flow.py +113 -0
- vortex/data/geometries.ini +2689 -0
- vortex/data/geometries.py +703 -0
- vortex/data/handlers.py +1021 -0
- vortex/data/outflow.py +67 -0
- vortex/data/providers.py +465 -0
- vortex/data/resources.py +201 -0
- vortex/data/stores.py +1271 -0
- vortex/gloves.py +282 -0
- vortex/layout/__init__.py +27 -0
- vortex/layout/appconf.py +109 -0
- vortex/layout/contexts.py +511 -0
- vortex/layout/dataflow.py +1069 -0
- vortex/layout/jobs.py +1276 -0
- vortex/layout/monitor.py +833 -0
- vortex/layout/nodes.py +1424 -0
- vortex/layout/subjobs.py +464 -0
- vortex/nwp/__init__.py +11 -0
- vortex/nwp/algo/__init__.py +12 -0
- vortex/nwp/algo/assim.py +483 -0
- vortex/nwp/algo/clim.py +920 -0
- vortex/nwp/algo/coupling.py +609 -0
- vortex/nwp/algo/eda.py +632 -0
- vortex/nwp/algo/eps.py +613 -0
- vortex/nwp/algo/forecasts.py +745 -0
- vortex/nwp/algo/fpserver.py +927 -0
- vortex/nwp/algo/ifsnaming.py +403 -0
- vortex/nwp/algo/ifsroot.py +311 -0
- vortex/nwp/algo/monitoring.py +202 -0
- vortex/nwp/algo/mpitools.py +554 -0
- vortex/nwp/algo/odbtools.py +974 -0
- vortex/nwp/algo/oopsroot.py +735 -0
- vortex/nwp/algo/oopstests.py +186 -0
- vortex/nwp/algo/request.py +579 -0
- vortex/nwp/algo/stdpost.py +1285 -0
- vortex/nwp/data/__init__.py +12 -0
- vortex/nwp/data/assim.py +392 -0
- vortex/nwp/data/boundaries.py +261 -0
- vortex/nwp/data/climfiles.py +539 -0
- vortex/nwp/data/configfiles.py +149 -0
- vortex/nwp/data/consts.py +929 -0
- vortex/nwp/data/ctpini.py +133 -0
- vortex/nwp/data/diagnostics.py +181 -0
- vortex/nwp/data/eda.py +148 -0
- vortex/nwp/data/eps.py +383 -0
- vortex/nwp/data/executables.py +1039 -0
- vortex/nwp/data/fields.py +96 -0
- vortex/nwp/data/gridfiles.py +308 -0
- vortex/nwp/data/logs.py +551 -0
- vortex/nwp/data/modelstates.py +334 -0
- vortex/nwp/data/monitoring.py +220 -0
- vortex/nwp/data/namelists.py +644 -0
- vortex/nwp/data/obs.py +748 -0
- vortex/nwp/data/oopsexec.py +72 -0
- vortex/nwp/data/providers.py +182 -0
- vortex/nwp/data/query.py +217 -0
- vortex/nwp/data/stores.py +147 -0
- vortex/nwp/data/surfex.py +338 -0
- vortex/nwp/syntax/__init__.py +9 -0
- vortex/nwp/syntax/stdattrs.py +375 -0
- vortex/nwp/tools/__init__.py +10 -0
- vortex/nwp/tools/addons.py +35 -0
- vortex/nwp/tools/agt.py +55 -0
- vortex/nwp/tools/bdap.py +48 -0
- vortex/nwp/tools/bdcp.py +38 -0
- vortex/nwp/tools/bdm.py +21 -0
- vortex/nwp/tools/bdmp.py +49 -0
- vortex/nwp/tools/conftools.py +1311 -0
- vortex/nwp/tools/drhook.py +62 -0
- vortex/nwp/tools/grib.py +268 -0
- vortex/nwp/tools/gribdiff.py +99 -0
- vortex/nwp/tools/ifstools.py +163 -0
- vortex/nwp/tools/igastuff.py +249 -0
- vortex/nwp/tools/mars.py +56 -0
- vortex/nwp/tools/odb.py +548 -0
- vortex/nwp/tools/partitioning.py +234 -0
- vortex/nwp/tools/satrad.py +56 -0
- vortex/nwp/util/__init__.py +6 -0
- vortex/nwp/util/async.py +184 -0
- vortex/nwp/util/beacon.py +40 -0
- vortex/nwp/util/diffpygram.py +359 -0
- vortex/nwp/util/ens.py +198 -0
- vortex/nwp/util/hooks.py +128 -0
- vortex/nwp/util/taskdeco.py +81 -0
- vortex/nwp/util/usepygram.py +591 -0
- vortex/nwp/util/usetnt.py +87 -0
- vortex/proxy.py +6 -0
- vortex/sessions.py +341 -0
- vortex/syntax/__init__.py +9 -0
- vortex/syntax/stdattrs.py +628 -0
- vortex/syntax/stddeco.py +176 -0
- vortex/toolbox.py +982 -0
- vortex/tools/__init__.py +11 -0
- vortex/tools/actions.py +457 -0
- vortex/tools/addons.py +297 -0
- vortex/tools/arm.py +76 -0
- vortex/tools/compression.py +322 -0
- vortex/tools/date.py +20 -0
- vortex/tools/ddhpack.py +10 -0
- vortex/tools/delayedactions.py +672 -0
- vortex/tools/env.py +513 -0
- vortex/tools/folder.py +663 -0
- vortex/tools/grib.py +559 -0
- vortex/tools/lfi.py +746 -0
- vortex/tools/listings.py +354 -0
- vortex/tools/names.py +575 -0
- vortex/tools/net.py +1790 -0
- vortex/tools/odb.py +10 -0
- vortex/tools/parallelism.py +336 -0
- vortex/tools/prestaging.py +186 -0
- vortex/tools/rawfiles.py +10 -0
- vortex/tools/schedulers.py +413 -0
- vortex/tools/services.py +871 -0
- vortex/tools/storage.py +1061 -0
- vortex/tools/surfex.py +61 -0
- vortex/tools/systems.py +3396 -0
- vortex/tools/targets.py +384 -0
- vortex/util/__init__.py +9 -0
- vortex/util/config.py +1071 -0
- vortex/util/empty.py +24 -0
- vortex/util/helpers.py +184 -0
- vortex/util/introspection.py +63 -0
- vortex/util/iosponge.py +76 -0
- vortex/util/roles.py +51 -0
- vortex/util/storefunctions.py +103 -0
- vortex/util/structs.py +26 -0
- vortex/util/worker.py +150 -0
- vortex_nwp-2.0.0b1.dist-info/LICENSE +517 -0
- vortex_nwp-2.0.0b1.dist-info/METADATA +50 -0
- vortex_nwp-2.0.0b1.dist-info/RECORD +146 -0
- vortex_nwp-2.0.0b1.dist-info/WHEEL +5 -0
- 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)
|