Skip to content

Commit dbf426b

Browse files
committed
Add design matrix valudation in ensemble experiment panel
- Prefil active realization box with realizations from design matrix - Use design_matrix parameters in ensemble experiment - add test run cli with design matrix and poly example - add test that save parameters internalize DataFrame parameters in the storage
1 parent 3b1587a commit dbf426b

File tree

6 files changed

+241
-20
lines changed

6 files changed

+241
-20
lines changed

src/ert/enkf_main.py

+23-7
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,17 @@
99
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Mapping, Optional, Union
1010

1111
import orjson
12+
import xarray as xr
1213
from numpy.random import SeedSequence
1314

14-
from .config import (
15-
ExtParamConfig,
16-
Field,
17-
GenKwConfig,
18-
ParameterConfig,
19-
SurfaceConfig,
20-
)
15+
from .config import ExtParamConfig, Field, GenKwConfig, ParameterConfig, SurfaceConfig
16+
from .config.design_matrix import DESIGN_MATRIX_GROUP
2117
from .run_arg import RunArg
2218
from .runpaths import Runpaths
2319

2420
if TYPE_CHECKING:
21+
import pandas as pd
22+
2523
from .config import ErtConfig
2624
from .storage import Ensemble
2725

@@ -148,6 +146,24 @@ def _seed_sequence(seed: Optional[int]) -> int:
148146
return int_seed
149147

150148

149+
def save_design_matrix_to_ensemble(
150+
design_matrix_df: pd.DataFrame,
151+
ensemble: Ensemble,
152+
active_realizations: Iterable[int],
153+
) -> None:
154+
assert not design_matrix_df.empty
155+
for realization_nr in active_realizations:
156+
row = design_matrix_df.loc[realization_nr][DESIGN_MATRIX_GROUP]
157+
ds = xr.Dataset(
158+
{
159+
"values": ("names", list(row.values)),
160+
"transformed_values": ("names", list(row.values)),
161+
"names": list(row.keys()),
162+
}
163+
)
164+
ensemble.save_parameters(DESIGN_MATRIX_GROUP, realization_nr, ds)
165+
166+
151167
def sample_prior(
152168
ensemble: Ensemble,
153169
active_realizations: Iterable[int],

src/ert/gui/simulation/ensemble_experiment_panel.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from ert.gui.tools.design_matrix.design_matrix_panel import DesignMatrixPanel
1616
from ert.mode_definitions import ENSEMBLE_EXPERIMENT_MODE
1717
from ert.run_models import EnsembleExperiment
18-
from ert.validation import RangeStringArgument
18+
from ert.validation import ActiveRange, RangeStringArgument
1919
from ert.validation.proper_name_argument import ExperimentValidation, ProperNameArgument
2020

2121
from .experiment_config_panel import ExperimentConfigPanel
@@ -85,6 +85,13 @@ def __init__(
8585

8686
design_matrix = analysis_config.design_matrix
8787
if design_matrix is not None:
88+
if design_matrix.design_matrix_df is None:
89+
design_matrix.read_design_matrix()
90+
91+
if design_matrix.active_realizations:
92+
self._active_realizations_field.setText(
93+
ActiveRange(design_matrix.active_realizations).rangestring
94+
)
8895
show_dm_param_button = QPushButton("Show parameters")
8996
show_dm_param_button.setObjectName("show-dm-parameters")
9097
show_dm_param_button.setMinimumWidth(50)

src/ert/run_models/ensemble_experiment.py

+47-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
import numpy as np
88

9-
from ert.enkf_main import sample_prior
9+
from ert.config.design_matrix import DESIGN_MATRIX_GROUP
10+
from ert.enkf_main import sample_prior, save_design_matrix_to_ensemble
1011
from ert.ensemble_evaluator import EvaluatorServerConfig
1112
from ert.storage import Ensemble, Experiment, Storage
1213

@@ -61,7 +62,35 @@ def run_experiment(
6162
restart: bool = False,
6263
) -> None:
6364
self.log_at_startup()
64-
if not restart:
65+
# If design matrix is present, we substitute the experiment parameters
66+
# with those in the design matrix
67+
if self.ert_config.analysis_config.design_matrix is not None:
68+
if (
69+
self.ert_config.analysis_config.design_matrix.parameter_configuration
70+
is None
71+
):
72+
self.ert_config.analysis_config.design_matrix.read_design_matrix()
73+
assert (
74+
self.ert_config.analysis_config.design_matrix.parameter_configuration
75+
is not None
76+
)
77+
parameters_config = [
78+
self.ert_config.analysis_config.design_matrix.parameter_configuration[
79+
DESIGN_MATRIX_GROUP
80+
]
81+
]
82+
self.experiment = self._storage.create_experiment(
83+
name=self.experiment_name,
84+
parameters=parameters_config,
85+
observations=self.ert_config.observations,
86+
responses=self.ert_config.ensemble_config.response_configuration,
87+
)
88+
self.ensemble = self._storage.create_ensemble(
89+
self.experiment,
90+
name=self.ensemble_name,
91+
ensemble_size=self.ensemble_size,
92+
)
93+
elif not restart:
6594
self.experiment = self._storage.create_experiment(
6695
name=self.experiment_name,
6796
parameters=self.ert_config.ensemble_config.parameter_configuration,
@@ -87,11 +116,22 @@ def run_experiment(
87116
np.array(self.active_realizations, dtype=bool),
88117
ensemble=self.ensemble,
89118
)
90-
sample_prior(
91-
self.ensemble,
92-
np.where(self.active_realizations)[0],
93-
random_seed=self.random_seed,
94-
)
119+
if (
120+
self.ert_config.analysis_config.design_matrix is not None
121+
and self.ert_config.analysis_config.design_matrix.design_matrix_df
122+
is not None
123+
):
124+
save_design_matrix_to_ensemble(
125+
self.ert_config.analysis_config.design_matrix.design_matrix_df,
126+
self.ensemble,
127+
np.where(self.active_realizations)[0],
128+
)
129+
else:
130+
sample_prior(
131+
self.ensemble,
132+
np.where(self.active_realizations)[0],
133+
random_seed=self.random_seed,
134+
)
95135

96136
self._evaluate_and_postprocess(
97137
run_args,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import os
2+
import stat
3+
from textwrap import dedent
4+
5+
import numpy as np
6+
import pandas as pd
7+
import pytest
8+
9+
from ert.config import ErtConfig
10+
from ert.mode_definitions import ENSEMBLE_EXPERIMENT_MODE
11+
from ert.storage import open_storage
12+
from tests.ert.ui_tests.cli.run_cli import run_cli
13+
14+
15+
@pytest.mark.usefixtures("copy_poly_case")
16+
def test_run_poly_example_with_design_matrix():
17+
design_matrix = "poly_design.xlsx"
18+
num_realizations = 10
19+
a_values = list(range(num_realizations))
20+
design_matrix_df = pd.DataFrame(
21+
{
22+
"REAL": list(range(num_realizations)),
23+
"a": a_values,
24+
}
25+
)
26+
default_sheet_df = pd.DataFrame([["b", 1], ["c", 2]])
27+
with pd.ExcelWriter(design_matrix) as xl_write:
28+
design_matrix_df.to_excel(xl_write, index=False, sheet_name="DesignSheet01")
29+
default_sheet_df.to_excel(
30+
xl_write, index=False, sheet_name="DefaultSheet", header=False
31+
)
32+
33+
with open("poly.ert", "w", encoding="utf-8") as fout:
34+
fout.write(
35+
dedent(
36+
"""\
37+
QUEUE_OPTION LOCAL MAX_RUNNING 10
38+
RUNPATH poly_out/realization-<IENS>/iter-<ITER>
39+
NUM_REALIZATIONS 10
40+
MIN_REALIZATIONS 1
41+
GEN_DATA POLY_RES RESULT_FILE:poly.out
42+
DESIGN_MATRIX poly_design.xlsx DESIGN_SHEET:DesignSheet01 DEFAULT_SHEET:DefaultSheet
43+
INSTALL_JOB poly_eval POLY_EVAL
44+
FORWARD_MODEL poly_eval
45+
"""
46+
)
47+
)
48+
49+
with open("poly_eval.py", "w", encoding="utf-8") as f:
50+
f.write(
51+
dedent(
52+
"""\
53+
#!/usr/bin/env python
54+
import numpy as np
55+
import sys
56+
import json
57+
58+
def _load_coeffs(filename):
59+
with open(filename, encoding="utf-8") as f:
60+
return json.load(f)["DESIGN_MATRIX"]
61+
62+
def _evaluate(coeffs, x):
63+
return coeffs["a"] * x**2 + coeffs["b"] * x + coeffs["c"]
64+
65+
if __name__ == "__main__":
66+
coeffs = _load_coeffs("parameters.json")
67+
output = [_evaluate(coeffs, x) for x in range(10)]
68+
with open("poly.out", "w", encoding="utf-8") as f:
69+
f.write("\\n".join(map(str, output)))
70+
"""
71+
)
72+
)
73+
os.chmod(
74+
"poly_eval.py",
75+
os.stat("poly_eval.py").st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH,
76+
)
77+
78+
run_cli(
79+
ENSEMBLE_EXPERIMENT_MODE,
80+
"--disable-monitor",
81+
"poly.ert",
82+
"--experiment-name",
83+
"test-experiment",
84+
)
85+
storage_path = ErtConfig.from_file("poly.ert").ens_path
86+
with open_storage(storage_path) as storage:
87+
experiment = storage.get_experiment_by_name("test-experiment")
88+
params = experiment.get_ensemble_by_name("default").load_parameters(
89+
"DESIGN_MATRIX"
90+
)["values"]
91+
np.testing.assert_array_equal(params[:, 0], a_values)
92+
np.testing.assert_array_equal(params[:, 1], 10 * [1])
93+
np.testing.assert_array_equal(params[:, 2], 10 * [2])

tests/ert/unit_tests/gui/simulation/test_run_dialog.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from queue import SimpleQueue
33
from unittest.mock import MagicMock, Mock, patch
44

5+
import pandas as pd
56
import pytest
67
from pytestqt.qtbot import QtBot
78
from qtpy import QtWidgets
@@ -719,15 +720,26 @@ def test_that_stdout_and_stderr_buttons_react_to_file_content(
719720
def test_that_design_matrix_show_parameters_button_is_visible(
720721
design_matrix_entry, qtbot: QtBot, storage
721722
):
722-
xls_filename = "design_matrix.xls"
723-
with open(f"{xls_filename}", "w", encoding="utf-8"):
724-
pass
723+
xls_filename = "design_matrix.xlsx"
724+
design_matrix_df = pd.DataFrame(
725+
{
726+
"REAL": list(range(3)),
727+
"a": [0, 1, 2],
728+
}
729+
)
730+
default_sheet_df = pd.DataFrame([["b", 1], ["c", 2]])
731+
with pd.ExcelWriter(xls_filename) as xl_write:
732+
design_matrix_df.to_excel(xl_write, index=False, sheet_name="DesignSheet01")
733+
default_sheet_df.to_excel(
734+
xl_write, index=False, sheet_name="DefaultSheet", header=False
735+
)
736+
725737
config_file = "minimal_config.ert"
726738
with open(config_file, "w", encoding="utf-8") as f:
727739
f.write("NUM_REALIZATIONS 1")
728740
if design_matrix_entry:
729741
f.write(
730-
f"\nDESIGN_MATRIX {xls_filename} DESIGN_SHEET:DesignSheet01 DEFAULT_SHEET:DefaultValues"
742+
f"\nDESIGN_MATRIX {xls_filename} DESIGN_SHEET:DesignSheet01 DEFAULT_SHEET:DefaultSheet"
731743
)
732744

733745
args_mock = Mock()

tests/ert/unit_tests/test_libres_facade.py

+54-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
from datetime import datetime, timedelta
33
from textwrap import dedent
44

5+
import numpy as np
56
import pytest
7+
from pandas import ExcelWriter
68
from pandas.core.frame import DataFrame
79
from resdata.summary import Summary
810

911
from ert.config import ErtConfig
10-
from ert.enkf_main import sample_prior
12+
from ert.config.design_matrix import DESIGN_MATRIX_GROUP, DesignMatrix
13+
from ert.enkf_main import sample_prior, save_design_matrix_to_ensemble
1114
from ert.libres_facade import LibresFacade
1215
from ert.storage import open_storage
1316

@@ -241,3 +244,53 @@ def test_load_gen_kw_not_sorted(storage, tmpdir, snapshot):
241244

242245
data = ensemble.load_all_gen_kw_data()
243246
snapshot.assert_match(data.round(12).to_csv(), "gen_kw_unsorted")
247+
248+
249+
@pytest.mark.parametrize(
250+
"reals, expect_error",
251+
[
252+
pytest.param(
253+
list(range(10)),
254+
False,
255+
id="correct_active_realizations",
256+
),
257+
pytest.param([10, 11], True, id="incorrect_active_realizations"),
258+
],
259+
)
260+
def test_save_parameters_to_storage_from_design_dataframe(
261+
tmp_path, reals, expect_error
262+
):
263+
design_path = tmp_path / "design_matrix.xlsx"
264+
ensemble_size = 10
265+
a_values = np.random.default_rng().uniform(-5, 5, 10)
266+
b_values = np.random.default_rng().uniform(-5, 5, 10)
267+
c_values = np.random.default_rng().uniform(-5, 5, 10)
268+
design_matrix_df = DataFrame({"a": a_values, "b": b_values, "c": c_values})
269+
with ExcelWriter(design_path) as xl_write:
270+
design_matrix_df.to_excel(xl_write, index=False, sheet_name="DesignSheet01")
271+
DataFrame().to_excel(
272+
xl_write, index=False, sheet_name="DefaultValues", header=False
273+
)
274+
design_matrix = DesignMatrix(design_path, "DesignSheet01", "DefaultValues")
275+
design_matrix.read_design_matrix()
276+
with open_storage(tmp_path / "storage", mode="w") as storage:
277+
experiment_id = storage.create_experiment(
278+
parameters=[design_matrix.parameter_configuration[DESIGN_MATRIX_GROUP]]
279+
)
280+
ensemble = storage.create_ensemble(
281+
experiment_id, name="default", ensemble_size=ensemble_size
282+
)
283+
if expect_error:
284+
with pytest.raises(KeyError):
285+
save_design_matrix_to_ensemble(
286+
design_matrix.design_matrix_df, ensemble, reals
287+
)
288+
else:
289+
save_design_matrix_to_ensemble(
290+
design_matrix.design_matrix_df, ensemble, reals
291+
)
292+
params = ensemble.load_parameters(DESIGN_MATRIX_GROUP)["values"]
293+
all(params.names.values == ["a", "b", "c"])
294+
np.testing.assert_array_almost_equal(params[:, 0], a_values)
295+
np.testing.assert_array_almost_equal(params[:, 1], b_values)
296+
np.testing.assert_array_almost_equal(params[:, 2], c_values)

0 commit comments

Comments
 (0)