ert 18.0.9__py3-none-any.whl → 19.0.0rc0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _ert/forward_model_runner/client.py +6 -2
- ert/__main__.py +20 -6
- ert/analysis/_es_update.py +6 -19
- ert/cli/main.py +7 -3
- ert/config/__init__.py +3 -4
- ert/config/_create_observation_dataframes.py +57 -8
- ert/config/_get_num_cpu.py +1 -1
- ert/config/_observations.py +77 -1
- ert/config/distribution.py +1 -1
- ert/config/ensemble_config.py +3 -3
- ert/config/ert_config.py +50 -8
- ert/config/{ext_param_config.py → everest_control.py} +8 -12
- ert/config/everest_response.py +3 -5
- ert/config/field.py +76 -14
- ert/config/forward_model_step.py +12 -9
- ert/config/gen_data_config.py +3 -4
- ert/config/gen_kw_config.py +2 -12
- ert/config/parameter_config.py +1 -16
- ert/config/parsing/_option_dict.py +10 -2
- ert/config/parsing/config_keywords.py +1 -0
- ert/config/parsing/config_schema.py +8 -0
- ert/config/parsing/config_schema_deprecations.py +14 -3
- ert/config/parsing/config_schema_item.py +12 -3
- ert/config/parsing/context_values.py +3 -3
- ert/config/parsing/file_context_token.py +1 -1
- ert/config/parsing/observations_parser.py +6 -2
- ert/config/parsing/queue_system.py +9 -0
- ert/config/queue_config.py +0 -1
- ert/config/response_config.py +0 -1
- ert/config/rft_config.py +78 -33
- ert/config/summary_config.py +1 -2
- ert/config/surface_config.py +59 -16
- ert/dark_storage/common.py +1 -1
- ert/dark_storage/compute/misfits.py +4 -1
- ert/dark_storage/endpoints/compute/misfits.py +4 -2
- ert/dark_storage/endpoints/experiment_server.py +12 -9
- ert/dark_storage/endpoints/experiments.py +2 -2
- ert/dark_storage/endpoints/observations.py +4 -2
- ert/dark_storage/endpoints/parameters.py +2 -18
- ert/dark_storage/endpoints/responses.py +10 -5
- ert/dark_storage/json_schema/experiment.py +1 -1
- ert/data/_measured_data.py +6 -5
- ert/ensemble_evaluator/config.py +2 -1
- ert/field_utils/field_utils.py +1 -1
- ert/field_utils/grdecl_io.py +9 -26
- ert/field_utils/roff_io.py +1 -1
- ert/gui/__init__.py +5 -2
- ert/gui/ertnotifier.py +1 -1
- ert/gui/ertwidgets/pathchooser.py +0 -3
- ert/gui/ertwidgets/suggestor/suggestor.py +63 -30
- ert/gui/main.py +27 -5
- ert/gui/main_window.py +0 -5
- ert/gui/simulation/experiment_panel.py +12 -7
- ert/gui/simulation/run_dialog.py +2 -16
- ert/gui/summarypanel.py +0 -19
- ert/gui/tools/manage_experiments/export_dialog.py +136 -0
- ert/gui/tools/manage_experiments/storage_info_widget.py +110 -9
- ert/gui/tools/plot/plot_api.py +24 -15
- ert/gui/tools/plot/plot_widget.py +10 -2
- ert/gui/tools/plot/plot_window.py +26 -18
- ert/gui/tools/plot/plottery/plots/__init__.py +2 -0
- ert/gui/tools/plot/plottery/plots/cesp.py +3 -1
- ert/gui/tools/plot/plottery/plots/distribution.py +6 -1
- ert/gui/tools/plot/plottery/plots/ensemble.py +3 -1
- ert/gui/tools/plot/plottery/plots/gaussian_kde.py +12 -2
- ert/gui/tools/plot/plottery/plots/histogram.py +3 -1
- ert/gui/tools/plot/plottery/plots/misfits.py +436 -0
- ert/gui/tools/plot/plottery/plots/observations.py +18 -4
- ert/gui/tools/plot/plottery/plots/statistics.py +3 -1
- ert/gui/tools/plot/plottery/plots/std_dev.py +3 -1
- ert/plugins/hook_implementations/workflows/csv_export.py +2 -3
- ert/plugins/plugin_manager.py +4 -0
- ert/resources/forward_models/run_reservoirsimulator.py +8 -3
- ert/run_models/_create_run_path.py +3 -3
- ert/run_models/everest_run_model.py +13 -11
- ert/run_models/initial_ensemble_run_model.py +2 -2
- ert/run_models/run_model.py +30 -1
- ert/services/_base_service.py +6 -5
- ert/services/ert_server.py +4 -4
- ert/shared/_doc_utils/__init__.py +4 -2
- ert/shared/net_utils.py +43 -18
- ert/shared/version.py +3 -3
- ert/storage/__init__.py +2 -0
- ert/storage/local_ensemble.py +13 -7
- ert/storage/local_experiment.py +2 -2
- ert/storage/local_storage.py +41 -25
- ert/storage/migration/to11.py +1 -1
- ert/storage/migration/to18.py +0 -1
- ert/storage/migration/to19.py +34 -0
- ert/storage/migration/to20.py +23 -0
- ert/storage/migration/to21.py +25 -0
- ert/workflow_runner.py +2 -1
- {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/METADATA +1 -1
- {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/RECORD +112 -112
- {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/WHEEL +1 -1
- everest/bin/everlint_script.py +0 -2
- everest/bin/utils.py +2 -1
- everest/bin/visualization_script.py +4 -11
- everest/config/control_config.py +4 -4
- everest/config/control_variable_config.py +2 -2
- everest/config/everest_config.py +9 -0
- everest/config/utils.py +2 -2
- everest/config/validation_utils.py +7 -1
- everest/config_file_loader.py +0 -2
- everest/detached/client.py +3 -3
- everest/everest_storage.py +0 -2
- everest/gui/everest_client.py +2 -2
- everest/optimizer/everest2ropt.py +4 -4
- everest/optimizer/opt_model_transforms.py +2 -2
- ert/config/violations.py +0 -0
- ert/gui/tools/export/__init__.py +0 -3
- ert/gui/tools/export/export_panel.py +0 -83
- ert/gui/tools/export/export_tool.py +0 -69
- ert/gui/tools/export/exporter.py +0 -36
- {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/entry_points.txt +0 -0
- {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/licenses/COPYING +0 -0
- {ert-18.0.9.dist-info → ert-19.0.0rc0.dist-info}/top_level.txt +0 -0
|
@@ -6,7 +6,7 @@ from polars.datatypes import DataTypeClass
|
|
|
6
6
|
from pydantic import BaseModel, Field, field_validator
|
|
7
7
|
|
|
8
8
|
from ert.config import (
|
|
9
|
-
|
|
9
|
+
EverestControl,
|
|
10
10
|
GenKwConfig,
|
|
11
11
|
KnownResponseTypes,
|
|
12
12
|
SurfaceConfig,
|
|
@@ -56,7 +56,7 @@ class InitialEnsembleRunModelConfig(RunModelConfig):
|
|
|
56
56
|
design_matrix: DictEncodedDataFrame | None
|
|
57
57
|
parameter_configuration: list[
|
|
58
58
|
Annotated[
|
|
59
|
-
(GenKwConfig | SurfaceConfig | FieldConfig |
|
|
59
|
+
(GenKwConfig | SurfaceConfig | FieldConfig | EverestControl),
|
|
60
60
|
Field(discriminator="type"),
|
|
61
61
|
]
|
|
62
62
|
]
|
ert/run_models/run_model.py
CHANGED
|
@@ -63,6 +63,7 @@ from ert.mode_definitions import MODULE_MODE
|
|
|
63
63
|
from ert.runpaths import Runpaths
|
|
64
64
|
from ert.storage import (
|
|
65
65
|
Ensemble,
|
|
66
|
+
LocalStorage,
|
|
66
67
|
Storage,
|
|
67
68
|
open_storage,
|
|
68
69
|
)
|
|
@@ -208,6 +209,10 @@ class RunModel(RunModelConfig, ABC):
|
|
|
208
209
|
def model_post_init(self, ctx: Any) -> None:
|
|
209
210
|
self._initial_realizations_mask = self.active_realizations.copy()
|
|
210
211
|
self._completed_realizations_mask = [False] * len(self.active_realizations)
|
|
212
|
+
|
|
213
|
+
if LocalStorage.check_migration_needed(Path(self.storage_path)):
|
|
214
|
+
LocalStorage.perform_migration(Path(self.storage_path))
|
|
215
|
+
|
|
211
216
|
self._storage = open_storage(self.storage_path, mode="w")
|
|
212
217
|
self._rng = np.random.default_rng(self.random_seed)
|
|
213
218
|
self._start_iteration = self.start_iteration
|
|
@@ -248,8 +253,28 @@ class RunModel(RunModelConfig, ABC):
|
|
|
248
253
|
for key, value in self.__dict__.items()
|
|
249
254
|
if key not in keys_to_drop
|
|
250
255
|
}
|
|
256
|
+
settings_summary = {
|
|
257
|
+
"run_model": self.name(),
|
|
258
|
+
"num_realizations": self.runpath_config.num_realizations,
|
|
259
|
+
"num_active_realizations": self.active_realizations.count(True),
|
|
260
|
+
"num_parameters": (
|
|
261
|
+
sum(
|
|
262
|
+
len(param_config.parameter_keys)
|
|
263
|
+
for param_config in self.parameter_configuration
|
|
264
|
+
)
|
|
265
|
+
if hasattr(self, "parameter_configuration")
|
|
266
|
+
else "NA"
|
|
267
|
+
),
|
|
268
|
+
"localization": getattr(
|
|
269
|
+
settings_dict.get("analysis_settings", {}), "localization", "NA"
|
|
270
|
+
),
|
|
271
|
+
}
|
|
251
272
|
|
|
252
|
-
logger.info(
|
|
273
|
+
logger.info(
|
|
274
|
+
f"Running '{self.name()}'\n\n"
|
|
275
|
+
f"Settings summary: {settings_summary}\n\n"
|
|
276
|
+
f"Settings: {settings_dict}"
|
|
277
|
+
)
|
|
253
278
|
|
|
254
279
|
@field_validator("env_vars", mode="after")
|
|
255
280
|
@classmethod
|
|
@@ -813,6 +838,10 @@ class RunModel(RunModelConfig, ABC):
|
|
|
813
838
|
if self._max_parallelism_violation.amount > 0 and isinstance(
|
|
814
839
|
self._max_parallelism_violation.message, str
|
|
815
840
|
):
|
|
841
|
+
logger.info(
|
|
842
|
+
"Warning displayed to user for NUM_CPU misconfiguration:\n"
|
|
843
|
+
f"{self._max_parallelism_violation.message}"
|
|
844
|
+
)
|
|
816
845
|
warnings.warn(
|
|
817
846
|
self._max_parallelism_violation.message,
|
|
818
847
|
PostSimulationWarning,
|
ert/services/_base_service.py
CHANGED
|
@@ -12,6 +12,7 @@ import os
|
|
|
12
12
|
import signal
|
|
13
13
|
import sys
|
|
14
14
|
import threading
|
|
15
|
+
import types
|
|
15
16
|
from collections.abc import Callable, Mapping, Sequence
|
|
16
17
|
from logging import Logger, getLogger
|
|
17
18
|
from pathlib import Path
|
|
@@ -23,7 +24,7 @@ from types import FrameType
|
|
|
23
24
|
from typing import TYPE_CHECKING, Any, Generic, Self, TypedDict, TypeVar
|
|
24
25
|
|
|
25
26
|
if TYPE_CHECKING:
|
|
26
|
-
|
|
27
|
+
pass
|
|
27
28
|
|
|
28
29
|
T = TypeVar("T", bound="BaseService")
|
|
29
30
|
|
|
@@ -87,9 +88,9 @@ class _Context(Generic[T]):
|
|
|
87
88
|
|
|
88
89
|
def __exit__(
|
|
89
90
|
self,
|
|
90
|
-
exc_type: type[BaseException],
|
|
91
|
-
exc_value: BaseException,
|
|
92
|
-
traceback:
|
|
91
|
+
exc_type: type[BaseException] | None,
|
|
92
|
+
exc_value: BaseException | None,
|
|
93
|
+
traceback: types.TracebackType | None,
|
|
93
94
|
) -> bool:
|
|
94
95
|
self._service.shutdown()
|
|
95
96
|
return exc_type is None
|
|
@@ -277,7 +278,7 @@ class BaseService:
|
|
|
277
278
|
)
|
|
278
279
|
|
|
279
280
|
@classmethod
|
|
280
|
-
def start_server(cls
|
|
281
|
+
def start_server(cls, *args: Any, **kwargs: Any) -> _Context[Self]:
|
|
281
282
|
if cls._instance is not None:
|
|
282
283
|
raise RuntimeError("Server already running")
|
|
283
284
|
cls._instance = obj = cls(*args, **kwargs)
|
ert/services/ert_server.py
CHANGED
|
@@ -5,8 +5,8 @@ import logging
|
|
|
5
5
|
import os
|
|
6
6
|
import sys
|
|
7
7
|
import threading
|
|
8
|
+
import types
|
|
8
9
|
from collections.abc import Mapping
|
|
9
|
-
from inspect import Traceback
|
|
10
10
|
from pathlib import Path
|
|
11
11
|
from tempfile import NamedTemporaryFile
|
|
12
12
|
from time import sleep
|
|
@@ -28,9 +28,9 @@ class ErtServerContext:
|
|
|
28
28
|
|
|
29
29
|
def __exit__(
|
|
30
30
|
self,
|
|
31
|
-
exc_type: type[BaseException],
|
|
32
|
-
exc_value: BaseException,
|
|
33
|
-
traceback:
|
|
31
|
+
exc_type: type[BaseException] | None,
|
|
32
|
+
exc_value: BaseException | None,
|
|
33
|
+
traceback: types.TracebackType | None,
|
|
34
34
|
) -> bool:
|
|
35
35
|
self._service.shutdown()
|
|
36
36
|
return exc_type is None
|
|
@@ -8,12 +8,14 @@ from docutils.statemachine import StringList
|
|
|
8
8
|
from sphinx.util.nodes import nested_parse_with_titles
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def _parse_raw_rst(rst_string: str, node: nodes.
|
|
11
|
+
def _parse_raw_rst(rst_string: str, node: nodes.Element, state: Any) -> None:
|
|
12
12
|
string_list = docutils.statemachine.StringList(list(rst_string.split("\n")))
|
|
13
13
|
_parse_string_list(string_list, node, state)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def _parse_string_list(
|
|
16
|
+
def _parse_string_list(
|
|
17
|
+
string_list: StringList, node: nodes.Element, state: Any
|
|
18
|
+
) -> None:
|
|
17
19
|
nested_parse_with_titles(state, string_list, node)
|
|
18
20
|
|
|
19
21
|
|
ert/shared/net_utils.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import ipaddress
|
|
1
2
|
import logging
|
|
2
3
|
import random
|
|
3
4
|
import socket
|
|
4
5
|
from functools import lru_cache
|
|
5
6
|
|
|
7
|
+
import psutil
|
|
6
8
|
from dns import exception, resolver, reversename
|
|
7
9
|
|
|
8
10
|
|
|
@@ -50,6 +52,7 @@ def get_machine_name() -> str:
|
|
|
50
52
|
def find_available_socket(
|
|
51
53
|
host: str | None = None,
|
|
52
54
|
port_range: range = range(51820, 51840 + 1),
|
|
55
|
+
prioritize_private_ip_address: bool = False,
|
|
53
56
|
) -> socket.socket:
|
|
54
57
|
"""
|
|
55
58
|
The default and recommended approach here is to return a bound socket to the
|
|
@@ -71,7 +74,9 @@ def find_available_socket(
|
|
|
71
74
|
|
|
72
75
|
See e.g. implementation and comments in EvaluatorServerConfig
|
|
73
76
|
"""
|
|
74
|
-
current_host =
|
|
77
|
+
current_host = (
|
|
78
|
+
host if host is not None else get_ip_address(prioritize_private_ip_address)
|
|
79
|
+
)
|
|
75
80
|
|
|
76
81
|
if port_range.start == port_range.stop:
|
|
77
82
|
ports = list(range(port_range.start, port_range.stop + 1))
|
|
@@ -135,20 +140,40 @@ def get_family(host: str) -> socket.AddressFamily:
|
|
|
135
140
|
return socket.AF_INET6
|
|
136
141
|
|
|
137
142
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
143
|
+
def get_ip_address(prioritize_private: bool = False) -> str:
|
|
144
|
+
"""
|
|
145
|
+
Get the first (private or public) IPv4 address of the current machine on the LAN.
|
|
146
|
+
Default behaviour returns the first public IP if found, then private, then loopback.
|
|
147
|
+
|
|
148
|
+
Parameters:
|
|
149
|
+
prioritize_private (bool): If True, private IP addresses are prioritized
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
str: The selected IP address as a string.
|
|
153
|
+
"""
|
|
154
|
+
loopback = ""
|
|
155
|
+
public = ""
|
|
156
|
+
private = ""
|
|
157
|
+
interfaces = psutil.net_if_addrs()
|
|
158
|
+
for addresses in interfaces.values():
|
|
159
|
+
for address in addresses:
|
|
160
|
+
if address.family.name == "AF_INET":
|
|
161
|
+
ip = address.address
|
|
162
|
+
if ipaddress.ip_address(ip).is_loopback and not loopback:
|
|
163
|
+
loopback = ip
|
|
164
|
+
elif ipaddress.ip_address(ip).is_private and not private:
|
|
165
|
+
private = ip
|
|
166
|
+
elif not public:
|
|
167
|
+
public = ip
|
|
168
|
+
|
|
169
|
+
# Select first non-empty value, based on prioritization
|
|
170
|
+
if prioritize_private:
|
|
171
|
+
selected_ip = private or public or loopback
|
|
172
|
+
else:
|
|
173
|
+
selected_ip = public or private or loopback
|
|
174
|
+
|
|
175
|
+
if selected_ip:
|
|
176
|
+
return selected_ip
|
|
177
|
+
else:
|
|
178
|
+
logger.warning("Cannot determine ip-address. Falling back to 127.0.0.1")
|
|
179
|
+
return "127.0.0.1"
|
ert/shared/version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '
|
|
32
|
-
__version_tuple__ = version_tuple = (
|
|
31
|
+
__version__ = version = '19.0.0rc0'
|
|
32
|
+
__version_tuple__ = version_tuple = (19, 0, 0, 'rc0')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'gacbd663a0'
|
ert/storage/__init__.py
CHANGED
|
@@ -39,6 +39,8 @@ class ErtStoragePermissionError(ErtStorageException):
|
|
|
39
39
|
def open_storage(
|
|
40
40
|
path: str | os.PathLike[str], mode: ModeLiteral | Mode = "r"
|
|
41
41
|
) -> Storage:
|
|
42
|
+
_ = LocalStorage.check_migration_needed(Path(path))
|
|
43
|
+
|
|
42
44
|
try:
|
|
43
45
|
return LocalStorage(Path(path), Mode(mode))
|
|
44
46
|
except PermissionError as err:
|
ert/storage/local_ensemble.py
CHANGED
|
@@ -562,12 +562,18 @@ class LocalEnsemble(BaseMode):
|
|
|
562
562
|
df = pl.scan_parquet(group_path)
|
|
563
563
|
return df
|
|
564
564
|
|
|
565
|
-
def
|
|
565
|
+
def load_scalar_keys(
|
|
566
566
|
self,
|
|
567
|
-
keys: list[str],
|
|
567
|
+
keys: list[str] | None = None,
|
|
568
568
|
realizations: int | npt.NDArray[np.int_] | None = None,
|
|
569
569
|
transformed: bool = False,
|
|
570
570
|
) -> pl.DataFrame:
|
|
571
|
+
if keys is None:
|
|
572
|
+
keys = self.experiment.parameter_keys
|
|
573
|
+
elif set(keys) - set(self.experiment.parameter_keys):
|
|
574
|
+
missing = set(keys) - set(self.experiment.parameter_keys)
|
|
575
|
+
raise KeyError(f"Parameters not registered to the experiment: {missing}")
|
|
576
|
+
|
|
571
577
|
df_lazy = self._load_parameters_lazy(SCALAR_FILENAME)
|
|
572
578
|
df_lazy = df_lazy.select(["realization", *keys])
|
|
573
579
|
if realizations is not None:
|
|
@@ -620,7 +626,7 @@ class LocalEnsemble(BaseMode):
|
|
|
620
626
|
cardinality = next(cfg.cardinality for cfg in cfgs)
|
|
621
627
|
|
|
622
628
|
if cardinality == ParameterCardinality.multiple_configs_per_ensemble_dataset:
|
|
623
|
-
return self.
|
|
629
|
+
return self.load_scalar_keys(
|
|
624
630
|
[cfg.name for cfg in cfgs], realizations, transformed
|
|
625
631
|
)
|
|
626
632
|
return self._load_dataset(
|
|
@@ -647,7 +653,7 @@ class LocalEnsemble(BaseMode):
|
|
|
647
653
|
]
|
|
648
654
|
if keys:
|
|
649
655
|
return (
|
|
650
|
-
self.
|
|
656
|
+
self.load_scalar_keys(keys, realizations)
|
|
651
657
|
.drop("realization")
|
|
652
658
|
.to_numpy()
|
|
653
659
|
.T.copy()
|
|
@@ -947,7 +953,7 @@ class LocalEnsemble(BaseMode):
|
|
|
947
953
|
data = self.load_parameters(parameter_group)
|
|
948
954
|
if isinstance(data, pl.DataFrame):
|
|
949
955
|
return data.drop("realization").std().to_numpy().reshape(-1)
|
|
950
|
-
return data.std("realizations")["values"].
|
|
956
|
+
return data.std("realizations")["values"].to_numpy()
|
|
951
957
|
|
|
952
958
|
def get_parameter_state(
|
|
953
959
|
self, realization: int
|
|
@@ -1048,7 +1054,7 @@ class LocalEnsemble(BaseMode):
|
|
|
1048
1054
|
pl.col(col).is_in(observed_values.implode())
|
|
1049
1055
|
)
|
|
1050
1056
|
|
|
1051
|
-
pivoted = responses.collect(engine="streaming").pivot(
|
|
1057
|
+
pivoted = responses.collect(engine="streaming").pivot( # noqa: PD010
|
|
1052
1058
|
on="realization",
|
|
1053
1059
|
index=["response_key", *response_cls.primary_key],
|
|
1054
1060
|
values="values",
|
|
@@ -1224,7 +1230,7 @@ class LocalEnsemble(BaseMode):
|
|
|
1224
1230
|
how="horizontal",
|
|
1225
1231
|
)
|
|
1226
1232
|
|
|
1227
|
-
responses_wide = responses["realization", "response_key", "values"].pivot(
|
|
1233
|
+
responses_wide = responses["realization", "response_key", "values"].pivot( # noqa: PD010
|
|
1228
1234
|
on="response_key", values="values"
|
|
1229
1235
|
)
|
|
1230
1236
|
|
ert/storage/local_experiment.py
CHANGED
|
@@ -16,7 +16,7 @@ from pydantic import BaseModel, Field, TypeAdapter
|
|
|
16
16
|
from surfio import IrapSurface
|
|
17
17
|
|
|
18
18
|
from ert.config import (
|
|
19
|
-
|
|
19
|
+
EverestControl,
|
|
20
20
|
GenKwConfig,
|
|
21
21
|
KnownResponseTypes,
|
|
22
22
|
ParameterConfig,
|
|
@@ -66,7 +66,7 @@ _responses_adapter = TypeAdapter( # type: ignore
|
|
|
66
66
|
|
|
67
67
|
_parameters_adapter = TypeAdapter( # type: ignore
|
|
68
68
|
Annotated[
|
|
69
|
-
(GenKwConfig | SurfaceConfig | FieldConfig |
|
|
69
|
+
(GenKwConfig | SurfaceConfig | FieldConfig | EverestControl),
|
|
70
70
|
Field(discriminator="type"),
|
|
71
71
|
]
|
|
72
72
|
)
|
ert/storage/local_storage.py
CHANGED
|
@@ -5,14 +5,14 @@ import logging
|
|
|
5
5
|
import os
|
|
6
6
|
import re
|
|
7
7
|
import shutil
|
|
8
|
+
import types
|
|
8
9
|
from collections.abc import Generator, MutableSequence
|
|
9
10
|
from datetime import datetime
|
|
10
11
|
from functools import cached_property
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
from tempfile import NamedTemporaryFile
|
|
13
14
|
from textwrap import dedent
|
|
14
|
-
from
|
|
15
|
-
from typing import Any
|
|
15
|
+
from typing import Any, Self
|
|
16
16
|
from uuid import UUID, uuid4
|
|
17
17
|
|
|
18
18
|
import polars as pl
|
|
@@ -20,6 +20,7 @@ import xarray as xr
|
|
|
20
20
|
from filelock import FileLock, Timeout
|
|
21
21
|
from pydantic import BaseModel, Field
|
|
22
22
|
|
|
23
|
+
import ert.storage
|
|
23
24
|
from ert.config import ErtConfig, ParameterConfig, ResponseConfig
|
|
24
25
|
from ert.shared import __version__
|
|
25
26
|
|
|
@@ -30,7 +31,7 @@ from .realization_storage_state import RealizationStorageState
|
|
|
30
31
|
|
|
31
32
|
logger = logging.getLogger(__name__)
|
|
32
33
|
|
|
33
|
-
_LOCAL_STORAGE_VERSION =
|
|
34
|
+
_LOCAL_STORAGE_VERSION = 21
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
class _Migrations(BaseModel):
|
|
@@ -64,6 +65,7 @@ class LocalStorage(BaseMode):
|
|
|
64
65
|
self,
|
|
65
66
|
path: str | os.PathLike[str],
|
|
66
67
|
mode: Mode,
|
|
68
|
+
stage_for_migration: bool = False,
|
|
67
69
|
) -> None:
|
|
68
70
|
"""
|
|
69
71
|
Initializes the LocalStorage instance.
|
|
@@ -74,6 +76,8 @@ class LocalStorage(BaseMode):
|
|
|
74
76
|
The file system path to the storage.
|
|
75
77
|
mode : Mode
|
|
76
78
|
The access mode for the storage (read/write).
|
|
79
|
+
stage_for_migration : bool
|
|
80
|
+
Whether to avoid reloading storage to allow migration
|
|
77
81
|
"""
|
|
78
82
|
|
|
79
83
|
self.path = Path(path).absolute()
|
|
@@ -82,9 +86,9 @@ class LocalStorage(BaseMode):
|
|
|
82
86
|
if mode.can_write:
|
|
83
87
|
self._acquire_lock()
|
|
84
88
|
|
|
85
|
-
self._experiments: dict[UUID, LocalExperiment]
|
|
86
|
-
self._ensembles: dict[UUID, LocalEnsemble]
|
|
87
|
-
self._index: _Index
|
|
89
|
+
self._experiments: dict[UUID, LocalExperiment] = {}
|
|
90
|
+
self._ensembles: dict[UUID, LocalEnsemble] = {}
|
|
91
|
+
self._index: _Index = _Index()
|
|
88
92
|
|
|
89
93
|
try:
|
|
90
94
|
self.version = _storage_version(self.path)
|
|
@@ -101,19 +105,18 @@ class LocalStorage(BaseMode):
|
|
|
101
105
|
raise ValueError(f"No index.json, but found: {errors}") from err
|
|
102
106
|
self.version = _LOCAL_STORAGE_VERSION
|
|
103
107
|
|
|
104
|
-
if self.check_migration_needed(Path(self.path)):
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
)
|
|
111
|
-
else:
|
|
112
|
-
self._migrate(self.version)
|
|
108
|
+
if self.check_migration_needed(Path(self.path)) and not self.can_write:
|
|
109
|
+
raise RuntimeError(
|
|
110
|
+
f"Cannot open storage '{self.path}' in read-only mode: "
|
|
111
|
+
f"Storage version {self.version} is too old. "
|
|
112
|
+
f"Run ert to initiate migration."
|
|
113
|
+
)
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
if not stage_for_migration:
|
|
116
|
+
self.reload()
|
|
117
|
+
|
|
118
|
+
if mode.can_write:
|
|
119
|
+
self._save_index()
|
|
117
120
|
|
|
118
121
|
@staticmethod
|
|
119
122
|
def check_migration_needed(storage_dir: Path) -> bool:
|
|
@@ -123,7 +126,7 @@ class LocalStorage(BaseMode):
|
|
|
123
126
|
version = _LOCAL_STORAGE_VERSION
|
|
124
127
|
|
|
125
128
|
if version > _LOCAL_STORAGE_VERSION:
|
|
126
|
-
raise
|
|
129
|
+
raise ert.storage.ErtStorageException(
|
|
127
130
|
f"Cannot open storage '{storage_dir.absolute()}': Storage version "
|
|
128
131
|
f"{version} is newer than the current version {_LOCAL_STORAGE_VERSION}"
|
|
129
132
|
f", upgrade ert to continue, or run with a different ENSPATH"
|
|
@@ -131,7 +134,14 @@ class LocalStorage(BaseMode):
|
|
|
131
134
|
|
|
132
135
|
return version < _LOCAL_STORAGE_VERSION
|
|
133
136
|
|
|
134
|
-
|
|
137
|
+
@staticmethod
|
|
138
|
+
def perform_migration(path: Path) -> None:
|
|
139
|
+
if LocalStorage.check_migration_needed(path):
|
|
140
|
+
with LocalStorage(path, Mode("w"), True) as storage:
|
|
141
|
+
storage._migrate(storage.version)
|
|
142
|
+
storage.reload()
|
|
143
|
+
|
|
144
|
+
def reload(self) -> None:
|
|
135
145
|
"""
|
|
136
146
|
Reloads the index, experiments, and ensembles from the storage.
|
|
137
147
|
|
|
@@ -267,14 +277,14 @@ class LocalStorage(BaseMode):
|
|
|
267
277
|
def _swap_path(self) -> Path:
|
|
268
278
|
return self.path / self.SWAP_PATH
|
|
269
279
|
|
|
270
|
-
def __enter__(self) ->
|
|
280
|
+
def __enter__(self) -> Self:
|
|
271
281
|
return self
|
|
272
282
|
|
|
273
283
|
def __exit__(
|
|
274
284
|
self,
|
|
275
|
-
exception:
|
|
276
|
-
exception_type:
|
|
277
|
-
traceback: TracebackType,
|
|
285
|
+
exception: type[BaseException] | None,
|
|
286
|
+
exception_type: BaseException | None,
|
|
287
|
+
traceback: types.TracebackType | None,
|
|
278
288
|
) -> None:
|
|
279
289
|
self.close()
|
|
280
290
|
|
|
@@ -504,6 +514,9 @@ class LocalStorage(BaseMode):
|
|
|
504
514
|
to16,
|
|
505
515
|
to17,
|
|
506
516
|
to18,
|
|
517
|
+
to19,
|
|
518
|
+
to20,
|
|
519
|
+
to21,
|
|
507
520
|
)
|
|
508
521
|
|
|
509
522
|
try:
|
|
@@ -533,7 +546,7 @@ class LocalStorage(BaseMode):
|
|
|
533
546
|
|
|
534
547
|
self._index = self._load_index()
|
|
535
548
|
|
|
536
|
-
logger.info("
|
|
549
|
+
logger.info("Storage backed up for version less than 5")
|
|
537
550
|
print(self._legacy_storage_migration_message(bkup_path, "14.6.*"))
|
|
538
551
|
return None
|
|
539
552
|
elif version < _LOCAL_STORAGE_VERSION:
|
|
@@ -551,6 +564,9 @@ class LocalStorage(BaseMode):
|
|
|
551
564
|
15: to16,
|
|
552
565
|
16: to17,
|
|
553
566
|
17: to18,
|
|
567
|
+
18: to19,
|
|
568
|
+
19: to20,
|
|
569
|
+
20: to21,
|
|
554
570
|
}
|
|
555
571
|
for from_version in range(version, _LOCAL_STORAGE_VERSION):
|
|
556
572
|
migrations[from_version].migrate(self.path)
|
ert/storage/migration/to11.py
CHANGED
|
@@ -44,7 +44,7 @@ def migrate(path: Path) -> None:
|
|
|
44
44
|
array = ds.isel(realizations=0, drop=True)["values"]
|
|
45
45
|
realization = int(real_dir.name.split("-")[1])
|
|
46
46
|
|
|
47
|
-
def parse_value(value: float |
|
|
47
|
+
def parse_value(value: float | str) -> float | int | str:
|
|
48
48
|
if isinstance(value, float | int):
|
|
49
49
|
return value
|
|
50
50
|
try:
|
ert/storage/migration/to18.py
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
info = "Add dimensionality attribute to parameters"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def migrate_param(parameters_json: dict[str, Any]) -> dict[str, Any]:
|
|
9
|
+
new_configs = {}
|
|
10
|
+
for param_config in parameters_json.values():
|
|
11
|
+
if param_config["type"] == "surface":
|
|
12
|
+
param_config["dimensionality"] = 2
|
|
13
|
+
elif param_config["type"] == "field":
|
|
14
|
+
param_config["dimensionality"] = 3
|
|
15
|
+
else:
|
|
16
|
+
param_config["dimensionality"] = 1
|
|
17
|
+
|
|
18
|
+
new_configs[param_config["name"]] = param_config
|
|
19
|
+
return new_configs
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def migrate_parameters_for_experiment(experiment: Path) -> None:
|
|
23
|
+
with open(experiment / "parameter.json", encoding="utf-8") as fin:
|
|
24
|
+
parameters_json = json.load(fin)
|
|
25
|
+
|
|
26
|
+
new_parameter_configs = migrate_param(parameters_json)
|
|
27
|
+
Path(experiment / "parameter.json").write_text(
|
|
28
|
+
json.dumps(new_parameter_configs, indent=2), encoding="utf-8"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def migrate(path: Path) -> None:
|
|
33
|
+
for experiment in path.glob("experiments/*"):
|
|
34
|
+
migrate_parameters_for_experiment(experiment)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
info = "Remove redundant .name attribute from responses."
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def config_without_name_attr(config: dict[str, Any]) -> dict[str, Any]:
|
|
9
|
+
new_json = {**config}
|
|
10
|
+
new_json.pop("name", None)
|
|
11
|
+
|
|
12
|
+
return new_json
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def migrate(path: Path) -> None:
|
|
16
|
+
for response_json_path in path.glob("experiments/*/responses.json"):
|
|
17
|
+
old_json = json.loads((response_json_path).read_text(encoding="utf-8"))
|
|
18
|
+
new_json = {
|
|
19
|
+
response_type: config_without_name_attr(config)
|
|
20
|
+
for response_type, config in old_json.items()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
response_json_path.write_text(json.dumps(new_json, indent=2), encoding="utf-8")
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
info = "Remove refcase from summary response configs"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def config_without_refcase(summary_config: dict[str, Any]) -> dict[str, Any]:
|
|
9
|
+
new_json = {**summary_config}
|
|
10
|
+
new_json.pop("refcase", None)
|
|
11
|
+
|
|
12
|
+
return new_json
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def migrate(path: Path) -> None:
|
|
16
|
+
for response_json_path in path.glob("experiments/*/responses.json"):
|
|
17
|
+
old_json = json.loads((response_json_path).read_text(encoding="utf-8"))
|
|
18
|
+
new_json = {
|
|
19
|
+
response_type: config_without_refcase(response_config)
|
|
20
|
+
if response_config["type"] == "summary"
|
|
21
|
+
else response_config
|
|
22
|
+
for response_type, response_config in old_json.items()
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
response_json_path.write_text(json.dumps(new_json, indent=2), encoding="utf-8")
|
ert/workflow_runner.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
+
import types
|
|
4
5
|
from concurrent import futures
|
|
5
6
|
from concurrent.futures import Future
|
|
6
7
|
from typing import Any, Self
|
|
@@ -127,7 +128,7 @@ class WorkflowRunner:
|
|
|
127
128
|
self,
|
|
128
129
|
exc_type: type[BaseException] | None,
|
|
129
130
|
exc_value: BaseException | None,
|
|
130
|
-
traceback:
|
|
131
|
+
traceback: types.TracebackType | None,
|
|
131
132
|
) -> None:
|
|
132
133
|
self.wait()
|
|
133
134
|
|