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/core/server.py CHANGED
@@ -8,7 +8,6 @@ In order to report the progress to user, ``wrfrun`` provides :class:`WRFRunServe
8
8
  .. autosummary::
9
9
  :toctree: generated/
10
10
 
11
- get_wrf_simulated_seconds
12
11
  WRFRunServer
13
12
  WRFRunServerHandler
14
13
  stop_server
@@ -16,11 +15,11 @@ In order to report the progress to user, ``wrfrun`` provides :class:`WRFRunServe
16
15
 
17
16
  import socket
18
17
  import socketserver
19
- import subprocess
18
+ from collections.abc import Callable
20
19
  from datetime import datetime
21
20
  from json import dumps
22
21
  from time import time
23
- from typing import Tuple, Optional
22
+ from typing import Tuple
24
23
 
25
24
  from .config import WRFRUNConfig
26
25
  from ..utils import logger
@@ -29,40 +28,6 @@ WRFRUN_SERVER_INSTANCE = None
29
28
  WRFRUN_SERVER_THREAD = None
30
29
 
31
30
 
32
- def get_wrf_simulated_seconds(start_datetime: datetime, log_file_path: Optional[str] = None) -> int:
33
- """
34
- Read the latest line of WRF's log file and calculate how many seconds WRF has integrated.
35
-
36
- :param start_datetime: WRF start datetime.
37
- :type start_datetime: datetime
38
- :param log_file_path: Absolute path of the log file to be parsed.
39
- :type log_file_path: str
40
- :return: Integrated seconds. If this method fails to calculate the time, the returned value is ``-1``.
41
- :rtype: int
42
- """
43
- # use linux cmd to get the latest line of wrf log files
44
- if log_file_path is None:
45
- log_file_path = WRFRUNConfig.parse_resource_uri(f"{WRFRUNConfig.WRF_WORK_PATH}/rsl.out.0000")
46
- res = subprocess.run(["tail", "-n", "1", log_file_path], capture_output=True)
47
- log_text = res.stdout.decode()
48
-
49
- if not (log_text.startswith("d01") or log_text.startswith("d02")):
50
- return -1
51
-
52
- time_string = log_text.split()[1]
53
-
54
- try:
55
- current_datetime = datetime.strptime(time_string, "%Y-%m-%d_%H:%M:%S")
56
- # remove timezone info so we can calculate.
57
- date_delta = current_datetime - start_datetime.replace(tzinfo=None)
58
- seconds = date_delta.days * 24 * 60 * 60 + date_delta.seconds
59
-
60
- except ValueError:
61
- seconds = -1
62
-
63
- return seconds
64
-
65
-
66
31
  class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
67
32
  """
68
33
  A socket server to report time usage.
@@ -98,13 +63,13 @@ class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
98
63
  Path of the log file the server will read.
99
64
  """
100
65
 
101
- def __init__(self, start_date: datetime, wrf_simulate_seconds: int, *args, **kwargs) -> None:
66
+ def __init__(self, start_date: datetime, total_simulate_seconds: int, *args, **kwargs) -> None:
102
67
  """
103
68
 
104
69
  :param start_date: The simulation's start date.
105
70
  :type start_date: datetime
106
- :param wrf_simulate_seconds: The total seconds the simulation will integrate.
107
- :type wrf_simulate_seconds: int
71
+ :param total_simulate_seconds: The total seconds the simulation will integrate.
72
+ :type total_simulate_seconds: int
108
73
  :param args: Other positional arguments passed to parent class.
109
74
  :type args:
110
75
  :param kwargs: Other keyword arguments passed to parent class.
@@ -119,12 +84,7 @@ class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
119
84
  self.start_date = start_date
120
85
 
121
86
  # record how many seconds the wrf will integral
122
- self.wrf_simulate_seconds = wrf_simulate_seconds
123
-
124
- # we need to parse the log file to track the simulation progress.
125
- self.wrf_log_path = WRFRUNConfig.parse_resource_uri(f"{WRFRUNConfig.WRF_WORK_PATH}/rsl.out.0000")
126
- logger.debug("WRFRun Server will try to track simulation progress with following log files:")
127
- logger.debug(f"WRF: {self.wrf_log_path}")
87
+ self.total_simulate_seconds = total_simulate_seconds
128
88
 
129
89
  def server_bind(self):
130
90
  """
@@ -144,33 +104,84 @@ class WRFRunServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
144
104
  """
145
105
  return self.start_timestamp
146
106
 
147
- def get_wrf_simulate_settings(self) -> Tuple[datetime, int]:
107
+ def get_model_simulate_settings(self) -> Tuple[datetime, int]:
148
108
  """
149
109
  Get the start date of the case the NWP simulates and the total seconds of the simulation.
150
110
 
151
111
  :return: (start date, simulation seconds)
152
112
  :rtype: tuple
153
113
  """
154
- return self.start_date, self.wrf_simulate_seconds
114
+ return self.start_date, self.total_simulate_seconds
155
115
 
156
116
 
157
117
  class WRFRunServerHandler(socketserver.StreamRequestHandler):
158
118
  """
159
- Socket server handler.
119
+ :class:`WRFRunServer` handler.
120
+
121
+ This handler can report time usage and simulation progress of the running model, simulation settings, and stop the server:
122
+
123
+ 1. If this handler receives ``"stop"``, it stops the server.
124
+ 2. If this handler receives ``"debug"``, it returns the simulation settings in JSON.
125
+ 3. It returns time usage and simulation progress in JSON when receiving any other messages.
126
+
127
+ **On receiving "stop"**
128
+
129
+ This handler will stop the server, and return a plain message ``Server stop``.
130
+
131
+ **On receiving "debug"**
132
+
133
+ This handler will return simulation settings in a JSON string like:
134
+
135
+ .. code-block:: json
136
+
137
+ {
138
+ "start_date": "2021-03-25 00:00",
139
+ "total_simulate_seconds": 360000,
140
+ }
141
+
142
+ **On receiving any other messages**
143
+
144
+ This handler will return time usage and simulation progress in a JSON string like:
145
+
146
+ .. code-block:: json
147
+
148
+ {
149
+ "usage": 3600,
150
+ "status": "geogrid",
151
+ "progress": 35,
152
+ }
153
+
154
+ where ``usage`` represents the seconds ``wrfrun`` has spent running the NWP model,
155
+ ``status`` represents work status,
156
+ ``progress`` represents simulation progress of the status in percentage.
160
157
  """
161
- def __init__(self, request, client_address, server: WRFRunServer) -> None:
158
+ def __init__(self, request, client_address, server: WRFRunServer, log_parse_func: Callable[[datetime], int] | None) -> None:
159
+ """
160
+ :class:`WRFRunServer` handler.
161
+
162
+ :param request:
163
+ :type request:
164
+ :param client_address:
165
+ :type client_address:
166
+ :param server: :class:`WRFRunServer` instance.
167
+ :type server: WRFRunServer
168
+ :param log_parse_func: Function used to get simulated seconds from model's log file.
169
+ If the function can't parse the simulated seconds, it should return ``-1``.
170
+ :type log_parse_func: Callable[[datetime], int]
171
+ """
162
172
  super().__init__(request, client_address, server)
163
173
 
164
174
  # get server
165
175
  self.server: WRFRunServer = server
176
+ self.log_parse_func = log_parse_func
166
177
 
167
- def calculate_time_usage(self) -> str:
178
+ def calculate_time_usage(self) -> int:
168
179
  """
169
180
  Calculate the duration from the server's start time to the present,
170
181
  which represents the time ``wrfrun`` has spent running the NWP model.
171
182
 
172
- :return: A time string in ``%H:%M:%S`` format.
173
- :rtype: str
183
+ :return: Seconds.
184
+ :rtype: int
174
185
  """
175
186
  # get current timestamp
176
187
  current_timestamp = datetime.fromtimestamp(time())
@@ -179,42 +190,34 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
179
190
  seconds_diff = current_timestamp - self.server.get_start_time()
180
191
  seconds_diff = seconds_diff.seconds
181
192
 
182
- # calculate hours, minutes and seconds
183
- seconds = seconds_diff % 60
184
- minutes = (seconds_diff % 3600) // 60
185
- hours = seconds_diff // 3600
186
-
187
- time_usage = ":".join([
188
- str(hours).rjust(2, '0'),
189
- str(minutes).rjust(2, '0'),
190
- str(seconds).rjust(2, '0')
191
- ])
193
+ return seconds_diff
192
194
 
193
- return time_usage
194
-
195
- def calculate_progress(self) -> str:
195
+ def calculate_progress(self) -> tuple[str, int]:
196
196
  """
197
197
  Read the log file and calculate the simulation progress.
198
198
 
199
- :return: A JSON string with two keys: ``status`` and ``progress``.
200
- :rtype: str
199
+ :return: ``(status, progress)``. ``status`` represents work status,
200
+ ``progress`` represents simulation progress of the status in percentage.
201
+ :rtype: tuple[str, int]
201
202
  """
202
- start_date, simulate_seconds = self.server.get_wrf_simulate_settings()
203
+ start_date, simulate_seconds = self.server.get_model_simulate_settings()
203
204
 
204
- simulated_seconds = get_wrf_simulated_seconds(start_date, self.server.wrf_log_path)
205
+ if self.log_parse_func is None:
206
+ simulated_seconds = -1
207
+ else:
208
+ simulated_seconds = self.log_parse_func(start_date)
205
209
 
206
210
  if simulated_seconds > 0:
207
211
  progress = simulated_seconds * 100 // simulate_seconds
208
-
209
212
  else:
210
- progress = 0
213
+ progress = -1
211
214
 
212
215
  status = WRFRUNConfig.WRFRUN_WORK_STATUS
213
216
 
214
217
  if status == "":
215
218
  status = "*"
216
219
 
217
- return dumps({"status": status, "progress": progress})
220
+ return status, progress
218
221
 
219
222
  def handle(self) -> None:
220
223
  """
@@ -229,17 +232,17 @@ class WRFRunServerHandler(socketserver.StreamRequestHandler):
229
232
  self.wfile.write(f"Server stop\n".encode())
230
233
 
231
234
  elif msg == "debug":
232
- start_date, simulate_seconds = self.server.get_wrf_simulate_settings()
235
+ start_date, simulate_seconds = self.server.get_model_simulate_settings()
233
236
  start_date = start_date.strftime("%Y-%m-%d %H:%M")
234
237
 
235
- self.wfile.write(f"{start_date}\n{simulate_seconds}\n".encode())
238
+ self.wfile.write(dumps({"start_date": start_date, "total_seconds": simulate_seconds}).encode())
236
239
 
237
240
  else:
238
- progress = self.calculate_progress()
241
+ status, progress = self.calculate_progress()
239
242
  time_usage = self.calculate_time_usage()
240
243
 
241
244
  # send the message
242
- self.wfile.write(f"{progress}\n{time_usage}".encode())
245
+ self.wfile.write(dumps({"usage": time_usage, "status": status, "progress": progress}).encode())
243
246
 
244
247
 
245
248
  def stop_server(socket_ip: str, socket_port: int):
@@ -266,4 +269,4 @@ def stop_server(socket_ip: str, socket_port: int):
266
269
  logger.warning("Fail to stop WRFRunServer, maybe it doesn't start at all.")
267
270
 
268
271
 
269
- __all__ = ["WRFRunServer", "WRFRunServerHandler", "get_wrf_simulated_seconds", "stop_server"]
272
+ __all__ = ["WRFRunServer", "WRFRunServerHandler", "stop_server"]
@@ -4,11 +4,11 @@ wrfrun.extension.goos_sst
4
4
 
5
5
  This extension can help you create a GRIB file from ERA5 skin temperature (SKT) data and NEAR-GOOS sea surface temperature (SST) data.
6
6
 
7
- ================================================== =============================================
8
- :doc:`goos_sst </api/extension.goos_sst.goos_sst>` Core functionality submodule.
9
- :doc:`res </api/extension.goos_sst.res>` Resource files provided by this extension.
10
- :doc:`utils </api/extension.goos_sst.utils>` Utility submodule used by the core submodule.
11
- ================================================== =============================================
7
+ ============================================= =============================================
8
+ :doc:`core </api/extension.goos_sst.core>` Core functionality submodule.
9
+ :doc:`res </api/extension.goos_sst.res>` Resource files provided by this extension.
10
+ :doc:`utils </api/extension.goos_sst.utils>` Utility submodule used by the core submodule.
11
+ ============================================= =============================================
12
12
 
13
13
  Important Note
14
14
  **************
@@ -16,7 +16,6 @@ from os.path import dirname
16
16
  import cfgrib as cf
17
17
  import numpy as np
18
18
  from pandas import to_datetime
19
- from seafog import goos_sst_find_data, goos_sst_parser
20
19
  from xarray import DataArray
21
20
 
22
21
  from .utils import create_sst_grib
@@ -40,6 +39,10 @@ def merge_era5_goos_sst_grib(surface_grib_path: str, save_path: str, sst_data_sa
40
39
  Please check ``seafog.goos_sst_find_data`` for more information.
41
40
  :type resolution: str
42
41
  """
42
+ # lazy import seafog to fix libcurl error in readthedocs
43
+ # T^T
44
+ from seafog import goos_sst_find_data, goos_sst_parser
45
+
43
46
  dataset_list = cf.open_datasets(surface_grib_path)
44
47
 
45
48
  dataset = None
@@ -4,4 +4,4 @@ Code | Code | 1 | 2 | Name | Units | Description
4
4
  34 | 1 | 0 | | SST | K | Sea-Surface Temperature |
5
5
  -----+------+------+------+----------+----------+------------------------------------------+
6
6
 
7
- # Vtable setting for GRIB file created by wrfrun.extension.sst
7
+ # Vtable setting for GRIB file created by wrfrun.extension.goos_sst
@@ -1,3 +1,20 @@
1
+ """
2
+ wrfrun.extension.goos_sst.res
3
+ #############################
4
+
5
+ Resource files provided by :doc:`/api/extension.goos_sst`.
6
+
7
+ VTABLE_ERA_GOOS_SST
8
+ *******************
9
+
10
+ .. py:data:: VTABLE_ERA_GOOS_SST
11
+ :type: str
12
+ :value: Absolute file path.
13
+
14
+ Vtable file used to input the GRIB data created by :doc:`/api/extension.goos_sst` to WRF.
15
+
16
+ """
17
+
1
18
  from os.path import abspath, dirname
2
19
 
3
20
 
@@ -1,3 +1,15 @@
1
+ """
2
+ wrfrun.extension.goos_sst.utils
3
+ ###############################
4
+
5
+ Functions that are used by :doc:`/api/extension.goos_sst`.
6
+
7
+ .. autosummary::
8
+ :toctree: generated/
9
+
10
+ create_sst_grib
11
+ """
12
+
1
13
  from cfgrib.xarray_to_grib import to_grib
2
14
  from numpy.dtypes import DateTime64DType
3
15
  from pandas import to_datetime
@@ -8,12 +20,16 @@ from wrfrun.utils import logger
8
20
 
9
21
  def create_sst_grib(data: DataArray, save_path: str):
10
22
  """
11
- Create a dataset and save it to a GRIB file.
12
-
13
- Args:
14
- data: DataArray data, it should at least contain three dimensions: ``["time", "latitude", "longitude"]``.
15
- save_path: GRIB file path.
23
+ Write SST data to a GRIB file.
24
+
25
+ This function creates GRIB file using ``cfgrib`` package.
26
+ While GRIB write support is experimental in ``cfgrib``,
27
+ this function may **FAIL TO CREATE GRIB FILE**.
16
28
 
29
+ :param data: ``xarray.DataArray``, which at least has three dimensions: ``["time", "latitude", "longitude"]``.
30
+ :type data: DataArray
31
+ :param save_path: Output GRIB file path.
32
+ :type save_path: str
17
33
  """
18
34
  # check the data's dimensions.
19
35
  for _dim in ["time", "longitude", "latitude"]:
@@ -1 +1,57 @@
1
- from .utils import *
1
+ """
2
+ wrfrun.extension.littler
3
+ ########################
4
+
5
+ This extension can help you manage observation data, and create ``LITTLE_R`` file for data assimilation.
6
+
7
+ ========================================= =============================
8
+ :doc:`core </api/extension.littler.core>` Core functionality submodule.
9
+ ========================================= =============================
10
+
11
+ What Can This Extension Do?
12
+ ***************************
13
+
14
+ According to the `WRFDA Online Tutorial <https://www2.mmm.ucar.edu/wrf/users/wrfda/OnlineTutorial/Help/littler.html>`_,
15
+ ``LITTLE_R`` is an ASCII-based observation file format that is designed to be an intermediate format
16
+ so that WRFDA might be able to assimilate as many observation types as possible in a universal manner.
17
+
18
+ However, ``LITTLE_R`` is really hard to process elegantly from the point of view of Python.
19
+ To help users create ``LITTLE_R`` file easily, this extension introduces :class:`LittleR <core.LittleR>`,
20
+ and **Zipped Little R** file.
21
+
22
+ :class:`LittleR <core.LittleR>` accepts observation datas, and can generate observation reports in proper format.
23
+ Besides, it can save an observation report to a Zipped Little R file,
24
+ so you can read the report later or process the observation data with other program.
25
+ Please check :class:`LittleR <core.LittleR>` for more information.
26
+
27
+ How To Use This Extension?
28
+ **************************
29
+
30
+ The code snap below shows you how to use this extension.
31
+
32
+ .. code-block:: Python
33
+ :caption: main.py
34
+
35
+ from wrfrun.extension.littler import LittleR
36
+
37
+
38
+ if __name__ == '__main__':
39
+ littler = LittleR()
40
+ littler.set_header(
41
+ longitude=120, latitude=60, fm="FM-19", elevation=0,
42
+ is_bogus=True, date="20250902070000"
43
+ )
44
+ # write to LITTLE_R file
45
+ with open("data/test", "w") as f:
46
+ f.write(str(littler))
47
+ # write to zlr file
48
+ littler.to_zlr("data/test.zlr")
49
+
50
+ .. toctree::
51
+ :maxdepth: 1
52
+ :hidden:
53
+
54
+ core <extension.littler.core>
55
+ """
56
+
57
+ from .core import *