ert 16.0.9__py3-none-any.whl → 19.0.0rc2__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/events.py +19 -2
- _ert/forward_model_runner/client.py +6 -2
- _ert/forward_model_runner/fm_dispatch.py +9 -6
- _ert/forward_model_runner/reporting/event.py +1 -0
- _ert/forward_model_runner/runner.py +1 -2
- _ert/utils.py +12 -0
- ert/__main__.py +58 -38
- ert/analysis/_enif_update.py +8 -4
- ert/analysis/_es_update.py +19 -6
- ert/analysis/_update_commons.py +16 -6
- ert/base_model_context.py +1 -1
- ert/cli/main.py +17 -12
- ert/cli/monitor.py +7 -0
- ert/config/__init__.py +17 -6
- ert/config/_create_observation_dataframes.py +118 -21
- ert/config/_get_num_cpu.py +1 -1
- ert/config/_observations.py +91 -2
- ert/config/_read_summary.py +74 -328
- ert/config/design_matrix.py +62 -23
- ert/config/distribution.py +1 -1
- ert/config/ensemble_config.py +9 -17
- ert/config/ert_config.py +155 -58
- ert/config/everest_control.py +234 -0
- ert/config/{everest_constraints_config.py → everest_response.py} +27 -15
- ert/config/field.py +99 -90
- ert/config/forward_model_step.py +122 -17
- ert/config/gen_data_config.py +5 -10
- ert/config/gen_kw_config.py +11 -41
- ert/config/known_response_types.py +14 -0
- ert/config/parameter_config.py +1 -33
- ert/config/parsing/_option_dict.py +10 -2
- ert/config/parsing/config_errors.py +1 -1
- ert/config/parsing/config_keywords.py +2 -1
- ert/config/parsing/config_schema.py +23 -11
- ert/config/parsing/config_schema_deprecations.py +3 -3
- ert/config/parsing/config_schema_item.py +26 -11
- 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/parsing/schema_item_type.py +1 -0
- ert/config/queue_config.py +42 -50
- ert/config/response_config.py +0 -8
- ert/config/rft_config.py +275 -0
- ert/config/summary_config.py +3 -8
- ert/config/surface_config.py +73 -26
- ert/config/workflow_fixtures.py +2 -1
- ert/config/workflow_job.py +135 -54
- ert/dark_storage/client/__init__.py +2 -2
- ert/dark_storage/client/_session.py +4 -4
- ert/dark_storage/client/client.py +2 -2
- ert/dark_storage/common.py +12 -3
- ert/dark_storage/compute/misfits.py +11 -7
- ert/dark_storage/endpoints/compute/misfits.py +6 -4
- ert/dark_storage/endpoints/ensembles.py +4 -0
- ert/dark_storage/endpoints/experiment_server.py +30 -24
- ert/dark_storage/endpoints/experiments.py +2 -2
- ert/dark_storage/endpoints/observations.py +8 -6
- ert/dark_storage/endpoints/parameters.py +4 -12
- ert/dark_storage/endpoints/responses.py +24 -5
- ert/dark_storage/json_schema/ensemble.py +3 -0
- ert/dark_storage/json_schema/experiment.py +1 -1
- ert/data/_measured_data.py +6 -5
- ert/ensemble_evaluator/__init__.py +8 -1
- ert/ensemble_evaluator/config.py +2 -1
- ert/ensemble_evaluator/evaluator.py +81 -29
- ert/ensemble_evaluator/event.py +6 -0
- ert/ensemble_evaluator/snapshot.py +3 -1
- ert/ensemble_evaluator/state.py +1 -0
- ert/field_utils/__init__.py +8 -0
- ert/field_utils/field_utils.py +228 -15
- ert/field_utils/grdecl_io.py +1 -1
- ert/field_utils/roff_io.py +1 -1
- ert/gui/__init__.py +5 -2
- ert/gui/ertnotifier.py +1 -1
- ert/gui/ertwidgets/__init__.py +23 -16
- ert/gui/ertwidgets/analysismoduleedit.py +2 -2
- ert/gui/ertwidgets/checklist.py +1 -1
- ert/gui/ertwidgets/closabledialog.py +2 -0
- ert/gui/ertwidgets/copyablelabel.py +2 -0
- ert/gui/ertwidgets/create_experiment_dialog.py +3 -1
- ert/gui/ertwidgets/ensembleselector.py +2 -2
- ert/gui/ertwidgets/listeditbox.py +2 -0
- ert/gui/ertwidgets/models/__init__.py +2 -0
- ert/gui/ertwidgets/models/activerealizationsmodel.py +5 -1
- ert/gui/ertwidgets/models/path_model.py +1 -1
- ert/gui/ertwidgets/models/targetensemblemodel.py +5 -1
- ert/gui/ertwidgets/models/text_model.py +4 -1
- ert/gui/ertwidgets/pathchooser.py +0 -3
- ert/gui/ertwidgets/searchbox.py +17 -4
- ert/gui/ertwidgets/stringbox.py +2 -0
- ert/gui/{suggestor → ertwidgets/suggestor}/_suggestor_message.py +13 -4
- ert/gui/{suggestor → ertwidgets/suggestor}/suggestor.py +63 -30
- ert/gui/main.py +41 -13
- ert/gui/main_window.py +3 -7
- ert/gui/model/fm_step_list.py +3 -0
- ert/gui/model/real_list.py +1 -0
- ert/gui/model/snapshot.py +1 -0
- ert/gui/simulation/combobox_with_description.py +3 -0
- ert/gui/simulation/ensemble_experiment_panel.py +8 -2
- ert/gui/simulation/ensemble_information_filter_panel.py +7 -2
- ert/gui/simulation/ensemble_smoother_panel.py +8 -2
- ert/gui/simulation/evaluate_ensemble_panel.py +17 -7
- ert/gui/simulation/experiment_panel.py +18 -6
- ert/gui/simulation/manual_update_panel.py +35 -10
- ert/gui/simulation/multiple_data_assimilation_panel.py +13 -9
- ert/gui/simulation/run_dialog.py +47 -20
- ert/gui/simulation/single_test_run_panel.py +6 -3
- ert/gui/simulation/view/progress_widget.py +2 -0
- ert/gui/simulation/view/realization.py +5 -1
- ert/gui/simulation/view/update.py +2 -0
- ert/gui/summarypanel.py +20 -1
- ert/gui/tools/event_viewer/panel.py +3 -4
- ert/gui/tools/event_viewer/tool.py +2 -0
- ert/gui/tools/load_results/load_results_panel.py +1 -1
- ert/gui/tools/load_results/load_results_tool.py +2 -0
- ert/gui/tools/manage_experiments/export_dialog.py +136 -0
- ert/gui/tools/manage_experiments/manage_experiments_panel.py +2 -0
- ert/gui/tools/manage_experiments/storage_info_widget.py +121 -16
- ert/gui/tools/manage_experiments/storage_widget.py +4 -3
- ert/gui/tools/plot/customize/color_chooser.py +5 -2
- ert/gui/tools/plot/customize/customize_plot_dialog.py +2 -0
- ert/gui/tools/plot/customize/default_customization_view.py +4 -0
- ert/gui/tools/plot/customize/limits_customization_view.py +3 -0
- ert/gui/tools/plot/customize/statistics_customization_view.py +3 -0
- ert/gui/tools/plot/customize/style_chooser.py +2 -0
- ert/gui/tools/plot/customize/style_customization_view.py +3 -0
- ert/gui/tools/plot/data_type_keys_widget.py +2 -0
- ert/gui/tools/plot/data_type_proxy_model.py +3 -0
- ert/gui/tools/plot/plot_api.py +50 -28
- ert/gui/tools/plot/plot_ensemble_selection_widget.py +17 -10
- ert/gui/tools/plot/plot_widget.py +15 -2
- ert/gui/tools/plot/plot_window.py +41 -19
- ert/gui/tools/plot/plottery/plot_config.py +2 -0
- ert/gui/tools/plot/plottery/plot_context.py +14 -0
- 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 +13 -5
- 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 +62 -20
- ert/gui/tools/plot/plottery/plots/std_dev.py +3 -1
- ert/gui/tools/plot/widgets/clearable_line_edit.py +9 -0
- ert/gui/tools/plot/widgets/filter_popup.py +2 -0
- ert/gui/tools/plot/widgets/filterable_kw_list_model.py +3 -0
- ert/gui/tools/plugins/plugin.py +1 -1
- ert/gui/tools/plugins/plugins_tool.py +2 -0
- ert/gui/tools/plugins/process_job_dialog.py +3 -0
- ert/gui/tools/workflows/workflow_dialog.py +2 -0
- ert/gui/tools/workflows/workflows_tool.py +2 -0
- ert/libres_facade.py +5 -7
- ert/logging/__init__.py +4 -1
- ert/mode_definitions.py +2 -0
- ert/plugins/__init__.py +4 -6
- ert/plugins/hook_implementations/workflows/csv_export.py +2 -3
- ert/plugins/hook_implementations/workflows/gen_data_rft_export.py +10 -2
- ert/plugins/hook_specifications/__init__.py +0 -10
- ert/plugins/hook_specifications/jobs.py +0 -9
- ert/plugins/plugin_manager.py +53 -124
- ert/resources/forward_models/run_reservoirsimulator.py +8 -4
- ert/resources/forward_models/template_render.py +10 -10
- ert/resources/shell_scripts/delete_directory.py +2 -2
- ert/run_models/__init__.py +24 -6
- ert/run_models/_create_run_path.py +133 -38
- ert/run_models/ensemble_experiment.py +10 -4
- ert/run_models/ensemble_information_filter.py +8 -1
- ert/run_models/ensemble_smoother.py +9 -3
- ert/run_models/evaluate_ensemble.py +8 -6
- ert/run_models/event.py +7 -3
- ert/run_models/everest_run_model.py +337 -113
- ert/run_models/initial_ensemble_run_model.py +25 -24
- ert/run_models/manual_update.py +6 -3
- ert/run_models/manual_update_enif.py +37 -0
- ert/run_models/model_factory.py +78 -18
- ert/run_models/multiple_data_assimilation.py +22 -11
- ert/run_models/run_model.py +72 -73
- ert/run_models/single_test_run.py +7 -4
- ert/run_models/update_run_model.py +4 -2
- ert/runpaths.py +5 -6
- ert/sample_prior.py +9 -4
- ert/scheduler/__init__.py +10 -5
- ert/scheduler/driver.py +40 -0
- ert/scheduler/event.py +3 -1
- ert/scheduler/job.py +23 -13
- ert/scheduler/lsf_driver.py +15 -5
- ert/scheduler/openpbs_driver.py +10 -4
- ert/scheduler/scheduler.py +5 -0
- ert/scheduler/slurm_driver.py +20 -5
- ert/services/__init__.py +2 -2
- ert/services/_base_service.py +37 -20
- ert/services/_storage_main.py +20 -18
- ert/services/ert_server.py +317 -0
- ert/shared/_doc_utils/__init__.py +4 -2
- ert/shared/_doc_utils/ert_jobs.py +1 -4
- ert/shared/net_utils.py +43 -18
- ert/shared/storage/connection.py +3 -3
- ert/shared/version.py +3 -3
- ert/storage/__init__.py +14 -1
- ert/storage/local_ensemble.py +44 -13
- ert/storage/local_experiment.py +54 -34
- ert/storage/local_storage.py +90 -58
- ert/storage/migration/to10.py +3 -2
- ert/storage/migration/to11.py +9 -10
- ert/storage/migration/to12.py +19 -20
- ert/storage/migration/to13.py +28 -27
- ert/storage/migration/to14.py +3 -3
- ert/storage/migration/to15.py +25 -0
- ert/storage/migration/to16.py +38 -0
- ert/storage/migration/to17.py +42 -0
- ert/storage/migration/to18.py +11 -0
- ert/storage/migration/to19.py +34 -0
- ert/storage/migration/to20.py +23 -0
- ert/storage/migration/to21.py +25 -0
- ert/storage/migration/to6.py +3 -2
- ert/storage/migration/to7.py +12 -13
- ert/storage/migration/to8.py +9 -11
- ert/storage/migration/to9.py +5 -4
- ert/storage/realization_storage_state.py +7 -7
- ert/substitutions.py +12 -28
- ert/validation/active_range.py +7 -7
- ert/validation/ensemble_realizations_argument.py +4 -2
- ert/validation/rangestring.py +16 -16
- ert/workflow_runner.py +6 -3
- {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/METADATA +21 -15
- ert-19.0.0rc2.dist-info/RECORD +524 -0
- {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/WHEEL +1 -1
- everest/api/everest_data_api.py +14 -1
- everest/assets/everest_logo.svg +406 -0
- everest/bin/config_branch_script.py +30 -14
- everest/bin/everconfigdump_script.py +2 -10
- everest/bin/everest_script.py +53 -33
- everest/bin/everlint_script.py +3 -5
- everest/bin/kill_script.py +7 -5
- everest/bin/main.py +11 -24
- everest/bin/monitor_script.py +64 -35
- everest/bin/utils.py +58 -43
- everest/bin/visualization_script.py +23 -13
- everest/config/__init__.py +4 -1
- everest/config/control_config.py +81 -6
- everest/config/control_variable_config.py +4 -3
- everest/config/everest_config.py +102 -79
- everest/config/forward_model_config.py +5 -3
- everest/config/install_data_config.py +7 -5
- everest/config/install_job_config.py +45 -3
- everest/config/install_template_config.py +3 -3
- everest/config/optimization_config.py +19 -6
- everest/config/output_constraint_config.py +8 -2
- everest/config/server_config.py +6 -55
- everest/config/simulator_config.py +62 -17
- everest/config/utils.py +25 -105
- everest/config/validation_utils.py +34 -15
- everest/config_file_loader.py +30 -21
- everest/detached/__init__.py +0 -6
- everest/detached/client.py +7 -52
- everest/detached/everserver.py +19 -45
- everest/everest_storage.py +24 -40
- everest/gui/everest_client.py +2 -3
- everest/gui/main_window.py +2 -2
- everest/optimizer/everest2ropt.py +68 -42
- everest/optimizer/opt_model_transforms.py +15 -20
- everest/optimizer/utils.py +0 -29
- everest/plugins/hook_specs.py +0 -24
- everest/strings.py +1 -6
- everest/util/__init__.py +3 -1
- ert/config/everest_objective_config.py +0 -95
- ert/config/ext_param_config.py +0 -107
- 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 -67
- ert/gui/tools/export/exporter.py +0 -36
- ert/plugins/hook_specifications/ecl_config.py +0 -29
- ert/services/storage_service.py +0 -127
- ert/summary_key_type.py +0 -234
- ert-16.0.9.dist-info/RECORD +0 -521
- everest/bin/everexport_script.py +0 -53
- everest/config/sampler_config.py +0 -103
- everest/simulator/__init__.py +0 -88
- everest/simulator/everest_to_ert.py +0 -252
- /ert/gui/{suggestor → ertwidgets/suggestor}/__init__.py +0 -0
- /ert/gui/{suggestor → ertwidgets/suggestor}/_colors.py +0 -0
- {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/entry_points.txt +0 -0
- {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/licenses/COPYING +0 -0
- {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/top_level.txt +0 -0
ert/storage/local_experiment.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
3
4
|
import json
|
|
4
5
|
import shutil
|
|
5
6
|
from collections.abc import Generator
|
|
6
7
|
from datetime import datetime
|
|
8
|
+
from enum import StrEnum, auto
|
|
7
9
|
from functools import cached_property
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from typing import TYPE_CHECKING, Annotated, Any
|
|
@@ -14,14 +16,11 @@ from pydantic import BaseModel, Field, TypeAdapter
|
|
|
14
16
|
from surfio import IrapSurface
|
|
15
17
|
|
|
16
18
|
from ert.config import (
|
|
17
|
-
|
|
18
|
-
EverestObjectivesConfig,
|
|
19
|
-
ExtParamConfig,
|
|
20
|
-
GenDataConfig,
|
|
19
|
+
EverestControl,
|
|
21
20
|
GenKwConfig,
|
|
21
|
+
KnownResponseTypes,
|
|
22
22
|
ParameterConfig,
|
|
23
23
|
ResponseConfig,
|
|
24
|
-
SummaryConfig,
|
|
25
24
|
SurfaceConfig,
|
|
26
25
|
)
|
|
27
26
|
from ert.config import Field as FieldConfig
|
|
@@ -34,6 +33,20 @@ if TYPE_CHECKING:
|
|
|
34
33
|
from .local_storage import LocalStorage
|
|
35
34
|
|
|
36
35
|
|
|
36
|
+
class ExperimentState(StrEnum):
|
|
37
|
+
pending = auto()
|
|
38
|
+
running = auto()
|
|
39
|
+
completed = auto()
|
|
40
|
+
stopped = auto()
|
|
41
|
+
failed = auto()
|
|
42
|
+
never_run = auto()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ExperimentStatus(BaseModel):
|
|
46
|
+
message: str = Field(default="")
|
|
47
|
+
status: ExperimentState = Field(default=ExperimentState.pending)
|
|
48
|
+
|
|
49
|
+
|
|
37
50
|
class _Index(BaseModel):
|
|
38
51
|
id: UUID
|
|
39
52
|
name: str
|
|
@@ -41,21 +54,19 @@ class _Index(BaseModel):
|
|
|
41
54
|
# from a different experiment. For example, a manual update
|
|
42
55
|
# is a separate experiment from the one that created the prior.
|
|
43
56
|
ensembles: list[UUID]
|
|
57
|
+
status: ExperimentStatus | None = Field(default=None)
|
|
44
58
|
|
|
45
59
|
|
|
46
60
|
_responses_adapter = TypeAdapter( # type: ignore
|
|
47
61
|
Annotated[
|
|
48
|
-
|
|
49
|
-
| SummaryConfig
|
|
50
|
-
| EverestConstraintsConfig
|
|
51
|
-
| EverestObjectivesConfig,
|
|
62
|
+
KnownResponseTypes,
|
|
52
63
|
Field(discriminator="type"),
|
|
53
64
|
]
|
|
54
65
|
)
|
|
55
66
|
|
|
56
67
|
_parameters_adapter = TypeAdapter( # type: ignore
|
|
57
68
|
Annotated[
|
|
58
|
-
(GenKwConfig | SurfaceConfig | FieldConfig |
|
|
69
|
+
(GenKwConfig | SurfaceConfig | FieldConfig | EverestControl),
|
|
59
70
|
Field(discriminator="type"),
|
|
60
71
|
]
|
|
61
72
|
)
|
|
@@ -73,6 +84,7 @@ class LocalExperiment(BaseMode):
|
|
|
73
84
|
_responses_file = Path("responses.json")
|
|
74
85
|
_metadata_file = Path("metadata.json")
|
|
75
86
|
_templates_file = Path("templates.json")
|
|
87
|
+
_index_file = Path("index.json")
|
|
76
88
|
|
|
77
89
|
def __init__(
|
|
78
90
|
self,
|
|
@@ -97,7 +109,7 @@ class LocalExperiment(BaseMode):
|
|
|
97
109
|
self._storage = storage
|
|
98
110
|
self._path = path
|
|
99
111
|
self._index = _Index.model_validate_json(
|
|
100
|
-
(path /
|
|
112
|
+
(path / self._index_file).read_text(encoding="utf-8")
|
|
101
113
|
)
|
|
102
114
|
|
|
103
115
|
@classmethod
|
|
@@ -147,9 +159,9 @@ class LocalExperiment(BaseMode):
|
|
|
147
159
|
name = datetime.today().isoformat()
|
|
148
160
|
|
|
149
161
|
storage._write_transaction(
|
|
150
|
-
path /
|
|
162
|
+
path / cls._index_file,
|
|
151
163
|
_Index(id=uuid, name=name, ensembles=[])
|
|
152
|
-
.model_dump_json(indent=2)
|
|
164
|
+
.model_dump_json(indent=2, exclude_none=True)
|
|
153
165
|
.encode("utf-8"),
|
|
154
166
|
)
|
|
155
167
|
|
|
@@ -181,9 +193,7 @@ class LocalExperiment(BaseMode):
|
|
|
181
193
|
|
|
182
194
|
response_data = {}
|
|
183
195
|
for response in responses or []:
|
|
184
|
-
response_data.update(
|
|
185
|
-
{response.response_type: response.model_dump(mode="json")}
|
|
186
|
-
)
|
|
196
|
+
response_data.update({response.type: response.model_dump(mode="json")})
|
|
187
197
|
storage._write_transaction(
|
|
188
198
|
path / cls._responses_file,
|
|
189
199
|
json.dumps(response_data, default=str, indent=2).encode("utf-8"),
|
|
@@ -280,8 +290,7 @@ class LocalExperiment(BaseMode):
|
|
|
280
290
|
path = self.mount_point / self._metadata_file
|
|
281
291
|
if not path.exists():
|
|
282
292
|
raise ValueError(f"{self._metadata_file!s} does not exist")
|
|
283
|
-
|
|
284
|
-
return json.load(f)
|
|
293
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
285
294
|
|
|
286
295
|
@property
|
|
287
296
|
def relative_weights(self) -> str:
|
|
@@ -295,25 +304,37 @@ class LocalExperiment(BaseMode):
|
|
|
295
304
|
def id(self) -> UUID:
|
|
296
305
|
return self._index.id
|
|
297
306
|
|
|
307
|
+
@property
|
|
308
|
+
def status(self) -> ExperimentStatus | None:
|
|
309
|
+
return self._index.status
|
|
310
|
+
|
|
311
|
+
@status.setter
|
|
312
|
+
@require_write
|
|
313
|
+
def status(self, status: ExperimentStatus) -> None:
|
|
314
|
+
if status != self._index.status:
|
|
315
|
+
self._index.status = status
|
|
316
|
+
self._storage._write_transaction(
|
|
317
|
+
self.mount_point / self._index_file,
|
|
318
|
+
self._index.model_dump_json(indent=2).encode("utf-8"),
|
|
319
|
+
)
|
|
320
|
+
|
|
298
321
|
@property
|
|
299
322
|
def mount_point(self) -> Path:
|
|
300
323
|
return self._path
|
|
301
324
|
|
|
302
325
|
@property
|
|
303
326
|
def parameter_info(self) -> dict[str, Any]:
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
return info
|
|
327
|
+
return json.loads(
|
|
328
|
+
(self.mount_point / self._parameter_file).read_text(encoding="utf-8")
|
|
329
|
+
)
|
|
308
330
|
|
|
309
331
|
@property
|
|
310
332
|
def templates_configuration(self) -> list[tuple[str, str]]:
|
|
311
333
|
templates: list[tuple[str, str]] = []
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
pass
|
|
334
|
+
with contextlib.suppress(FileNotFoundError, json.JSONDecodeError):
|
|
335
|
+
templates = json.loads(
|
|
336
|
+
(self.mount_point / self._templates_file).read_text(encoding="utf-8")
|
|
337
|
+
)
|
|
317
338
|
templates_with_content: list[tuple[str, str]] = []
|
|
318
339
|
for source_file, target_file in templates:
|
|
319
340
|
try:
|
|
@@ -327,10 +348,9 @@ class LocalExperiment(BaseMode):
|
|
|
327
348
|
|
|
328
349
|
@property
|
|
329
350
|
def response_info(self) -> dict[str, Any]:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
return info
|
|
351
|
+
return json.loads(
|
|
352
|
+
(self.mount_point / self._responses_file).read_text(encoding="utf-8")
|
|
353
|
+
)
|
|
334
354
|
|
|
335
355
|
def get_surface(self, name: str) -> IrapSurface:
|
|
336
356
|
"""
|
|
@@ -390,7 +410,7 @@ class LocalExperiment(BaseMode):
|
|
|
390
410
|
|
|
391
411
|
for data in self.response_info.values():
|
|
392
412
|
response_instance = _responses_adapter.validate_python(data)
|
|
393
|
-
responses[response_instance.
|
|
413
|
+
responses[response_instance.type] = response_instance
|
|
394
414
|
|
|
395
415
|
return responses
|
|
396
416
|
|
|
@@ -423,7 +443,7 @@ class LocalExperiment(BaseMode):
|
|
|
423
443
|
mapping = {}
|
|
424
444
|
for config in self.response_configuration.values():
|
|
425
445
|
for key in config.keys if config.has_finalized_keys else []:
|
|
426
|
-
mapping[key] = config.
|
|
446
|
+
mapping[key] = config.type
|
|
427
447
|
|
|
428
448
|
return mapping
|
|
429
449
|
|
|
@@ -475,7 +495,7 @@ class LocalExperiment(BaseMode):
|
|
|
475
495
|
self._path / self._responses_file,
|
|
476
496
|
json.dumps(
|
|
477
497
|
{
|
|
478
|
-
c.
|
|
498
|
+
c.type: c.model_dump(mode="json")
|
|
479
499
|
for c in responses_configuration.values()
|
|
480
500
|
},
|
|
481
501
|
default=str,
|
ert/storage/local_storage.py
CHANGED
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import contextlib
|
|
4
3
|
import json
|
|
5
4
|
import logging
|
|
6
5
|
import os
|
|
7
6
|
import re
|
|
8
7
|
import shutil
|
|
8
|
+
import types
|
|
9
9
|
from collections.abc import Generator, MutableSequence
|
|
10
10
|
from datetime import datetime
|
|
11
11
|
from functools import cached_property
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from tempfile import NamedTemporaryFile
|
|
14
14
|
from textwrap import dedent
|
|
15
|
-
from
|
|
16
|
-
from typing import Any
|
|
15
|
+
from typing import Any, Self
|
|
17
16
|
from uuid import UUID, uuid4
|
|
18
17
|
|
|
19
18
|
import polars as pl
|
|
@@ -21,6 +20,7 @@ import xarray as xr
|
|
|
21
20
|
from filelock import FileLock, Timeout
|
|
22
21
|
from pydantic import BaseModel, Field
|
|
23
22
|
|
|
23
|
+
import ert.storage
|
|
24
24
|
from ert.config import ErtConfig, ParameterConfig, ResponseConfig
|
|
25
25
|
from ert.shared import __version__
|
|
26
26
|
|
|
@@ -31,7 +31,7 @@ from .realization_storage_state import RealizationStorageState
|
|
|
31
31
|
|
|
32
32
|
logger = logging.getLogger(__name__)
|
|
33
33
|
|
|
34
|
-
_LOCAL_STORAGE_VERSION =
|
|
34
|
+
_LOCAL_STORAGE_VERSION = 21
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
class _Migrations(BaseModel):
|
|
@@ -65,8 +65,7 @@ class LocalStorage(BaseMode):
|
|
|
65
65
|
self,
|
|
66
66
|
path: str | os.PathLike[str],
|
|
67
67
|
mode: Mode,
|
|
68
|
-
|
|
69
|
-
ignore_migration_check: bool = False,
|
|
68
|
+
stage_for_migration: bool = False,
|
|
70
69
|
) -> None:
|
|
71
70
|
"""
|
|
72
71
|
Initializes the LocalStorage instance.
|
|
@@ -77,19 +76,22 @@ class LocalStorage(BaseMode):
|
|
|
77
76
|
The file system path to the storage.
|
|
78
77
|
mode : Mode
|
|
79
78
|
The access mode for the storage (read/write).
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
stage_for_migration : bool
|
|
80
|
+
Whether to avoid reloading storage to allow migration
|
|
82
81
|
"""
|
|
83
82
|
|
|
84
|
-
super().__init__(mode)
|
|
85
83
|
self.path = Path(path).absolute()
|
|
84
|
+
super().__init__(mode)
|
|
86
85
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
86
|
+
if mode.can_write:
|
|
87
|
+
self._acquire_lock()
|
|
88
|
+
|
|
89
|
+
self._experiments: dict[UUID, LocalExperiment] = {}
|
|
90
|
+
self._ensembles: dict[UUID, LocalEnsemble] = {}
|
|
91
|
+
self._index: _Index = _Index()
|
|
90
92
|
|
|
91
93
|
try:
|
|
92
|
-
version = _storage_version(self.path)
|
|
94
|
+
self.version = _storage_version(self.path)
|
|
93
95
|
except FileNotFoundError as err:
|
|
94
96
|
# No index json, will have a problem if other components of storage exists
|
|
95
97
|
errors = []
|
|
@@ -101,29 +103,45 @@ class LocalStorage(BaseMode):
|
|
|
101
103
|
errors.append(f"ensemble path: {self.path / self.ENSEMBLES_PATH}")
|
|
102
104
|
if errors:
|
|
103
105
|
raise ValueError(f"No index.json, but found: {errors}") from err
|
|
104
|
-
version = _LOCAL_STORAGE_VERSION
|
|
106
|
+
self.version = _LOCAL_STORAGE_VERSION
|
|
105
107
|
|
|
106
|
-
if
|
|
107
|
-
raise RuntimeError(
|
|
108
|
-
f"Cannot open storage '{self.path}': Storage version {version} "
|
|
109
|
-
f"is newer than the current version {_LOCAL_STORAGE_VERSION}, "
|
|
110
|
-
"upgrade ert to continue, or run with a different ENSPATH"
|
|
111
|
-
)
|
|
112
|
-
if self.can_write:
|
|
113
|
-
self._acquire_lock()
|
|
114
|
-
if version < _LOCAL_STORAGE_VERSION and not ignore_migration_check:
|
|
115
|
-
self._migrate(version)
|
|
116
|
-
self._index = self._load_index()
|
|
117
|
-
self._ensure_fs_version_exists()
|
|
118
|
-
self._save_index()
|
|
119
|
-
elif version < _LOCAL_STORAGE_VERSION:
|
|
108
|
+
if self.check_migration_needed(Path(self.path)) and not self.can_write:
|
|
120
109
|
raise RuntimeError(
|
|
121
110
|
f"Cannot open storage '{self.path}' in read-only mode: "
|
|
122
|
-
f"Storage version {version} is too old.
|
|
111
|
+
f"Storage version {self.version} is too old. "
|
|
112
|
+
f"Run ert to initiate migration."
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if not stage_for_migration:
|
|
116
|
+
self.reload()
|
|
117
|
+
|
|
118
|
+
if mode.can_write:
|
|
119
|
+
self._save_index()
|
|
120
|
+
|
|
121
|
+
@staticmethod
|
|
122
|
+
def check_migration_needed(storage_dir: Path) -> bool:
|
|
123
|
+
try:
|
|
124
|
+
version = _storage_version(storage_dir)
|
|
125
|
+
except FileNotFoundError:
|
|
126
|
+
version = _LOCAL_STORAGE_VERSION
|
|
127
|
+
|
|
128
|
+
if version > _LOCAL_STORAGE_VERSION:
|
|
129
|
+
raise ert.storage.ErtStorageException(
|
|
130
|
+
f"Cannot open storage '{storage_dir.absolute()}': Storage version "
|
|
131
|
+
f"{version} is newer than the current version {_LOCAL_STORAGE_VERSION}"
|
|
132
|
+
f", upgrade ert to continue, or run with a different ENSPATH"
|
|
123
133
|
)
|
|
124
|
-
self.refresh()
|
|
125
134
|
|
|
126
|
-
|
|
135
|
+
return version < _LOCAL_STORAGE_VERSION
|
|
136
|
+
|
|
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:
|
|
127
145
|
"""
|
|
128
146
|
Reloads the index, experiments, and ensembles from the storage.
|
|
129
147
|
|
|
@@ -207,6 +225,12 @@ class LocalStorage(BaseMode):
|
|
|
207
225
|
return _Index.model_validate_json(
|
|
208
226
|
(self.path / "index.json").read_text(encoding="utf-8")
|
|
209
227
|
)
|
|
228
|
+
except PermissionError as e:
|
|
229
|
+
logger.error(
|
|
230
|
+
"Permission error when loading index from path: "
|
|
231
|
+
f"{self.path / 'index.json'}. Error: {e}",
|
|
232
|
+
)
|
|
233
|
+
raise e
|
|
210
234
|
except FileNotFoundError:
|
|
211
235
|
return _Index()
|
|
212
236
|
|
|
@@ -218,6 +242,12 @@ class LocalStorage(BaseMode):
|
|
|
218
242
|
try:
|
|
219
243
|
ensemble = LocalEnsemble(self, ensemble_path, self.mode)
|
|
220
244
|
ensembles.append(ensemble)
|
|
245
|
+
except PermissionError as e:
|
|
246
|
+
logger.error(
|
|
247
|
+
"Permission error when loading ensemble from path: "
|
|
248
|
+
f"{ensemble_path}. Error: {e}",
|
|
249
|
+
)
|
|
250
|
+
raise e
|
|
221
251
|
except FileNotFoundError:
|
|
222
252
|
logger.exception(
|
|
223
253
|
"Failed to load an ensemble from path: %s", ensemble_path
|
|
@@ -247,24 +277,17 @@ class LocalStorage(BaseMode):
|
|
|
247
277
|
def _swap_path(self) -> Path:
|
|
248
278
|
return self.path / self.SWAP_PATH
|
|
249
279
|
|
|
250
|
-
def __enter__(self) ->
|
|
280
|
+
def __enter__(self) -> Self:
|
|
251
281
|
return self
|
|
252
282
|
|
|
253
283
|
def __exit__(
|
|
254
284
|
self,
|
|
255
|
-
exception:
|
|
256
|
-
exception_type:
|
|
257
|
-
traceback: TracebackType,
|
|
285
|
+
exception: type[BaseException] | None,
|
|
286
|
+
exception_type: BaseException | None,
|
|
287
|
+
traceback: types.TracebackType | None,
|
|
258
288
|
) -> None:
|
|
259
289
|
self.close()
|
|
260
290
|
|
|
261
|
-
@require_write
|
|
262
|
-
def _ensure_fs_version_exists(self) -> None:
|
|
263
|
-
# ERT 4 checks that this file exists and if it exists tells the user
|
|
264
|
-
# that their ERT storage is incompatible
|
|
265
|
-
with contextlib.suppress(FileExistsError):
|
|
266
|
-
(self.path / ".fs_version").symlink_to("index.json")
|
|
267
|
-
|
|
268
291
|
@require_write
|
|
269
292
|
def _acquire_lock(self) -> None:
|
|
270
293
|
self._lock = FileLock(self.path / "storage.lock")
|
|
@@ -280,22 +303,16 @@ class LocalStorage(BaseMode):
|
|
|
280
303
|
def close(self) -> None:
|
|
281
304
|
"""
|
|
282
305
|
Closes the storage, releasing any acquired locks and saving the index.
|
|
283
|
-
|
|
284
306
|
This method should be called to cleanly close the storage, especially
|
|
285
307
|
when it was opened in write mode. Failing to call this method may leave
|
|
286
308
|
a lock file behind, which would interfere with subsequent access to
|
|
287
309
|
the storage.
|
|
288
310
|
"""
|
|
289
|
-
|
|
290
311
|
self._ensembles.clear()
|
|
291
312
|
self._experiments.clear()
|
|
292
313
|
|
|
293
|
-
if not self.can_write:
|
|
294
|
-
return
|
|
295
|
-
|
|
296
|
-
self._save_index()
|
|
297
|
-
|
|
298
314
|
if self.can_write:
|
|
315
|
+
self._save_index()
|
|
299
316
|
self._release_lock()
|
|
300
317
|
|
|
301
318
|
def _release_lock(self) -> None:
|
|
@@ -493,6 +510,13 @@ class LocalStorage(BaseMode):
|
|
|
493
510
|
to12,
|
|
494
511
|
to13,
|
|
495
512
|
to14,
|
|
513
|
+
to15,
|
|
514
|
+
to16,
|
|
515
|
+
to17,
|
|
516
|
+
to18,
|
|
517
|
+
to19,
|
|
518
|
+
to20,
|
|
519
|
+
to21,
|
|
496
520
|
)
|
|
497
521
|
|
|
498
522
|
try:
|
|
@@ -522,7 +546,7 @@ class LocalStorage(BaseMode):
|
|
|
522
546
|
|
|
523
547
|
self._index = self._load_index()
|
|
524
548
|
|
|
525
|
-
logger.info("
|
|
549
|
+
logger.info("Storage backed up for version less than 5")
|
|
526
550
|
print(self._legacy_storage_migration_message(bkup_path, "14.6.*"))
|
|
527
551
|
return None
|
|
528
552
|
elif version < _LOCAL_STORAGE_VERSION:
|
|
@@ -536,6 +560,13 @@ class LocalStorage(BaseMode):
|
|
|
536
560
|
11: to12,
|
|
537
561
|
12: to13,
|
|
538
562
|
13: to14,
|
|
563
|
+
14: to15,
|
|
564
|
+
15: to16,
|
|
565
|
+
16: to17,
|
|
566
|
+
17: to18,
|
|
567
|
+
18: to19,
|
|
568
|
+
19: to20,
|
|
569
|
+
20: to21,
|
|
539
570
|
}
|
|
540
571
|
for from_version in range(version, _LOCAL_STORAGE_VERSION):
|
|
541
572
|
migrations[from_version].migrate(self.path)
|
|
@@ -553,7 +584,7 @@ class LocalStorage(BaseMode):
|
|
|
553
584
|
Get a unique experiment name
|
|
554
585
|
|
|
555
586
|
If an experiment with the given name exists an _0 is appended
|
|
556
|
-
or _n+1 where n is the
|
|
587
|
+
or _n+1 where n is the largest postfix found for the given experiment name
|
|
557
588
|
"""
|
|
558
589
|
if not experiment_name:
|
|
559
590
|
return self.get_unique_experiment_name("default")
|
|
@@ -623,16 +654,17 @@ class LocalStorage(BaseMode):
|
|
|
623
654
|
def _storage_version(path: Path) -> int:
|
|
624
655
|
if not path.exists():
|
|
625
656
|
return _LOCAL_STORAGE_VERSION
|
|
626
|
-
|
|
627
|
-
with open(path / "index.json", encoding="utf-8") as f:
|
|
628
|
-
return int(json.load(f)["version"])
|
|
629
|
-
except KeyError as exc:
|
|
630
|
-
raise NotImplementedError("Incompatible ERT Local Storage") from exc
|
|
631
|
-
except FileNotFoundError:
|
|
657
|
+
if not (path / "index.json").exists():
|
|
632
658
|
if _is_block_storage(path):
|
|
633
659
|
return 0
|
|
634
660
|
else:
|
|
635
|
-
raise
|
|
661
|
+
raise FileNotFoundError(path / "index.json")
|
|
662
|
+
try:
|
|
663
|
+
return int(
|
|
664
|
+
json.loads((path / "index.json").read_text(encoding="utf-8"))["version"]
|
|
665
|
+
)
|
|
666
|
+
except KeyError as exc:
|
|
667
|
+
raise NotImplementedError("Incompatible ERT Local Storage") from exc
|
|
636
668
|
|
|
637
669
|
|
|
638
670
|
_migration_ert_config: ErtConfig | None = None
|
ert/storage/migration/to10.py
CHANGED
|
@@ -22,8 +22,9 @@ def migrate(path: Path) -> None:
|
|
|
22
22
|
shutil.copyfile(incoming_template, template_file_path)
|
|
23
23
|
templates_abs.append((str(template_file_path.relative_to(experiment)), dst))
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
parameters_json = json.loads(
|
|
26
|
+
(experiment / "parameter.json").read_text(encoding="utf-8")
|
|
27
|
+
)
|
|
27
28
|
|
|
28
29
|
for param in parameters_json.values():
|
|
29
30
|
if param["_ert_kind"] == "GenKwConfig":
|
ert/storage/migration/to11.py
CHANGED
|
@@ -15,18 +15,17 @@ def migrate(path: Path) -> None:
|
|
|
15
15
|
ensembles = path.glob("ensembles/*")
|
|
16
16
|
|
|
17
17
|
experiment_id = None
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
experiment_id = exp_index["id"]
|
|
18
|
+
exp_index = json.loads((experiment / "index.json").read_text(encoding="utf-8"))
|
|
19
|
+
experiment_id = exp_index["id"]
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
parameters_json = json.loads(
|
|
22
|
+
(experiment / "parameter.json").read_text(encoding="utf-8")
|
|
23
|
+
)
|
|
24
24
|
|
|
25
25
|
for ens in ensembles:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
continue
|
|
26
|
+
ens_file = json.loads((ens / "index.json").read_text(encoding="utf-8"))
|
|
27
|
+
if ens_file["experiment_id"] != experiment_id:
|
|
28
|
+
continue
|
|
30
29
|
|
|
31
30
|
real_dirs = [*ens.glob("realization-*")]
|
|
32
31
|
for param_config in parameters_json.values():
|
|
@@ -45,7 +44,7 @@ def migrate(path: Path) -> None:
|
|
|
45
44
|
array = ds.isel(realizations=0, drop=True)["values"]
|
|
46
45
|
realization = int(real_dir.name.split("-")[1])
|
|
47
46
|
|
|
48
|
-
def parse_value(value: float |
|
|
47
|
+
def parse_value(value: float | str) -> float | int | str:
|
|
49
48
|
if isinstance(value, float | int):
|
|
50
49
|
return value
|
|
51
50
|
try:
|
ert/storage/migration/to12.py
CHANGED
|
@@ -25,22 +25,20 @@ def migrate_everest_param(config: dict[str, Any]) -> dict[str, Any]:
|
|
|
25
25
|
|
|
26
26
|
def migrate(path: Path) -> None:
|
|
27
27
|
def _replace_ert_kind(file: Path, kind_to_type: dict[str, str]) -> None:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
new_json = {}
|
|
28
|
+
old_json = json.loads(file.read_text(encoding="utf-8"))
|
|
29
|
+
new_json = {}
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
for key, config in old_json.items():
|
|
32
|
+
ert_kind = config.pop("_ert_kind")
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
if ert_kind == "ExtParamConfig":
|
|
35
|
+
new_json[key] = migrate_everest_param(config) | {
|
|
36
|
+
"type": "everest_parameters"
|
|
37
|
+
}
|
|
38
|
+
else:
|
|
39
|
+
new_json[key] = config | {"type": kind_to_type[ert_kind]}
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
json.dump(new_json, fout, indent=2)
|
|
41
|
+
file.write_text(json.dumps(new_json, indent=2), encoding="utf-8")
|
|
44
42
|
|
|
45
43
|
for experiment in path.glob("experiments/*"):
|
|
46
44
|
_replace_ert_kind(
|
|
@@ -63,12 +61,13 @@ def migrate(path: Path) -> None:
|
|
|
63
61
|
},
|
|
64
62
|
)
|
|
65
63
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
old_json = json.loads(
|
|
65
|
+
(experiment / "responses.json").read_text(encoding="utf-8")
|
|
66
|
+
)
|
|
67
|
+
new_json = {}
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
for key, config in old_json.items():
|
|
70
|
+
if config["type"] == "summary" and "refcase" in config:
|
|
71
|
+
config.pop("refcase")
|
|
73
72
|
|
|
74
|
-
|
|
73
|
+
new_json[key] = config
|