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
vortex/nwp/algo/clim.py
ADDED
|
@@ -0,0 +1,920 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AlgoComponents to build model's climatology files.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
|
|
7
|
+
from bronx.datagrip import namelist
|
|
8
|
+
from bronx.fancies import loggers
|
|
9
|
+
import footprints
|
|
10
|
+
|
|
11
|
+
from vortex.algo.components import BlindRun, AlgoComponent, Parallel, TaylorRun
|
|
12
|
+
from vortex.data.geometries import HorizontalGeometry
|
|
13
|
+
from vortex.tools.grib import EcGribDecoMixin
|
|
14
|
+
from vortex.tools.parallelism import TaylorVortexWorker
|
|
15
|
+
from .ifsroot import IFSParallel
|
|
16
|
+
from ..tools.drhook import DrHookDecoMixin
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
#: No automatic export
|
|
20
|
+
__all__ = []
|
|
21
|
+
|
|
22
|
+
logger = loggers.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BuildPGD(BlindRun, DrHookDecoMixin, EcGribDecoMixin):
|
|
26
|
+
"""Preparation of physiographic fields for Surfex."""
|
|
27
|
+
|
|
28
|
+
_footprint = dict(
|
|
29
|
+
info = "Physiographic fields for Surfex.",
|
|
30
|
+
attr = dict(
|
|
31
|
+
kind = dict(
|
|
32
|
+
values = ['buildpgd'],
|
|
33
|
+
),
|
|
34
|
+
)
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class BuildPGD_MPI(Parallel, DrHookDecoMixin, EcGribDecoMixin):
|
|
39
|
+
"""Preparation of physiographic fields for Surfex."""
|
|
40
|
+
|
|
41
|
+
_footprint = dict(
|
|
42
|
+
info = "Physiographic fields for Surfex.",
|
|
43
|
+
attr = dict(
|
|
44
|
+
kind = dict(
|
|
45
|
+
values = ['buildpgd'],
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class C923(IFSParallel):
|
|
52
|
+
"""Preparation of climatologic fields."""
|
|
53
|
+
|
|
54
|
+
_footprint = dict(
|
|
55
|
+
info = "Climatologic fields for Arpege/Arome.",
|
|
56
|
+
attr = dict(
|
|
57
|
+
kind = dict(
|
|
58
|
+
values = ['c923'],
|
|
59
|
+
),
|
|
60
|
+
step = dict(
|
|
61
|
+
info = """Step of conf 923 (NAMCLI::N923).
|
|
62
|
+
Defines the kind of fields and database processed.""",
|
|
63
|
+
type = int,
|
|
64
|
+
values = footprints.util.rangex(1, 10)
|
|
65
|
+
),
|
|
66
|
+
orog_in_pgd = dict(
|
|
67
|
+
info = """Whether orography may be read in a PGD file.
|
|
68
|
+
(NAMCLA::LIPGD=.TRUE.)""",
|
|
69
|
+
type = bool,
|
|
70
|
+
optional = True,
|
|
71
|
+
default = False,
|
|
72
|
+
),
|
|
73
|
+
input_orog_name = dict(
|
|
74
|
+
info = "Filename for input orography file (case LNORO=.T.).",
|
|
75
|
+
optional = True,
|
|
76
|
+
default = 'Neworog',
|
|
77
|
+
),
|
|
78
|
+
xpname = dict(
|
|
79
|
+
default = 'CLIM',
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def prepare(self, rh, opts):
|
|
85
|
+
super().prepare(rh, opts)
|
|
86
|
+
# check PGD if needed
|
|
87
|
+
if self.orog_in_pgd:
|
|
88
|
+
pgd = self.context.sequence.effective_inputs(role=('Pgd',))
|
|
89
|
+
if len(pgd) == 0:
|
|
90
|
+
raise ValueError("As long as 'orog_in_pgd' attribute of this " +
|
|
91
|
+
"algo component is True, a 'Role: Pgd' " +
|
|
92
|
+
"resource must be provided.")
|
|
93
|
+
pgd = pgd[0].rh
|
|
94
|
+
if pgd.resource.nativefmt == 'fa':
|
|
95
|
+
self.algoassert(
|
|
96
|
+
pgd.container.basename == self.input_orog_name,
|
|
97
|
+
"Local name for resource Pgd must be '{}'".
|
|
98
|
+
format(self.input_orog_name))
|
|
99
|
+
elif pgd.resource.nativefmt == 'lfi':
|
|
100
|
+
raise NotImplementedError('CY43T2 onwards: lfi PGD should not be used.')
|
|
101
|
+
|
|
102
|
+
def find_namelists(self, opts=None):
|
|
103
|
+
namrh_list = [x.rh
|
|
104
|
+
for x in self.context.sequence.effective_inputs(role=('Namelist',))]
|
|
105
|
+
self.algoassert(len(namrh_list) == 1,
|
|
106
|
+
"One and only one namelist necessary as input.")
|
|
107
|
+
return namrh_list
|
|
108
|
+
|
|
109
|
+
def prepare_namelist_delta(self, rh, namcontents, namlocal):
|
|
110
|
+
super().prepare_namelist_delta(rh, namcontents, namlocal)
|
|
111
|
+
namcontents['NAMMCC']['N923'] = self.step
|
|
112
|
+
namcontents.setmacro('LPGD', self.orog_in_pgd)
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class FinalizePGD(AlgoComponent):
|
|
117
|
+
"""
|
|
118
|
+
Finalise PGD file: report spectrally optimized orography from Clim to PGD,
|
|
119
|
+
and add E-zone.
|
|
120
|
+
|
|
121
|
+
.. deprecated:: since Vortex 1.3.0, use :class:`SetFilteredOrogInPGD` instead.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
_footprint = dict(
|
|
125
|
+
info = "Finalisation of PGD.",
|
|
126
|
+
attr = dict(
|
|
127
|
+
kind = dict(
|
|
128
|
+
values = ['finalize_pgd'],
|
|
129
|
+
),
|
|
130
|
+
pgd_out_name = dict(
|
|
131
|
+
optional = True,
|
|
132
|
+
default = 'PGD_final.fa'
|
|
133
|
+
),
|
|
134
|
+
)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def __init__(self, *args, **kwargs):
|
|
138
|
+
super().__init__(*args, **kwargs)
|
|
139
|
+
from ..util.usepygram import epygram_checker
|
|
140
|
+
ev = '1.2.14'
|
|
141
|
+
self.algoassert(epygram_checker.is_available(version=ev), "Epygram >= " + ev +
|
|
142
|
+
" is needed here")
|
|
143
|
+
|
|
144
|
+
def execute(self, rh, opts): # @UnusedVariable
|
|
145
|
+
"""Convert SURFGEOPOTENTIEL from clim to SFX.ZS in pgd."""
|
|
146
|
+
import numpy
|
|
147
|
+
from ..util.usepygram import epygram, epy_env_prepare
|
|
148
|
+
from bronx.meteo.constants import g0
|
|
149
|
+
# Handle resources
|
|
150
|
+
clim = self.context.sequence.effective_inputs(role=('Clim',))
|
|
151
|
+
self.algoassert(len(clim) == 1, "One and only one Clim has to be provided")
|
|
152
|
+
pgdin = self.context.sequence.effective_inputs(role=('InputPGD',))
|
|
153
|
+
self.algoassert(len(pgdin) == 1, "One and only one InputPGD has to be provided")
|
|
154
|
+
if self.system.path.exists(self.pgd_out_name):
|
|
155
|
+
raise OSError("The output pgd file {!r} already exists.".format(self.pgd_out_name))
|
|
156
|
+
# copy fields
|
|
157
|
+
with epy_env_prepare(self.ticket):
|
|
158
|
+
epyclim = clim[0].rh.contents.data
|
|
159
|
+
epypgd = pgdin[0].rh.contents.data
|
|
160
|
+
epyclim.open()
|
|
161
|
+
epypgd.open()
|
|
162
|
+
pgdout = epygram.formats.resource(self.pgd_out_name, 'w', fmt='FA',
|
|
163
|
+
headername=epyclim.headername,
|
|
164
|
+
geometry=epyclim.geometry,
|
|
165
|
+
cdiden=epypgd.cdiden,
|
|
166
|
+
validity=epypgd.validity,
|
|
167
|
+
processtype=epypgd.processtype)
|
|
168
|
+
g = epyclim.readfield('SURFGEOPOTENTIEL')
|
|
169
|
+
g.operation('/', g0)
|
|
170
|
+
g.fid['FA'] = 'SFX.ZS'
|
|
171
|
+
for f in epypgd.listfields():
|
|
172
|
+
fld = epypgd.readfield(f)
|
|
173
|
+
if f == 'SFX.ZS':
|
|
174
|
+
fld = g
|
|
175
|
+
elif (isinstance(fld, epygram.fields.H2DField) and
|
|
176
|
+
fld.geometry.grid.get('LAMzone') is not None):
|
|
177
|
+
ext_data = numpy.ma.masked_equal(numpy.zeros(g.data.shape), 0.)
|
|
178
|
+
ext_data[:fld.geometry.dimensions['Y'],
|
|
179
|
+
:fld.geometry.dimensions['X']] = fld.data[:, :]
|
|
180
|
+
fld = footprints.proxy.fields.almost_clone(fld, geometry=g.geometry)
|
|
181
|
+
fld.setdata(ext_data)
|
|
182
|
+
pgdout.writefield(fld, compression=epypgd.fieldscompression.get(f, None))
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class SetFilteredOrogInPGD(AlgoComponent):
|
|
186
|
+
"""
|
|
187
|
+
Report spectrally optimized, filtered orography from Clim to PGD.
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
_footprint = dict(
|
|
191
|
+
info = "Report spectrally optimized, filtered orography from Clim to PGD.",
|
|
192
|
+
attr = dict(
|
|
193
|
+
kind = dict(
|
|
194
|
+
values = ['set_filtered_orog_in_pgd'],
|
|
195
|
+
),
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def __init__(self, *args, **kwargs):
|
|
200
|
+
super().__init__(*args, **kwargs)
|
|
201
|
+
from ..util.usepygram import epygram_checker
|
|
202
|
+
ev = '1.3.2'
|
|
203
|
+
self.algoassert(epygram_checker.is_available(version=ev), "Epygram >= " + ev +
|
|
204
|
+
" is needed here")
|
|
205
|
+
|
|
206
|
+
def execute(self, rh, opts): # @UnusedVariable
|
|
207
|
+
"""Convert SURFGEOPOTENTIEL from clim to SFX.ZS in pgd."""
|
|
208
|
+
from ..util.usepygram import epygram_checker, epy_env_prepare
|
|
209
|
+
from bronx.meteo.constants import g0
|
|
210
|
+
# Handle resources
|
|
211
|
+
clim = self.context.sequence.effective_inputs(role=('Clim',))
|
|
212
|
+
self.algoassert(len(clim) == 1, "One and only one Clim to be provided")
|
|
213
|
+
pgdin = self.context.sequence.effective_inputs(role=('InputPGD',))
|
|
214
|
+
self.algoassert(len(pgdin) == 1, "One and only one InputPGD to be provided")
|
|
215
|
+
# copy fields
|
|
216
|
+
with epy_env_prepare(self.ticket):
|
|
217
|
+
epyclim = clim[0].rh.contents.data
|
|
218
|
+
epypgd = pgdin[0].rh.contents.data
|
|
219
|
+
epyclim.open()
|
|
220
|
+
epypgd.open(openmode='a')
|
|
221
|
+
# read spectrally fitted surface geopotential
|
|
222
|
+
g = epyclim.readfield('SURFGEOPOTENTIEL')
|
|
223
|
+
# convert to SURFEX orography
|
|
224
|
+
g.operation('/', g0)
|
|
225
|
+
g.fid['FA'] = 'SFX.ZS'
|
|
226
|
+
# write as orography
|
|
227
|
+
if epygram_checker.is_available(version='1.3.6'):
|
|
228
|
+
epypgd.fieldencoding(g.fid['FA'], update_fieldscompression=True)
|
|
229
|
+
else:
|
|
230
|
+
# blank read, just to update fieldscompression
|
|
231
|
+
epypgd.readfield(g.fid['FA'], getdata=False)
|
|
232
|
+
epypgd.writefield(g, compression=epypgd.fieldscompression.get(g.fid['FA'], None))
|
|
233
|
+
epypgd.close()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class MakeLAMDomain(AlgoComponent):
|
|
237
|
+
"""
|
|
238
|
+
Wrapper to call Epygram domain making functions and generate
|
|
239
|
+
namelist deltas for geometry (BuildPGD & C923).
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
_footprint = dict(
|
|
243
|
+
attr = dict(
|
|
244
|
+
kind = dict(
|
|
245
|
+
values = ['make_domain', 'make_lam_domain'],
|
|
246
|
+
),
|
|
247
|
+
geometry = dict(
|
|
248
|
+
info = "The horizontal geometry to be generated.",
|
|
249
|
+
type = HorizontalGeometry,
|
|
250
|
+
),
|
|
251
|
+
mode = dict(
|
|
252
|
+
info = ("Kind of input for building geometry:" +
|
|
253
|
+
"'center_dims' to build domain given its centre and" +
|
|
254
|
+
"dimensions; 'lonlat_included' to build domain given" +
|
|
255
|
+
"an included lon/lat area."),
|
|
256
|
+
values = ['center_dims', 'lonlat_included']
|
|
257
|
+
),
|
|
258
|
+
geom_params = dict(
|
|
259
|
+
info = ("Set of parameters and/or options to be passed to" +
|
|
260
|
+
"epygram.geometries.domain_making.build.build_geometry()" +
|
|
261
|
+
"or" +
|
|
262
|
+
"epygram.geometries.domain_making.build.build_geometry_fromlonlat()"),
|
|
263
|
+
type = footprints.FPDict,
|
|
264
|
+
),
|
|
265
|
+
truncation = dict(
|
|
266
|
+
info = ("Type of spectral truncation, among" +
|
|
267
|
+
"('linear', 'quadratic', 'cubic')."),
|
|
268
|
+
optional = True,
|
|
269
|
+
default = 'linear',
|
|
270
|
+
),
|
|
271
|
+
orography_truncation = dict(
|
|
272
|
+
info = ("Type of truncation of orography, among" +
|
|
273
|
+
"('linear', 'quadratic', 'cubic')."),
|
|
274
|
+
optional = True,
|
|
275
|
+
default = 'quadratic',
|
|
276
|
+
),
|
|
277
|
+
e_zone_in_pgd = dict(
|
|
278
|
+
info = "Add E-zone sizes in BuildPGD namelist.",
|
|
279
|
+
optional = True,
|
|
280
|
+
type = bool,
|
|
281
|
+
default = False
|
|
282
|
+
),
|
|
283
|
+
i_width_in_pgd = dict(
|
|
284
|
+
info = "Add I-width size in BuildPGD namelist.",
|
|
285
|
+
optional = True,
|
|
286
|
+
type = bool,
|
|
287
|
+
default = False
|
|
288
|
+
),
|
|
289
|
+
# plot
|
|
290
|
+
illustration = dict(
|
|
291
|
+
info = "Create the domain illustration image.",
|
|
292
|
+
type = bool,
|
|
293
|
+
optional = True,
|
|
294
|
+
default = True
|
|
295
|
+
),
|
|
296
|
+
illustration_fmt = dict(
|
|
297
|
+
info = "The format of the domain illustration image.",
|
|
298
|
+
values = ['png', 'pdf'],
|
|
299
|
+
optional = True,
|
|
300
|
+
default = 'png'
|
|
301
|
+
),
|
|
302
|
+
plot_params = dict(
|
|
303
|
+
info = "Plot geometry parameters.",
|
|
304
|
+
type = footprints.FPDict,
|
|
305
|
+
optional = True,
|
|
306
|
+
default = footprints.FPDict({'background': True})
|
|
307
|
+
),
|
|
308
|
+
)
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
def __init__(self, *args, **kwargs):
|
|
312
|
+
super().__init__(*args, **kwargs)
|
|
313
|
+
from ..util.usepygram import epygram_checker
|
|
314
|
+
ev = '1.2.14'
|
|
315
|
+
if self.e_zone_in_pgd:
|
|
316
|
+
ev = '1.3.2'
|
|
317
|
+
if self.i_width_in_pgd:
|
|
318
|
+
ev = '1.3.3'
|
|
319
|
+
self.algoassert(epygram_checker.is_available(version=ev), "Epygram >= " + ev +
|
|
320
|
+
" is needed here")
|
|
321
|
+
self._check_geometry()
|
|
322
|
+
|
|
323
|
+
def _check_geometry(self):
|
|
324
|
+
if self.mode == 'center_dims':
|
|
325
|
+
params = ['center_lon', 'center_lat', 'Xpoints_CI', 'Ypoints_CI',
|
|
326
|
+
'resolution']
|
|
327
|
+
params_extended = params + ['tilting', 'Iwidth', 'force_projection',
|
|
328
|
+
'maximize_CI_in_E', 'reference_lat']
|
|
329
|
+
elif self.mode == 'lonlat_included':
|
|
330
|
+
params = ['lonmin', 'lonmax', 'latmin', 'latmax',
|
|
331
|
+
'resolution']
|
|
332
|
+
params_extended = params + ['Iwidth', 'force_projection', 'maximize_CI_in_E']
|
|
333
|
+
self.algoassert(set(params).issubset(set(self.geom_params.keys())),
|
|
334
|
+
"With mode=={!s}, geom_params must contain at least {!s}".
|
|
335
|
+
format(self.mode, params))
|
|
336
|
+
self.algoassert(set(self.geom_params.keys()).issubset(set(params_extended)),
|
|
337
|
+
"With mode=={!s}, geom_params must contain at most {!s}".
|
|
338
|
+
format(self.mode, params))
|
|
339
|
+
|
|
340
|
+
def execute(self, rh, opts): # @UnusedVariable
|
|
341
|
+
from ..util.usepygram import epygram
|
|
342
|
+
dm = epygram.geometries.domain_making
|
|
343
|
+
if self.mode == 'center_dims':
|
|
344
|
+
build_func = dm.build.build_geometry
|
|
345
|
+
lonlat_included = None
|
|
346
|
+
elif self.mode == 'lonlat_included':
|
|
347
|
+
build_func = dm.build.build_geometry_fromlonlat
|
|
348
|
+
lonlat_included = self.geom_params
|
|
349
|
+
# build geometry
|
|
350
|
+
geometry = build_func(interactive=False, **self.geom_params)
|
|
351
|
+
# summary, plot, namelists:
|
|
352
|
+
with open(self.geometry.tag + '_summary.txt', 'w') as o:
|
|
353
|
+
o.write(str(dm.output.summary(geometry)))
|
|
354
|
+
if self.illustration:
|
|
355
|
+
dm.output.plot_geometry(geometry,
|
|
356
|
+
lonlat_included=lonlat_included,
|
|
357
|
+
out='.'.join([self.geometry.tag,
|
|
358
|
+
self.illustration_fmt]),
|
|
359
|
+
**self.plot_params)
|
|
360
|
+
dm_extra_params = dict()
|
|
361
|
+
if self.e_zone_in_pgd:
|
|
362
|
+
dm_extra_params['Ezone_in_pgd'] = self.e_zone_in_pgd
|
|
363
|
+
if self.i_width_in_pgd:
|
|
364
|
+
dm_extra_params['Iwidth_in_pgd'] = self.i_width_in_pgd
|
|
365
|
+
namelists = dm.output.lam_geom2namelists(geometry,
|
|
366
|
+
truncation=self.truncation,
|
|
367
|
+
orography_subtruncation=self.orography_truncation,
|
|
368
|
+
**dm_extra_params)
|
|
369
|
+
dm.output.write_namelists(namelists, prefix=self.geometry.tag)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class MakeGaussGeometry(Parallel):
|
|
373
|
+
"""
|
|
374
|
+
Wrapper to call Gauss geometry making RGRID and generate
|
|
375
|
+
namelist deltas for geometry (BuildPGD & C923).
|
|
376
|
+
"""
|
|
377
|
+
|
|
378
|
+
_footprint = dict(
|
|
379
|
+
attr = dict(
|
|
380
|
+
kind = dict(
|
|
381
|
+
values = ['make_gauss_grid'],
|
|
382
|
+
),
|
|
383
|
+
geometry = dict(
|
|
384
|
+
info = "The vortex horizontal geometry to be generated.",
|
|
385
|
+
type = HorizontalGeometry,
|
|
386
|
+
),
|
|
387
|
+
truncation = dict(
|
|
388
|
+
info = 'nominal truncation',
|
|
389
|
+
type = int,
|
|
390
|
+
),
|
|
391
|
+
grid = dict(
|
|
392
|
+
info = 'type of grid with regards to truncation, among (linear, quadratic, cubic)',
|
|
393
|
+
optional = True,
|
|
394
|
+
default = 'linear'
|
|
395
|
+
),
|
|
396
|
+
orography_grid = dict(
|
|
397
|
+
info = 'orography subtruncation (linear, quadratic, cubic)',
|
|
398
|
+
optional = True,
|
|
399
|
+
default = 'quadratic'
|
|
400
|
+
),
|
|
401
|
+
stretching = dict(
|
|
402
|
+
info = 'stretching factor',
|
|
403
|
+
type = float,
|
|
404
|
+
optional = True,
|
|
405
|
+
default = 1.,
|
|
406
|
+
),
|
|
407
|
+
pole = dict(
|
|
408
|
+
info = 'pole of stretching (lon, lat), angles in degrees',
|
|
409
|
+
type = footprints.FPDict,
|
|
410
|
+
optional = True,
|
|
411
|
+
default = {'lon': 0., 'lat': 90.}
|
|
412
|
+
),
|
|
413
|
+
# RGRID commandline options
|
|
414
|
+
latitudes = dict(
|
|
415
|
+
info = 'number of Gaussian latitudes',
|
|
416
|
+
type = int,
|
|
417
|
+
optional = True,
|
|
418
|
+
default = None
|
|
419
|
+
),
|
|
420
|
+
longitudes = dict(
|
|
421
|
+
info = 'maximum (equatorial) number of longitudes',
|
|
422
|
+
type = int,
|
|
423
|
+
optional = True,
|
|
424
|
+
default = None
|
|
425
|
+
),
|
|
426
|
+
orthogonality = dict(
|
|
427
|
+
info = 'orthogonality precision, as Log10() value',
|
|
428
|
+
type = int,
|
|
429
|
+
optional = True,
|
|
430
|
+
default = None
|
|
431
|
+
),
|
|
432
|
+
aliasing = dict(
|
|
433
|
+
info = 'allowed aliasing, as a Log10() value',
|
|
434
|
+
type = int,
|
|
435
|
+
optional = True,
|
|
436
|
+
default = None
|
|
437
|
+
),
|
|
438
|
+
oddity = dict(
|
|
439
|
+
info = 'odd numbers allowed (1) or not (0)',
|
|
440
|
+
type = int,
|
|
441
|
+
optional = True,
|
|
442
|
+
default = None
|
|
443
|
+
),
|
|
444
|
+
verbosity = dict(
|
|
445
|
+
info = 'verbosity (0 or 1)',
|
|
446
|
+
type = int,
|
|
447
|
+
optional = True,
|
|
448
|
+
default = None
|
|
449
|
+
),
|
|
450
|
+
# plot
|
|
451
|
+
illustration = dict(
|
|
452
|
+
info = "Create the domain illustration image.",
|
|
453
|
+
type = bool,
|
|
454
|
+
optional = True,
|
|
455
|
+
default = True
|
|
456
|
+
),
|
|
457
|
+
illustration_fmt = dict(
|
|
458
|
+
info = "The format of the domain illustration image.",
|
|
459
|
+
values = ['png', 'pdf'],
|
|
460
|
+
optional = True,
|
|
461
|
+
default = 'png'
|
|
462
|
+
),
|
|
463
|
+
plot_params = dict(
|
|
464
|
+
info = "Plot geometry parameters.",
|
|
465
|
+
type = footprints.FPDict,
|
|
466
|
+
optional = True,
|
|
467
|
+
default = footprints.FPDict({'background': True})
|
|
468
|
+
),
|
|
469
|
+
)
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
def __init__(self, *args, **kwargs):
|
|
473
|
+
super().__init__(*args, **kwargs)
|
|
474
|
+
from ..util.usepygram import epygram_checker
|
|
475
|
+
ev = '1.2.14'
|
|
476
|
+
self.algoassert(epygram_checker.is_available(version=ev), "Epygram >= " + ev +
|
|
477
|
+
" is needed here")
|
|
478
|
+
self._complete_dimensions()
|
|
479
|
+
self._unit = 4
|
|
480
|
+
|
|
481
|
+
def _complete_dimensions(self):
|
|
482
|
+
from ..util.usepygram import epygram_checker
|
|
483
|
+
if epygram_checker.is_available(version='1.4.4'):
|
|
484
|
+
from epygram.geometries.SpectralGeometry import complete_gridpoint_dimensions
|
|
485
|
+
longitudes, latitudes = complete_gridpoint_dimensions(self.longitudes,
|
|
486
|
+
self.latitudes,
|
|
487
|
+
self.truncation,
|
|
488
|
+
self.grid,
|
|
489
|
+
self.stretching)
|
|
490
|
+
self._attributes['longitudes'] = longitudes
|
|
491
|
+
self._attributes['latitudes'] = latitudes
|
|
492
|
+
else:
|
|
493
|
+
self._old_internal_complete_dimensions()
|
|
494
|
+
|
|
495
|
+
def _old_internal_complete_dimensions(self):
|
|
496
|
+
from epygram.geometries.SpectralGeometry import gridpoint_dims_from_truncation
|
|
497
|
+
if self.latitudes is None and self.longitudes is None:
|
|
498
|
+
dims = gridpoint_dims_from_truncation({'max': self.truncation},
|
|
499
|
+
grid=self.grid)
|
|
500
|
+
self._attributes['latitudes'] = dims['lat_number']
|
|
501
|
+
self._attributes['longitudes'] = dims['max_lon_number']
|
|
502
|
+
elif self.longitudes is None:
|
|
503
|
+
self._attributes['longitudes'] = 2 * self.latitudes
|
|
504
|
+
elif self.latitudes is None:
|
|
505
|
+
if self.longitudes % 4 != 0:
|
|
506
|
+
self._attributes['latitudes'] = self.longitudes // 2 + 1
|
|
507
|
+
else:
|
|
508
|
+
self._attributes['latitudes'] = self.longitudes // 2
|
|
509
|
+
|
|
510
|
+
def spawn_command_options(self):
|
|
511
|
+
"""Prepare options for the resource's command line."""
|
|
512
|
+
options = {'t': str(self.truncation),
|
|
513
|
+
'g': str(self.latitudes),
|
|
514
|
+
'l': str(self.longitudes),
|
|
515
|
+
'f': str(self._unit)}
|
|
516
|
+
options_dict = {'orthogonality': 'o',
|
|
517
|
+
'aliasing': 'a',
|
|
518
|
+
'oddity': 'n',
|
|
519
|
+
'verbosity': 'v'}
|
|
520
|
+
for k in options_dict.keys():
|
|
521
|
+
if getattr(self, k) is not None:
|
|
522
|
+
options[options_dict[k]] = str(getattr(self, k))
|
|
523
|
+
return options
|
|
524
|
+
|
|
525
|
+
def postfix(self, rh, opts):
|
|
526
|
+
"""Complete and write namelists."""
|
|
527
|
+
from ..util.usepygram import epygram_checker
|
|
528
|
+
if epygram_checker.is_available(version='1.4.4'):
|
|
529
|
+
from epygram.geometries.domain_making.output import gauss_rgrid2namelists
|
|
530
|
+
gauss_rgrid2namelists('fort.{!s}'.format(self._unit),
|
|
531
|
+
self.geometry.tag,
|
|
532
|
+
self.latitudes,
|
|
533
|
+
self.longitudes,
|
|
534
|
+
self.truncation,
|
|
535
|
+
self.stretching,
|
|
536
|
+
self.orography_grid,
|
|
537
|
+
self.pole)
|
|
538
|
+
else:
|
|
539
|
+
self._old_internal_postfix(rh, opts)
|
|
540
|
+
super().postfix(rh, opts)
|
|
541
|
+
|
|
542
|
+
def _old_internal_postfix(self, rh, opts):
|
|
543
|
+
"""Complete and write namelists."""
|
|
544
|
+
import math
|
|
545
|
+
from epygram.geometries.SpectralGeometry import truncation_from_gridpoint_dims
|
|
546
|
+
# complete scalar parameters
|
|
547
|
+
nam = namelist.NamelistSet()
|
|
548
|
+
nam.add(namelist.NamelistBlock('NAM_PGD_GRID'))
|
|
549
|
+
nam.add(namelist.NamelistBlock('NAMDIM'))
|
|
550
|
+
nam.add(namelist.NamelistBlock('NAMGEM'))
|
|
551
|
+
nam['NAM_PGD_GRID']['CGRID'] = 'GAUSS'
|
|
552
|
+
nam['NAMDIM']['NDGLG'] = self.latitudes
|
|
553
|
+
nam['NAMDIM']['NDLON'] = self.longitudes
|
|
554
|
+
nam['NAMDIM']['NSMAX'] = self.truncation
|
|
555
|
+
nam['NAMGEM']['NHTYP'] = 2
|
|
556
|
+
nam['NAMGEM']['NSTTYP'] = 2 if self.pole != {'lon': 0., 'lat': 90.} else 1
|
|
557
|
+
nam['NAMGEM']['RMUCEN'] = math.sin(math.radians(float(self.pole['lat'])))
|
|
558
|
+
nam['NAMGEM']['RLOCEN'] = math.radians(float(self.pole['lon']))
|
|
559
|
+
nam['NAMGEM']['RSTRET'] = self.stretching
|
|
560
|
+
# numbers of longitudes
|
|
561
|
+
with open('fort.{!s}'.format(self._unit)) as n:
|
|
562
|
+
namrgri = namelist.namparse(n)
|
|
563
|
+
nam.merge(namrgri)
|
|
564
|
+
# PGD namelist
|
|
565
|
+
nam_pgd = copy.deepcopy(nam)
|
|
566
|
+
nam_pgd['NAMGEM'].delvar('NHTYP')
|
|
567
|
+
nam_pgd['NAMGEM'].delvar('NSTTYP')
|
|
568
|
+
nam_pgd['NAMDIM'].delvar('NSMAX')
|
|
569
|
+
nam_pgd['NAMDIM'].delvar('NDLON')
|
|
570
|
+
with open('.'.join([self.geometry.tag,
|
|
571
|
+
'namel_buildpgd',
|
|
572
|
+
'geoblocks']),
|
|
573
|
+
'w') as out:
|
|
574
|
+
out.write(nam_pgd.dumps(sorting=namelist.SECOND_ORDER_SORTING))
|
|
575
|
+
# C923 namelist
|
|
576
|
+
del nam['NAM_PGD_GRID']
|
|
577
|
+
with open('.'.join([self.geometry.tag,
|
|
578
|
+
'namel_c923',
|
|
579
|
+
'geoblocks']),
|
|
580
|
+
'w') as out:
|
|
581
|
+
out.write(nam.dumps(sorting=namelist.SECOND_ORDER_SORTING))
|
|
582
|
+
# subtruncated grid for orography
|
|
583
|
+
from ..util.usepygram import epygram_checker
|
|
584
|
+
ev = '1.4.4'
|
|
585
|
+
if epygram_checker.is_available(version=ev):
|
|
586
|
+
trunc_nsmax = truncation_from_gridpoint_dims({'lat_number': self.latitudes,
|
|
587
|
+
'max_lon_number': self.longitudes},
|
|
588
|
+
grid=self.orography_grid,
|
|
589
|
+
stretching_coef=self.stretching
|
|
590
|
+
)['max']
|
|
591
|
+
else:
|
|
592
|
+
trunc_nsmax = truncation_from_gridpoint_dims({'lat_number': self.latitudes,
|
|
593
|
+
'max_lon_number': self.longitudes},
|
|
594
|
+
grid=self.orography_grid
|
|
595
|
+
)['max']
|
|
596
|
+
nam['NAMDIM']['NSMAX'] = trunc_nsmax
|
|
597
|
+
with open('.'.join([self.geometry.tag,
|
|
598
|
+
'namel_c923_orography',
|
|
599
|
+
'geoblocks']),
|
|
600
|
+
'w') as out:
|
|
601
|
+
out.write(nam.dumps(sorting=namelist.SECOND_ORDER_SORTING))
|
|
602
|
+
# C927 (fullpos) namelist
|
|
603
|
+
nam = namelist.NamelistSet()
|
|
604
|
+
nam.add(namelist.NamelistBlock('NAMFPD'))
|
|
605
|
+
nam.add(namelist.NamelistBlock('NAMFPG'))
|
|
606
|
+
nam['NAMFPD']['NLAT'] = self.latitudes
|
|
607
|
+
nam['NAMFPD']['NLON'] = self.longitudes
|
|
608
|
+
nam['NAMFPG']['NFPMAX'] = self.truncation
|
|
609
|
+
nam['NAMFPG']['NFPHTYP'] = 2
|
|
610
|
+
nam['NAMFPG']['NFPTTYP'] = 2 if self.pole != {'lon': 0., 'lat': 90.} else 1
|
|
611
|
+
nam['NAMFPG']['FPMUCEN'] = math.sin(math.radians(float(self.pole['lat'])))
|
|
612
|
+
nam['NAMFPG']['FPLOCEN'] = math.radians(float(self.pole['lon']))
|
|
613
|
+
nam['NAMFPG']['FPSTRET'] = self.stretching
|
|
614
|
+
nrgri = [v for _, v in sorted(namrgri['NAMRGRI'].items())]
|
|
615
|
+
for i in range(len(nrgri)):
|
|
616
|
+
nam['NAMFPG']['NFPRGRI({:>4})'.format(i + 1)] = nrgri[i]
|
|
617
|
+
with open('.'.join([self.geometry.tag,
|
|
618
|
+
'namel_c927',
|
|
619
|
+
'geoblocks']),
|
|
620
|
+
'w') as out:
|
|
621
|
+
out.write(nam.dumps(sorting=namelist.SECOND_ORDER_SORTING))
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
class MakeBDAPDomain(AlgoComponent):
|
|
625
|
+
"""
|
|
626
|
+
Wrapper to call Epygram domain making functions and generate
|
|
627
|
+
namelist deltas for BDAP (lonlat) geometry (BuildPGD & C923).
|
|
628
|
+
"""
|
|
629
|
+
|
|
630
|
+
_footprint = dict(
|
|
631
|
+
attr = dict(
|
|
632
|
+
kind = dict(
|
|
633
|
+
values = ['make_domain', 'make_bdap_domain'],
|
|
634
|
+
),
|
|
635
|
+
geometry = dict(
|
|
636
|
+
info = "The horizontal geometry to be generated.",
|
|
637
|
+
type = HorizontalGeometry,
|
|
638
|
+
),
|
|
639
|
+
mode = dict(
|
|
640
|
+
info = ("Kind of input for building geometry:" +
|
|
641
|
+
"'boundaries' to build domain given its lon/lat boundaries" +
|
|
642
|
+
"(+ resolution); 'inside_model' to build domain given" +
|
|
643
|
+
"a model geometry to be included in (+ resolution)."),
|
|
644
|
+
values = ['boundaries', 'inside_model']
|
|
645
|
+
),
|
|
646
|
+
resolution = dict(
|
|
647
|
+
info = "Resolution in degrees.",
|
|
648
|
+
type = float,
|
|
649
|
+
optional = True,
|
|
650
|
+
default = None,
|
|
651
|
+
),
|
|
652
|
+
resolution_x=dict(
|
|
653
|
+
info="X resolution in degrees (if different from Y).",
|
|
654
|
+
type=float,
|
|
655
|
+
optional = True,
|
|
656
|
+
default = None,
|
|
657
|
+
),
|
|
658
|
+
resolution_y=dict(
|
|
659
|
+
info="Y resolution in degrees (if different from X).",
|
|
660
|
+
type=float,
|
|
661
|
+
optional = True,
|
|
662
|
+
default = None,
|
|
663
|
+
),
|
|
664
|
+
boundaries = dict(
|
|
665
|
+
info = "Lonlat boundaries of the domain, case mode='boundaries'.",
|
|
666
|
+
type = footprints.FPDict,
|
|
667
|
+
optional = True,
|
|
668
|
+
default = None,
|
|
669
|
+
),
|
|
670
|
+
model_clim = dict(
|
|
671
|
+
info = "Filename of the model clim, case mode='inside_model'.",
|
|
672
|
+
optional = True,
|
|
673
|
+
default = None,
|
|
674
|
+
),
|
|
675
|
+
# plot
|
|
676
|
+
illustration = dict(
|
|
677
|
+
info = "Create the domain illustration image.",
|
|
678
|
+
type = bool,
|
|
679
|
+
optional = True,
|
|
680
|
+
default = True
|
|
681
|
+
),
|
|
682
|
+
illustration_fmt = dict(
|
|
683
|
+
info = "The format of the domain illustration image.",
|
|
684
|
+
values = ['png', 'pdf'],
|
|
685
|
+
optional = True,
|
|
686
|
+
default = 'png'
|
|
687
|
+
),
|
|
688
|
+
plot_params = dict(
|
|
689
|
+
info = "Plot geometry parameters.",
|
|
690
|
+
type = footprints.FPDict,
|
|
691
|
+
optional = True,
|
|
692
|
+
default = footprints.FPDict({'background': True})
|
|
693
|
+
),
|
|
694
|
+
)
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
def __init__(self, *args, **kwargs):
|
|
698
|
+
super().__init__(*args, **kwargs)
|
|
699
|
+
from ..util.usepygram import epygram_checker
|
|
700
|
+
ev = '1.2.14'
|
|
701
|
+
self.algoassert(epygram_checker.is_available(version=ev), "Epygram >= " + ev +
|
|
702
|
+
" is needed here")
|
|
703
|
+
if self.mode == 'boundaries':
|
|
704
|
+
params = ['lonmin', 'lonmax', 'latmin', 'latmax']
|
|
705
|
+
self.algoassert(set(params) == set(self.boundaries.keys()),
|
|
706
|
+
"With mode=={}, boundaries must contain at least {}".
|
|
707
|
+
format(self.mode, str(params)))
|
|
708
|
+
if self.model_clim is not None:
|
|
709
|
+
logger.info('attribute *model_clim* ignored')
|
|
710
|
+
elif self.mode == 'inside_model':
|
|
711
|
+
self.algoassert(self.model_clim is not None,
|
|
712
|
+
"attribute *model_clim* must be provided with " +
|
|
713
|
+
"mode=='inside_model'.")
|
|
714
|
+
self.algoassert(self.sh.path.exists(self.model_clim))
|
|
715
|
+
if self.boundaries is not None:
|
|
716
|
+
logger.info('attribute *boundaries* ignored')
|
|
717
|
+
if self.resolution is None:
|
|
718
|
+
self.algoassert(None not in (self.resolution_x, self.resolution_y),
|
|
719
|
+
"Must provide *resolution* OR *resolution_x/resolution_y*")
|
|
720
|
+
else:
|
|
721
|
+
self.algoassert(self.resolution_x is None and self.resolution_y is None,
|
|
722
|
+
"Must provide *resolution* OR *resolution_x/resolution_y*")
|
|
723
|
+
|
|
724
|
+
def execute(self, rh, opts): # @UnusedVariable
|
|
725
|
+
from ..util.usepygram import epygram
|
|
726
|
+
dm = epygram.geometries.domain_making
|
|
727
|
+
if self.mode == 'inside_model':
|
|
728
|
+
r = epygram.formats.resource(self.model_clim, 'r')
|
|
729
|
+
if r.format == 'FA':
|
|
730
|
+
g = r.readfield('SURFGEOPOTENTIEL')
|
|
731
|
+
else:
|
|
732
|
+
raise NotImplementedError()
|
|
733
|
+
boundaries = dm.build.compute_lonlat_included(g.geometry)
|
|
734
|
+
else:
|
|
735
|
+
boundaries = self.boundaries
|
|
736
|
+
# build geometry
|
|
737
|
+
if self.resolution is None:
|
|
738
|
+
geometry = dm.build.build_lonlat_geometry(boundaries,
|
|
739
|
+
resolution=(self.resolution_x,
|
|
740
|
+
self.resolution_y))
|
|
741
|
+
else:
|
|
742
|
+
geometry = dm.build.build_lonlat_geometry(boundaries,
|
|
743
|
+
resolution=self.resolution)
|
|
744
|
+
# summary, plot, namelists:
|
|
745
|
+
if self.illustration:
|
|
746
|
+
fig, _ = geometry.plotgeometry(color='red',
|
|
747
|
+
title=self.geometry.tag,
|
|
748
|
+
**self.plot_params)
|
|
749
|
+
fig.savefig('.'.join([self.geometry.tag,
|
|
750
|
+
self.illustration_fmt]),
|
|
751
|
+
bbox_inches='tight')
|
|
752
|
+
namelists = dm.output.regll_geom2namelists(geometry)
|
|
753
|
+
dm.output.write_namelists(namelists, prefix=self.geometry.tag)
|
|
754
|
+
self.system.symlink('.'.join([self.geometry.tag,
|
|
755
|
+
'namel_c923',
|
|
756
|
+
'geoblocks']),
|
|
757
|
+
'.'.join([self.geometry.tag,
|
|
758
|
+
'namel_c923_orography',
|
|
759
|
+
'geoblocks']))
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
class AddPolesToGLOB(TaylorRun):
|
|
763
|
+
"""
|
|
764
|
+
Add poles to a GLOB* regular FA Lon/Lat file that do not contain them.
|
|
765
|
+
"""
|
|
766
|
+
|
|
767
|
+
_footprint = dict(
|
|
768
|
+
info = "Add poles to a GLOB* regular FA Lon/Lat file that do not contain them.",
|
|
769
|
+
attr = dict(
|
|
770
|
+
kind = dict(
|
|
771
|
+
values = ['add_poles'],
|
|
772
|
+
),
|
|
773
|
+
)
|
|
774
|
+
)
|
|
775
|
+
|
|
776
|
+
def __init__(self, *args, **kwargs):
|
|
777
|
+
super().__init__(*args, **kwargs)
|
|
778
|
+
from ..util.usepygram import epygram_checker
|
|
779
|
+
ev = '1.3.4'
|
|
780
|
+
self.algoassert(epygram_checker.is_available(version=ev), "Epygram >= " + ev +
|
|
781
|
+
" is needed here")
|
|
782
|
+
|
|
783
|
+
def execute(self, rh, opts): # @UnusedVariable
|
|
784
|
+
"""Add poles to a GLOB* regular FA Lon/Lat file that do not contain them."""
|
|
785
|
+
self._default_pre_execute(rh, opts)
|
|
786
|
+
common_i = self._default_common_instructions(rh, opts)
|
|
787
|
+
clims = self.context.sequence.effective_inputs(role=('Clim',))
|
|
788
|
+
self._add_instructions(common_i, dict(filename=[s.rh.container.localpath()
|
|
789
|
+
for s in clims]))
|
|
790
|
+
self._default_post_execute(rh, opts)
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
class _AddPolesWorker(TaylorVortexWorker):
|
|
794
|
+
_footprint = dict(
|
|
795
|
+
attr = dict(
|
|
796
|
+
kind = dict(
|
|
797
|
+
values = ['add_poles']
|
|
798
|
+
),
|
|
799
|
+
filename = dict(
|
|
800
|
+
info='The file to be processed.'),
|
|
801
|
+
)
|
|
802
|
+
)
|
|
803
|
+
|
|
804
|
+
def vortex_task(self, **_):
|
|
805
|
+
from ..util.usepygram import add_poles_to_reglonlat_file, epy_env_prepare
|
|
806
|
+
with epy_env_prepare(self.ticket):
|
|
807
|
+
add_poles_to_reglonlat_file(self.filename)
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
class Festat(Parallel):
|
|
811
|
+
"""
|
|
812
|
+
Class to run the festat binary.
|
|
813
|
+
"""
|
|
814
|
+
|
|
815
|
+
_footprint = dict(
|
|
816
|
+
info = "Run festat",
|
|
817
|
+
attr = dict(
|
|
818
|
+
kind = dict(
|
|
819
|
+
values = ['run_festat', ],
|
|
820
|
+
),
|
|
821
|
+
nb_digits = dict(
|
|
822
|
+
info = "Number of digits on which the name of the files should be written",
|
|
823
|
+
type = int,
|
|
824
|
+
default = 3,
|
|
825
|
+
optional = True,
|
|
826
|
+
),
|
|
827
|
+
prefix = dict(
|
|
828
|
+
info = "Name of the files for the binary",
|
|
829
|
+
optional = True,
|
|
830
|
+
default = "CNAME",
|
|
831
|
+
),
|
|
832
|
+
),
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
_nb_input_files = 0
|
|
836
|
+
|
|
837
|
+
def prepare(self, rh, opts):
|
|
838
|
+
# Check the namelist
|
|
839
|
+
input_namelist = self.context.sequence.effective_inputs(role="Namelist", kind="namelist")
|
|
840
|
+
if len(input_namelist) != 1:
|
|
841
|
+
logger.error("One and only one namelist must be provided.")
|
|
842
|
+
raise ValueError("One and only one namelist must be provided.")
|
|
843
|
+
else:
|
|
844
|
+
input_namelist = input_namelist[0].rh
|
|
845
|
+
# Create links for the input files
|
|
846
|
+
maxinsec = 10 ** self.nb_digits
|
|
847
|
+
insec = self.context.sequence.effective_inputs(role="InputFiles")
|
|
848
|
+
nbinsec = len(insec)
|
|
849
|
+
if nbinsec > maxinsec:
|
|
850
|
+
logger.error("The number of input files %s exceed the maximum number of files available %s.",
|
|
851
|
+
nbinsec, maxinsec)
|
|
852
|
+
raise ValueError("The number of input files exceed the maximum number of files available.")
|
|
853
|
+
else:
|
|
854
|
+
logger.info("%s input files will be treated.", nbinsec)
|
|
855
|
+
i = 0
|
|
856
|
+
for sec in insec:
|
|
857
|
+
i += 1
|
|
858
|
+
self.system.symlink(sec.rh.container.actualpath(),
|
|
859
|
+
"{prefix}{number}".format(prefix=self.prefix,
|
|
860
|
+
number=str(i).zfill(self.nb_digits)))
|
|
861
|
+
# Put the number of sections and the prefix of the input files in the namelist
|
|
862
|
+
namcontents = input_namelist.contents
|
|
863
|
+
logger.info('Setup macro CNAME=%s in %s', self.prefix, input_namelist.container.actualpath())
|
|
864
|
+
namcontents.setmacro('CNAME', self.prefix)
|
|
865
|
+
logger.info('Setup macro NCASES=%s in %s', i, input_namelist.container.actualpath())
|
|
866
|
+
namcontents.setmacro('NCASES', i)
|
|
867
|
+
namcontents.rewrite(input_namelist.container)
|
|
868
|
+
self._nb_input_files = i
|
|
869
|
+
# Call the super class
|
|
870
|
+
super().prepare(rh, opts)
|
|
871
|
+
|
|
872
|
+
def postfix(self, rh, opts):
|
|
873
|
+
# Rename stabal files
|
|
874
|
+
list_stabal = self.system.glob("stab*")
|
|
875
|
+
for stabal in list_stabal:
|
|
876
|
+
self.system.mv(stabal,
|
|
877
|
+
"{stabal}.ncases_{ncases}".format(stabal=stabal, ncases=self._nb_input_files))
|
|
878
|
+
# Deal with diag files
|
|
879
|
+
list_diag_stat = self.system.glob("co*y")
|
|
880
|
+
if len(list_diag_stat) > 0:
|
|
881
|
+
diastat_dir_name = "dia.stat.ncases_{ncases}".format(ncases=self._nb_input_files)
|
|
882
|
+
self.system.mkdir(diastat_dir_name)
|
|
883
|
+
for file in list_diag_stat:
|
|
884
|
+
self.system.mv(file, diastat_dir_name + "/")
|
|
885
|
+
self.system.tar(diastat_dir_name + ".tar", diastat_dir_name)
|
|
886
|
+
list_diag_expl = self.system.glob("expl*y")
|
|
887
|
+
if len(list_diag_expl) > 0:
|
|
888
|
+
diaexpl_dir_name = "dia.expl.ncases_{ncases}".format(ncases=self._nb_input_files)
|
|
889
|
+
self.system.mkdir(diaexpl_dir_name)
|
|
890
|
+
for file in list_diag_expl:
|
|
891
|
+
self.system.mv(file, diaexpl_dir_name + "/")
|
|
892
|
+
self.system.tar(diaexpl_dir_name + ".tar", diaexpl_dir_name)
|
|
893
|
+
# Call the superclass
|
|
894
|
+
super().postfix(rh, opts)
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
class Fediacov(Parallel):
|
|
898
|
+
"""
|
|
899
|
+
Class to compute diagnostics about covariance.
|
|
900
|
+
"""
|
|
901
|
+
|
|
902
|
+
_footprint = dict(
|
|
903
|
+
info = "Run fediacov",
|
|
904
|
+
attr = dict(
|
|
905
|
+
kind = dict(
|
|
906
|
+
values = ['run_fediacov', ],
|
|
907
|
+
),
|
|
908
|
+
),
|
|
909
|
+
)
|
|
910
|
+
|
|
911
|
+
def postfix(self, rh, opts):
|
|
912
|
+
# Deal with diag files
|
|
913
|
+
list_diag = self.system.glob("*y")
|
|
914
|
+
if len(list_diag) > 0:
|
|
915
|
+
self.system.mkdir("diag")
|
|
916
|
+
for file in list_diag:
|
|
917
|
+
self.system.mv(file, "diag/")
|
|
918
|
+
self.system.tar("diag.tar", "diag")
|
|
919
|
+
# Call the superclass
|
|
920
|
+
super().postfix(rh, opts)
|