wrfrun 0.2.0__py3-none-any.whl → 0.3.1__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 (66) hide show
  1. wrfrun/__init__.py +8 -3
  2. wrfrun/cli.py +69 -29
  3. wrfrun/core/__init__.py +27 -10
  4. wrfrun/core/_config.py +308 -0
  5. wrfrun/core/_constant.py +236 -0
  6. wrfrun/core/_exec_db.py +105 -0
  7. wrfrun/core/_namelist.py +287 -0
  8. wrfrun/core/_record.py +178 -0
  9. wrfrun/core/_resource.py +172 -0
  10. wrfrun/core/base.py +132 -406
  11. wrfrun/core/core.py +196 -0
  12. wrfrun/core/error.py +28 -2
  13. wrfrun/core/replay.py +10 -96
  14. wrfrun/core/server.py +52 -27
  15. wrfrun/core/type.py +171 -0
  16. wrfrun/data.py +304 -139
  17. wrfrun/extension/goos_sst/__init__.py +2 -2
  18. wrfrun/extension/goos_sst/core.py +9 -14
  19. wrfrun/extension/goos_sst/res/__init__.py +0 -1
  20. wrfrun/extension/goos_sst/utils.py +50 -44
  21. wrfrun/extension/littler/core.py +105 -88
  22. wrfrun/extension/utils.py +4 -3
  23. wrfrun/log.py +117 -0
  24. wrfrun/model/__init__.py +11 -7
  25. wrfrun/model/constants.py +52 -0
  26. wrfrun/model/palm/__init__.py +30 -0
  27. wrfrun/model/palm/core.py +145 -0
  28. wrfrun/model/palm/namelist.py +33 -0
  29. wrfrun/model/plot.py +99 -119
  30. wrfrun/model/type.py +116 -0
  31. wrfrun/model/utils.py +9 -20
  32. wrfrun/model/wrf/__init__.py +4 -9
  33. wrfrun/model/wrf/core.py +246 -161
  34. wrfrun/model/wrf/exec_wrap.py +13 -12
  35. wrfrun/model/wrf/geodata.py +116 -100
  36. wrfrun/model/wrf/log.py +103 -0
  37. wrfrun/model/wrf/namelist.py +90 -73
  38. wrfrun/model/wrf/plot.py +102 -0
  39. wrfrun/model/wrf/scheme.py +108 -52
  40. wrfrun/model/wrf/utils.py +39 -25
  41. wrfrun/model/wrf/vtable.py +35 -3
  42. wrfrun/plot/__init__.py +20 -0
  43. wrfrun/plot/wps.py +96 -73
  44. wrfrun/res/__init__.py +103 -5
  45. wrfrun/res/config/config.template.toml +8 -0
  46. wrfrun/res/config/palm.template.toml +23 -0
  47. wrfrun/run.py +105 -77
  48. wrfrun/scheduler/__init__.py +1 -0
  49. wrfrun/scheduler/lsf.py +3 -2
  50. wrfrun/scheduler/pbs.py +3 -2
  51. wrfrun/scheduler/script.py +17 -5
  52. wrfrun/scheduler/slurm.py +3 -2
  53. wrfrun/scheduler/utils.py +14 -2
  54. wrfrun/utils.py +88 -199
  55. wrfrun/workspace/__init__.py +8 -5
  56. wrfrun/workspace/core.py +20 -12
  57. wrfrun/workspace/palm.py +137 -0
  58. wrfrun/workspace/wrf.py +16 -15
  59. wrfrun-0.3.1.dist-info/METADATA +239 -0
  60. wrfrun-0.3.1.dist-info/RECORD +78 -0
  61. wrfrun/core/config.py +0 -923
  62. wrfrun/model/base.py +0 -14
  63. wrfrun-0.2.0.dist-info/METADATA +0 -68
  64. wrfrun-0.2.0.dist-info/RECORD +0 -62
  65. {wrfrun-0.2.0.dist-info → wrfrun-0.3.1.dist-info}/WHEEL +0 -0
  66. {wrfrun-0.2.0.dist-info → wrfrun-0.3.1.dist-info}/entry_points.txt +0 -0
wrfrun/utils.py CHANGED
@@ -1,82 +1,33 @@
1
- import logging
2
- import subprocess
3
- from datetime import datetime
4
- from os import chdir, getcwd, makedirs, environ
5
- from os.path import exists
6
- from shutil import rmtree
7
- from time import time
8
- from typing import Optional, List, Dict
9
-
10
- from rich.logging import RichHandler
11
-
12
-
13
- def set_logger(logger_list: List[str], logger_level: Optional[Dict] = None):
14
- """
15
- This function will replace all handlers of each logger in ``logger_list`` with RichHandler.
16
- If there are some custom handlers in logger, they will be replaced too.
17
-
18
- :param logger_list: A list contains loggers.
19
- :type logger_list: list
20
- :param logger_level: You can specify the log level in ``logger_level``, with the name of logger is the key, and the level of logger is the value.
21
- Default if None, with which all loggers' level will be set to ``logging.WARNING``.
22
- :type logger_level: list | None
23
- :return:
24
- :rtype:
25
- """
26
- formatter = logging.Formatter(
27
- "%(name)s :: %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
28
- )
29
- # use rich handler
30
- handler = RichHandler()
31
- handler.setFormatter(formatter)
1
+ """
2
+ wrfrun.utils
3
+ ############
32
4
 
33
- for logger_name in logger_list:
34
- if logger_name in logging.root.manager.loggerDict:
35
- _logger = logging.getLogger(logger_name)
36
- for _handler in _logger.handlers:
37
- _logger.removeHandler(_handler)
38
- _logger.addHandler(handler)
5
+ .. autosummary::
6
+ :toctree: generated/
39
7
 
40
- if logger_level is not None and logger_name in logger_level:
41
- _logger.setLevel(logger_level[logger_name])
42
- else:
43
- _logger.setLevel(logging.WARNING)
44
-
45
-
46
- # init wrfrun logger
47
- logger = logging.getLogger("wrfrun")
48
- # check environment variables and set logger level
49
- if "WRFRUN_DEBUG_MODE" in environ and environ["WRFRUN_DEBUG_MODE"]:
50
- _logger_level = logging.DEBUG
51
- else:
52
- _logger_level = logging.INFO
53
- set_logger(["wrfrun", ], {"wrfrun": _logger_level})
8
+ check_path
9
+ rectify_domain_size
10
+ _calculate_domain_shape
11
+ calculate_domain_shape
12
+ _check_domain_shape
13
+ check_domain_shape
54
14
 
15
+ Utility submodule.
16
+ """
55
17
 
56
- def unify_logger_format():
57
- """
58
- This function is only supposed to be used internally.
59
- This function will replace all handlers of each logger with ``rich.logging.RichHandler``.
60
- Use this carefully.
61
-
62
- :return:
63
- :rtype:
64
- """
65
- set_logger(
66
- ["cdsapi", "cfgrib", "datapi"],
67
- {
68
- "cdsapi": logging.INFO,
69
- "cfgrib": logging.ERROR,
70
- "datapi": logging.INFO
71
- }
72
- )
18
+ from os import makedirs
19
+ from os.path import exists
20
+ from shutil import rmtree
73
21
 
74
22
 
75
23
  def check_path(*args, force=False):
76
- """Check and create all the path in *args
77
-
78
- Returns:
24
+ """
25
+ Check and create all the path in args.
79
26
 
27
+ :param args: Path list.
28
+ :type args: list[str]
29
+ :param force: If ``True``, delete existed directory and create a new.
30
+ :type force: bool
80
31
  """
81
32
  for _path in args:
82
33
  if exists(_path) and force:
@@ -85,32 +36,16 @@ def check_path(*args, force=False):
85
36
  makedirs(_path)
86
37
 
87
38
 
88
- def logger_add_file_handler(log_path: str):
89
- """Add file handler to logger
90
-
91
- Args:
92
- log_path (str): Folder to place log file
93
- """
94
- # check log save path
95
- check_path(log_path)
96
-
97
- # add file handler
98
- file_handler = logging.FileHandler(
99
- f"{log_path}/{datetime.fromtimestamp(time()).strftime('%Y-%m-%d %H:%M:%S')}.log")
100
- file_handler.setFormatter(logging.Formatter(
101
- "%(asctime)s - %(name)s - %(levelname)s :: %(message)s", datefmt="%m-%d %H:%M:%S"))
102
- logger.addHandler(file_handler)
103
-
104
-
105
39
  def rectify_domain_size(point_num: int, nest_ratio: int) -> int:
106
- """Rectify domain size.
107
-
108
- Args:
109
- point_num (int): Point number of one side.
110
- nest_ratio (int): The nesting ratio relative to the domain’s parent.
111
-
112
- Returns:
113
- int: New size.
40
+ """
41
+ Rectify domain size.
42
+
43
+ :param point_num: Point number of one side.
44
+ :type point_num: int
45
+ :param nest_ratio: The nesting ratio relative to the domain's parent.
46
+ :type nest_ratio: int
47
+ :return: New size.
48
+ :rtype: int
114
49
  """
115
50
  # calculate remainder
116
51
  point_num_mod = (point_num - 1) % nest_ratio
@@ -123,22 +58,23 @@ def rectify_domain_size(point_num: int, nest_ratio: int) -> int:
123
58
  point_num -= point_num_mod
124
59
  else:
125
60
  # # point_num_mod is closer to nest_ratio than (nest_ratio - 1)
126
- point_num += (nest_ratio - point_num_mod)
61
+ point_num += nest_ratio - point_num_mod
127
62
 
128
63
  return point_num
129
64
 
130
65
 
131
66
  def _calculate_domain_shape(step: float, resolution: int, grid_resolution=110, nest_ratio=1) -> int:
132
- """Calculate domain shape based on its area
133
-
134
- Args:
135
- step (float): Length of the side. Unit: degree.
136
- resolution (int): Resolution of domain. Unit: km.
137
- grid_resolution (int, optional): Resolution of grid. Unit: km. Defaults to 110.
138
- nest_ratio (int, optional): The nesting ratio relative to the domain’s parent.
139
-
140
- Returns:
141
- tuple[int, int]: (length of x, length of y)
67
+ """
68
+ Calculate domain shape based on its area.
69
+
70
+ :param step: Length of the side. Unit: degree.
71
+ :type step: float
72
+ :param resolution: Resolution of domain. Unit: km.
73
+ :type resolution: int
74
+ :param grid_resolution: Resolution of grid. Unit: km. Defaults to 110.
75
+ :param nest_ratio: The nesting ratio relative to the domain’s parent.
76
+ :return: ``(length of x, length of y)``.
77
+ :rtype: int
142
78
  """
143
79
  # calculate res based on doamin area and resolution
144
80
  res = int(step * grid_resolution) // resolution
@@ -148,37 +84,39 @@ def _calculate_domain_shape(step: float, resolution: int, grid_resolution=110, n
148
84
  return rectify_domain_size(res, nest_ratio=nest_ratio)
149
85
 
150
86
 
151
- def calculate_domain_shape(lon_step: float, lat_step: float, resolution: int, grid_resolution=110, nest_ratio=1) -> tuple[int, int]:
152
- """Calculate domain shape based on its area
153
-
154
- Args:
155
- lon_step (float): Length in X direction. Unit: degree.
156
- lat_step (float): Length in Y direction. Unit: degree.
157
- resolution (int): Resolution of domain. Unit: km.
158
- grid_resolution (int, optional): Resolution of grid. Unit: km. Defaults to 110.
159
- nest_ratio (int, optional): The nesting ratio relative to the domain’s parent.
160
-
161
- Returns:
162
- tuple[int, int]: (length of x, length of y)
87
+ def calculate_domain_shape(
88
+ lon_step: float, lat_step: float, resolution: int, grid_resolution=110, nest_ratio=1
89
+ ) -> tuple[int, int]:
90
+ """
91
+ Calculate domain shape based on its area.
92
+
93
+ :param lon_step: Length in X direction. Unit: degree.
94
+ :type lon_step: float
95
+ :param lat_step: Length in Y direction. Unit: degree.
96
+ :type lat_step: float
97
+ :param resolution: Resolution of domain. Unit: km.
98
+ :type resolution: int
99
+ :param grid_resolution: Resolution of grid. Unit: km. Defaults to 110.
100
+ :param nest_ratio: The nesting ratio relative to the domain’s parent.
101
+ :return: ``(length of x, length of y)``.
102
+ :rtype: tuple[int, int]
163
103
  """
164
-
165
104
  return (
166
- _calculate_domain_shape(
167
- lon_step, resolution, grid_resolution=grid_resolution, nest_ratio=nest_ratio),
168
- _calculate_domain_shape(
169
- lat_step, resolution, grid_resolution=grid_resolution, nest_ratio=nest_ratio)
105
+ _calculate_domain_shape(lon_step, resolution, grid_resolution=grid_resolution, nest_ratio=nest_ratio),
106
+ _calculate_domain_shape(lat_step, resolution, grid_resolution=grid_resolution, nest_ratio=nest_ratio),
170
107
  )
171
108
 
172
109
 
173
110
  def _check_domain_shape(point_num: int, nest_ratio: int) -> bool:
174
- """Check domain shape.
175
-
176
- Args:
177
- point_num (int): Point number of one side.
178
- nest_ratio (int): The nesting ratio relative to the domain’s parent.
179
-
180
- Returns:
181
- bool: True if pass check else False.
111
+ """
112
+ Check domain shape.
113
+
114
+ :param point_num: Point number of one side.
115
+ :type point_num: int
116
+ :param nest_ratio: The nesting ratio relative to the domain’s parent.
117
+ :type nest_ratio: int
118
+ :return: True if pass check else False.
119
+ :rtype: bool
182
120
  """
183
121
  if (point_num - 1) % nest_ratio != 0:
184
122
  return False
@@ -187,76 +125,27 @@ def _check_domain_shape(point_num: int, nest_ratio: int) -> bool:
187
125
 
188
126
 
189
127
  def check_domain_shape(x_point_num: int, y_point_num: int, nest_ratio: int) -> tuple[bool, bool]:
190
- """Check domain shape.
191
-
192
- Args:
193
- x_point_num (int): Point number of X side.
194
- y_point_num (int): Point number of Y side.
195
- nest_ratio (int): The nesting ratio relative to the domain’s parent.
196
-
197
- Returns:
198
- tuple[bool, bool]: Tuple of bool. True if pass check else False
128
+ """
129
+ Check domain shape.
130
+
131
+ :param x_point_num: Point number of X side.
132
+ :type x_point_num: int
133
+ :param y_point_num: Point number of Y side.
134
+ :type y_point_num: int
135
+ :param nest_ratio: The nesting ratio relative to the domain's parent.
136
+ :type nest_ratio: int
137
+ :return: Tuple of bool. True if pass check else False
138
+ :rtype: tuple[bool, bool]
199
139
  """
200
140
  return (
201
141
  _check_domain_shape(x_point_num, nest_ratio=nest_ratio),
202
- _check_domain_shape(y_point_num, nest_ratio=nest_ratio)
142
+ _check_domain_shape(y_point_num, nest_ratio=nest_ratio),
203
143
  )
204
144
 
205
145
 
206
- def check_subprocess_status(status: subprocess.CompletedProcess):
207
- """Check subprocess return code and print log if their return code != 0
208
-
209
- Args:
210
- status (CompletedProcess): Status from subprocess.
211
- """
212
- if status.returncode != 0:
213
- # print command
214
- command = status.args
215
- logger.error(f"Failed to exec command: {command}")
216
-
217
- # print log
218
- logger.error(f"====== stdout ======")
219
- logger.error(status.stdout.decode())
220
- logger.error(f"====== ====== ======")
221
- logger.error(f"====== stderr ======")
222
- logger.error(status.stderr.decode())
223
- logger.error(f"====== ====== ======")
224
-
225
- # raise error
226
- raise RuntimeError(f"Failed to exec command: '{command}'. Please check the log above.")
227
-
228
-
229
- def call_subprocess(command: list[str], work_path: Optional[str] = None, print_output=False):
230
- """
231
- Execute the given command in system shell.
232
-
233
- :param command: A list contains the command and parameters to be executed.
234
- :type command: list
235
- :param work_path: The work path of the command.
236
- If None, works in current directory.
237
- :type work_path: str | None
238
- :param print_output: If print standard output and error in the logger.
239
- :type print_output: bool
240
- :return:
241
- :rtype:
242
- """
243
- if work_path is not None:
244
- origin_path = getcwd()
245
- chdir(work_path)
246
- else:
247
- origin_path = None
248
-
249
- status = subprocess.run(' '.join(command), shell=True, capture_output=True)
250
-
251
- if origin_path is not None:
252
- chdir(origin_path)
253
-
254
- check_subprocess_status(status)
255
-
256
- if print_output:
257
- logger.info(status.stdout.decode())
258
- logger.warning(status.stderr.decode())
259
-
260
-
261
- __all__ = ["logger", "check_path", "logger_add_file_handler", "calculate_domain_shape", "rectify_domain_size", "check_domain_shape",
262
- "check_subprocess_status", "call_subprocess", "set_logger", "unify_logger_format"]
146
+ __all__ = [
147
+ "check_path",
148
+ "calculate_domain_shape",
149
+ "rectify_domain_size",
150
+ "check_domain_shape",
151
+ ]
@@ -9,6 +9,7 @@ Submodules
9
9
 
10
10
  ================================= ===========================================================
11
11
  :doc:`core </api/workspace.core>` Core functions of this submodule.
12
+ :doc:`palm </api/workspace.palm>` Functions to prepare workspace for PALM model.
12
13
  :doc:`wrf </api/workspace.wrf>` Functions to prepare workspace for WPS/WRF model.
13
14
  ================================= ===========================================================
14
15
 
@@ -16,15 +17,16 @@ Workspace
16
17
  *********
17
18
 
18
19
  ``workspace`` is a collection of several directories where ``wrfrun``, extensions and numerical model works.
19
- These directories and their purpose are listed below.
20
+ These directories and their purpose are listed below. ``$ROOT`` is the root work path set in config file,
21
+ or ``$HOME/.config/wrfrun``.
20
22
 
21
23
  =================================== ===========================================================
22
24
  Director Path Purpose
23
25
  =================================== ===========================================================
24
- ``/tmp/wrfrun`` Store temporary files.
25
- ``$HOME/.config/wrfrun`` Main work directory.
26
- ``$HOME/.config/wrfrun/replay`` Work directory for :doc:`replay <wrfrun.core.replay>`.
27
- ``$HOME/.config/wrfrun/model`` Work directory for numerical models.
26
+ ``$ROOT`` Main work directory.
27
+ ``$ROOT/tmp`` Store temporary files.
28
+ ``$ROOT/replay`` Work directory for :doc:`replay </api/core.replay>`.
29
+ ``$ROOT/workspace`` Work directory for numerical models.
28
30
  =================================== ===========================================================
29
31
 
30
32
  .. toctree::
@@ -32,6 +34,7 @@ Director Path Purpose
32
34
  :hidden:
33
35
 
34
36
  core <workspace.core>
37
+ palm <workspace.palm>
35
38
  wrf <workspace.wrf>
36
39
  """
37
40
 
wrfrun/workspace/core.py CHANGED
@@ -8,13 +8,16 @@ Core functions to prepare ``wrfrun`` workspace.
8
8
  :toctree: generated/
9
9
 
10
10
  prepare_workspace
11
+ check_workspace
11
12
  """
12
13
 
13
14
  from os.path import exists
14
15
  from shutil import rmtree
15
16
 
16
- from wrfrun.core import get_wrfrun_config
17
- from wrfrun.utils import check_path, logger
17
+ from wrfrun.core import WRFRUN
18
+ from wrfrun.log import check_path, logger
19
+
20
+ from .palm import prepare_palm_workspace
18
21
  from .wrf import check_wrf_workspace, prepare_wrf_workspace
19
22
 
20
23
 
@@ -34,16 +37,17 @@ def prepare_workspace():
34
37
 
35
38
  1. :doc:`WPS/WRF model </api/workspace.wrf>`
36
39
  """
37
- WRFRUNConfig = get_wrfrun_config()
38
- logger.info(f"Initialize main workspace.")
40
+ WRFRUNConfig = WRFRUN.config
39
41
 
40
42
  wrfrun_temp_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_TEMP_PATH)
41
43
  workspace_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_WORKSPACE_ROOT)
42
44
  replay_work_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_WORKSPACE_REPLAY)
43
45
  output_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_OUTPUT_PATH)
44
46
 
47
+ logger.info(f"Initialize main workspace at: {workspace_path}")
48
+
45
49
  if exists(workspace_path):
46
- logger.info(f"Remove old files in workspace.")
50
+ logger.info("Remove old files in workspace.")
47
51
  rmtree(workspace_path)
48
52
 
49
53
  # check folder
@@ -51,12 +55,14 @@ def prepare_workspace():
51
55
  check_path(replay_work_path)
52
56
  check_path(output_path)
53
57
 
54
- func_map = {
55
- "wrf": prepare_wrf_workspace
56
- }
58
+ func_map = {"wrf": prepare_wrf_workspace, "palm": prepare_palm_workspace}
57
59
  model_configs = WRFRUNConfig["model"]
58
60
 
59
61
  for model_name in model_configs:
62
+ if model_name not in func_map:
63
+ logger.warning(f"Function to prepare '{model_name}' workspace not found, workspace may be incomplete")
64
+ continue
65
+
60
66
  func_map[model_name](model_configs[model_name])
61
67
 
62
68
 
@@ -67,7 +73,7 @@ def check_workspace() -> bool:
67
73
  :return: ``True`` if workspace exists, ``False`` otherwise.
68
74
  :rtype: bool
69
75
  """
70
- WRFRUNConfig = get_wrfrun_config()
76
+ WRFRUNConfig = WRFRUN.config
71
77
 
72
78
  wrfrun_temp_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_TEMP_PATH)
73
79
  workspace_path = WRFRUNConfig.parse_resource_uri(WRFRUNConfig.WRFRUN_WORKSPACE_ROOT)
@@ -77,15 +83,17 @@ def check_workspace() -> bool:
77
83
  flag = True
78
84
  flag = flag & exists(wrfrun_temp_path) & exists(replay_work_path) & exists(output_path) & exists(workspace_path)
79
85
 
80
- func_map = {
81
- "wrf": check_wrf_workspace
82
- }
86
+ func_map = {"wrf": check_wrf_workspace}
83
87
  model_configs = WRFRUNConfig["model"]
84
88
 
85
89
  for model_name in model_configs:
86
90
  if model_name == "debug_level":
87
91
  continue
88
92
 
93
+ if model_name not in func_map:
94
+ logger.info(f"Function to check '{model_name}' workspace not found, skip")
95
+ continue
96
+
89
97
  flag = flag & func_map[model_name](model_configs[model_name])
90
98
 
91
99
  return True
@@ -0,0 +1,137 @@
1
+ """
2
+ wrfrun.workspace.palm
3
+ #####################
4
+
5
+ Functions to prepare workspace for PALM model.
6
+
7
+ .. autosummary::
8
+ :toctree: generated/
9
+
10
+ get_palm_workspace_path
11
+ prepare_palm_workspace
12
+ """
13
+
14
+ from os import remove, symlink
15
+ from os.path import abspath, exists, islink
16
+ from shutil import copyfile, move, rmtree
17
+ from typing import Literal
18
+
19
+ from wrfrun.core import WRFRUN, WRFRunConfig
20
+ from wrfrun.log import logger
21
+ from wrfrun.utils import check_path
22
+
23
+ WORKSPACE_PALM = ""
24
+
25
+
26
+ def _register_palm_workspace_uri(wrfrun_config: WRFRunConfig):
27
+ """
28
+ This function doesn't register any URI.
29
+
30
+ This is a hook to initializes some global strings.
31
+
32
+ :param wrfrun_config: ``WRFRunConfig`` instance.
33
+ :type wrfrun_config: WRFRunConfig
34
+ """
35
+ global WORKSPACE_PALM
36
+
37
+ WORKSPACE_PALM = f"{wrfrun_config.WRFRUN_WORKSPACE_MODEL}/PALM"
38
+
39
+
40
+ WRFRUN.set_config_register_func(_register_palm_workspace_uri)
41
+
42
+
43
+ def get_palm_workspace_path(node: Literal["root", "job", "input", "output"] = "root") -> str:
44
+ """
45
+ Get workspace of PALM model.
46
+
47
+ :param node: Which dir.
48
+ :type node: str
49
+ :return: Workspace path.
50
+ :rtype: str
51
+ """
52
+ match node:
53
+ case "root":
54
+ return WORKSPACE_PALM
55
+
56
+ case "job":
57
+ return f"{WORKSPACE_PALM}/job"
58
+
59
+ case "input":
60
+ return f"{WORKSPACE_PALM}/job/wrfrun/INPUT"
61
+
62
+ case "output":
63
+ return f"{WORKSPACE_PALM}/job/wrfrun/OUTPUT"
64
+
65
+
66
+ def prepare_palm_workspace(model_config: dict):
67
+ """
68
+ Initialize workspace for PALM model.
69
+
70
+ This function will check following paths,
71
+ and create them or delete old files inside:
72
+
73
+ 1. ``$HOME/.config/wrfrun/model/PALM``
74
+
75
+ :param model_config: Model config.
76
+ :type model_config: dict
77
+ """
78
+ logger.info("Initialize workspace for PALM.")
79
+
80
+ WRFRUNConfig = WRFRUN.config
81
+
82
+ palm_path = model_config["palm_path"]
83
+ config_id = model_config["config_identifier"]
84
+ config_file = model_config["config_file_path"]
85
+
86
+ palm_path = abspath(palm_path)
87
+
88
+ if not (exists(palm_path) and exists(f"{palm_path}/bin")):
89
+ logger.error("Your PALM path is wrong.")
90
+ raise FileNotFoundError("Your PALM path is wrong.")
91
+
92
+ palm_work_path = WRFRUNConfig.parse_resource_uri(WORKSPACE_PALM)
93
+ workspace_job_path = f"{palm_work_path}/job"
94
+ workspace_input_path = f"{workspace_job_path}/wrfrun/INPUT"
95
+ check_path(palm_work_path, workspace_job_path, workspace_input_path, force=True)
96
+
97
+ if not exists(f"{palm_path}/bin/palmrun"):
98
+ logger.error("Script 'palmrun' not found in your PALM dir.")
99
+ raise FileNotFoundError("Script 'palmrun' not found in your PALM dir.")
100
+
101
+ symlink(f"{palm_path}/bin/palmrun", f"{palm_work_path}/palmrun")
102
+
103
+ # we need some tricks to hack palm runtime directory.
104
+ job_path = f"{palm_path}/JOBS"
105
+ if exists(job_path):
106
+ if islink(job_path):
107
+ remove(job_path)
108
+
109
+ else:
110
+ new_job_path = f"{job_path}_wrfrun_bak"
111
+ if exists(new_job_path):
112
+ rmtree(new_job_path)
113
+
114
+ logger.warning(f"You have an existed PALM JOBS dir: {job_path}")
115
+ logger.warning(f"wrfrun has renamed it to: {new_job_path}")
116
+ logger.warning("If you have important files in it, please backup it to other positions,")
117
+ logger.warning("cause wrfrun may delete the renamed depository in the future.")
118
+
119
+ move(job_path, new_job_path)
120
+
121
+ symlink(workspace_job_path, job_path)
122
+
123
+ # PALM config file.
124
+ if config_file == "":
125
+ if not exists(f"{palm_path}/.palm.config.default"):
126
+ logger.error(f"Can't find the default config file: '{palm_path}/.palm.config.default', please provide one.")
127
+ raise FileNotFoundError(
128
+ f"Can't find the default config file: '{palm_path}/.palm.config.default', please provide one."
129
+ )
130
+
131
+ copyfile(f"{palm_path}/.palm.config.default", f"{palm_work_path}/.palm.config.{config_id}")
132
+
133
+ else:
134
+ symlink(abspath(config_file), f"{palm_work_path}/.palm.config.{config_id}")
135
+
136
+
137
+ __all__ = ["get_palm_workspace_path", "prepare_palm_workspace"]