Skip to content

Commit 6f7dea0

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
1 parent df48cc8 commit 6f7dea0

File tree

21 files changed

+143
-193
lines changed

21 files changed

+143
-193
lines changed

src/ert/config/design_matrix.py

-4
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,6 @@ def merge_with_existing_parameters(
158158
)
159159

160160
design_parameter_group.name = parameter_group.name
161-
design_parameter_group.template_file = parameter_group.template_file
162-
design_parameter_group.output_file = parameter_group.output_file
163161
design_group_added = True
164162
elif set(design_keys) & set(existing_keys):
165163
raise ConfigValidationError(
@@ -237,8 +235,6 @@ def read_and_validate_design_matrix(
237235
parameter_configuration = GenKwConfig(
238236
name=DESIGN_MATRIX_GROUP,
239237
forward_init=False,
240-
template_file=None,
241-
output_file=None,
242238
transform_function_definitions=transform_function_definitions,
243239
update=False,
244240
)

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/storage/local_storage.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ def _migrate(self, version: int) -> None:
456456
to7,
457457
to8,
458458
to9,
459+
to10,
459460
)
460461

461462
try:
@@ -475,7 +476,8 @@ def _migrate(self, version: int) -> None:
475476

476477
logger.info("Blockfs storage backed up")
477478
print(
478-
dedent(f"""
479+
dedent(
480+
f"""
479481
Detected outdated storage (blockfs), which is no longer supported
480482
by ERT. Its contents are copied to:
481483
@@ -494,13 +496,14 @@ def _migrate(self, version: int) -> None:
494496
This is not guaranteed to work. Other than setting the custom
495497
ENSPATH, the ERT config should ideally be the same as it was
496498
when the old blockfs storage was created.
497-
""")
499+
"""
500+
)
498501
)
499502
return None
500503

501504
elif version < _LOCAL_STORAGE_VERSION:
502505
migrations = list(
503-
enumerate([to2, to3, to4, to5, to6, to7, to8, to9], start=1)
506+
enumerate([to2, to3, to4, to5, to6, to7, to8, to9, to10], start=1)
504507
)
505508
for from_version, migration in migrations[version - 1 :]:
506509
print(f"* Updating storage to version: {from_version + 1}")

src/ert/storage/migration/to10.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import json
2+
from pathlib import Path
3+
4+
info = "Migrate template GEN_KW feature into RUN_TEMPLATE"
5+
6+
7+
def migrate(path: Path) -> None:
8+
for experiment in path.glob("experiments/*"):
9+
with open(experiment / "parameter.json", encoding="utf-8") as fin:
10+
parameters_json = json.load(fin)
11+
12+
with open(experiment / "parameter.json", "w", encoding="utf-8") as fout:
13+
for param in parameters_json.values():
14+
if param["_ert_kind"] == "GenKwConfig":
15+
param.pop("template_file", None)
16+
param.pop("output_file", None)
17+
fout.write(json.dumps(parameters_json, indent=4))

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:

tests/ert/performance_tests/test_obs_and_responses_performance.py

-2
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,6 @@ def create_experiment_args(
5757
) -> ExperimentInfo:
5858
gen_kw_config = GenKwConfig(
5959
name="all_my_parameters_live_here",
60-
template_file=None,
61-
output_file=None,
6260
forward_init=False,
6361
update=True,
6462
transform_function_definitions=[

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

0 commit comments

Comments
 (0)