Skip to content

Commit 971d1d1

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
1 parent 82660b9 commit 971d1d1

15 files changed

+108
-170
lines changed

src/ert/config/design_matrix.py

-2
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,6 @@ def read_and_validate_design_matrix(
238238
parameter_configuration = GenKwConfig(
239239
name=DESIGN_MATRIX_GROUP,
240240
forward_init=False,
241-
template_file=None,
242-
output_file=None,
243241
transform_function_definitions=transform_function_definitions,
244242
update=False,
245243
)

src/ert/config/ensemble_config.py

+10
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,16 @@ def _check_for_forward_init_in_gen_kw(gen_kw_list: list[GenKwConfig]) -> None:
103103
if gen_kw.forward_init_file is not None:
104104
logger.info(f"GEN_KW uses FORWARD_INIT: {gen_kw}")
105105

106+
@no_type_check
107+
@staticmethod
108+
def get_gen_kw_templates(config_dict: ConfigDict) -> list[tuple[str, str]]:
109+
gen_kw_list = config_dict.get(ConfigKeys.GEN_KW, [])
110+
return [
111+
template
112+
for g in gen_kw_list
113+
if (template := GenKwConfig.templates_from_config(g)) is not None
114+
]
115+
106116
@no_type_check
107117
@classmethod
108118
def from_dict(cls, config_dict: ConfigDict) -> EnsembleConfig:

src/ert/config/ert_config.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,7 @@
5757
ObservationConfigError,
5858
SummaryValues,
5959
)
60-
from .parsing.observations_parser import (
61-
parse_content as parse_observations,
62-
)
60+
from .parsing.observations_parser import parse_content as parse_observations
6361
from .queue_config import QueueConfig
6462
from .workflow import Workflow
6563
from .workflow_job import (
@@ -299,6 +297,7 @@ def read_templates(config_dict) -> list[tuple[str, str]]:
299297
"it is synced with your DATA file."
300298
)
301299
templates.append(template)
300+
templates.extend(EnsembleConfig.get_gen_kw_templates(config_dict))
302301
return templates
303302

304303

src/ert/config/gen_kw_config.py

+52-44
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
@@ -59,8 +58,6 @@ class TransformFunctionDefinition:
5958

6059
@dataclass
6160
class GenKwConfig(ParameterConfig):
62-
template_file: str | None
63-
output_file: str | None
6461
transform_function_definitions: list[TransformFunctionDefinition]
6562
forward_init_file: str | None = None
6663

@@ -85,6 +82,36 @@ def __contains__(self, item: str) -> bool:
8582
def __len__(self) -> int:
8683
return len(self.transform_functions)
8784

85+
@classmethod
86+
def templates_from_config(
87+
cls, gen_kw: list[str | dict[str, str]]
88+
) -> tuple[str, str] | None:
89+
gen_kw_key = cast(str, gen_kw[0])
90+
positional_args = cast(list[str], gen_kw[:-1])
91+
92+
if len(positional_args) == 4:
93+
output_file = positional_args[2]
94+
parameter_file_context = positional_args[3][0]
95+
template_file = _get_abs_path(positional_args[1][0])
96+
if not os.path.isfile(template_file):
97+
raise ConfigValidationError.with_context(
98+
f"No such template file: {template_file}", positional_args[1]
99+
)
100+
elif Path(template_file).stat().st_size == 0:
101+
token = getattr(parameter_file_context, "token", parameter_file_context)
102+
ConfigWarning.deprecation_warn(
103+
f"The template file for GEN_KW ({gen_kw_key}) is empty. If templating is not needed, you "
104+
f"can use GEN_KW with just the distribution file instead: GEN_KW {gen_kw_key} {token}",
105+
positional_args[1],
106+
)
107+
if output_file.startswith("/"):
108+
raise ConfigValidationError.with_context(
109+
f"Output file cannot have an absolute path {output_file}",
110+
positional_args[2],
111+
)
112+
return template_file, output_file
113+
return None
114+
88115
@classmethod
89116
def from_config_list(cls, gen_kw: list[str | dict[str, str]]) -> Self:
90117
gen_kw_key = cast(str, gen_kw[0])
@@ -99,26 +126,9 @@ def from_config_list(cls, gen_kw: list[str | dict[str, str]]) -> Self:
99126
if len(positional_args) == 2:
100127
parameter_file_contents = positional_args[1][1]
101128
parameter_file_context = positional_args[1][0]
102-
template_file = None
103-
output_file = None
104129
elif len(positional_args) == 4:
105-
output_file = positional_args[2]
106130
parameter_file_contents = positional_args[3][1]
107131
parameter_file_context = positional_args[3][0]
108-
template_file = _get_abs_path(positional_args[1][0])
109-
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-
)
114-
)
115-
elif Path(template_file).stat().st_size == 0:
116-
token = getattr(parameter_file_context, "token", parameter_file_context)
117-
ConfigWarning.deprecation_warn(
118-
f"The template file for GEN_KW ({gen_kw_key}) is empty. If templating is not needed, you "
119-
f"can use GEN_KW with just the distribution file instead: GEN_KW {gen_kw_key} {token}",
120-
positional_args[1],
121-
)
122132

123133
else:
124134
raise ConfigValidationError(
@@ -185,8 +195,6 @@ def from_config_list(cls, gen_kw: list[str | dict[str, str]]) -> Self:
185195
return cls(
186196
name=gen_kw_key,
187197
forward_init=forward_init,
188-
template_file=template_file,
189-
output_file=output_file,
190198
forward_init_file=init_file,
191199
transform_function_definitions=transform_function_definitions,
192200
update=update_parameter,
@@ -358,21 +366,21 @@ def parse_value(value: float | int | str) -> float | int | str:
358366
if tf.use_log
359367
}
360368

361-
if self.template_file is not None and self.output_file is not None:
362-
target_file = substitute_runpath_name(
363-
self.output_file, real_nr, ensemble.iteration
364-
)
365-
target_file = target_file.removeprefix("/")
366-
(run_path / target_file).parent.mkdir(exist_ok=True, parents=True)
367-
template_file_path = (
368-
ensemble.experiment.mount_point / Path(self.template_file).name
369-
)
370-
with open(template_file_path, encoding="utf-8") as f:
371-
template = f.read()
372-
for key, value in data.items():
373-
template = template.replace(f"<{key}>", f"{value:.6g}")
374-
with open(run_path / target_file, "w", encoding="utf-8") as f:
375-
f.write(template)
369+
# if self.template_file is not None and self.output_file is not None:
370+
# target_file = substitute_runpath_name(
371+
# self.output_file, real_nr, ensemble.iteration
372+
# )
373+
# target_file = target_file.removeprefix("/")
374+
# (run_path / target_file).parent.mkdir(exist_ok=True, parents=True)
375+
# template_file_path = (
376+
# ensemble.experiment.mount_point / Path(self.template_file).name
377+
# )
378+
# with open(template_file_path, encoding="utf-8") as f:
379+
# template = f.read()
380+
# for key, value in data.items():
381+
# template = template.replace(f"<{key}>", f"{value:.6g}")
382+
# with open(run_path / target_file, "w", encoding="utf-8") as f:
383+
# f.write(template)
376384
if log10_data:
377385
return {self.name: data, f"LOG10_{self.name}": log10_data}
378386
else:
@@ -542,13 +550,13 @@ def _parse_transform_function_definition(
542550
calc_func=PRIOR_FUNCTIONS[t.param_name],
543551
)
544552

545-
def save_experiment_data(self, experiment_path: Path) -> None:
546-
if self.template_file:
547-
incoming_template_file_path = Path(self.template_file)
548-
template_file_path = Path(
549-
experiment_path / incoming_template_file_path.name
550-
)
551-
shutil.copyfile(incoming_template_file_path, template_file_path)
553+
# def save_experiment_data(self, experiment_path: Path) -> None:
554+
# if self.template_file:
555+
# incoming_template_file_path = Path(self.template_file)
556+
# template_file_path = Path(
557+
# experiment_path / incoming_template_file_path.name
558+
# )
559+
# shutil.copyfile(incoming_template_file_path, template_file_path)
552560

553561

554562
@dataclass

src/ert/enkf_main.py

+16-10
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ def _generate_parameter_files(
9191
iens: int,
9292
fs: Ensemble,
9393
iteration: int,
94-
) -> None:
94+
) -> dict[str, dict[str, float]]:
9595
"""
9696
Generate parameter files that are placed in each runtime directory for
9797
forward-model jobs to consume.
@@ -120,6 +120,7 @@ def _generate_parameter_files(
120120

121121
_value_export_txt(run_path, export_base_name, exports)
122122
_value_export_json(run_path, export_base_name, exports)
123+
return exports
123124

124125

125126
def _manifest_to_json(ensemble: Ensemble, iens: int, iter: int) -> dict[str, Any]:
@@ -242,6 +243,15 @@ def create_run_path(
242243
run_path = Path(run_arg.runpath)
243244
if run_arg.active:
244245
run_path.mkdir(parents=True, exist_ok=True)
246+
param_data = _generate_parameter_files(
247+
ensemble.experiment.parameter_configuration.values(),
248+
parameters_file,
249+
run_path,
250+
run_arg.iens,
251+
ensemble,
252+
ensemble.iteration,
253+
)
254+
245255
for source_file, target_file in templates:
246256
target_file = substitutions.substitute_real_iter(
247257
target_file, run_arg.iens, ensemble.iteration
@@ -258,6 +268,11 @@ def create_run_path(
258268
run_arg.iens,
259269
ensemble.iteration,
260270
)
271+
result = substitutions.substitute_parameters(
272+
result,
273+
param_data,
274+
)
275+
261276
target = run_path / target_file
262277
if not target.parent.exists():
263278
os.makedirs(
@@ -266,15 +281,6 @@ def create_run_path(
266281
)
267282
target.write_text(result)
268283

269-
_generate_parameter_files(
270-
ensemble.experiment.parameter_configuration.values(),
271-
parameters_file,
272-
run_path,
273-
run_arg.iens,
274-
ensemble,
275-
ensemble.iteration,
276-
)
277-
278284
path = run_path / "jobs.json"
279285
_backup_if_existing(path)
280286

src/ert/substitutions.py

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

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

tests/ert/ui_tests/cli/analysis/test_es_update.py

-2
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,9 @@ def uniform_parameter():
2424
return GenKwConfig(
2525
name="PARAMETER",
2626
forward_init=False,
27-
template_file="",
2827
transform_function_definitions=[
2928
TransformFunctionDefinition("KEY1", "UNIFORM", [0, 1]),
3029
],
31-
output_file="kw.txt",
3230
update=True,
3331
)
3432

tests/ert/unit_tests/analysis/test_es_update.py

+1-13
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@
88
import xtgeo
99
from tabulate import tabulate
1010

11-
from ert.analysis import (
12-
ErtAnalysisError,
13-
ObservationStatus,
14-
smoother_update,
15-
)
11+
from ert.analysis import ErtAnalysisError, ObservationStatus, smoother_update
1612
from ert.analysis._es_update import (
1713
_load_observations_and_responses,
1814
_load_param_ensemble_array,
@@ -30,11 +26,9 @@ def uniform_parameter():
3026
return GenKwConfig(
3127
name="PARAMETER",
3228
forward_init=False,
33-
template_file="",
3429
transform_function_definitions=[
3530
TransformFunctionDefinition("KEY1", "UNIFORM", [0, 1]),
3631
],
37-
output_file="kw.txt",
3832
update=True,
3933
)
4034

@@ -208,8 +202,6 @@ def test_update_handles_precision_loss_in_std_dev(tmp_path):
208202
name="COEFFS",
209203
forward_init=False,
210204
update=True,
211-
template_file=None,
212-
output_file=None,
213205
transform_function_definitions=[
214206
TransformFunctionDefinition(
215207
name="coeff_0", param_name="CONST", values=["0.1"]
@@ -331,8 +323,6 @@ def test_update_raises_on_singular_matrix(tmp_path):
331323
name="COEFFS",
332324
forward_init=False,
333325
update=True,
334-
template_file=None,
335-
output_file=None,
336326
transform_function_definitions=[
337327
TransformFunctionDefinition(
338328
name="coeff_0", param_name="CONST", values=["0.1"]
@@ -1002,11 +992,9 @@ def test_update_subset_parameters(storage, uniform_parameter, obs):
1002992
no_update_param = GenKwConfig(
1003993
name="EXTRA_PARAMETER",
1004994
forward_init=False,
1005-
template_file="",
1006995
transform_function_definitions=[
1007996
TransformFunctionDefinition("KEY1", "UNIFORM", [0, 1]),
1008997
],
1009-
output_file=None,
1010998
update=False,
1011999
)
10121000
resp = GenDataConfig(keys=["RESPONSE"])

0 commit comments

Comments
 (0)