wrfrun 0.1.8__py3-none-any.whl → 0.2.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 (53) hide show
  1. wrfrun/cli.py +131 -0
  2. wrfrun/core/base.py +52 -19
  3. wrfrun/core/config.py +257 -170
  4. wrfrun/core/error.py +8 -1
  5. wrfrun/core/replay.py +1 -1
  6. wrfrun/core/server.py +91 -71
  7. wrfrun/data.py +14 -16
  8. wrfrun/extension/goos_sst/__init__.py +5 -5
  9. wrfrun/extension/goos_sst/core.py +4 -1
  10. wrfrun/extension/goos_sst/res/Vtable.ERA_GOOS_SST +1 -1
  11. wrfrun/extension/goos_sst/res/__init__.py +17 -0
  12. wrfrun/extension/goos_sst/utils.py +21 -5
  13. wrfrun/extension/littler/__init__.py +57 -1
  14. wrfrun/extension/littler/{utils.py → core.py} +329 -43
  15. wrfrun/extension/utils.py +24 -22
  16. wrfrun/model/__init__.py +24 -1
  17. wrfrun/model/plot.py +259 -36
  18. wrfrun/model/utils.py +19 -9
  19. wrfrun/model/wrf/__init__.py +41 -0
  20. wrfrun/model/wrf/core.py +229 -101
  21. wrfrun/model/wrf/exec_wrap.py +49 -35
  22. wrfrun/model/wrf/geodata.py +2 -1
  23. wrfrun/model/wrf/namelist.py +78 -4
  24. wrfrun/model/wrf/{_metgrid.py → utils.py} +38 -3
  25. wrfrun/model/wrf/vtable.py +9 -5
  26. wrfrun/res/__init__.py +22 -7
  27. wrfrun/res/config/config.template.toml +57 -0
  28. wrfrun/res/{config.toml.template → config/wrf.template.toml} +7 -46
  29. wrfrun/res/run.template.sh +10 -0
  30. wrfrun/res/scheduler/lsf.template +5 -0
  31. wrfrun/res/{job_scheduler → scheduler}/pbs.template +1 -1
  32. wrfrun/res/{job_scheduler → scheduler}/slurm.template +2 -1
  33. wrfrun/run.py +39 -27
  34. wrfrun/scheduler/__init__.py +35 -0
  35. wrfrun/scheduler/env.py +44 -0
  36. wrfrun/scheduler/lsf.py +49 -0
  37. wrfrun/scheduler/pbs.py +50 -0
  38. wrfrun/scheduler/script.py +72 -0
  39. wrfrun/scheduler/slurm.py +50 -0
  40. wrfrun/scheduler/utils.py +14 -0
  41. wrfrun/utils.py +8 -3
  42. wrfrun/workspace/__init__.py +38 -0
  43. wrfrun/workspace/core.py +94 -0
  44. wrfrun/workspace/wrf.py +165 -0
  45. {wrfrun-0.1.8.dist-info → wrfrun-0.2.0.dist-info}/METADATA +3 -2
  46. wrfrun-0.2.0.dist-info/RECORD +62 -0
  47. wrfrun-0.2.0.dist-info/entry_points.txt +3 -0
  48. wrfrun/model/wrf/_ndown.py +0 -39
  49. wrfrun/pbs.py +0 -86
  50. wrfrun/res/run.sh.template +0 -16
  51. wrfrun/workspace.py +0 -88
  52. wrfrun-0.1.8.dist-info/RECORD +0 -51
  53. {wrfrun-0.1.8.dist-info → wrfrun-0.2.0.dist-info}/WHEEL +0 -0
wrfrun/model/plot.py CHANGED
@@ -1,54 +1,277 @@
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
4
3
 
5
- 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
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
10
11
 
12
+ from wrfrun.core import get_wrfrun_config
13
+ from wrfrun.utils import check_path, logger
11
14
 
12
- def plot_domain_area():
13
- """Generate namelist and plot domain area with WRF NCL script.
14
15
 
16
+ class DomainSetting(TypedDict):
15
17
  """
16
- prepare_wps_namelist()
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.
17
39
 
18
- # get save path
19
- save_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_OUTPUT_PATH)
40
+ :param domain_settings: Dictionary contains domain settings.
41
+ :type domain_settings: DomainSetting | None
42
+ :return: (X offset, Y offset)
43
+ :rtype: tuple
44
+ """
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
20
48
 
21
- # check
22
- check_path(save_path)
23
49
 
24
- # conver to absolute path
25
- save_path = abspath(save_path)
26
- save_path = f"{save_path}/wps_show_dom.png"
50
+ def create_projection(domain_settings: DomainSetting) -> crs.Projection:
51
+ """
52
+ Create a projection from domain settings which can be used to draw images.
53
+
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)
27
139
 
28
- # record original path
29
- origin_path = getcwd()
140
+ namelist = f90nml.read(namelist).todict()
30
141
 
31
- # enter WPS WORK PATH
32
- chdir(WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WPS_WORK_PATH))
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
+ }
33
158
 
34
- # save namelist
35
- WRFRUNConfig.write_namelist(f"./{NamelistName.WPS}", "wps", overwrite=True)
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 = get_wrfrun_config().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)
205
+
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)
211
+
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
+ )
217
+
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 crs.LambertConformal:
245
+ false_easting, false_northing = _calculate_x_y_offset(domain_settings)
246
+ ax.set_extent([0, false_easting * 2, 0, false_northing * 2], crs=proj)
247
+
248
+ case _:
249
+ logger.error(f"Unsupported project type: {type(proj)}")
250
+
251
+
252
+ def generate_domain_area():
253
+ """
254
+ Generate domain area for each model based on user's config.
255
+ Images are saved to the output directory with name: "${model_name}_domain.png".
256
+
257
+ :return:
258
+ :rtype:
259
+ """
260
+ WRFRUNConfig = get_wrfrun_config()
261
+ save_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_OUTPUT_PATH)
262
+ check_path(save_path)
263
+ save_path = abspath(save_path)
36
264
 
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)
265
+ fig = plt.figure(figsize=(10.24, 10.24))
40
266
 
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)
267
+ model_configs = WRFRUNConfig["model"]
268
+ for model_name in model_configs:
269
+ plot_domain_area(fig, model_name=model_name)
47
270
 
48
- logger.info(f"The image of domain area has been saved to {save_path}")
271
+ _save_path = f"{save_path}/{model_name}_domain.png"
272
+ fig.savefig(_save_path)
49
273
 
50
- # go back
51
- chdir(origin_path)
274
+ logger.info(f"Save domain image for '{model_name}' to '{_save_path}'")
52
275
 
53
276
 
54
- __all__ = ["plot_domain_area"]
277
+ __all__ = ["plot_domain_area", "DomainSetting", "create_projection", "parse_domain_setting", "generate_domain_area"]
wrfrun/model/utils.py CHANGED
@@ -1,23 +1,33 @@
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
- from ..core import WRFRUNConfig
16
+ from ..core import get_wrfrun_config
5
17
  from ..utils import check_path, logger
18
+ from ..workspace.wrf import get_wrf_workspace_path
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
  """
26
+ WRFRUNConfig = get_wrfrun_config()
17
27
  work_status = WRFRUNConfig.WRFRUN_WORK_STATUS
18
- work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRF_WORK_PATH)
28
+ work_path = WRFRUNConfig.parse_resource_uri(get_wrf_workspace_path("wrf"))
19
29
 
20
- log_files = [x for x in listdir(work_path) if x.startswith("rsl.")]
30
+ log_files = [x for x in listdir(work_path) if x.startswith("rsl.") or x.endswith(".log")]
21
31
 
22
32
  if len(log_files) > 0:
23
33
  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 *