ert 19.0.0rc4__py3-none-any.whl → 20.0.0b0__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/__main__.py +94 -63
- ert/analysis/_es_update.py +11 -14
- ert/config/__init__.py +3 -2
- ert/config/_create_observation_dataframes.py +51 -375
- ert/config/_observations.py +483 -200
- ert/config/_read_summary.py +4 -5
- ert/config/ert_config.py +53 -80
- ert/config/everest_control.py +40 -39
- ert/config/everest_response.py +1 -13
- ert/config/field.py +0 -72
- ert/config/forward_model_step.py +17 -1
- ert/config/gen_data_config.py +14 -17
- ert/config/observation_config_migrations.py +821 -0
- ert/config/parameter_config.py +18 -28
- ert/config/parsing/__init__.py +0 -1
- ert/config/parsing/_parse_zonemap.py +45 -0
- ert/config/parsing/config_keywords.py +1 -1
- ert/config/parsing/config_schema.py +2 -8
- ert/config/parsing/observations_parser.py +2 -0
- ert/config/response_config.py +5 -23
- ert/config/rft_config.py +44 -19
- ert/config/summary_config.py +1 -13
- ert/config/surface_config.py +0 -57
- ert/dark_storage/compute/misfits.py +0 -42
- ert/dark_storage/endpoints/__init__.py +0 -2
- ert/dark_storage/endpoints/experiments.py +2 -5
- ert/dark_storage/json_schema/experiment.py +1 -2
- ert/field_utils/__init__.py +0 -2
- ert/field_utils/field_utils.py +1 -117
- ert/gui/ertwidgets/listeditbox.py +9 -1
- ert/gui/ertwidgets/models/ertsummary.py +20 -6
- ert/gui/ertwidgets/pathchooser.py +9 -1
- ert/gui/ertwidgets/stringbox.py +11 -3
- ert/gui/ertwidgets/textbox.py +10 -3
- ert/gui/ertwidgets/validationsupport.py +19 -1
- ert/gui/main_window.py +11 -6
- ert/gui/simulation/experiment_panel.py +1 -1
- ert/gui/simulation/run_dialog.py +11 -1
- ert/gui/tools/manage_experiments/export_dialog.py +4 -0
- ert/gui/tools/manage_experiments/manage_experiments_panel.py +1 -0
- ert/gui/tools/manage_experiments/storage_info_widget.py +5 -2
- ert/gui/tools/manage_experiments/storage_widget.py +18 -3
- ert/gui/tools/plot/data_type_proxy_model.py +1 -1
- ert/gui/tools/plot/plot_api.py +35 -27
- ert/gui/tools/plot/plot_widget.py +5 -0
- ert/gui/tools/plot/plot_window.py +4 -7
- ert/run_models/ensemble_experiment.py +1 -3
- ert/run_models/ensemble_smoother.py +1 -3
- ert/run_models/everest_run_model.py +12 -13
- ert/run_models/initial_ensemble_run_model.py +19 -22
- ert/run_models/model_factory.py +7 -7
- ert/run_models/multiple_data_assimilation.py +1 -3
- ert/sample_prior.py +12 -14
- ert/services/__init__.py +7 -3
- ert/services/_storage_main.py +59 -22
- ert/services/ert_server.py +186 -24
- ert/shared/version.py +3 -3
- ert/storage/local_ensemble.py +46 -115
- ert/storage/local_experiment.py +0 -16
- ert/utils/__init__.py +20 -0
- ert/warnings/specific_warning_handler.py +3 -2
- {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/METADATA +4 -51
- {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/RECORD +75 -80
- everest/bin/everest_script.py +5 -5
- everest/bin/kill_script.py +2 -2
- everest/bin/monitor_script.py +2 -2
- everest/bin/utils.py +4 -4
- everest/detached/everserver.py +6 -6
- everest/gui/everest_client.py +0 -6
- everest/gui/main_window.py +2 -2
- everest/util/__init__.py +1 -19
- ert/dark_storage/compute/__init__.py +0 -0
- ert/dark_storage/endpoints/compute/__init__.py +0 -0
- ert/dark_storage/endpoints/compute/misfits.py +0 -95
- ert/services/_base_service.py +0 -387
- ert/services/webviz_ert_service.py +0 -20
- ert/shared/storage/command.py +0 -38
- ert/shared/storage/extraction.py +0 -42
- {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/WHEEL +0 -0
- {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/entry_points.txt +0 -0
- {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/licenses/COPYING +0 -0
- {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/top_level.txt +0 -0
ert/services/ert_server.py
CHANGED
|
@@ -1,23 +1,187 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import contextlib
|
|
4
|
+
import io
|
|
3
5
|
import json
|
|
4
6
|
import logging
|
|
5
7
|
import os
|
|
8
|
+
import signal
|
|
6
9
|
import sys
|
|
7
10
|
import threading
|
|
8
11
|
import types
|
|
9
|
-
from collections.abc import Mapping
|
|
12
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
10
13
|
from pathlib import Path
|
|
14
|
+
from select import PIPE_BUF, select
|
|
15
|
+
from subprocess import Popen, TimeoutExpired
|
|
11
16
|
from tempfile import NamedTemporaryFile
|
|
12
17
|
from time import sleep
|
|
13
|
-
from typing import Any, cast
|
|
18
|
+
from typing import Any, TypedDict, cast
|
|
14
19
|
|
|
15
20
|
import requests
|
|
16
21
|
|
|
17
22
|
from ert.dark_storage.client import Client, ErtClientConnectionInfo
|
|
18
|
-
from ert.services._base_service import ErtServerConnectionInfo, _Proc
|
|
19
23
|
from ert.trace import get_traceparent
|
|
20
24
|
|
|
25
|
+
SERVICE_CONF_PATHS: set[str] = set()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ErtServerConnectionInfo(TypedDict):
|
|
29
|
+
urls: list[str]
|
|
30
|
+
authtoken: str
|
|
31
|
+
host: str
|
|
32
|
+
port: str
|
|
33
|
+
cert: str
|
|
34
|
+
auth: str
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ErtServerExit(OSError):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def cleanup_service_files(signum: int, frame: types.FrameType | None) -> None:
|
|
42
|
+
for file_path in SERVICE_CONF_PATHS:
|
|
43
|
+
file = Path(file_path)
|
|
44
|
+
if file.exists():
|
|
45
|
+
file.unlink()
|
|
46
|
+
raise ErtServerExit(f"Signal {signum} received.")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if threading.current_thread() is threading.main_thread():
|
|
50
|
+
signal.signal(signal.SIGTERM, cleanup_service_files)
|
|
51
|
+
signal.signal(signal.SIGINT, cleanup_service_files)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ServerBootFail(RuntimeError):
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class _Proc(threading.Thread):
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
service_name: str,
|
|
62
|
+
exec_args: Sequence[str],
|
|
63
|
+
timeout: int,
|
|
64
|
+
on_connection_info_received: Callable[
|
|
65
|
+
[ErtServerConnectionInfo | Exception | None], None
|
|
66
|
+
],
|
|
67
|
+
project: Path,
|
|
68
|
+
) -> None:
|
|
69
|
+
super().__init__()
|
|
70
|
+
|
|
71
|
+
self._shutdown = threading.Event()
|
|
72
|
+
|
|
73
|
+
self._service_name = service_name
|
|
74
|
+
self._exec_args = exec_args
|
|
75
|
+
self._timeout = timeout
|
|
76
|
+
self._propagate_connection_info_from_childproc = on_connection_info_received
|
|
77
|
+
self._service_config_path = project / f"{self._service_name}_server.json"
|
|
78
|
+
|
|
79
|
+
fd_read, fd_write = os.pipe()
|
|
80
|
+
self._comm_pipe = os.fdopen(fd_read)
|
|
81
|
+
|
|
82
|
+
env = os.environ.copy()
|
|
83
|
+
env["ERT_COMM_FD"] = str(fd_write)
|
|
84
|
+
|
|
85
|
+
SERVICE_CONF_PATHS.add(str(self._service_config_path))
|
|
86
|
+
|
|
87
|
+
# The process is waited for in _do_shutdown()
|
|
88
|
+
self._childproc = Popen(
|
|
89
|
+
self._exec_args,
|
|
90
|
+
pass_fds=(fd_write,),
|
|
91
|
+
env=env,
|
|
92
|
+
close_fds=True,
|
|
93
|
+
)
|
|
94
|
+
os.close(fd_write)
|
|
95
|
+
|
|
96
|
+
def run(self) -> None:
|
|
97
|
+
comm = self._read_connection_info_from_process(self._childproc)
|
|
98
|
+
|
|
99
|
+
if comm is None:
|
|
100
|
+
self._propagate_connection_info_from_childproc(TimeoutError())
|
|
101
|
+
return # _read_conn_info() has already cleaned up in this case
|
|
102
|
+
|
|
103
|
+
conn_info: ErtServerConnectionInfo | Exception | None = None
|
|
104
|
+
try:
|
|
105
|
+
conn_info = json.loads(comm)
|
|
106
|
+
except json.JSONDecodeError:
|
|
107
|
+
conn_info = ServerBootFail()
|
|
108
|
+
except Exception as exc:
|
|
109
|
+
conn_info = exc
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
self._propagate_connection_info_from_childproc(conn_info)
|
|
113
|
+
|
|
114
|
+
while True:
|
|
115
|
+
if self._childproc.poll() is not None:
|
|
116
|
+
break
|
|
117
|
+
if self._shutdown.wait(1):
|
|
118
|
+
self._do_shutdown()
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
except Exception as e:
|
|
122
|
+
print(str(e))
|
|
123
|
+
self.logger.exception(e)
|
|
124
|
+
|
|
125
|
+
finally:
|
|
126
|
+
self._ensure_connection_info_file_is_deleted()
|
|
127
|
+
|
|
128
|
+
def shutdown(self) -> int:
|
|
129
|
+
"""Shutdown the server."""
|
|
130
|
+
self._shutdown.set()
|
|
131
|
+
self.join()
|
|
132
|
+
|
|
133
|
+
return self._childproc.returncode
|
|
134
|
+
|
|
135
|
+
def _read_connection_info_from_process(self, proc: Popen[bytes]) -> str | None:
|
|
136
|
+
comm_buf = io.StringIO()
|
|
137
|
+
first_iter = True
|
|
138
|
+
while first_iter or proc.poll() is None:
|
|
139
|
+
first_iter = False
|
|
140
|
+
ready = select([self._comm_pipe], [], [], self._timeout)
|
|
141
|
+
|
|
142
|
+
# Timeout reached, exit with a failure
|
|
143
|
+
if ready == ([], [], []):
|
|
144
|
+
self._do_shutdown()
|
|
145
|
+
self._ensure_connection_info_file_is_deleted()
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
x = self._comm_pipe.read(PIPE_BUF)
|
|
149
|
+
if not x: # EOF
|
|
150
|
+
break
|
|
151
|
+
comm_buf.write(x)
|
|
152
|
+
return comm_buf.getvalue()
|
|
153
|
+
|
|
154
|
+
def _do_shutdown(self) -> None:
|
|
155
|
+
if self._childproc is None:
|
|
156
|
+
return
|
|
157
|
+
try:
|
|
158
|
+
self._childproc.terminate()
|
|
159
|
+
self._childproc.wait(10) # Give it 10s to shut down cleanly..
|
|
160
|
+
except TimeoutExpired:
|
|
161
|
+
try:
|
|
162
|
+
self._childproc.kill() # ... then kick it harder...
|
|
163
|
+
self._childproc.wait(self._timeout) # ... and wait again
|
|
164
|
+
except TimeoutExpired:
|
|
165
|
+
self.logger.error(
|
|
166
|
+
f"waiting for child-process exceeded timeout {self._timeout}s"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def _ensure_connection_info_file_is_deleted(self) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Ensure that the JSON connection information file is deleted
|
|
172
|
+
"""
|
|
173
|
+
with contextlib.suppress(OSError):
|
|
174
|
+
if self._service_config_path.exists():
|
|
175
|
+
self._service_config_path.unlink()
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def logger(self) -> logging.Logger:
|
|
179
|
+
return logging.getLogger("ert.shared.storage")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
_ERT_SERVER_CONNECTION_INFO_FILE = "storage_server.json"
|
|
183
|
+
_ERT_SERVER_EXECUTABLE_FILE = str(Path(__file__).parent / "_storage_main.py")
|
|
184
|
+
|
|
21
185
|
|
|
22
186
|
class ErtServerContext:
|
|
23
187
|
def __init__(self, service: ErtServer) -> None:
|
|
@@ -70,7 +234,7 @@ class ErtServer:
|
|
|
70
234
|
|
|
71
235
|
run_storage_main_cmd = [
|
|
72
236
|
sys.executable,
|
|
73
|
-
|
|
237
|
+
_ERT_SERVER_EXECUTABLE_FILE,
|
|
74
238
|
"--project",
|
|
75
239
|
storage_path,
|
|
76
240
|
]
|
|
@@ -91,7 +255,7 @@ class ErtServer:
|
|
|
91
255
|
self._thread_that_starts_server_process = _Proc(
|
|
92
256
|
service_name="storage",
|
|
93
257
|
exec_args=run_storage_main_cmd,
|
|
94
|
-
timeout=
|
|
258
|
+
timeout=timeout,
|
|
95
259
|
on_connection_info_received=self.on_connection_info_received_from_server_process,
|
|
96
260
|
project=Path(self._storage_path),
|
|
97
261
|
)
|
|
@@ -167,21 +331,6 @@ class ErtServer:
|
|
|
167
331
|
"None of the URLs provided for the ert storage server worked."
|
|
168
332
|
)
|
|
169
333
|
|
|
170
|
-
@classmethod
|
|
171
|
-
def session(cls, project: os.PathLike[str], timeout: int | None = None) -> Client:
|
|
172
|
-
"""
|
|
173
|
-
Start a HTTP transaction with the server
|
|
174
|
-
"""
|
|
175
|
-
inst = cls.connect(timeout=timeout, project=project)
|
|
176
|
-
info = inst.fetch_connection_info()
|
|
177
|
-
return Client(
|
|
178
|
-
conn_info=ErtClientConnectionInfo(
|
|
179
|
-
base_url=inst.fetch_url(),
|
|
180
|
-
auth_token=inst.fetch_auth()[1],
|
|
181
|
-
cert=info["cert"],
|
|
182
|
-
)
|
|
183
|
-
)
|
|
184
|
-
|
|
185
334
|
@property
|
|
186
335
|
def logger(self) -> logging.Logger:
|
|
187
336
|
return logging.getLogger("ert.shared.storage")
|
|
@@ -218,12 +367,12 @@ class ErtServer:
|
|
|
218
367
|
timeout = 240
|
|
219
368
|
t = -1
|
|
220
369
|
while t < timeout:
|
|
221
|
-
storage_server_path = path /
|
|
370
|
+
storage_server_path = path / _ERT_SERVER_CONNECTION_INFO_FILE
|
|
222
371
|
if (
|
|
223
372
|
storage_server_path.exists()
|
|
224
373
|
and storage_server_path.stat().st_size > 0
|
|
225
374
|
):
|
|
226
|
-
with (path /
|
|
375
|
+
with (path / _ERT_SERVER_CONNECTION_INFO_FILE).open() as f:
|
|
227
376
|
storage_server_content = json.load(f)
|
|
228
377
|
|
|
229
378
|
return ErtServer(
|
|
@@ -277,9 +426,9 @@ class ErtServer:
|
|
|
277
426
|
if self._storage_path is not None:
|
|
278
427
|
if not Path(self._storage_path).exists():
|
|
279
428
|
raise RuntimeError(f"No storage exists at : {self._storage_path}")
|
|
280
|
-
path = f"{self._storage_path}/
|
|
429
|
+
path = f"{self._storage_path}/{_ERT_SERVER_CONNECTION_INFO_FILE}"
|
|
281
430
|
else:
|
|
282
|
-
path =
|
|
431
|
+
path = _ERT_SERVER_CONNECTION_INFO_FILE
|
|
283
432
|
|
|
284
433
|
if isinstance(info, Mapping):
|
|
285
434
|
with NamedTemporaryFile(dir=f"{self._storage_path}", delete=False) as f:
|
|
@@ -315,3 +464,16 @@ class ErtServer:
|
|
|
315
464
|
def wait(self) -> None:
|
|
316
465
|
if self._thread_that_starts_server_process is not None:
|
|
317
466
|
self._thread_that_starts_server_process.join()
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def create_ertserver_client(project: Path, timeout: int | None = None) -> Client:
|
|
470
|
+
"""Read connection info from file in path and create HTTP client."""
|
|
471
|
+
connection = ErtServer.connect(timeout=timeout, project=project)
|
|
472
|
+
info = connection.fetch_connection_info()
|
|
473
|
+
return Client(
|
|
474
|
+
conn_info=ErtClientConnectionInfo(
|
|
475
|
+
base_url=connection.fetch_url(),
|
|
476
|
+
auth_token=connection.fetch_auth()[1],
|
|
477
|
+
cert=info["cert"],
|
|
478
|
+
)
|
|
479
|
+
)
|
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 = '20.0.0b0'
|
|
32
|
+
__version_tuple__ = version_tuple = (20, 0, 0, 'b0')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g23469e640'
|
ert/storage/local_ensemble.py
CHANGED
|
@@ -15,15 +15,18 @@ from typing import TYPE_CHECKING
|
|
|
15
15
|
from uuid import UUID
|
|
16
16
|
|
|
17
17
|
import numpy as np
|
|
18
|
-
import pandas as pd
|
|
19
18
|
import polars as pl
|
|
20
19
|
import resfo
|
|
21
20
|
import xarray as xr
|
|
22
21
|
from pydantic import BaseModel
|
|
23
22
|
from typing_extensions import TypedDict
|
|
24
23
|
|
|
25
|
-
from ert.config import
|
|
26
|
-
|
|
24
|
+
from ert.config import (
|
|
25
|
+
InvalidResponseFile,
|
|
26
|
+
ParameterCardinality,
|
|
27
|
+
ParameterConfig,
|
|
28
|
+
SummaryConfig,
|
|
29
|
+
)
|
|
27
30
|
from ert.substitutions import substitute_runpath_name
|
|
28
31
|
|
|
29
32
|
from .load_status import LoadResult
|
|
@@ -570,30 +573,51 @@ class LocalEnsemble(BaseMode):
|
|
|
570
573
|
) -> pl.DataFrame:
|
|
571
574
|
if keys is None:
|
|
572
575
|
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
576
|
|
|
577
577
|
df_lazy = self._load_parameters_lazy(SCALAR_FILENAME)
|
|
578
|
-
|
|
578
|
+
names = df_lazy.collect_schema().names()
|
|
579
|
+
matches = [key for key in keys if any(key in item for item in names)]
|
|
580
|
+
if len(matches) != len(keys):
|
|
581
|
+
missing = set(keys) - set(matches)
|
|
582
|
+
raise KeyError(f"Parameters not registered to the experiment: {missing}")
|
|
583
|
+
|
|
584
|
+
parameter_keys = [
|
|
585
|
+
key
|
|
586
|
+
for e in keys
|
|
587
|
+
for key in self.experiment.parameter_configuration[e].parameter_keys
|
|
588
|
+
]
|
|
589
|
+
df_lazy_filtered = df_lazy.select(["realization", *parameter_keys])
|
|
590
|
+
|
|
579
591
|
if realizations is not None:
|
|
580
592
|
if isinstance(realizations, int):
|
|
581
593
|
realizations = np.array([realizations])
|
|
582
|
-
|
|
583
|
-
|
|
594
|
+
df_lazy_filtered = df_lazy_filtered.filter(
|
|
595
|
+
pl.col("realization").is_in(realizations)
|
|
596
|
+
)
|
|
597
|
+
df = df_lazy_filtered.collect(engine="streaming")
|
|
584
598
|
if df.is_empty():
|
|
585
599
|
raise IndexError(
|
|
586
600
|
f"No matching realizations {realizations} found for {keys}"
|
|
587
601
|
)
|
|
588
602
|
|
|
589
603
|
if transformed:
|
|
604
|
+
tmp_configuration: dict[str, ParameterConfig] = {}
|
|
605
|
+
for key in keys:
|
|
606
|
+
for col in df.columns:
|
|
607
|
+
if col == "realization":
|
|
608
|
+
continue
|
|
609
|
+
if col.startswith(key):
|
|
610
|
+
tmp_configuration[col] = (
|
|
611
|
+
self.experiment.parameter_configuration[key]
|
|
612
|
+
)
|
|
613
|
+
|
|
590
614
|
df = df.with_columns(
|
|
591
615
|
[
|
|
592
616
|
pl.col(col)
|
|
593
617
|
.map_elements(
|
|
594
|
-
|
|
595
|
-
return_dtype=df[col].dtype,
|
|
618
|
+
tmp_configuration[col].transform_data(),
|
|
596
619
|
)
|
|
620
|
+
.cast(df[col].dtype)
|
|
597
621
|
.alias(col)
|
|
598
622
|
for col in df.columns
|
|
599
623
|
if col != "realization"
|
|
@@ -618,13 +642,11 @@ class LocalEnsemble(BaseMode):
|
|
|
618
642
|
for p in self.experiment.parameter_configuration.values()
|
|
619
643
|
if group in {p.name, p.group_name}
|
|
620
644
|
]
|
|
621
|
-
|
|
622
645
|
if not cfgs:
|
|
623
646
|
raise KeyError(f"{group} is not registered to the experiment.")
|
|
624
647
|
|
|
625
648
|
# if group refers to a group name, we expect the same cardinality
|
|
626
649
|
cardinality = next(cfg.cardinality for cfg in cfgs)
|
|
627
|
-
|
|
628
650
|
if cardinality == ParameterCardinality.multiple_configs_per_ensemble_dataset:
|
|
629
651
|
return self.load_scalar_keys(
|
|
630
652
|
[cfg.name for cfg in cfgs], realizations, transformed
|
|
@@ -741,20 +763,21 @@ class LocalEnsemble(BaseMode):
|
|
|
741
763
|
@staticmethod
|
|
742
764
|
def sample_parameter(
|
|
743
765
|
parameter: ParameterConfig,
|
|
744
|
-
|
|
766
|
+
active_realizations: list[int],
|
|
745
767
|
random_seed: int,
|
|
768
|
+
num_realizations: int,
|
|
746
769
|
) -> pl.DataFrame:
|
|
747
|
-
|
|
748
|
-
str(random_seed),
|
|
749
|
-
real_nr,
|
|
770
|
+
parameter_values = parameter.sample_values(
|
|
771
|
+
str(random_seed), active_realizations, num_realizations=num_realizations
|
|
750
772
|
)
|
|
751
773
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
parameter_dict,
|
|
756
|
-
schema={parameter.name: pl.Float64, "realization": pl.Int64},
|
|
774
|
+
parameters = pl.DataFrame(
|
|
775
|
+
{parameter.name: parameter_values},
|
|
776
|
+
schema={parameter.name: pl.Float64},
|
|
757
777
|
)
|
|
778
|
+
realizations_series = pl.Series("realization", list(active_realizations))
|
|
779
|
+
|
|
780
|
+
return parameters.with_columns(realizations_series)
|
|
758
781
|
|
|
759
782
|
def load_responses(self, key: str, realizations: tuple[int, ...]) -> pl.DataFrame:
|
|
760
783
|
"""Load responses for key and realizations into xarray Dataset.
|
|
@@ -1057,7 +1080,7 @@ class LocalEnsemble(BaseMode):
|
|
|
1057
1080
|
pl.col(col).is_in(observed_values.implode())
|
|
1058
1081
|
)
|
|
1059
1082
|
|
|
1060
|
-
pivoted = responses.collect(engine="streaming").pivot(
|
|
1083
|
+
pivoted = responses.collect(engine="streaming").pivot(
|
|
1061
1084
|
on="realization",
|
|
1062
1085
|
index=["response_key", *response_cls.primary_key],
|
|
1063
1086
|
values="values",
|
|
@@ -1190,98 +1213,6 @@ class LocalEnsemble(BaseMode):
|
|
|
1190
1213
|
self._path / "index.json", self._index.model_dump_json().encode("utf-8")
|
|
1191
1214
|
)
|
|
1192
1215
|
|
|
1193
|
-
@property
|
|
1194
|
-
def all_parameters_and_gen_data(self) -> pl.DataFrame | None:
|
|
1195
|
-
"""
|
|
1196
|
-
Only for Everest wrt objectives/constraints,
|
|
1197
|
-
disregards summary data and primary key values
|
|
1198
|
-
"""
|
|
1199
|
-
param_dfs = []
|
|
1200
|
-
for param_group in self.experiment.parameter_configuration:
|
|
1201
|
-
params_pd = self.load_parameters(param_group)["values"].to_pandas()
|
|
1202
|
-
|
|
1203
|
-
assert isinstance(params_pd, pd.DataFrame)
|
|
1204
|
-
params_pd = params_pd.reset_index()
|
|
1205
|
-
param_df = pl.from_pandas(params_pd)
|
|
1206
|
-
|
|
1207
|
-
param_columns = [c for c in param_df.columns if c != "realizations"]
|
|
1208
|
-
param_df = param_df.rename(
|
|
1209
|
-
{
|
|
1210
|
-
**{
|
|
1211
|
-
c: param_group + "." + c.replace("\0", ".")
|
|
1212
|
-
for c in param_columns
|
|
1213
|
-
},
|
|
1214
|
-
"realizations": "realization",
|
|
1215
|
-
}
|
|
1216
|
-
)
|
|
1217
|
-
param_df = param_df.cast(
|
|
1218
|
-
{
|
|
1219
|
-
"realization": pl.UInt16,
|
|
1220
|
-
**{c: pl.Float64 for c in param_df.columns if c != "realization"},
|
|
1221
|
-
}
|
|
1222
|
-
)
|
|
1223
|
-
param_dfs.append(param_df)
|
|
1224
|
-
|
|
1225
|
-
responses = self.load_responses(
|
|
1226
|
-
"gen_data", tuple(self.get_realization_list_with_responses())
|
|
1227
|
-
)
|
|
1228
|
-
|
|
1229
|
-
if responses is None:
|
|
1230
|
-
return pl.concat(param_dfs)
|
|
1231
|
-
|
|
1232
|
-
params_wide = pl.concat(
|
|
1233
|
-
[
|
|
1234
|
-
(
|
|
1235
|
-
pdf.sort("realization").drop("realization")
|
|
1236
|
-
if i > 0
|
|
1237
|
-
else pdf.sort("realization")
|
|
1238
|
-
)
|
|
1239
|
-
for i, pdf in enumerate(param_dfs)
|
|
1240
|
-
],
|
|
1241
|
-
how="horizontal",
|
|
1242
|
-
)
|
|
1243
|
-
|
|
1244
|
-
responses_wide = responses["realization", "response_key", "values"].pivot( # noqa: PD010
|
|
1245
|
-
on="response_key", values="values"
|
|
1246
|
-
)
|
|
1247
|
-
|
|
1248
|
-
# If responses are missing for some realizations, this _left_ join will
|
|
1249
|
-
# put null (polars) which maps to nan when doing .to_numpy() into the
|
|
1250
|
-
# response columns for those realizations
|
|
1251
|
-
params_and_responses = params_wide.join(
|
|
1252
|
-
responses_wide, on="realization", how="left"
|
|
1253
|
-
).with_columns(pl.lit(self.iteration).alias("batch"))
|
|
1254
|
-
|
|
1255
|
-
assert self.everest_realization_info is not None
|
|
1256
|
-
|
|
1257
|
-
model_realization_mapping = {
|
|
1258
|
-
k: v["model_realization"] for k, v in self.everest_realization_info.items()
|
|
1259
|
-
}
|
|
1260
|
-
perturbation_mapping = {
|
|
1261
|
-
k: v["perturbation"] for k, v in self.everest_realization_info.items()
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
params_and_responses = params_and_responses.with_columns(
|
|
1265
|
-
pl.col("realization")
|
|
1266
|
-
.replace(model_realization_mapping)
|
|
1267
|
-
.alias("model_realization"),
|
|
1268
|
-
pl.col("realization")
|
|
1269
|
-
.cast(pl.Int32)
|
|
1270
|
-
.replace(perturbation_mapping)
|
|
1271
|
-
.alias("perturbation"),
|
|
1272
|
-
)
|
|
1273
|
-
|
|
1274
|
-
column_order = [
|
|
1275
|
-
"batch",
|
|
1276
|
-
"model_realization",
|
|
1277
|
-
"perturbation",
|
|
1278
|
-
"realization",
|
|
1279
|
-
*[c for c in responses_wide.columns if c != "realization"],
|
|
1280
|
-
*[c for c in params_wide.columns if c != "realization"],
|
|
1281
|
-
]
|
|
1282
|
-
|
|
1283
|
-
return params_and_responses[column_order]
|
|
1284
|
-
|
|
1285
1216
|
|
|
1286
1217
|
async def _read_parameters(
|
|
1287
1218
|
run_path: str,
|
ert/storage/local_experiment.py
CHANGED
|
@@ -508,19 +508,3 @@ class LocalExperiment(BaseMode):
|
|
|
508
508
|
|
|
509
509
|
if self.response_type_to_response_keys is not None:
|
|
510
510
|
del self.response_type_to_response_keys
|
|
511
|
-
|
|
512
|
-
@property
|
|
513
|
-
def all_parameters_and_gen_data(self) -> pl.DataFrame | None:
|
|
514
|
-
if not self.ensembles:
|
|
515
|
-
return None
|
|
516
|
-
|
|
517
|
-
ensemble_dfs = [
|
|
518
|
-
e.all_parameters_and_gen_data
|
|
519
|
-
for e in self.ensembles
|
|
520
|
-
if e.all_parameters_and_gen_data is not None
|
|
521
|
-
]
|
|
522
|
-
|
|
523
|
-
if not ensemble_dfs:
|
|
524
|
-
return None
|
|
525
|
-
|
|
526
|
-
return pl.concat(ensemble_dfs)
|
ert/utils/__init__.py
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
3
|
from collections.abc import Callable
|
|
4
|
+
from datetime import UTC, datetime
|
|
4
5
|
from functools import wraps
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
from typing import ParamSpec, TypeVar
|
|
6
8
|
|
|
9
|
+
from _ert.utils import file_safe_timestamp
|
|
10
|
+
|
|
7
11
|
P = ParamSpec("P")
|
|
8
12
|
R = TypeVar("R")
|
|
9
13
|
|
|
@@ -30,3 +34,19 @@ def log_duration(
|
|
|
30
34
|
return wrapper
|
|
31
35
|
|
|
32
36
|
return decorator
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def makedirs_if_needed(path: Path, roll_if_exists: bool = False) -> None:
|
|
40
|
+
if path.is_dir():
|
|
41
|
+
if not roll_if_exists:
|
|
42
|
+
return
|
|
43
|
+
_roll_dir(path) # exists and should be rolled
|
|
44
|
+
path.mkdir(parents=True, exist_ok=False)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _roll_dir(old_name: Path) -> None:
|
|
48
|
+
old_name = old_name.resolve()
|
|
49
|
+
timestamp = file_safe_timestamp(datetime.now(UTC).isoformat())
|
|
50
|
+
new_name = f"{old_name}__{timestamp}"
|
|
51
|
+
old_name.rename(new_name)
|
|
52
|
+
logging.getLogger().info(f"renamed {old_name} to {new_name}")
|
|
@@ -7,7 +7,7 @@ from typing import TextIO
|
|
|
7
7
|
@contextmanager
|
|
8
8
|
def capture_specific_warning(
|
|
9
9
|
warning_class_to_capture: type[Warning],
|
|
10
|
-
propagate_warning: Callable[[Warning | str], None],
|
|
10
|
+
propagate_warning: Callable[[Warning | str], None] | None = None,
|
|
11
11
|
) -> Generator[None, None, None]:
|
|
12
12
|
original_warning_handler = warnings.showwarning
|
|
13
13
|
|
|
@@ -20,7 +20,8 @@ def capture_specific_warning(
|
|
|
20
20
|
line: str | None = None,
|
|
21
21
|
) -> None:
|
|
22
22
|
if issubclass(category, warning_class_to_capture):
|
|
23
|
-
propagate_warning
|
|
23
|
+
if propagate_warning is not None:
|
|
24
|
+
propagate_warning(message)
|
|
24
25
|
else:
|
|
25
26
|
original_warning_handler(message, category, filename, lineno, file, line)
|
|
26
27
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ert
|
|
3
|
-
Version:
|
|
3
|
+
Version: 20.0.0b0
|
|
4
4
|
Summary: Ensemble based Reservoir Tool (ERT)
|
|
5
5
|
Author-email: Equinor ASA <fg_sib-scout@equinor.com>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -45,9 +45,9 @@ Requires-Dist: opentelemetry-instrumentation-threading
|
|
|
45
45
|
Requires-Dist: opentelemetry-sdk
|
|
46
46
|
Requires-Dist: orjson
|
|
47
47
|
Requires-Dist: packaging
|
|
48
|
-
Requires-Dist: pandas
|
|
48
|
+
Requires-Dist: pandas<3
|
|
49
49
|
Requires-Dist: pluggy>=1.3.0
|
|
50
|
-
Requires-Dist: polars
|
|
50
|
+
Requires-Dist: polars!=1.35,>=1.32.3
|
|
51
51
|
Requires-Dist: progressbar2
|
|
52
52
|
Requires-Dist: psutil
|
|
53
53
|
Requires-Dist: pyarrow
|
|
@@ -74,53 +74,6 @@ Requires-Dist: websockets
|
|
|
74
74
|
Requires-Dist: xarray
|
|
75
75
|
Requires-Dist: xtgeo>=3.3.0
|
|
76
76
|
Requires-Dist: resfo-utilities>=0.5.0
|
|
77
|
-
Provides-Extra: dev
|
|
78
|
-
Requires-Dist: furo; extra == "dev"
|
|
79
|
-
Requires-Dist: hypothesis!=6.102.0,!=6.112.3,>=6.85; extra == "dev"
|
|
80
|
-
Requires-Dist: jsonpath_ng; extra == "dev"
|
|
81
|
-
Requires-Dist: jupyter; extra == "dev"
|
|
82
|
-
Requires-Dist: jupytext; extra == "dev"
|
|
83
|
-
Requires-Dist: nbsphinx; extra == "dev"
|
|
84
|
-
Requires-Dist: oil_reservoir_synthesizer; extra == "dev"
|
|
85
|
-
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
86
|
-
Requires-Dist: pytest-benchmark; extra == "dev"
|
|
87
|
-
Requires-Dist: pytest-cov; extra == "dev"
|
|
88
|
-
Requires-Dist: pytest-memray; extra == "dev"
|
|
89
|
-
Requires-Dist: pytest-mock; extra == "dev"
|
|
90
|
-
Requires-Dist: pytest-mpl; extra == "dev"
|
|
91
|
-
Requires-Dist: pytest-qt; extra == "dev"
|
|
92
|
-
Requires-Dist: pytest-raises; extra == "dev"
|
|
93
|
-
Requires-Dist: pytest-rerunfailures!=16.0; extra == "dev"
|
|
94
|
-
Requires-Dist: pytest-snapshot; extra == "dev"
|
|
95
|
-
Requires-Dist: pytest-timeout; extra == "dev"
|
|
96
|
-
Requires-Dist: pytest-xdist; extra == "dev"
|
|
97
|
-
Requires-Dist: pytest-durations; extra == "dev"
|
|
98
|
-
Requires-Dist: pytest>6; extra == "dev"
|
|
99
|
-
Requires-Dist: resdata; extra == "dev"
|
|
100
|
-
Requires-Dist: rust-just; extra == "dev"
|
|
101
|
-
Requires-Dist: scikit-image; extra == "dev"
|
|
102
|
-
Requires-Dist: sphinx; extra == "dev"
|
|
103
|
-
Requires-Dist: sphinx-argparse; extra == "dev"
|
|
104
|
-
Requires-Dist: sphinx-autoapi; extra == "dev"
|
|
105
|
-
Requires-Dist: sphinx-copybutton; extra == "dev"
|
|
106
|
-
Requires-Dist: sphinxcontrib.datatemplates; extra == "dev"
|
|
107
|
-
Requires-Dist: json-schema-for-humans; extra == "dev"
|
|
108
|
-
Requires-Dist: xlsxwriter>=3.2.3; extra == "dev"
|
|
109
|
-
Requires-Dist: resfo-utilities[testing]>=0.5.0; extra == "dev"
|
|
110
|
-
Provides-Extra: style
|
|
111
|
-
Requires-Dist: pre-commit; extra == "style"
|
|
112
|
-
Provides-Extra: types
|
|
113
|
-
Requires-Dist: mypy; extra == "types"
|
|
114
|
-
Requires-Dist: types-lxml; extra == "types"
|
|
115
|
-
Requires-Dist: types-requests; extra == "types"
|
|
116
|
-
Requires-Dist: types-PyYAML; extra == "types"
|
|
117
|
-
Requires-Dist: types-python-dateutil; extra == "types"
|
|
118
|
-
Requires-Dist: types-decorator; extra == "types"
|
|
119
|
-
Requires-Dist: types-docutils; extra == "types"
|
|
120
|
-
Requires-Dist: types-tqdm; extra == "types"
|
|
121
|
-
Requires-Dist: types-psutil; extra == "types"
|
|
122
|
-
Requires-Dist: types-setuptools; extra == "types"
|
|
123
|
-
Requires-Dist: types-networkx; extra == "types"
|
|
124
77
|
Dynamic: license-file
|
|
125
78
|
|
|
126
79
|
<h1 align="center">
|
|
@@ -184,7 +137,7 @@ Once uv is installed, you can get a development environment by running:
|
|
|
184
137
|
```sh
|
|
185
138
|
git clone https://github.com/equinor/ert
|
|
186
139
|
cd ert
|
|
187
|
-
uv sync --all-
|
|
140
|
+
uv sync --all-groups
|
|
188
141
|
```
|
|
189
142
|
|
|
190
143
|
### Test setup
|