wrfrun 0.1.8__py3-none-any.whl → 0.1.9__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 (50) hide show
  1. wrfrun/cli.py +128 -0
  2. wrfrun/core/base.py +8 -5
  3. wrfrun/core/config.py +81 -150
  4. wrfrun/core/replay.py +1 -1
  5. wrfrun/core/server.py +81 -78
  6. wrfrun/extension/goos_sst/__init__.py +5 -5
  7. wrfrun/extension/goos_sst/core.py +4 -1
  8. wrfrun/extension/goos_sst/res/Vtable.ERA_GOOS_SST +1 -1
  9. wrfrun/extension/goos_sst/res/__init__.py +17 -0
  10. wrfrun/extension/goos_sst/utils.py +21 -5
  11. wrfrun/extension/littler/__init__.py +57 -1
  12. wrfrun/extension/littler/{utils.py → core.py} +326 -40
  13. wrfrun/extension/utils.py +22 -21
  14. wrfrun/model/__init__.py +24 -1
  15. wrfrun/model/plot.py +253 -35
  16. wrfrun/model/utils.py +17 -8
  17. wrfrun/model/wrf/__init__.py +41 -0
  18. wrfrun/model/wrf/core.py +215 -99
  19. wrfrun/model/wrf/exec_wrap.py +49 -35
  20. wrfrun/model/wrf/namelist.py +79 -4
  21. wrfrun/model/wrf/{_metgrid.py → utils.py} +36 -2
  22. wrfrun/model/wrf/vtable.py +2 -1
  23. wrfrun/res/__init__.py +8 -5
  24. wrfrun/res/config/config.template.toml +50 -0
  25. wrfrun/res/{config.toml.template → config/wrf.template.toml} +7 -46
  26. wrfrun/res/run.template.sh +10 -0
  27. wrfrun/res/scheduler/lsf.template +5 -0
  28. wrfrun/res/{job_scheduler → scheduler}/pbs.template +1 -1
  29. wrfrun/res/{job_scheduler → scheduler}/slurm.template +2 -1
  30. wrfrun/run.py +19 -23
  31. wrfrun/scheduler/__init__.py +35 -0
  32. wrfrun/scheduler/env.py +44 -0
  33. wrfrun/scheduler/lsf.py +47 -0
  34. wrfrun/scheduler/pbs.py +48 -0
  35. wrfrun/scheduler/script.py +70 -0
  36. wrfrun/scheduler/slurm.py +48 -0
  37. wrfrun/scheduler/utils.py +14 -0
  38. wrfrun/utils.py +8 -3
  39. wrfrun/workspace/__init__.py +38 -0
  40. wrfrun/workspace/core.py +92 -0
  41. wrfrun/workspace/wrf.py +121 -0
  42. {wrfrun-0.1.8.dist-info → wrfrun-0.1.9.dist-info}/METADATA +3 -2
  43. wrfrun-0.1.9.dist-info/RECORD +62 -0
  44. wrfrun-0.1.9.dist-info/entry_points.txt +3 -0
  45. wrfrun/model/wrf/_ndown.py +0 -39
  46. wrfrun/pbs.py +0 -86
  47. wrfrun/res/run.sh.template +0 -16
  48. wrfrun/workspace.py +0 -88
  49. wrfrun-0.1.8.dist-info/RECORD +0 -51
  50. {wrfrun-0.1.8.dist-info → wrfrun-0.1.9.dist-info}/WHEEL +0 -0
wrfrun/model/plot.py CHANGED
@@ -1,54 +1,272 @@
1
- from os import chdir, getcwd
2
1
  from os.path import abspath, exists
3
- from shutil import copyfile, move
2
+ from typing import Literal, Optional, TypedDict, Union
3
+
4
+ import cartopy.feature as cfeature
5
+ import f90nml
6
+ import matplotlib.pyplot as plt
7
+ from cartopy import crs
8
+ from cartopy.mpl.geoaxes import GeoAxes
9
+ from cartopy.mpl.gridliner import LATITUDE_FORMATTER, LONGITUDE_FORMATTER
10
+ from haversine.haversine import Direction, Unit, inverse_haversine
4
11
 
5
12
  from wrfrun.core import WRFRUNConfig
6
- from wrfrun.res import EXT_NCL_PLOT_SCRIPT
7
- from wrfrun.utils import call_subprocess, check_path, logger
8
- from .base import NamelistName
9
- from .wrf.namelist import prepare_wps_namelist
13
+ from wrfrun.utils import check_path, logger
10
14
 
11
15
 
12
- def plot_domain_area():
13
- """Generate namelist and plot domain area with WRF NCL script.
16
+ class DomainSetting(TypedDict):
17
+ """
18
+ Domain settings which can be used to create a projection.
19
+ """
20
+ dx: int
21
+ dy: int
22
+ e_sn: Union[list[int], tuple[int]]
23
+ e_we: Union[list[int], tuple[int]]
24
+ i_parent_start: Union[list[int], tuple[int]]
25
+ j_parent_start: Union[list[int], tuple[int]]
26
+ max_dom: int
27
+ parent_grid_ratio: Union[list[int], tuple[int]]
28
+ map_proj: Literal["lambert", "polar", "mercator", "lat-lon"]
29
+ ref_lat: Union[int, float]
30
+ ref_lon: Union[int, float]
31
+ truelat1: Union[int, float]
32
+ truelat2: Union[int, float]
33
+ stand_lon: Union[int, float]
34
+
35
+
36
+ def _calculate_x_y_offset(domain_settings: DomainSetting) -> tuple[float, float]:
37
+ """
38
+ Calculate X and Y offset from planar origin in metres.
14
39
 
40
+ :param domain_settings: Dictionary contains domain settings.
41
+ :type domain_settings: DomainSetting | None
42
+ :return: (X offset, Y offset)
43
+ :rtype: tuple
15
44
  """
16
- prepare_wps_namelist()
45
+ false_easting = (domain_settings["e_we"][0] - 1) / 2 * domain_settings["dx"]
46
+ false_northing = (domain_settings["e_sn"][0] - 1) / 2 * domain_settings["dy"]
47
+ return false_easting, false_northing
17
48
 
18
- # get save path
19
- save_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_OUTPUT_PATH)
20
49
 
21
- # check
22
- check_path(save_path)
50
+ def create_projection(domain_settings: DomainSetting) -> crs.Projection:
51
+ """
52
+ Create a projection from domain settings which can be used to draw images.
23
53
 
24
- # conver to absolute path
25
- save_path = abspath(save_path)
26
- save_path = f"{save_path}/wps_show_dom.png"
54
+ You can give your custom domain settings to create the projection.
55
+ Please see ``wrfrun.mode.plot.DomainSetting`` for more information about ``domain_settings``.
56
+
57
+ :param domain_settings: Dictionary contains domain settings.
58
+ :type domain_settings: DomainSetting
59
+ :return: Projection object and the used domain settings.
60
+ :rtype: (Projection, domain settings)
61
+ """
62
+ match domain_settings["map_proj"]:
63
+
64
+ case "lat-lon":
65
+ proj = crs.PlateCarree(central_longitude=domain_settings["ref_lon"])
66
+
67
+ case "lambert":
68
+ false_easting, false_northing = _calculate_x_y_offset(domain_settings)
69
+ proj = crs.LambertConformal(
70
+ central_longitude=domain_settings["ref_lon"],
71
+ central_latitude=domain_settings["ref_lat"],
72
+ standard_parallels=(
73
+ domain_settings["truelat1"],
74
+ domain_settings["truelat2"]
75
+ ),
76
+ false_easting=false_easting,
77
+ false_northing=false_northing
78
+ )
79
+
80
+ case "polar":
81
+ ref_lat = domain_settings["ref_lat"]
82
+ if ref_lat > 0:
83
+ proj = crs.NorthPolarStereo(central_longitude=domain_settings["stand_lon"])
84
+
85
+ else:
86
+ proj = crs.SouthPolarStereo(central_longitude=domain_settings["stand_lon"])
87
+
88
+ case "mercator":
89
+ # false_easting, false_northing = _calculate_x_y_offset(domain_settings)
90
+ # central_longitude = domain_settings["ref_lon"]
91
+ # central_latitude = domain_settings["ref_lat"]
92
+ # ref_lat_distance = haversine(
93
+ # (0, central_longitude),
94
+ # (central_latitude, central_longitude),
95
+ # unit=Unit.METERS
96
+ # )
97
+ # ref_lat_distance = ref_lat_distance if central_latitude < 0 else -ref_lat_distance
98
+ # false_northing = ref_lat_distance + false_northing
99
+ proj = crs.Mercator(
100
+ central_longitude=domain_settings["ref_lon"],
101
+ latitude_true_scale=domain_settings["truelat1"],
102
+ # false_northing=false_northing,
103
+ # false_easting=false_easting
104
+ )
105
+
106
+ case _:
107
+ logger.error(f"Unknown projection name: {domain_settings['map_proj']}")
108
+ raise KeyError(f"Unknown projection name: {domain_settings['map_proj']}")
109
+
110
+ return proj
111
+
112
+
113
+ def parse_domain_setting(namelist: Union[str, dict]) -> DomainSetting:
114
+ """
115
+ Read values from namelist and return typed dict ``DomainSetting``.
116
+
117
+ You can give either the path of namelist file or the dict returned by ``f90nml``.
118
+
119
+ 1. Parse values in a namelist file.
120
+
121
+ >>> namelist_file_path = "./namelist.wps"
122
+ >>> domain_setting = parse_domain_setting(namelist_file_path)
123
+
124
+ 2. Parse values from dict.
125
+
126
+ >>> namelist_file_path = "./namelist.wps"
127
+ >>> namelist_data = f90nml.read(namelist_file_path).todict()
128
+ >>> domain_setting = parse_domain_setting(namelist_data)
129
+
130
+ :param namelist: Path of the namelist file or the dict returned by ``f90nml``.
131
+ :type namelist: str | dict
132
+ :return: ``DomainSetting`` dict.
133
+ :rtype: DomainSetting
134
+ """
135
+ if isinstance(namelist, str):
136
+ if not exists(namelist):
137
+ logger.error(f"Namelist file not found: {namelist}")
138
+ raise FileNotFoundError(namelist)
139
+
140
+ namelist = f90nml.read(namelist).todict()
141
+
142
+ domain_setting: DomainSetting = {
143
+ "dx": namelist["geogrid"]["dx"],
144
+ "dy": namelist["geogrid"]["dy"],
145
+ "e_sn": namelist["geogrid"]["e_sn"],
146
+ "e_we": namelist["geogrid"]["e_we"],
147
+ "i_parent_start": namelist["geogrid"]["i_parent_start"],
148
+ "j_parent_start": namelist["geogrid"]["j_parent_start"],
149
+ "max_dom": namelist["share"]["max_dom"],
150
+ "parent_grid_ratio": namelist["geogrid"]["parent_grid_ratio"],
151
+ "ref_lat": namelist["geogrid"]["ref_lat"],
152
+ "ref_lon": namelist["geogrid"]["ref_lon"],
153
+ "truelat1": namelist["geogrid"]["truelat1"],
154
+ "truelat2": namelist["geogrid"]["truelat2"],
155
+ "stand_lon": namelist["geogrid"]["stand_lon"],
156
+ "map_proj": namelist["geogrid"]["map_proj"]
157
+ }
158
+
159
+ return domain_setting
160
+
161
+
162
+ def plot_domain_area(
163
+ fig: plt.Figure,
164
+ domain_settings: Optional[DomainSetting] = None,
165
+ model_name: Optional[str] = None
166
+ ):
167
+ """
168
+ Plot domain area based on domain settings.
169
+
170
+ **WARNING**
171
+
172
+ This function is still unstable, result may be different from the ncl.
173
+
174
+ :param fig: Figure to plot domain.
175
+ :type fig: Figure
176
+ :param domain_settings: Dictionary contains domain settings. If None, read domain settings from ``WRFRUNConfig``.
177
+ :type domain_settings: DomainSetting | None
178
+ :param model_name: Model's name for reading domain settings.
179
+ :type model_name: str | None
180
+ """
181
+ if domain_settings is None:
182
+ if model_name is None:
183
+ logger.error("You need to give 'model_name' if `domain_settings == None`")
184
+ raise ValueError("You need to give 'model_name' if `domain_settings == None`")
185
+
186
+ user_settings = WRFRUNConfig.get_model_config(model_name)["domain"]
187
+ domain_settings: DomainSetting = {
188
+ "dx": user_settings["dx"],
189
+ "dy": user_settings["dy"],
190
+ "e_sn": user_settings["e_sn"],
191
+ "e_we": user_settings["e_we"],
192
+ "i_parent_start": user_settings["i_parent_start"],
193
+ "j_parent_start": user_settings["j_parent_start"],
194
+ "max_dom": user_settings["domain_num"],
195
+ "parent_grid_ratio": user_settings["parent_grid_ratio"],
196
+ "ref_lat": user_settings["ref_lat"],
197
+ "ref_lon": user_settings["ref_lon"],
198
+ "truelat1": user_settings["truelat1"],
199
+ "truelat2": user_settings["truelat2"],
200
+ "stand_lon": user_settings["stand_lon"],
201
+ "map_proj": user_settings["map_proj"]
202
+ }
203
+
204
+ proj = create_projection(domain_settings)
27
205
 
28
- # record original path
29
- origin_path = getcwd()
206
+ fig.clear()
207
+ ax: GeoAxes = fig.add_subplot(1, 1, 1, projection=proj) # type: ignore
208
+ ax.coastlines(resolution="50m")
209
+ ax.add_feature(cfeature.OCEAN)
210
+ ax.add_feature(cfeature.LAND)
30
211
 
31
- # enter WPS WORK PATH
32
- chdir(WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WPS_WORK_PATH))
212
+ # set gridline attributes, close the default labels
213
+ grid_line = ax.gridlines(
214
+ draw_labels=True, dms=True, linestyle=":", linewidth=0.3,
215
+ x_inline=False, y_inline=False, color='k'
216
+ )
33
217
 
34
- # save namelist
35
- WRFRUNConfig.write_namelist(f"./{NamelistName.WPS}", "wps", overwrite=True)
218
+ # close coordinates labels on the top and right
219
+ grid_line.top_labels = False
220
+ grid_line.right_labels = False
221
+
222
+ # align coordinates labels
223
+ grid_line.rotate_labels = None
224
+
225
+ # set label formatter
226
+ grid_line.xformattter = LONGITUDE_FORMATTER
227
+ grid_line.yformatter = LATITUDE_FORMATTER
228
+ ax.set_title("Domain Configuration")
229
+
230
+ # set area range
231
+ match type(proj):
232
+
233
+ case crs.Mercator:
234
+ # we may need to calculate the range of longitude and latitude
235
+ ref_lon = domain_settings["ref_lon"]
236
+ ref_lat = domain_settings["ref_lat"]
237
+ false_easting, false_northing = _calculate_x_y_offset(domain_settings)
238
+ _, start_lon = inverse_haversine((ref_lat, ref_lon), false_easting, direction=Direction.WEST, unit=Unit.METERS)
239
+ _, end_lon = inverse_haversine((ref_lat, ref_lon), false_easting, direction=Direction.EAST, unit=Unit.METERS)
240
+ start_lat, _ = inverse_haversine((ref_lat, ref_lon), false_northing, direction=Direction.SOUTH, unit=Unit.METERS)
241
+ end_lat, _ = inverse_haversine((ref_lat, ref_lon), false_northing, direction=Direction.NORTH, unit=Unit.METERS)
242
+ ax.set_extent([start_lon, end_lon, start_lat, end_lat])
243
+
244
+ case _:
245
+ logger.error(f"Unsupported project type: {type(proj)}")
246
+
247
+
248
+ def generate_domain_area():
249
+ """
250
+ Generate domain area for each model based on user's config.
251
+ Images are saved to the output directory with name: "${model_name}_domain.png".
252
+
253
+ :return:
254
+ :rtype:
255
+ """
256
+ save_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_OUTPUT_PATH)
257
+ check_path(save_path)
258
+ save_path = abspath(save_path)
36
259
 
37
- # copy plot script and plot
38
- copyfile(WRFRUNConfig.parse_resource_uri(EXT_NCL_PLOT_SCRIPT), f"./plotgrids.ncl")
39
- call_subprocess(["ncl", "./plotgrids.ncl"], print_output=True)
260
+ fig = plt.figure(figsize=(10.24, 10.24))
40
261
 
41
- # save image
42
- # we need to check image file, because sometimes ncl doesn't return error code
43
- if not exists("./wps_show_dom.png"):
44
- logger.error(f"Fail to plot domain with NCL. Check the log above")
45
- raise FileNotFoundError
46
- move("./wps_show_dom.png", save_path)
262
+ model_configs = WRFRUNConfig["model"]
263
+ for model_name in model_configs:
264
+ plot_domain_area(fig, model_name=model_name)
47
265
 
48
- logger.info(f"The image of domain area has been saved to {save_path}")
266
+ _save_path = f"{save_path}/{model_name}_domain.png"
267
+ fig.savefig(_save_path)
49
268
 
50
- # go back
51
- chdir(origin_path)
269
+ logger.info(f"Save domain image for '{model_name}' to '{_save_path}'")
52
270
 
53
271
 
54
- __all__ = ["plot_domain_area"]
272
+ __all__ = ["plot_domain_area", "DomainSetting", "create_projection", "parse_domain_setting", "generate_domain_area"]
wrfrun/model/utils.py CHANGED
@@ -1,23 +1,32 @@
1
+ """
2
+ wrfrun.model.utils
3
+ ##################
4
+
5
+ Utility functions used by models.
6
+
7
+ .. autosummary::
8
+ :toctree: generated/
9
+
10
+ clear_model_logs
11
+ """
12
+
1
13
  from os import listdir
2
14
  from shutil import move
3
15
 
4
16
  from ..core import WRFRUNConfig
5
17
  from ..utils import check_path, logger
18
+ from ..workspace.wrf import WORKSPACE_MODEL_WRF
6
19
 
7
20
 
8
21
  def clear_model_logs():
9
22
  """
10
- This function can automatically collect WRF log files and save them to ``output_path``.
11
- This function is used inside the wrfrun package.
12
- If you want to do something about the log files, check the corresponding code of interface functions in ``wrfrun.model.run``.
13
-
14
- :return:
15
- :rtype:
23
+ This function can automatically collect unsaved log files,
24
+ and save them to the corresponding output directory of the ``Executable``.
16
25
  """
17
26
  work_status = WRFRUNConfig.WRFRUN_WORK_STATUS
18
- work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRF_WORK_PATH)
27
+ work_path = WRFRUNConfig.parse_resource_uri(WORKSPACE_MODEL_WRF)
19
28
 
20
- log_files = [x for x in listdir(work_path) if x.startswith("rsl.")]
29
+ log_files = [x for x in listdir(work_path) if x.startswith("rsl.") or x.endswith(".log")]
21
30
 
22
31
  if len(log_files) > 0:
23
32
  logger.warning(f"Found unprocessed log files of {work_status}")
@@ -1,3 +1,44 @@
1
+ """
2
+ wrfrun.model.wrf
3
+ ################
4
+
5
+ Implementation of WRF model.
6
+
7
+ Submodules
8
+ **********
9
+
10
+ ============================================ ==================================================================================
11
+ :doc:`_metgrid </api/model.wrf._metgrid>` Utility functions used by :class:`MetGrid <core.MetGrid>`.
12
+ :doc:`_ndown </api/model.wrf._ndown>` Utility functions used by :class:`NDown <core.NDown>`.
13
+ :doc:`core </api/model.wrf.core>` Core implementation of WRF model.
14
+ :doc:`exec_wrap </api/model.wrf.exec_wrap>` Function wrappers for ``Executable`` defined in :doc:`core </api/model.wrf.core>`.
15
+ :doc:`geodata </api/model.wrf.geodata>` Utility functions to read / write geographical static datas.
16
+ :doc:`namelist </api/model.wrf.namelist>` Functions to process WPS / WRF namelist files.
17
+ :doc:`plot </api/model.wrf.plot>` Functions to create projection from namelist settings to plot simulation domain.
18
+ :doc:`scheme </api/model.wrf.scheme>` Scheme ``dataclass``.
19
+ :doc:`vtable </api/model.wrf.vtable>` Vtable files ``dataclass``.
20
+ ============================================ ==================================================================================
21
+
22
+ .. autosummary::
23
+ :toctree: generated/
24
+
25
+ prepare_namelist
26
+
27
+ .. toctree::
28
+ :maxdepth: 1
29
+ :hidden:
30
+
31
+ _metgrid <model.wrf._metgrid>
32
+ _ndown <model.wrf._ndown>
33
+ core <model.wrf.core>
34
+ exec_wrap <model.wrf.exec_wrap>
35
+ geodata <model.wrf.geodata>
36
+ namelist <model.wrf.namelist>
37
+ plot <model.wrf.plot>
38
+ scheme <model.wrf.scheme>
39
+ vtable <model.wrf.vtable>
40
+ """
41
+
1
42
  from .core import *
2
43
  from .exec_wrap import *
3
44
  from .geodata import *