Skip to content

Commit f24fb9b

Browse files
committed
trying to construct config from namelist
1 parent 200ac17 commit f24fb9b

4 files changed

Lines changed: 124 additions & 40 deletions

File tree

model/testing/src/icon4py/model/testing/datatest_utils.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
import pathlib
1212
import urllib.parse
13+
import ast
14+
import re
15+
1316

1417
import gt4py.next.typing as gtx_typing
1518

@@ -72,3 +75,85 @@ def create_icon_serial_data_provider(
7275
mpi_rank=rank,
7376
do_print=True,
7477
)
78+
79+
80+
def read_namelist(path: pathlib.Path) -> dict:
81+
"""ICON NAMELIST_ICON_output_atm reader.
82+
Returns a dictionary of dictionaries, where the keys are the namelist names
83+
and the values are dictionaries of key-value pairs from the namelist.
84+
85+
Use as:
86+
namelists = read_namelist("/path/to/NAMELIST_ICON_output_atm")
87+
print(namelists["NWP_TUNING_NML"]["TUNE_ZCEFF_MIN"])
88+
"""
89+
with path.open() as f:
90+
txt = f.read()
91+
namelist_set = re.findall(r"&(\w+)(.*?)\/", txt, re.S)
92+
full_namelist = {}
93+
for namelist_name, namelist_content in namelist_set:
94+
full_namelist[namelist_name] = _parse_namelist_content(namelist_content)
95+
return full_namelist
96+
97+
98+
def _parse_namelist_content(namelist_content):
99+
"""
100+
Parse the contents of a single namelist group to a Python dictionary.
101+
"""
102+
result = {}
103+
current_variable = None
104+
105+
# Remove comments
106+
namelist_content = re.sub(r"!.*", "", namelist_content)
107+
108+
# Split into lines
109+
lines = namelist_content.splitlines()
110+
111+
for line in lines:
112+
line = line.strip()
113+
# skip any non-meaningful empty line
114+
if not line:
115+
continue
116+
117+
# Remove trailing comma
118+
if line.endswith(","):
119+
line = line[:-1]
120+
121+
if "=" in line:
122+
# New variable-value pair
123+
variable, value = line.split("=", 1)
124+
current_variable = variable.strip()
125+
result[current_variable] = _parse_value(value)
126+
# TODO(Chia Rui): check if continuation lines is irrelevant in our tests
127+
# else:
128+
# # Continuation line (array or multi-line string)
129+
# if current_variable is not None:
130+
# val = _parse_value(line)
131+
# # convert to a list if we have multiple values for the same variable
132+
# if not isinstance(result[current_variable], list):
133+
# result[current_variable] = [result[current_variable]]
134+
# result[current_variable].append(val)
135+
136+
return result
137+
138+
139+
def _parse_value(value):
140+
"""
141+
Convert Fortran-style values to Python types.
142+
"""
143+
value = value.strip()
144+
145+
# Fortran logical
146+
if value in ("T", ".T."):
147+
return True
148+
if value in ("F", ".F."):
149+
return False
150+
151+
# Quoted string
152+
if value.startswith("'") and value.endswith("'"):
153+
return value[1:-1].rstrip()
154+
155+
# Try numeric conversion
156+
try:
157+
return ast.literal_eval(value)
158+
except Exception:
159+
return value

model/testing/src/icon4py/model/testing/definitions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
SERIALIZED_DATA_DIR: Final = "ser_icondata"
2929
SERIALIZED_DATA_SUBDIR: Final = "ser_data"
3030
GRID_DATA_DIR: Final = "grids"
31+
NAMELIST_FILENAME: Final = "NAMELIST_ICON_output_atm"
3132

3233

3334
def serialized_data_path() -> pathlib.Path:
@@ -201,6 +202,7 @@ def construct_diffusion_config(
201202
) -> diffusion.DiffusionConfig:
202203
from icon4py.model.atmosphere.diffusion import diffusion
203204

205+
204206
if experiment == Experiments.MCH_CH_R04B09:
205207
return diffusion.DiffusionConfig(
206208
diffusion_type=diffusion.DiffusionType.SMAGORINSKY_4TH_ORDER,

model/testing/src/icon4py/model/testing/fixtures/datatest.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import icon4py.model.common.decomposition.definitions as decomposition
1717
from icon4py.model.common import model_backends, model_options
1818
from icon4py.model.common.constants import RayleighType
19-
from icon4py.model.common.grid import base as base_grid
19+
from icon4py.model.common.grid import base as base_grid, vertical as v_grid
2020
from icon4py.model.testing import data_handling, datatest_utils as dt_utils, definitions
2121

2222

@@ -154,6 +154,42 @@ def data_provider(
154154
return dt_utils.create_icon_serial_data_provider(data_path, processor_props.rank, backend)
155155

156156

157+
@pytest.fixture
158+
def icon_namelist(
159+
download_ser_data: None, # downloads data as side-effect
160+
experiment: definitions.Experiment,
161+
processor_props: decomposition.ProcessProperties,
162+
) -> dict:
163+
experiment_dir = dt_utils.get_ranked_experiment_name_with_version(
164+
experiment,
165+
processor_props.comm_size,
166+
)
167+
namelist_path = definitions.serialized_data_path().joinpath(
168+
experiment_dir, definitions.NAMELIST_FILENAME
169+
)
170+
return dt_utils.read_namelist(namelist_path)
171+
172+
173+
@pytest.fixture
174+
def vertical_grid_config(
175+
icon_namelist: dict,
176+
) -> v_grid.VerticalGridConfig:
177+
return v_grid.VerticalGridConfig(
178+
# TODO (Chia Rui): where should we put the config construction? And remove this hardcoded style in next commits
179+
num_levels=icon_namelist["RUN_NML"]["NUM_LEV"],
180+
maximal_layer_thickness=icon_namelist["SLEVE_NML"]["MAX_LAY_THCKN"],
181+
top_height_limit_for_maximal_layer_thickness=icon_namelist["SLEVE_NML"]["HTOP_THCKNLIMIT"],
182+
lowest_layer_thickness=icon_namelist["SLEVE_NML"]["MIN_LAY_THCKN"],
183+
model_top_height=icon_namelist["SLEVE_NML"]["TOP_HEIGHT"],
184+
flat_height=icon_namelist["SLEVE_NML"]["FLAT_HEIGHT"],
185+
stretch_factor=icon_namelist["SLEVE_NML"]["STRETCH_FAC"],
186+
rayleigh_damping_height=icon_namelist["NONHYDROSTATIC_NML"]["DAMP_HEIGHT"],
187+
htop_moist_proc=icon_namelist["NONHYDROSTATIC_NML"]["HTOP_MOIST_PROC"],
188+
SLEVE_decay_scale_1=icon_namelist["SLEVE_NML"]["DECAY_SCALE_1"],
189+
SLEVE_decay_scale_2=icon_namelist["SLEVE_NML"]["DECAY_SCALE_2"],
190+
SLEVE_decay_exponent=icon_namelist["SLEVE_NML"]["DECAY_EXP"],
191+
)
192+
157193
@pytest.fixture
158194
def grid_savepoint(
159195
data_provider: serialbox.IconSerialDataProvider, experiment: definitions.Experiment

model/testing/src/icon4py/model/testing/namelist_reader.py

Lines changed: 0 additions & 39 deletions
This file was deleted.

0 commit comments

Comments
 (0)