wrfrun 0.1.9__py3-none-any.whl → 0.3.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 (66) hide show
  1. wrfrun/__init__.py +8 -3
  2. wrfrun/cli.py +74 -31
  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 +136 -380
  11. wrfrun/core/core.py +196 -0
  12. wrfrun/core/error.py +35 -2
  13. wrfrun/core/replay.py +10 -96
  14. wrfrun/core/server.py +74 -32
  15. wrfrun/core/type.py +171 -0
  16. wrfrun/data.py +312 -149
  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 +5 -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 -114
  30. wrfrun/model/type.py +116 -0
  31. wrfrun/model/utils.py +9 -19
  32. wrfrun/model/wrf/__init__.py +4 -9
  33. wrfrun/model/wrf/core.py +262 -165
  34. wrfrun/model/wrf/exec_wrap.py +13 -12
  35. wrfrun/model/wrf/geodata.py +116 -99
  36. wrfrun/model/wrf/log.py +103 -0
  37. wrfrun/model/wrf/namelist.py +92 -76
  38. wrfrun/model/wrf/plot.py +102 -0
  39. wrfrun/model/wrf/scheme.py +108 -52
  40. wrfrun/model/wrf/utils.py +39 -24
  41. wrfrun/model/wrf/vtable.py +42 -7
  42. wrfrun/plot/__init__.py +20 -0
  43. wrfrun/plot/wps.py +90 -73
  44. wrfrun/res/__init__.py +115 -5
  45. wrfrun/res/config/config.template.toml +15 -0
  46. wrfrun/res/config/palm.template.toml +23 -0
  47. wrfrun/run.py +121 -77
  48. wrfrun/scheduler/__init__.py +1 -0
  49. wrfrun/scheduler/lsf.py +4 -1
  50. wrfrun/scheduler/pbs.py +4 -1
  51. wrfrun/scheduler/script.py +19 -5
  52. wrfrun/scheduler/slurm.py +4 -1
  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 +21 -11
  57. wrfrun/workspace/palm.py +137 -0
  58. wrfrun/workspace/wrf.py +59 -14
  59. wrfrun-0.3.0.dist-info/METADATA +240 -0
  60. wrfrun-0.3.0.dist-info/RECORD +78 -0
  61. wrfrun/core/config.py +0 -767
  62. wrfrun/model/base.py +0 -14
  63. wrfrun-0.1.9.dist-info/METADATA +0 -68
  64. wrfrun-0.1.9.dist-info/RECORD +0 -62
  65. {wrfrun-0.1.9.dist-info → wrfrun-0.3.0.dist-info}/WHEEL +0 -0
  66. {wrfrun-0.1.9.dist-info → wrfrun-0.3.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,236 @@
1
+ """
2
+ wrfrun.core._constant
3
+ #####################
4
+
5
+ .. autosummary::
6
+ :toctree: generated/
7
+
8
+ ConstantMixIn
9
+
10
+ ConstantMixIn
11
+ *************
12
+
13
+ This class provides methods to manage runtime constants of ``wrfrun``.
14
+ """
15
+
16
+ from os import environ
17
+ from os.path import abspath
18
+ from sys import platform
19
+
20
+ from ..log import logger
21
+ from .error import WRFRunContextError
22
+
23
+
24
+ class ConstantMixIn:
25
+ """
26
+ Define all variables that will be used by other components.
27
+ """
28
+
29
+ def __init__(self, work_dir: str, *args, **kwargs):
30
+ """
31
+ Define all variables that will be used by other components.
32
+
33
+ These variables are related to ``wrfrun`` installation environments, configuration files and more.
34
+ They are defined either directly or mapped using URIs to ensure consistent access across all components.
35
+
36
+ :param work_dir: ``wrfrun`` work directory path.
37
+ :type work_dir: str
38
+ """
39
+ # check system
40
+ if platform != "linux":
41
+ logger.debug("Not Linux system!")
42
+
43
+ if work_dir != "" or platform != "linux":
44
+ # set temporary dir path
45
+ self._WRFRUN_TEMP_PATH = abspath(f"{work_dir}/tmp")
46
+ self._WRFRUN_HOME_PATH = abspath(work_dir)
47
+
48
+ else:
49
+ # the path we may need to store temp files,
50
+ # don't worry, it will be deleted once the system reboots
51
+ self._WRFRUN_TEMP_PATH = "/tmp/wrfrun"
52
+ user_home_path = f"{environ['HOME']}"
53
+
54
+ # WRF may need a large disk space to store output, we can't run wrf in /tmp,
55
+ # so we will create a folder in $HOME/.config to run wrf.
56
+ # we need to check if we're running as a root user
57
+ if user_home_path in ["/", "/root", ""]:
58
+ logger.warning(
59
+ f"User's home path is '{user_home_path}', which means you are running this program as a root user"
60
+ )
61
+ logger.warning("It's not recommended to use wrfrun as a root user")
62
+ logger.warning("Set user_home_path as /root")
63
+ user_home_path = "/root"
64
+
65
+ self._WRFRUN_HOME_PATH = f"{user_home_path}/.config/wrfrun"
66
+
67
+ # workspace root path
68
+ self._WRFRUN_WORKSPACE_ROOT = f"{self._WRFRUN_HOME_PATH}/workspace"
69
+ self._WRFRUN_WORKSPACE_MODEL = f"{self._WRFRUN_WORKSPACE_ROOT}/model"
70
+ self._WRFRUN_WORKSPACE_REPLAY = f"{self._WRFRUN_WORKSPACE_ROOT}/replay"
71
+
72
+ # record WRF progress status
73
+ self._WRFRUN_WORK_STATUS = ""
74
+
75
+ # record context status
76
+ self._WRFRUN_CONTEXT_STATUS = False
77
+
78
+ self._WRFRUN_OUTPUT_PATH = ":WRFRUN_OUTPUT_PATH:"
79
+ self._WRFRUN_RESOURCE_PATH = ":WRFRUN_RESOURCE_PATH:"
80
+
81
+ self.IS_IN_REPLAY: bool = False
82
+
83
+ self.IS_RECORDING: bool = False
84
+
85
+ # in this mode, wrfrun will do all things except call the numerical model.
86
+ # all output rules will also not be executed.
87
+ self.FAKE_SIMULATION_MODE = False
88
+
89
+ super().__init__(*args, **kwargs)
90
+
91
+ def _get_uri_map(self) -> dict[str, str]:
92
+ """
93
+ Return URIs and their values.
94
+ ``wrfrun`` will use this to register uri when initialize config.
95
+
96
+ :return: A dict in which URIs are keys and their values are dictionary values.
97
+ :rtype: dict
98
+ """
99
+ return {
100
+ self.WRFRUN_TEMP_PATH: self._WRFRUN_TEMP_PATH,
101
+ self.WRFRUN_HOME_PATH: self._WRFRUN_HOME_PATH,
102
+ self.WRFRUN_WORKSPACE_ROOT: self._WRFRUN_WORKSPACE_ROOT,
103
+ self.WRFRUN_WORKSPACE_MODEL: self._WRFRUN_WORKSPACE_MODEL,
104
+ self.WRFRUN_WORKSPACE_REPLAY: self._WRFRUN_WORKSPACE_REPLAY,
105
+ }
106
+
107
+ @property
108
+ def WRFRUN_WORKSPACE_REPLAY(self) -> str:
109
+ """
110
+ Path (URI) to store related files of ``wrfrun`` replay functionality.
111
+
112
+ :return: URI.
113
+ :rtype: str
114
+ """
115
+ return ":WRFRUN_WORKSPACE_REPLAY:"
116
+
117
+ @property
118
+ def WRFRUN_TEMP_PATH(self) -> str:
119
+ """
120
+ Path to store ``wrfrun`` temporary files.
121
+
122
+ :return: URI
123
+ :rtype: str
124
+ """
125
+ return ":WRFRUN_TEMP_PATH:"
126
+
127
+ @property
128
+ def WRFRUN_HOME_PATH(self) -> str:
129
+ """
130
+ Root path of all others directories. .
131
+
132
+ :return: URI
133
+ :rtype: str
134
+ """
135
+ return ":WRFRUN_HOME_PATH:"
136
+
137
+ @property
138
+ def WRFRUN_WORKSPACE_ROOT(self) -> str:
139
+ """
140
+ Path of the root workspace.
141
+
142
+ :return: URI
143
+ :rtype: str
144
+ """
145
+ return ":WRFRUN_WORKSPACE_ROOT:"
146
+
147
+ @property
148
+ def WRFRUN_WORKSPACE_MODEL(self) -> str:
149
+ """
150
+ Path of the model workspace, in which ``wrfrun`` runs numerical models.
151
+
152
+ :return: URI
153
+ :rtype: str
154
+ """
155
+ return ":WRFRUN_WORKSPACE_MODEL:"
156
+
157
+ @property
158
+ def WRFRUN_WORK_STATUS(self) -> str:
159
+ """
160
+ ``wrfrun`` work status.
161
+
162
+ This attribute can be changed by ``Executable`` to reflect the current work progress of ``wrfrun``.
163
+ The returned string is the name of ``Executable``.
164
+
165
+ :return: A string reflect the current work progress.
166
+ :rtype: str
167
+ """
168
+ return self._WRFRUN_WORK_STATUS
169
+
170
+ @WRFRUN_WORK_STATUS.setter
171
+ def WRFRUN_WORK_STATUS(self, value: str):
172
+ """
173
+ Set ``wrfrun`` work status.
174
+
175
+ ``wrfrun`` recommends ``Executable`` set the status string with their name,
176
+ so to avoid the possible conflicts with other ``Executable``,
177
+ and the user can easily understand the current work progress.
178
+
179
+ :param value: A string represents the work status.
180
+ :type value: str
181
+ """
182
+ self._WRFRUN_WORK_STATUS = value
183
+
184
+ @property
185
+ def WRFRUN_OUTPUT_PATH(self) -> str:
186
+ """
187
+ The root path to store all outputs of the ``wrfrun`` and NWP model.
188
+
189
+ :return: URI
190
+ :rtype: str
191
+ """
192
+ return self._WRFRUN_OUTPUT_PATH
193
+
194
+ @property
195
+ def WRFRUN_RESOURCE_PATH(self) -> str:
196
+ """
197
+ The root path of all ``wrfrun`` resource files.
198
+
199
+ :return: URI
200
+ :rtype: str
201
+ """
202
+ return self._WRFRUN_RESOURCE_PATH
203
+
204
+ def check_wrfrun_context(self, error=False) -> bool:
205
+ """
206
+ Check if in WRFRun context or not.
207
+
208
+ :param error: An exception :class:`WRFRunContextError` will be raised
209
+ if ``error==True`` when we are not in WRFRun context.
210
+ :type error: bool
211
+ :return: True or False.
212
+ :rtype: bool
213
+ """
214
+ if self._WRFRUN_CONTEXT_STATUS:
215
+ return self._WRFRUN_CONTEXT_STATUS
216
+
217
+ if not error:
218
+ logger.warning(
219
+ "You are using wrfrun without entering `WRFRun` context, which may cause some functions don't work."
220
+ )
221
+ return self._WRFRUN_CONTEXT_STATUS
222
+
223
+ logger.error("You need to enter `WRFRun` context to use wrfrun.")
224
+ raise WRFRunContextError("You need to enter `WRFRun` context to use wrfrun.")
225
+
226
+ def set_wrfrun_context(self, status: bool):
227
+ """
228
+ Change ``WRFRun`` context status to True or False.
229
+
230
+ :param status: ``True`` or ``False``.
231
+ :type status: bool
232
+ """
233
+ self._WRFRUN_CONTEXT_STATUS = status
234
+
235
+
236
+ __all__ = ["ConstantMixIn"]
@@ -0,0 +1,105 @@
1
+ """
2
+ wrfrun.core._exec_db
3
+ ####################
4
+
5
+ .. autosummary::
6
+ :toctree: generated/
7
+
8
+ ExecutableDB
9
+
10
+ ExecutableDB
11
+ ************
12
+
13
+ To be able to replay simulations, ``wrfrun`` use this class to store ``Executable`` information.
14
+ When replaying simulations, ``wrfrun`` gets the corresponding ``Executable`` and calls it with its name.
15
+ """
16
+
17
+ from typing import Callable
18
+
19
+ from ..log import logger
20
+ from .error import ExecRegisterError, GetExecClassError
21
+
22
+
23
+ class ExecutableDB:
24
+ """
25
+ This class provides the method to records ``Executable`` class with a unique ``name``.
26
+ Later you can get the class with the ``name``.
27
+ """
28
+
29
+ def __init__(self):
30
+ self._exec_db = {}
31
+
32
+ def apply_register_func(self, func_list: list[Callable[["ExecutableDB"], None]]):
33
+ """
34
+ Call register function provided by other submodules.
35
+
36
+ This method allows other submodules register ``Executable`` even after the whole wrfrun has been initialized.
37
+
38
+ :param func_list: A list contains register functions.
39
+ :type func_list: list[Callable[["WRFRunExecutableRegisterCenter"], None]]
40
+ """
41
+ while len(func_list) != 0:
42
+ _func = func_list.pop(0)
43
+ _func(self)
44
+
45
+ def register_exec(self, name: str, cls: type):
46
+ """
47
+ Register an ``Executable`` with a unique ``name``.
48
+
49
+ If the ``name`` has been used, :class:`ExecRegisterError <wrfrun.core.error.ExecRegisterError>` will be raised.
50
+
51
+ :param name: Unique name.
52
+ :type name: str
53
+ :param cls: ``Executable`` class.
54
+ :type cls: type
55
+ """
56
+ if name in self._exec_db:
57
+ logger.error(f"'{name}' has been registered.")
58
+ raise ExecRegisterError(f"'{name}' has been registered.")
59
+
60
+ self._exec_db[name] = cls
61
+
62
+ def unregister_exec(self, name: str):
63
+ """
64
+ Unregister an ``Executable``.
65
+
66
+ :param name: Unique name.
67
+ :type name: str
68
+ """
69
+ if name in self._exec_db:
70
+ logger.debug(f"Unregister Executable: '{name}'.")
71
+ self._exec_db.pop(name)
72
+
73
+ def is_registered(self, name: str) -> bool:
74
+ """
75
+ Check if an ``Executable`` has been registered.
76
+
77
+ :param name: Unique name.
78
+ :type name: str
79
+ :return: True or False.
80
+ :rtype: bool
81
+ """
82
+ if name in self._exec_db:
83
+ return True
84
+ else:
85
+ return False
86
+
87
+ def get_cls(self, name: str) -> type:
88
+ """
89
+ Get an ``Executable`` class with the ``name``.
90
+
91
+ If the ``name`` can't be found, :class:`ExecRegisterError <wrfrun.core.error.ExecRegisterError>` will be raised.
92
+
93
+ :param name: Unique name.
94
+ :type name: str
95
+ :return: ``Executable`` class.
96
+ :rtype: type
97
+ """
98
+ if name not in self._exec_db:
99
+ logger.error(f"Executable class '{name}' not found.")
100
+ raise GetExecClassError(f"Executable class '{name}' not found.")
101
+
102
+ return self._exec_db[name]
103
+
104
+
105
+ __all__ = ["ExecutableDB"]
@@ -0,0 +1,287 @@
1
+ """
2
+ wrfrun.core._namelist
3
+ #####################
4
+
5
+ .. autosummary::
6
+ :toctree: generated/
7
+
8
+ NamelistMixIn
9
+
10
+ NamelistMixIn
11
+ *************
12
+
13
+ This class provides methods to manage namelist settings of NWP models.
14
+ It can read namelist files, change namelist values or write values to a namelist file.
15
+
16
+ Namelist values are stored with a unique ``namelist_id``,
17
+ which enables wrfrun to manage multiple namelist settings simultaneously.
18
+
19
+ **Read namelist file**
20
+
21
+ .. code-block:: Python
22
+ :caption: main.py
23
+
24
+ namelist = NamelistMixIn()
25
+ namelist_file_path = "./namelist.wps"
26
+ namelist_id = "wps"
27
+
28
+ # remember to check namelist id first
29
+ if not namelist.check_namelist_id(namelist_id):
30
+ namelist.register_namelist_id(namelist_id)
31
+
32
+ # read namelist file
33
+ namelist.read_namelist(namelist_file_path, namelist_id)
34
+
35
+ **Write namelist file**
36
+
37
+ .. code-block:: Python
38
+ :caption: main.py
39
+
40
+ namelist_file_path = "./namelist.wps"
41
+ namelist_id = "wps"
42
+
43
+ # will overwrite existing file by default.
44
+ namelist.write_namelist(namelist_file_path, namelist_id)
45
+ # doesn't overwrite existing file
46
+ namelist.write_namelist(namelist_file_path, namelist_id, overwrite=False)
47
+
48
+ **Update namelist values**
49
+
50
+ 1. You can provide a whole namelist file
51
+
52
+ .. code-block:: Python
53
+ :caption: main.py
54
+
55
+ namelist_file_path = "./namelist.wps"
56
+ namelist_id = "wps"
57
+
58
+ namelist.update_namelist(namelist_file_path, namelist_id)
59
+
60
+ 2. Or just some values in a dictionary
61
+
62
+ .. code-block:: Python
63
+ :caption: main.py
64
+
65
+ namelist_value = {"share": {"max_dom": 1}}
66
+ namelist_id = "wps"
67
+
68
+ namelist.update_namelist(namelist_value, namelist_id)
69
+ """
70
+
71
+ from copy import deepcopy
72
+ from os.path import exists
73
+ from typing import Union
74
+
75
+ import f90nml
76
+
77
+ from ..log import logger
78
+ from .error import NamelistError, NamelistIDError
79
+
80
+
81
+ class NamelistMixIn:
82
+ """
83
+ Manage namelist settings of NWP models.
84
+ """
85
+
86
+ def __init__(self, *args, **kwargs):
87
+ self._namelist_dict = {}
88
+ self._namelist_id_list: tuple[str, ...] = ("param", "geog_static_data", "wps", "wrf", "wrfda", "palm")
89
+
90
+ super().__init__(*args, **kwargs)
91
+
92
+ def register_namelist_id(self, namelist_id: str) -> bool:
93
+ """
94
+ Register a unique ``namelist_id`` so you can read, update and write namelist with it later.
95
+
96
+ :param namelist_id: A unique namelist id.
97
+ :type namelist_id: str
98
+ :return: True if register successfully, else False.
99
+ :rtype: bool
100
+ """
101
+ if namelist_id in self._namelist_id_list:
102
+ return False
103
+
104
+ else:
105
+ self._namelist_id_list += (namelist_id,)
106
+ return True
107
+
108
+ def unregister_namelist_id(self, namelist_id: str):
109
+ """
110
+ Unregister a ``namelist_id``.
111
+ If unregister successfully, all values of this namelist will be deleted.
112
+
113
+ :param namelist_id: A unique namelist id.
114
+ :type namelist_id: str
115
+ """
116
+ if namelist_id not in self._namelist_id_list:
117
+ return
118
+
119
+ self.delete_namelist(namelist_id)
120
+ self._namelist_id_list = tuple(
121
+ set(self._namelist_id_list)
122
+ - {
123
+ namelist_id,
124
+ }
125
+ )
126
+
127
+ def check_namelist_id(self, namelist_id: str) -> bool:
128
+ """
129
+ Check if a ``namelist_id`` is registered.
130
+
131
+ :param namelist_id: A ``namelist_id``.
132
+ :type namelist_id: str
133
+ :return: True if the ``namelist_id`` is registered, else False.
134
+ :rtype: bool
135
+ """
136
+ if namelist_id in self._namelist_id_list:
137
+ return True
138
+ else:
139
+ return False
140
+
141
+ def read_namelist(self, file_path: str, namelist_id: str):
142
+ """
143
+ Read namelist values from a file and store them with the ``namelist_id``.
144
+
145
+ If ``wrfrun`` can't read the file, :class:`FileNotFoundError` will be raised.
146
+ If ``namelist_id`` isn't registered, :class:`NamelistIDError <wrfrun.core.error.NamelistIDError>` will be raised.
147
+
148
+ :param file_path: Namelist file path.
149
+ :type file_path: str
150
+ :param namelist_id: Registered ``namelist_id``.
151
+ :type namelist_id: str
152
+ """
153
+ # check the file path
154
+ if not exists(file_path):
155
+ logger.error(f"File not found: {file_path}")
156
+ raise FileNotFoundError
157
+
158
+ if namelist_id not in self._namelist_id_list:
159
+ logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
160
+ raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
161
+
162
+ self._namelist_dict[namelist_id] = f90nml.read(file_path).todict()
163
+
164
+ def write_namelist(self, save_path: str, namelist_id: str, overwrite=True):
165
+ """
166
+ Write namelist values of a ``namelist_id`` to a file.
167
+
168
+ If ``namelist_id`` hasn't been registered, or its namelist value can't be found,
169
+ :class:`NamelistIDError <wrfrun.core.error.NamelistIDError>` will be raised.
170
+
171
+ :param save_path: Target file path.
172
+ :type save_path: str
173
+ :param namelist_id: Registered ``namelist_id``.
174
+ :type namelist_id: str
175
+ :param overwrite: If overwrite the existed file.
176
+ :type overwrite: bool
177
+ """
178
+ if namelist_id not in self._namelist_id_list:
179
+ logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
180
+ raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
181
+
182
+ if namelist_id not in self._namelist_dict:
183
+ logger.error(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
184
+ raise NamelistError(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
185
+
186
+ f90nml.Namelist(self._namelist_dict[namelist_id]).write(save_path, force=overwrite)
187
+
188
+ def update_namelist(self, new_values: Union[str, dict], namelist_id: str):
189
+ """
190
+ Update namelist values of a ``namelist_id``.
191
+
192
+ You can give the path of a whole namelist file or a file only contains values you want to change.
193
+
194
+ >>> from wrfrun.core import WRFRUN
195
+ >>> namelist_file = "./namelist.wps"
196
+ >>> WRFRUN.config.update_namelist(namelist_file, namelist_id="wps")
197
+
198
+ >>> namelist_file = "./namelist.wrf"
199
+ >>> WRFRUN.config.update_namelist(namelist_file, namelist_id="wrf")
200
+
201
+ You can also give a dictionary contains values you want to change.
202
+
203
+ >>> namelist_values = {"ungrib": {"prefix": "./output/FILE"}}
204
+ >>> WRFRUN.config.update_namelist(namelist_values, namelist_id="wps")
205
+
206
+ >>> namelist_values = {"time_control": {"debug_level": 100}}
207
+ >>> WRFRUN.config.update_namelist(namelist_values, namelist_id="wrf")
208
+
209
+ :param new_values: The path of a namelist file, or a dict contains namelist values.
210
+ :type new_values: str | dict
211
+ :param namelist_id: Registered ``namelist_id``.
212
+ :type namelist_id: str
213
+ """
214
+ if namelist_id not in self._namelist_id_list:
215
+ logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
216
+ raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
217
+
218
+ elif namelist_id not in self._namelist_dict:
219
+ self._namelist_dict[namelist_id] = new_values
220
+ return
221
+
222
+ else:
223
+ reference = self._namelist_dict[namelist_id]
224
+
225
+ if isinstance(new_values, str):
226
+ if not exists(new_values):
227
+ logger.error(f"File not found: {new_values}")
228
+ raise FileNotFoundError(f"File not found: {new_values}")
229
+ new_values = f90nml.read(new_values).todict()
230
+
231
+ for key in new_values:
232
+ if key in reference:
233
+ reference[key].update(new_values[key])
234
+ else:
235
+ reference[key] = new_values[key]
236
+
237
+ def get_namelist(self, namelist_id: str) -> dict:
238
+ """
239
+ Get namelist values of a ``namelist_id``.
240
+
241
+ :param namelist_id: Registered ``namelist_id``.
242
+ :type namelist_id: str
243
+ :return: A dictionary which contains namelist values.
244
+ :rtype: dict
245
+ """
246
+ if namelist_id not in self._namelist_id_list:
247
+ logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
248
+ raise NamelistIDError(f"Unknown namelist id: {namelist_id}, register it first.")
249
+ elif namelist_id not in self._namelist_dict:
250
+ logger.error(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
251
+ raise NamelistError(f"Can't found custom namelist '{namelist_id}', maybe you forget to read it first")
252
+ else:
253
+ return deepcopy(self._namelist_dict[namelist_id])
254
+
255
+ def delete_namelist(self, namelist_id: str):
256
+ """
257
+ Delete namelist values of a ``namelist_id``.
258
+
259
+ :param namelist_id: Registered ``namelist_id``.
260
+ :type namelist_id: str
261
+ """
262
+ if namelist_id not in self._namelist_id_list:
263
+ logger.error(f"Unknown namelist id: {namelist_id}, register it first.")
264
+ raise ValueError(f"Unknown namelist id: {namelist_id}, register it first.")
265
+
266
+ if namelist_id not in self._namelist_dict:
267
+ return
268
+
269
+ self._namelist_dict.pop(namelist_id)
270
+
271
+ def check_namelist(self, namelist_id: str) -> bool:
272
+ """
273
+ Check if a namelist has been registered and loaded.
274
+
275
+ :param namelist_id: Registered ``namelist_id``.
276
+ :type namelist_id: str
277
+ :return: ``True`` if it is registered and loaded, else ``False``.
278
+ :rtype: bool
279
+ """
280
+ if namelist_id in self._namelist_id_list and namelist_id in self._namelist_dict:
281
+ return True
282
+
283
+ else:
284
+ return False
285
+
286
+
287
+ __all__ = ["NamelistMixIn"]