Skip to content

Commit 53487f8

Browse files
committed
Move GenKw template feature into run_template functionality
- Add parameter substition feature into substitute - Raise ConfigValidationError when absolute path is provided as output file - Storage migration - Remove forward_init from gen_kw - Update snapshot due to forward_init_file was removed
1 parent 469cae2 commit 53487f8

File tree

20 files changed

+96
-359
lines changed

20 files changed

+96
-359
lines changed

src/ert/config/design_matrix.py

-4
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,6 @@ def merge_with_existing_parameters(
168168
)
169169

170170
design_parameter_group.name = parameter_group.name
171-
design_parameter_group.template_file = parameter_group.template_file
172-
design_parameter_group.output_file = parameter_group.output_file
173171
design_group_added = True
174172
elif set(design_keys) & set(existing_keys):
175173
raise ConfigValidationError(
@@ -249,8 +247,6 @@ def read_and_validate_design_matrix(
249247
parameter_configuration = GenKwConfig(
250248
name=DESIGN_MATRIX_GROUP,
251249
forward_init=False,
252-
template_file=None,
253-
output_file=None,
254250
transform_function_definitions=transform_function_definitions,
255251
update=False,
256252
)

src/ert/config/ensemble_config.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,6 @@ def __post_init__(self) -> None:
6060
[p for p in self.parameter_configs.values() if isinstance(p, GenKwConfig)]
6161
)
6262

63-
self._check_for_forward_init_in_gen_kw(
64-
[p for p in self.parameter_configs.values() if isinstance(p, GenKwConfig)]
65-
)
66-
6763
self.grid_file = _get_abs_path(self.grid_file)
6864

6965
@staticmethod
@@ -97,11 +93,15 @@ def _check_for_duplicate_gen_kw_param_names(gen_kw_list: list[GenKwConfig]) -> N
9793
f"Found duplicate GEN_KW parameter names: {duplicates_formatted}"
9894
)
9995

96+
@no_type_check
10097
@staticmethod
101-
def _check_for_forward_init_in_gen_kw(gen_kw_list: list[GenKwConfig]) -> None:
102-
for gen_kw in gen_kw_list:
103-
if gen_kw.forward_init_file is not None:
104-
logger.info(f"GEN_KW uses FORWARD_INIT: {gen_kw}")
98+
def get_gen_kw_templates(config_dict: ConfigDict) -> list[tuple[str, str]]:
99+
gen_kw_list = config_dict.get(ConfigKeys.GEN_KW, [])
100+
return [
101+
template
102+
for g in gen_kw_list
103+
if (template := GenKwConfig.templates_from_config(g)) is not None
104+
]
105105

106106
@no_type_check
107107
@classmethod

src/ert/config/ert_config.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,7 @@
5858
ObservationConfigError,
5959
SummaryValues,
6060
)
61-
from .parsing.observations_parser import (
62-
parse_content as parse_observations,
63-
)
61+
from .parsing.observations_parser import parse_content as parse_observations
6462
from .queue_config import QueueConfig
6563
from .workflow import Workflow
6664
from .workflow_job import (
@@ -322,6 +320,7 @@ def read_templates(config_dict) -> list[tuple[str, str]]:
322320
"it is synced with your DATA file."
323321
)
324322
templates.append(template)
323+
templates.extend(EnsembleConfig.get_gen_kw_templates(config_dict))
325324
return templates
326325

327326

@@ -1220,8 +1219,9 @@ def env_pr_fm_step(self) -> dict[str, dict[str, Any]]:
12201219

12211220
@staticmethod
12221221
def _create_observations(
1223-
obs_config_content: dict[str, HistoryValues | SummaryValues | GenObsValues]
1224-
| None,
1222+
obs_config_content: (
1223+
dict[str, HistoryValues | SummaryValues | GenObsValues] | None
1224+
),
12251225
ensemble_config: EnsembleConfig,
12261226
time_map: list[datetime] | None,
12271227
history: HistorySource,

src/ert/config/gen_kw_config.py

+31-89
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import math
44
import os
5-
import shutil
65
import warnings
76
from collections.abc import Callable
87
from dataclasses import dataclass
@@ -16,8 +15,6 @@
1615
from scipy.stats import norm
1716
from typing_extensions import TypedDict
1817

19-
from ert.substitutions import substitute_runpath_name
20-
2118
from ._str_to_bool import str_to_bool
2219
from .parameter_config import ParameterConfig
2320
from .parsing import ConfigValidationError, ConfigWarning, ErrorInfo
@@ -59,10 +56,7 @@ class TransformFunctionDefinition:
5956

6057
@dataclass
6158
class GenKwConfig(ParameterConfig):
62-
template_file: str | None
63-
output_file: str | None
6459
transform_function_definitions: list[TransformFunctionDefinition]
65-
forward_init_file: str | None = None
6660

6761
def __post_init__(self) -> None:
6862
self.transform_functions: list[TransformFunction] = []
@@ -86,31 +80,19 @@ def __len__(self) -> int:
8680
return len(self.transform_functions)
8781

8882
@classmethod
89-
def from_config_list(cls, gen_kw: list[str | dict[str, str]]) -> Self:
83+
def templates_from_config(
84+
cls, gen_kw: list[str | dict[str, str]]
85+
) -> tuple[str, str] | None:
9086
gen_kw_key = cast(str, gen_kw[0])
91-
92-
options = cast(dict[str, str], gen_kw[-1])
9387
positional_args = cast(list[str], gen_kw[:-1])
94-
forward_init = str_to_bool(options.get("FORWARD_INIT", "FALSE"))
95-
init_file = _get_abs_path(options.get("INIT_FILES"))
96-
update_parameter = str_to_bool(options.get("UPDATE", "TRUE"))
97-
errors = []
9888

99-
if len(positional_args) == 2:
100-
parameter_file_contents = positional_args[1][1]
101-
parameter_file_context = positional_args[1][0]
102-
template_file = None
103-
output_file = None
104-
elif len(positional_args) == 4:
89+
if len(positional_args) == 4:
10590
output_file = positional_args[2]
106-
parameter_file_contents = positional_args[3][1]
10791
parameter_file_context = positional_args[3][0]
10892
template_file = _get_abs_path(positional_args[1][0])
10993
if not os.path.isfile(template_file):
110-
errors.append(
111-
ConfigValidationError.with_context(
112-
f"No such template file: {template_file}", positional_args[1]
113-
)
94+
raise ConfigValidationError.with_context(
95+
f"No such template file: {template_file}", positional_args[1]
11496
)
11597
elif Path(template_file).stat().st_size == 0:
11698
token = getattr(parameter_file_context, "token", parameter_file_context)
@@ -121,31 +103,35 @@ def from_config_list(cls, gen_kw: list[str | dict[str, str]]) -> Self:
121103
f"instead: GEN_KW {gen_kw_key} {token}",
122104
positional_args[1],
123105
)
106+
if output_file.startswith("/"):
107+
raise ConfigValidationError.with_context(
108+
f"Output file cannot have an absolute path {output_file}",
109+
positional_args[2],
110+
)
111+
return template_file, output_file
112+
return None
113+
114+
@classmethod
115+
def from_config_list(cls, gen_kw: list[str | dict[str, str]]) -> Self:
116+
gen_kw_key = cast(str, gen_kw[0])
117+
118+
options = cast(dict[str, str], gen_kw[-1])
119+
positional_args = cast(list[str], gen_kw[:-1])
120+
update_parameter = str_to_bool(options.get("UPDATE", "TRUE"))
121+
errors = []
122+
123+
if len(positional_args) == 2:
124+
parameter_file_contents = positional_args[1][1]
125+
parameter_file_context = positional_args[1][0]
126+
elif len(positional_args) == 4:
127+
parameter_file_contents = positional_args[3][1]
128+
parameter_file_context = positional_args[3][0]
124129

125130
else:
126131
raise ConfigValidationError(
127132
f"Unexpected positional arguments: {positional_args}"
128133
)
129134

130-
if forward_init:
131-
errors.append(
132-
ConfigValidationError.with_context(
133-
"Loading GEN_KW from files created by the forward "
134-
"model is not supported.",
135-
gen_kw,
136-
)
137-
)
138-
139-
if init_file and "%" not in init_file:
140-
errors.append(
141-
ConfigValidationError.with_context(
142-
"Loading GEN_KW from files requires %d in file format", gen_kw
143-
)
144-
)
145-
146-
if errors:
147-
raise ConfigValidationError.from_collected(errors)
148-
149135
transform_function_definitions: list[TransformFunctionDefinition] = []
150136
for line_number, item in enumerate(parameter_file_contents.splitlines()):
151137
item = item.split("--")[0] # remove comments
@@ -187,10 +173,7 @@ def from_config_list(cls, gen_kw: list[str | dict[str, str]]) -> Self:
187173
)
188174
return cls(
189175
name=gen_kw_key,
190-
forward_init=forward_init,
191-
template_file=template_file,
192-
output_file=output_file,
193-
forward_init_file=init_file,
176+
forward_init=False,
194177
transform_function_definitions=transform_function_definitions,
195178
update=update_parameter,
196179
)
@@ -281,9 +264,6 @@ def _check_valid_derrf_parameters(prior: PriorDict) -> None:
281264
def sample_or_load(
282265
self, real_nr: int, random_seed: int, ensemble_size: int
283266
) -> xr.Dataset:
284-
if self.forward_init_file:
285-
return self.read_from_runpath(Path(), real_nr, 0)
286-
287267
keys = [e.name for e in self.transform_functions]
288268
parameter_value = self._sample_value(
289269
self.name,
@@ -306,22 +286,7 @@ def read_from_runpath(
306286
real_nr: int,
307287
iteration: int,
308288
) -> xr.Dataset:
309-
keys = [e.name for e in self.transform_functions]
310-
if not self.forward_init_file:
311-
raise ValueError("loading gen_kw values requires forward_init_file")
312-
313-
parameter_value = self._values_from_file(
314-
substitute_runpath_name(self.forward_init_file, real_nr, iteration),
315-
keys,
316-
)
317-
318-
return xr.Dataset(
319-
{
320-
"values": ("names", parameter_value),
321-
"transformed_values": ("names", self.transform(parameter_value)),
322-
"names": keys,
323-
}
324-
)
289+
return xr.Dataset()
325290

326291
def write_to_runpath(
327292
self,
@@ -362,21 +327,6 @@ def parse_value(value: float | int | str) -> float | int | str:
362327
if tf.use_log
363328
}
364329

365-
if self.template_file is not None and self.output_file is not None:
366-
target_file = substitute_runpath_name(
367-
self.output_file, real_nr, ensemble.iteration
368-
)
369-
target_file = target_file.removeprefix("/")
370-
(run_path / target_file).parent.mkdir(exist_ok=True, parents=True)
371-
template_file_path = (
372-
ensemble.experiment.mount_point / Path(self.template_file).name
373-
)
374-
with open(template_file_path, encoding="utf-8") as f:
375-
template = f.read()
376-
for key, value in data.items():
377-
template = template.replace(f"<{key}>", f"{value:.6g}")
378-
with open(run_path / target_file, "w", encoding="utf-8") as f:
379-
f.write(template)
380330
if log10_data:
381331
return {self.name: data, f"LOG10_{self.name}": log10_data}
382332
else:
@@ -553,14 +503,6 @@ def _parse_transform_function_definition(
553503
calc_func=PRIOR_FUNCTIONS[t.param_name],
554504
)
555505

556-
def save_experiment_data(self, experiment_path: Path) -> None:
557-
if self.template_file:
558-
incoming_template_file_path = Path(self.template_file)
559-
template_file_path = Path(
560-
experiment_path / incoming_template_file_path.name
561-
)
562-
shutil.copyfile(incoming_template_file_path, template_file_path)
563-
564506

565507
@dataclass
566508
class TransformFunction:

src/ert/enkf_main.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def _generate_parameter_files(
9090
iens: int,
9191
fs: Ensemble,
9292
iteration: int,
93-
) -> None:
93+
) -> dict[str, dict[str, float]]:
9494
"""
9595
Generate parameter files that are placed in each runtime directory for
9696
forward-model jobs to consume.
@@ -119,6 +119,7 @@ def _generate_parameter_files(
119119

120120
_value_export_txt(run_path, export_base_name, exports)
121121
_value_export_json(run_path, export_base_name, exports)
122+
return exports
122123

123124

124125
def _manifest_to_json(ensemble: Ensemble, iens: int, iter: int) -> dict[str, Any]:
@@ -130,6 +131,7 @@ def _manifest_to_json(ensemble: Ensemble, iens: int, iter: int) -> dict[str, Any
130131
ExtParamConfig | GenKwConfig | Field | SurfaceConfig,
131132
)
132133
if param_config.forward_init and ensemble.iteration == 0:
134+
assert not isinstance(param_config, GenKwConfig)
133135
assert param_config.forward_init_file is not None
134136
file_path = substitute_runpath_name(
135137
param_config.forward_init_file, iens, iter
@@ -225,6 +227,15 @@ def create_run_path(
225227
run_path = Path(run_arg.runpath)
226228
if run_arg.active:
227229
run_path.mkdir(parents=True, exist_ok=True)
230+
param_data = _generate_parameter_files(
231+
ensemble.experiment.parameter_configuration.values(),
232+
parameters_file,
233+
run_path,
234+
run_arg.iens,
235+
ensemble,
236+
ensemble.iteration,
237+
)
238+
228239
for (
229240
source_file_content,
230241
target_file,
@@ -237,6 +248,11 @@ def create_run_path(
237248
run_arg.iens,
238249
ensemble.iteration,
239250
)
251+
result = substitutions.substitute_parameters(
252+
result,
253+
param_data,
254+
)
255+
240256
target = run_path / target_file
241257
if not target.parent.exists():
242258
os.makedirs(
@@ -245,15 +261,6 @@ def create_run_path(
245261
)
246262
target.write_text(result)
247263

248-
_generate_parameter_files(
249-
ensemble.experiment.parameter_configuration.values(),
250-
parameters_file,
251-
run_path,
252-
run_arg.iens,
253-
ensemble,
254-
ensemble.iteration,
255-
)
256-
257264
path = run_path / "jobs.json"
258265
_backup_if_existing(path)
259266

src/ert/storage/migration/to10.py

+12-1
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,24 @@
44

55
from ert.storage.local_storage import local_storage_get_ert_config
66

7-
info = "Internalizes run templates in to the storage"
7+
info = "Internalizes run templates in to the storage and migrate template GEN_KW feature into RUN_TEMPLATE"
88

99

1010
def migrate(path: Path) -> None:
1111
ert_config = local_storage_get_ert_config()
1212
if ert_config.ert_templates:
1313
for experiment in path.glob("experiments/*"):
14+
with open(experiment / "parameter.json", encoding="utf-8") as fin:
15+
parameters_json = json.load(fin)
16+
17+
with open(experiment / "parameter.json", "w", encoding="utf-8") as fout:
18+
for param in parameters_json.values():
19+
if param["_ert_kind"] == "GenKwConfig":
20+
param.pop("template_file", None)
21+
param.pop("output_file", None)
22+
param.pop("forward_init_file", None)
23+
fout.write(json.dumps(parameters_json, indent=4))
24+
1425
templates_abs: list[tuple[str, str]] = []
1526
templates_path = experiment / "templates"
1627
templates_path.mkdir(parents=True, exist_ok=True)

src/ert/substitutions.py

+8
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ def substitute(
3030
"""
3131
return _substitute(self, to_substitute, context, max_iterations, warn_max_iter)
3232

33+
def substitute_parameters(
34+
self, to_substitute: str, data: dict[str, dict[str, float]]
35+
) -> str:
36+
for values in data.values():
37+
for key, value in values.items():
38+
to_substitute = to_substitute.replace(f"<{key}>", f"{value:.6g}")
39+
return to_substitute
40+
3341
def substitute_real_iter(
3442
self, to_substitute: str, realization: int, iteration: int
3543
) -> str:

0 commit comments

Comments
 (0)