Skip to content

Commit

Permalink
Add support for design matrix keyword in ert config
Browse files Browse the repository at this point in the history
- Expects the format:
DESIGN_MATRIX file.xlsx DESIGN_SHEET:design DEFAULT_SHEET:default
where file.xlsx is an existing file.
- Scaffolding for further support for reading parameter values from
design matrix excel files.
  • Loading branch information
larsevj committed Oct 2, 2024
1 parent 4321e49 commit 18017c7
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 1 deletion.
8 changes: 8 additions & 0 deletions src/ert/config/analysis_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from pydantic import ValidationError

from .analysis_module import ESSettings, IESSettings
from .design_matrix import DesignMatrix
from .parsing import (
AnalysisMode,
ConfigDict,
Expand Down Expand Up @@ -40,6 +41,7 @@ class AnalysisConfig:
ies_module: IESSettings = field(default_factory=IESSettings)
observation_settings: UpdateSettings = field(default_factory=UpdateSettings)
num_iterations: int = 1
design_matrix_args: Optional[DesignMatrix] = None

@no_type_check
@classmethod
Expand Down Expand Up @@ -78,6 +80,9 @@ def from_dict(cls, config_dict: ConfigDict) -> "AnalysisConfig":
)

min_realization = min(min_realization, num_realization)

design_matrix_config_list = config_dict.get(ConfigKeys.DESIGN_MATRIX, None)

options: Dict[str, Dict[str, Any]] = {"STD_ENKF": {}, "IES_ENKF": {}}
observation_settings: Dict[str, Any] = {
"alpha": config_dict.get(ConfigKeys.ENKF_ALPHA, 3.0),
Expand Down Expand Up @@ -189,6 +194,9 @@ def from_dict(cls, config_dict: ConfigDict) -> "AnalysisConfig":
observation_settings=obs_settings,
es_module=es_settings,
ies_module=ies_settings,
design_matrix_args=DesignMatrix.from_config_list(design_matrix_config_list)
if design_matrix_config_list is not None
else None,
)
return config

Expand Down
52 changes: 52 additions & 0 deletions src/ert/config/design_matrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import List

from ._option_dict import option_dict
from .parsing import (
ConfigValidationError,
ErrorInfo,
)


@dataclass
class DesignMatrix:
xls_filename: Path
design_sheet: str
default_sheet: str

@classmethod
def from_config_list(cls, config_list: List[str]) -> "DesignMatrix":
filename = Path(config_list[0])
options = option_dict(config_list, 1)
design_sheet = options.get("DESIGN_SHEET")
default_sheet = options.get("DEFAULT_SHEET")
errors = []
if filename.suffix not in {
".xlsx",
".xls",
}:
errors.append(
ErrorInfo(
f"DESIGN_MATRIX must be of format .xls or .xlsx; is '{filename}'"
).set_context(config_list)
)
if design_sheet is None:
errors.append(
ErrorInfo("Missing required DESIGN_SHEET").set_context(config_list)
)
if default_sheet is None:
errors.append(
ErrorInfo("Missing required DEFAULT_SHEET").set_context(config_list)
)
if errors:
raise ConfigValidationError.from_collected(errors)
assert design_sheet is not None
assert default_sheet is not None
return cls(
xls_filename=filename,
design_sheet=design_sheet,
default_sheet=default_sheet,
)
1 change: 1 addition & 0 deletions src/ert/config/parsing/config_keywords.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class ConfigKeys(StrEnum):
JOB_SCRIPT = "JOB_SCRIPT"
JOBNAME = "JOBNAME"
MAX_SUBMIT = "MAX_SUBMIT"
DESIGN_MATRIX = "DESIGN_MATRIX"
NUM_REALIZATIONS = "NUM_REALIZATIONS"
MIN_REALIZATIONS = "MIN_REALIZATIONS"
OBS_CONFIG = "OBS_CONFIG"
Expand Down
15 changes: 15 additions & 0 deletions src/ert/config/parsing/config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,20 @@ def install_job_directory_keyword() -> SchemaItem:
)


def design_matrix_keyword() -> SchemaItem:
return SchemaItem(
kw=ConfigKeys.DESIGN_MATRIX,
argc_min=3,
argc_max=3,
type_map=[
SchemaItemType.EXISTING_PATH,
SchemaItemType.STRING,
SchemaItemType.STRING,
],
multi_occurrence=False,
)


class ConfigSchemaDict(SchemaItemDict):
def check_required(
self,
Expand Down Expand Up @@ -345,6 +359,7 @@ def init_user_config_schema() -> ConfigSchemaDict:
positive_int_keyword(ConfigKeys.NUM_CPU),
positive_int_keyword(ConfigKeys.MAX_RUNNING),
string_keyword(ConfigKeys.REALIZATION_MEMORY),
design_matrix_keyword(),
queue_system_keyword(False),
queue_option_keyword(),
job_script_keyword(),
Expand Down
54 changes: 53 additions & 1 deletion tests/ert/unit_tests/config/test_analysis_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,17 @@
from ert.config.parsing import ConfigKeys, ConfigWarning


def test_analysis_config_from_file_is_same_as_from_dict():
def test_analysis_config_from_file_is_same_as_from_dict(monkeypatch, tmp_path):
with open(tmp_path / "my_design_matrix.xlsx", "w", encoding="utf-8"):
pass
monkeypatch.chdir(tmp_path)
assert ErtConfig.from_file_contents(
dedent(
"""
NUM_REALIZATIONS 10
MIN_REALIZATIONS 10
ANALYSIS_SET_VAR STD_ENKF ENKF_TRUNCATION 0.8
DESIGN_MATRIX my_design_matrix.xlsx DESIGN_SHEET:my_sheet DEFAULT_SHEET:my_default_sheet
"""
)
).analysis_config == AnalysisConfig.from_dict(
Expand All @@ -30,6 +34,11 @@ def test_analysis_config_from_file_is_same_as_from_dict():
ConfigKeys.ANALYSIS_SET_VAR: [
("STD_ENKF", "ENKF_TRUNCATION", 0.8),
],
ConfigKeys.DESIGN_MATRIX: [
"my_design_matrix.xlsx",
"DESIGN_SHEET:my_sheet",
"DEFAULT_SHEET:my_default_sheet",
],
}
)

Expand Down Expand Up @@ -80,6 +89,49 @@ def test_invalid_min_realization_raises_config_validation_error():
)


def test_invalid_design_matrix_format_raises_validation_error():
with pytest.raises(
ConfigValidationError,
match="DESIGN_MATRIX must be of format .xls or .xlsx; is 'my_matrix.txt'",
):
AnalysisConfig.from_dict(
{
ConfigKeys.NUM_REALIZATIONS: 1,
ConfigKeys.DESIGN_MATRIX: [
"my_matrix.txt",
"DESIGN_SHEET:sheet1",
"DEFAULT_SHEET:sheet2",
],
}
)


def test_design_matrix_without_design_sheet_raises_validation_error():
with pytest.raises(ConfigValidationError, match="Missing required DESIGN_SHEET"):
AnalysisConfig.from_dict(
{
ConfigKeys.DESIGN_MATRIX: [
"my_matrix.xlsx",
"DESIGN_:design",
"DEFAULT_SHEET:default",
],
}
)


def test_design_matrix_without_default_sheet_raises_validation_error():
with pytest.raises(ConfigValidationError, match="Missing required DEFAULT_SHEET"):
AnalysisConfig.from_dict(
{
ConfigKeys.DESIGN_MATRIX: [
"my_matrix.xlsx",
"DESIGN_SHEET:design",
"DEFAULT_:default",
],
}
)


def test_invalid_min_realization_percentage_raises_config_validation_error():
with pytest.raises(
ConfigValidationError,
Expand Down

0 comments on commit 18017c7

Please sign in to comment.