vortex-nwp 2.0.0b1__py3-none-any.whl → 2.1.0__py3-none-any.whl

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