Skip to content

Commit 52a52c1

Browse files
committed
Make the wells section optional
- The `wells` section it is made optional. If not present, the well names are derived from the well type (`well_control`). - If no `wells:` section is present, a default `wells.json containing just the well names is generated, because the forward models in `everest-models` may require it.
1 parent 7f7ad70 commit 52a52c1

8 files changed

Lines changed: 118 additions & 25 deletions

File tree

src/ert/run_models/everest_run_model.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,13 +140,26 @@ class _EvaluationInfo:
140140

141141
def _get_well_file(ever_config: EverestConfig) -> tuple[Path, str]:
142142
assert ever_config.output_dir is not None
143+
144+
def _get_variables(controls: list[ControlConfig]) -> list[dict[str, Any]]:
145+
wells = []
146+
for control in controls:
147+
if control.type != "well_control":
148+
continue
149+
for variable in control.variables:
150+
if variable.name not in wells:
151+
wells.append(variable.name)
152+
return [{"name": name} for name in wells]
153+
143154
data_storage = (Path(ever_config.output_dir) / ".internal_data").resolve()
144155
return (
145156
data_storage / "wells.json",
146157
json.dumps(
147-
[
158+
_get_variables(ever_config.controls)
159+
if ever_config.wells is None
160+
else [
148161
x.model_dump(exclude_none=True, exclude_unset=True)
149-
for x in ever_config.wells or []
162+
for x in ever_config.wells
150163
]
151164
),
152165
)

src/everest/config/everest_config.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,20 @@ class EverestConfig(BaseModelWithContextSupport):
293293
well as the level of detail in the logs""",
294294
),
295295
)
296-
wells: list[WellConfig] = Field(
297-
default_factory=list,
296+
wells: list[WellConfig] | None = Field(
297+
default=None,
298298
description=dedent(
299299
"""
300-
A list of well configurations.
300+
An optional list of well configurations.
301+
302+
Each well configuration consists of a `name` field, and an optional
303+
`drill_time` field. All variables in control groups with
304+
`control.type == "well_control"` must also be listed in this
305+
section.
306+
307+
If not present, a minimal well configuration containing only the
308+
names of wells will be derived from controls that have
309+
`control.type == "well_control"`.
301310
"""
302311
),
303312
)
@@ -840,7 +849,7 @@ def validate_variable_name_match_well_name(self) -> Self:
840849
if not well_names:
841850
return self
842851
for c in controls:
843-
if c.type == "generic_control":
852+
if c.type != "well_control":
844853
continue
845854
for v in c.variables:
846855
if v.name not in well_names:
@@ -861,7 +870,9 @@ def validate_that_environment_sim_folder_is_writeable(self) -> Self:
861870
@field_validator("wells")
862871
@no_type_check
863872
@classmethod
864-
def validate_unique_well_names(cls, wells: list[WellConfig]):
873+
def validate_unique_well_names(cls, wells: list[WellConfig] | None):
874+
if wells is None:
875+
return None
865876
check_for_duplicate_names([w.name for w in wells], "well", "name")
866877
return wells
867878

src/everest/simulator/everest_to_ert.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,16 @@ def extract_summary_keys(ever_config: EverestConfig) -> list[str]:
4747
[] if ever_config.export is None else ever_config.export.keywords
4848
)
4949

50-
wells = [well.name for well in ever_config.wells]
50+
wells = (
51+
[
52+
variable.name
53+
for control in ever_config.controls
54+
for variable in control.variables
55+
if control.type == "well_control"
56+
]
57+
if ever_config.wells is None
58+
else [w.name for w in ever_config.wells]
59+
)
5160

5261
well_keys = [
5362
f"{sum_key}:{wname}"

test-data/everest/eightcells/everest/model/config.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
definitions:
22
eclbase: eclipse/model/EIGHTCELLS
33

4-
wells:
5-
- { name: OP1 }
6-
- { name: WI1 }
7-
84
controls:
95
-
106
name: well_rate

test-data/everest/math_func/config_advanced.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
wells: []
2-
31
controls:
42
- name: point
53
max: 1.0

tests/everest/test_controls.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from everest.config.control_variable_config import (
88
ControlVariableConfig,
99
)
10-
from everest.config.well_config import WellConfig
1110
from everest.optimizer.everest2ropt import everest2ropt
1211

1312

@@ -169,9 +168,9 @@ def test_that_control_variable_name_with_too_many_dots_is_invalid(min_config):
169168

170169
def test_that_control_variable_without_too_many_dots_does_not_raise(min_config):
171170
weirdo_name = "something/with-symbols_=/()*&%$#!"
171+
min_config["controls"][0]["variables"][0]["name"] = weirdo_name
172+
min_config["wells"] = [{"name": weirdo_name}]
172173
new_config = EverestConfig.model_validate(min_config)
173-
new_config.wells.append(WellConfig(name=weirdo_name))
174-
new_config.controls[0].variables[0].name = weirdo_name
175174
EverestConfig.model_validate(new_config.to_dict())
176175

177176

tests/everest/test_res_initialization.py

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -155,15 +155,34 @@ def test_default_installed_jobs(tmp_path, monkeypatch):
155155
@pytest.mark.filterwarnings(
156156
"ignore:Config contains a SUMMARY key but no forward model steps"
157157
)
158-
def test_combined_wells_everest_to_ert(tmp_path, monkeypatch):
158+
@pytest.mark.parametrize(
159+
"config_yaml",
160+
[
161+
dedent("""
162+
wells: [{ name: fakename}]
163+
"""),
164+
dedent("""
165+
controls:
166+
- name: default_group
167+
type: well_control
168+
initial_guess: 0.5
169+
perturbation_magnitude: 0.01
170+
variables:
171+
- name: fakename
172+
min: 0
173+
max: 1
174+
"""),
175+
],
176+
)
177+
def test_combined_wells_everest_to_ert(tmp_path, monkeypatch, config_yaml):
159178
monkeypatch.chdir(tmp_path)
160179
Path("my_file").touch()
161180
Path("my_executable").touch(mode=stat.S_IEXEC)
162181
ever_config = EverestConfig.with_defaults(
163182
**yaml.safe_load(
164-
dedent("""
183+
config_yaml
184+
+ dedent("""
165185
model: {"realizations": [0]}
166-
wells: [{ name: fakename}]
167186
definitions: {eclbase: my_test_case}
168187
install_jobs:
169188
- name: nothing
@@ -220,9 +239,22 @@ def test_install_data_no_init(tmp_path, source, target, symlink, cmd, monkeypatc
220239

221240
@pytest.mark.integration_test
222241
@pytest.mark.skip_mac_ci
223-
def test_summary_default_no_opm(tmp_path, monkeypatch):
242+
@pytest.mark.parametrize("wells_config", [None, [{"name": "default_name"}]])
243+
def test_summary_default_no_opm(tmp_path, monkeypatch, wells_config):
224244
monkeypatch.chdir(tmp_path)
225245
everconf = EverestConfig.with_defaults(
246+
wells=wells_config,
247+
controls=[
248+
{
249+
"name": "default_group",
250+
"type": "well_control",
251+
"initial_guess": 0.5,
252+
"perturbation_magnitude": 0.01,
253+
"variables": [
254+
{"name": "default_name", "min": 0, "max": 1},
255+
],
256+
}
257+
],
226258
forward_model=[
227259
{
228260
"job": "eclipse100 eclipse/model/EgG.DATA --version 2020.2",
@@ -232,10 +264,19 @@ def test_summary_default_no_opm(tmp_path, monkeypatch):
232264
"keys": ["*"],
233265
},
234266
}
235-
]
267+
],
236268
)
237269
# Read wells from the config instead of using opm
238-
wells = [w.name for w in everconf.wells]
270+
wells = (
271+
[
272+
variable.name
273+
for control in everconf.controls
274+
for variable in control.variables
275+
if control.type == "well_control"
276+
]
277+
if wells_config is None
278+
else [w.name for w in everconf.wells]
279+
)
239280
sum_keys = (
240281
list(everest.simulator.DEFAULT_DATA_SUMMARY_KEYS)
241282
+ list(everest.simulator.DEFAULT_FIELD_SUMMARY_KEYS)
@@ -714,7 +755,7 @@ def test_that_summary_keys_default_to_expected_keys_according_to_wells(
714755
min_config["controls"] = [
715756
{
716757
"name": "well_rate",
717-
"type": "generic_control",
758+
"type": "well_control",
718759
"perturbation_magnitude": 0.01,
719760
"variables": [
720761
{

tests/everest/test_wells.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ def test_that_well_names_must_be_unique(min_config):
7373
pytest.param([{"name": "test"}], id="Default value not in result"),
7474
],
7575
)
76-
def test_well_config_to_file(min_config, monkeypatch, tmp_path, config):
76+
def test_well_config_to_wells_json(min_config, monkeypatch, tmp_path, config):
7777
monkeypatch.chdir(tmp_path)
7878
min_config["wells"] = config
7979
ever_config = EverestConfig(**min_config)
@@ -84,3 +84,29 @@ def test_well_config_to_file(min_config, monkeypatch, tmp_path, config):
8484
with open("everest_output/.internal_data/wells.json", encoding="utf-8") as fin:
8585
wells_json = json.load(fin)
8686
assert wells_json == config
87+
88+
89+
@pytest.mark.parametrize(
90+
"variables",
91+
[
92+
[{"name": "test", "initial_guess": 0.1}],
93+
[{"name": "test", "initial_guess": 0.1, "index": 1}],
94+
[
95+
{"name": "test", "initial_guess": 0.1, "index": 1},
96+
{"name": "test", "initial_guess": 0.1, "index": 2},
97+
],
98+
[{"name": "test", "initial_guess": [0.1]}],
99+
[{"name": "test", "initial_guess": [0.1, 0.1]}],
100+
],
101+
)
102+
def test_controls_config_to_wells_json(min_config, monkeypatch, tmp_path, variables):
103+
monkeypatch.chdir(tmp_path)
104+
min_config["controls"][0]["variables"] = variables
105+
ever_config = EverestConfig(**min_config)
106+
everest_to_ert_config_dict(ever_config)
107+
for datafile, data in _get_internal_files(ever_config).items():
108+
datafile.parent.mkdir(exist_ok=True, parents=True)
109+
datafile.write_text(data, encoding="utf-8")
110+
with open("everest_output/.internal_data/wells.json", encoding="utf-8") as fin:
111+
wells_json = json.load(fin)
112+
assert wells_json == [{"name": "test"}]

0 commit comments

Comments
 (0)