ert 19.0.0__py3-none-any.whl → 19.0.0rc1__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/_create_observation_dataframes.py +12 -228
- ert/config/_observations.py +164 -152
- ert/config/_read_summary.py +4 -5
- ert/config/ert_config.py +1 -56
- ert/config/observation_config_migrations.py +793 -0
- ert/config/rft_config.py +1 -1
- ert/dark_storage/compute/misfits.py +0 -42
- ert/dark_storage/endpoints/__init__.py +0 -2
- ert/dark_storage/endpoints/experiments.py +0 -3
- ert/dark_storage/json_schema/experiment.py +0 -1
- ert/field_utils/grdecl_io.py +9 -26
- ert/gui/main_window.py +2 -0
- ert/gui/tools/manage_experiments/export_dialog.py +4 -0
- ert/gui/tools/manage_experiments/storage_info_widget.py +1 -5
- ert/gui/tools/plot/plot_api.py +10 -10
- ert/gui/tools/plot/plot_widget.py +12 -14
- ert/gui/tools/plot/plot_window.py +1 -10
- 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 +3 -107
- ert/storage/local_experiment.py +0 -16
- ert/storage/local_storage.py +1 -3
- ert/utils/__init__.py +20 -0
- {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/METADATA +2 -2
- {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/RECORD +40 -47
- {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/WHEEL +1 -1
- 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/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/storage/migration/to23.py +0 -49
- {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/entry_points.txt +0 -0
- {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/licenses/COPYING +0 -0
- {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/top_level.txt +0 -0
ert/config/rft_config.py
CHANGED
|
@@ -194,7 +194,7 @@ class RFTConfig(ResponseConfig):
|
|
|
194
194
|
.explode("location")
|
|
195
195
|
for (well, time), inner_dict in fetched.items()
|
|
196
196
|
for prop, vals in inner_dict.items()
|
|
197
|
-
if prop != "DEPTH"
|
|
197
|
+
if prop != "DEPTH" and len(vals) > 0
|
|
198
198
|
]
|
|
199
199
|
)
|
|
200
200
|
except KeyError as err:
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
from collections.abc import Mapping
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
import numpy.typing as npt
|
|
5
|
-
import pandas as pd
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def _calculate_signed_chi_squared_misfit(
|
|
9
|
-
obs_value: npt.NDArray[np.float64],
|
|
10
|
-
response_value: npt.NDArray[np.float64],
|
|
11
|
-
obs_std: npt.NDArray[np.float64],
|
|
12
|
-
) -> list[float]:
|
|
13
|
-
"""The signed version is intended for visualization. For data assimiliation one
|
|
14
|
-
would normally use the normal chi-square"""
|
|
15
|
-
residual = response_value - obs_value
|
|
16
|
-
return (np.sign(residual) * residual * residual / (obs_std * obs_std)).tolist()
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def calculate_signed_chi_squared_misfits(
|
|
20
|
-
reponses_dict: Mapping[int, pd.DataFrame],
|
|
21
|
-
observation: pd.DataFrame,
|
|
22
|
-
summary_misfits: bool = False,
|
|
23
|
-
) -> pd.DataFrame:
|
|
24
|
-
"""
|
|
25
|
-
Compute misfits from reponses_dict (real_id, values in dataframe)
|
|
26
|
-
and observation
|
|
27
|
-
"""
|
|
28
|
-
misfits_dict = {}
|
|
29
|
-
for realization_index in reponses_dict:
|
|
30
|
-
misfits_dict[realization_index] = _calculate_signed_chi_squared_misfit(
|
|
31
|
-
observation["values"],
|
|
32
|
-
reponses_dict[realization_index]
|
|
33
|
-
.loc[:, observation.index]
|
|
34
|
-
.to_numpy()
|
|
35
|
-
.flatten(),
|
|
36
|
-
observation["errors"],
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
df = pd.DataFrame(data=misfits_dict, index=observation.index)
|
|
40
|
-
if summary_misfits:
|
|
41
|
-
df = pd.DataFrame([df.abs().sum(axis=0)], columns=df.columns, index=[0])
|
|
42
|
-
return df.T
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from fastapi import APIRouter
|
|
2
2
|
|
|
3
|
-
from .compute.misfits import router as misfits_router
|
|
4
3
|
from .ensembles import router as ensembles_router
|
|
5
4
|
from .experiment_server import router as experiment_server_router
|
|
6
5
|
from .experiments import router as experiments_router
|
|
@@ -15,7 +14,6 @@ router.include_router(experiments_router)
|
|
|
15
14
|
router.include_router(ensembles_router)
|
|
16
15
|
router.include_router(observations_router)
|
|
17
16
|
router.include_router(updates_router)
|
|
18
|
-
router.include_router(misfits_router)
|
|
19
17
|
router.include_router(parameters_router)
|
|
20
18
|
router.include_router(responses_router)
|
|
21
19
|
router.include_router(experiment_server_router)
|
|
@@ -6,7 +6,6 @@ from fastapi import APIRouter, Body, Depends, HTTPException
|
|
|
6
6
|
from ert.config import SurfaceConfig
|
|
7
7
|
from ert.dark_storage import json_schema as js
|
|
8
8
|
from ert.dark_storage.common import get_storage
|
|
9
|
-
from ert.shared.storage.extraction import create_priors
|
|
10
9
|
from ert.storage import Storage
|
|
11
10
|
|
|
12
11
|
router = APIRouter(tags=["experiment"])
|
|
@@ -26,7 +25,6 @@ def get_experiments(
|
|
|
26
25
|
id=experiment.id,
|
|
27
26
|
name=experiment.name,
|
|
28
27
|
ensemble_ids=[ens.id for ens in experiment.ensembles],
|
|
29
|
-
priors=create_priors(experiment),
|
|
30
28
|
userdata={},
|
|
31
29
|
parameters={
|
|
32
30
|
group: config.model_dump()
|
|
@@ -62,7 +60,6 @@ def get_experiment_by_id(
|
|
|
62
60
|
name=experiment.name,
|
|
63
61
|
id=experiment.id,
|
|
64
62
|
ensemble_ids=[ens.id for ens in experiment.ensembles],
|
|
65
|
-
priors=create_priors(experiment),
|
|
66
63
|
userdata={},
|
|
67
64
|
parameters={
|
|
68
65
|
group: config.model_dump()
|
|
@@ -22,7 +22,6 @@ class ExperimentIn(_Experiment):
|
|
|
22
22
|
class ExperimentOut(_Experiment):
|
|
23
23
|
id: UUID
|
|
24
24
|
ensemble_ids: list[UUID]
|
|
25
|
-
priors: Mapping[str, dict[str, Any]]
|
|
26
25
|
userdata: Mapping[str, Any]
|
|
27
26
|
parameters: Mapping[str, dict[str, Any]]
|
|
28
27
|
responses: Mapping[str, list[dict[str, Any]]]
|
ert/field_utils/grdecl_io.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import io
|
|
4
3
|
import operator
|
|
5
4
|
import os
|
|
6
5
|
from collections.abc import Iterator
|
|
@@ -258,9 +257,6 @@ def import_bgrdecl(
|
|
|
258
257
|
raise ValueError(f"Did not find field parameter {field_name} in {file_path}")
|
|
259
258
|
|
|
260
259
|
|
|
261
|
-
_BUFFER_SIZE = 2**20 # 1.04 megabytes
|
|
262
|
-
|
|
263
|
-
|
|
264
260
|
def export_grdecl(
|
|
265
261
|
values: np.ma.MaskedArray[Any, np.dtype[np.float32]] | npt.NDArray[np.float32],
|
|
266
262
|
file_path: str | os.PathLike[str],
|
|
@@ -275,25 +271,12 @@ def export_grdecl(
|
|
|
275
271
|
if binary:
|
|
276
272
|
resfo.write(file_path, [(param_name.ljust(8), values.astype(np.float32))])
|
|
277
273
|
else:
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
io.TextIOWrapper(bw, write_through=True, encoding="utf-8") as tw,
|
|
288
|
-
):
|
|
289
|
-
tw.write(param_name + "\n")
|
|
290
|
-
i = 0
|
|
291
|
-
while i + per_iter <= length:
|
|
292
|
-
tw.write(fmt % tuple(values[i : i + per_iter]))
|
|
293
|
-
i += per_iter
|
|
294
|
-
|
|
295
|
-
for j, v in enumerate(values[length - (length % per_iter) :]):
|
|
296
|
-
tw.write(f" {v:3e}")
|
|
297
|
-
if j % 6 == 5:
|
|
298
|
-
tw.write("\n")
|
|
299
|
-
tw.write(" /\n")
|
|
274
|
+
with open(file_path, "w", encoding="utf-8") as fh:
|
|
275
|
+
fh.write(param_name + "\n")
|
|
276
|
+
for i, v in enumerate(values):
|
|
277
|
+
fh.write(" ")
|
|
278
|
+
fh.write(f"{v:3e}")
|
|
279
|
+
if i % 6 == 5:
|
|
280
|
+
fh.write("\n")
|
|
281
|
+
|
|
282
|
+
fh.write(" /\n")
|
ert/gui/main_window.py
CHANGED
|
@@ -116,8 +116,10 @@ class ErtMainWindow(QMainWindow):
|
|
|
116
116
|
|
|
117
117
|
if is_dark_mode():
|
|
118
118
|
self.side_frame.setStyleSheet("background-color: rgb(64, 64, 64);")
|
|
119
|
+
logger.info("Running Ert with dark mode")
|
|
119
120
|
else:
|
|
120
121
|
self.side_frame.setStyleSheet("background-color: lightgray;")
|
|
122
|
+
logger.info("Running Ert with light mode")
|
|
121
123
|
|
|
122
124
|
if is_high_contrast_mode():
|
|
123
125
|
msg_box = QMessageBox()
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import contextlib
|
|
2
|
+
import logging
|
|
2
3
|
from pathlib import Path
|
|
3
4
|
from typing import cast
|
|
4
5
|
|
|
@@ -22,6 +23,8 @@ from PyQt6.QtWidgets import (
|
|
|
22
23
|
QWidget,
|
|
23
24
|
)
|
|
24
25
|
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
class ExportDialog(QDialog):
|
|
27
30
|
"""Base dialog for exporting ensemble-related data to files."""
|
|
@@ -85,6 +88,7 @@ class ExportDialog(QDialog):
|
|
|
85
88
|
f"<span style='color: red;'>Could not export data: {e!s}</span><br>"
|
|
86
89
|
)
|
|
87
90
|
finally:
|
|
91
|
+
logger.info(f"Export dialog used: '{self.windowTitle()}'")
|
|
88
92
|
self._export_button.setEnabled(True)
|
|
89
93
|
|
|
90
94
|
@Slot()
|
|
@@ -11,7 +11,6 @@ from polars import DataFrame
|
|
|
11
11
|
from PyQt6.QtCore import Qt
|
|
12
12
|
from PyQt6.QtCore import pyqtSlot as Slot
|
|
13
13
|
from PyQt6.QtWidgets import (
|
|
14
|
-
QAbstractItemView,
|
|
15
14
|
QFrame,
|
|
16
15
|
QHBoxLayout,
|
|
17
16
|
QLabel,
|
|
@@ -166,9 +165,6 @@ class _EnsembleWidget(QWidget):
|
|
|
166
165
|
observations_frame.setLayout(observations_layout)
|
|
167
166
|
|
|
168
167
|
self._parameters_table = QTableWidget()
|
|
169
|
-
self._parameters_table.setEditTriggers(
|
|
170
|
-
QAbstractItemView.EditTrigger.NoEditTriggers
|
|
171
|
-
)
|
|
172
168
|
self._export_params_button = QPushButton("Export...")
|
|
173
169
|
self._export_params_button.clicked.connect(self.onClickExportParameters)
|
|
174
170
|
|
|
@@ -253,8 +249,8 @@ class _EnsembleWidget(QWidget):
|
|
|
253
249
|
df[x_axis_col]
|
|
254
250
|
.map_elements(
|
|
255
251
|
lambda x: response_config.display_column(x, x_axis_col),
|
|
256
|
-
return_dtype=pl.String,
|
|
257
252
|
)
|
|
253
|
+
.cast(pl.String)
|
|
258
254
|
.alias("temp")
|
|
259
255
|
).filter(pl.col("temp").eq(observation_label))[
|
|
260
256
|
[x for x in df.columns if x != "temp"]
|
ert/gui/tools/plot/plot_api.py
CHANGED
|
@@ -19,7 +19,7 @@ from pandas.errors import ParserError
|
|
|
19
19
|
from resfo_utilities import history_key
|
|
20
20
|
|
|
21
21
|
from ert.config import ParameterConfig, ResponseMetadata
|
|
22
|
-
from ert.services import
|
|
22
|
+
from ert.services import create_ertserver_client
|
|
23
23
|
from ert.storage.local_experiment import _parameters_adapter as parameter_config_adapter
|
|
24
24
|
from ert.storage.realization_storage_state import RealizationStorageState
|
|
25
25
|
|
|
@@ -51,13 +51,13 @@ class PlotApiKeyDefinition(NamedTuple):
|
|
|
51
51
|
|
|
52
52
|
class PlotApi:
|
|
53
53
|
def __init__(self, ens_path: Path) -> None:
|
|
54
|
-
self.ens_path = ens_path
|
|
54
|
+
self.ens_path: Path = ens_path
|
|
55
55
|
self._all_ensembles: list[EnsembleObject] | None = None
|
|
56
56
|
self._timeout = 120
|
|
57
57
|
|
|
58
58
|
@property
|
|
59
59
|
def api_version(self) -> str:
|
|
60
|
-
with
|
|
60
|
+
with create_ertserver_client(self.ens_path) as client:
|
|
61
61
|
try:
|
|
62
62
|
response = client.get("/version", timeout=self._timeout)
|
|
63
63
|
self._check_response(response)
|
|
@@ -83,7 +83,7 @@ class PlotApi:
|
|
|
83
83
|
return self._all_ensembles
|
|
84
84
|
|
|
85
85
|
self._all_ensembles = []
|
|
86
|
-
with
|
|
86
|
+
with create_ertserver_client(self.ens_path) as client:
|
|
87
87
|
try:
|
|
88
88
|
response = client.get("/experiments", timeout=self._timeout)
|
|
89
89
|
self._check_response(response)
|
|
@@ -139,7 +139,7 @@ class PlotApi:
|
|
|
139
139
|
all_keys: dict[str, PlotApiKeyDefinition] = {}
|
|
140
140
|
all_params = {}
|
|
141
141
|
|
|
142
|
-
with
|
|
142
|
+
with create_ertserver_client(self.ens_path) as client:
|
|
143
143
|
response = client.get("/experiments", timeout=self._timeout)
|
|
144
144
|
self._check_response(response)
|
|
145
145
|
|
|
@@ -166,7 +166,7 @@ class PlotApi:
|
|
|
166
166
|
def responses_api_key_defs(self) -> list[PlotApiKeyDefinition]:
|
|
167
167
|
key_defs: dict[str, PlotApiKeyDefinition] = {}
|
|
168
168
|
|
|
169
|
-
with
|
|
169
|
+
with create_ertserver_client(self.ens_path) as client:
|
|
170
170
|
response = client.get("/experiments", timeout=self._timeout)
|
|
171
171
|
self._check_response(response)
|
|
172
172
|
|
|
@@ -228,7 +228,7 @@ class PlotApi:
|
|
|
228
228
|
response_key: str,
|
|
229
229
|
filter_on: dict[str, Any] | None = None,
|
|
230
230
|
) -> pd.DataFrame:
|
|
231
|
-
with
|
|
231
|
+
with create_ertserver_client(self.ens_path) as client:
|
|
232
232
|
response = client.get(
|
|
233
233
|
f"/ensembles/{ensemble_id}/responses/{PlotApi.escape(response_key)}",
|
|
234
234
|
headers={"accept": "application/x-parquet"},
|
|
@@ -256,7 +256,7 @@ class PlotApi:
|
|
|
256
256
|
return df
|
|
257
257
|
|
|
258
258
|
def data_for_parameter(self, ensemble_id: str, parameter_key: str) -> pd.DataFrame:
|
|
259
|
-
with
|
|
259
|
+
with create_ertserver_client(self.ens_path) as client:
|
|
260
260
|
parameter = client.get(
|
|
261
261
|
f"/ensembles/{ensemble_id}/parameters/{PlotApi.escape(parameter_key)}",
|
|
262
262
|
headers={"accept": "application/x-parquet"},
|
|
@@ -298,7 +298,7 @@ class PlotApi:
|
|
|
298
298
|
assert key_def.response_metadata is not None
|
|
299
299
|
actual_response_key = key_def.response_metadata.response_key
|
|
300
300
|
filter_on = key_def.filter_on
|
|
301
|
-
with
|
|
301
|
+
with create_ertserver_client(self.ens_path) as client:
|
|
302
302
|
response = client.get(
|
|
303
303
|
f"/ensembles/{ensemble.id}/responses/{PlotApi.escape(actual_response_key)}/observations",
|
|
304
304
|
timeout=self._timeout,
|
|
@@ -386,7 +386,7 @@ class PlotApi:
|
|
|
386
386
|
if not ensemble:
|
|
387
387
|
return np.array([])
|
|
388
388
|
|
|
389
|
-
with
|
|
389
|
+
with create_ertserver_client(self.ens_path) as client:
|
|
390
390
|
response = client.get(
|
|
391
391
|
f"/ensembles/{ensemble.id}/parameters/{PlotApi.escape(key)}/std_dev",
|
|
392
392
|
params={"z": z},
|
|
@@ -153,6 +153,7 @@ class PlotWidget(QWidget):
|
|
|
153
153
|
# only for histogram plot see _sync_log_checkbox
|
|
154
154
|
self._log_checkbox.setVisible(False)
|
|
155
155
|
self._log_checkbox.setToolTip("Toggle data domain to log scale and back")
|
|
156
|
+
self._log_checkbox.clicked.connect(self.logLogScaleButtonUsage)
|
|
156
157
|
|
|
157
158
|
log_checkbox_row = QHBoxLayout()
|
|
158
159
|
log_checkbox_row.addWidget(self._log_checkbox)
|
|
@@ -163,7 +164,6 @@ class PlotWidget(QWidget):
|
|
|
163
164
|
vbox.addSpacing(8)
|
|
164
165
|
self.setLayout(vbox)
|
|
165
166
|
|
|
166
|
-
self._negative_values_in_data = False
|
|
167
167
|
self._dirty = True
|
|
168
168
|
self._active = False
|
|
169
169
|
self.resetPlot()
|
|
@@ -176,15 +176,11 @@ class PlotWidget(QWidget):
|
|
|
176
176
|
self._figure.clear()
|
|
177
177
|
|
|
178
178
|
def _sync_log_checkbox(self) -> None:
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
"GaussianKDEPlot",
|
|
185
|
-
}
|
|
186
|
-
and self._negative_values_in_data is False
|
|
187
|
-
):
|
|
179
|
+
if type(self._plotter).__name__ in {
|
|
180
|
+
"HistogramPlot",
|
|
181
|
+
"DistributionPlot",
|
|
182
|
+
"GaussianKDEPlot",
|
|
183
|
+
}:
|
|
188
184
|
self._log_checkbox.setVisible(True)
|
|
189
185
|
else:
|
|
190
186
|
self._log_checkbox.setVisible(False)
|
|
@@ -193,6 +189,10 @@ class PlotWidget(QWidget):
|
|
|
193
189
|
def name(self) -> str:
|
|
194
190
|
return self._name
|
|
195
191
|
|
|
192
|
+
def logLogScaleButtonUsage(self) -> None:
|
|
193
|
+
logger.info(f"Plotwidget utility used: 'Log scale button' in tab '{self.name}'")
|
|
194
|
+
self._log_checkbox.clicked.disconnect() # Log only once
|
|
195
|
+
|
|
196
196
|
def updatePlot(
|
|
197
197
|
self,
|
|
198
198
|
plot_context: "PlotContext",
|
|
@@ -203,11 +203,8 @@ class PlotWidget(QWidget):
|
|
|
203
203
|
) -> None:
|
|
204
204
|
self.resetPlot()
|
|
205
205
|
try:
|
|
206
|
-
self._sync_log_checkbox()
|
|
207
206
|
plot_context.log_scale = (
|
|
208
|
-
self._log_checkbox.isVisible()
|
|
209
|
-
and self._log_checkbox.isChecked()
|
|
210
|
-
and self._negative_values_in_data is False
|
|
207
|
+
self._log_checkbox.isVisible() and self._log_checkbox.isChecked()
|
|
211
208
|
)
|
|
212
209
|
self._plotter.plot(
|
|
213
210
|
self._figure,
|
|
@@ -218,6 +215,7 @@ class PlotWidget(QWidget):
|
|
|
218
215
|
key_def,
|
|
219
216
|
)
|
|
220
217
|
self._canvas.draw()
|
|
218
|
+
self._sync_log_checkbox()
|
|
221
219
|
except Exception as e:
|
|
222
220
|
exc_type, _, exc_tb = sys.exc_info()
|
|
223
221
|
sys.stderr.write("-" * 80 + "\n")
|
|
@@ -25,7 +25,7 @@ from PyQt6.QtWidgets import (
|
|
|
25
25
|
from ert.config.field import Field
|
|
26
26
|
from ert.dark_storage.common import get_storage_api_version
|
|
27
27
|
from ert.gui.ertwidgets import CopyButton, showWaitCursorWhileWaiting
|
|
28
|
-
from ert.services
|
|
28
|
+
from ert.services import ServerBootFail
|
|
29
29
|
from ert.utils import log_duration
|
|
30
30
|
|
|
31
31
|
from .customize import PlotCustomizer
|
|
@@ -284,15 +284,6 @@ class PlotWindow(QMainWindow):
|
|
|
284
284
|
except BaseException as e:
|
|
285
285
|
handle_exception(e)
|
|
286
286
|
|
|
287
|
-
negative_values_in_data = False
|
|
288
|
-
if key_def.parameter is not None and key_def.parameter.type == "gen_kw":
|
|
289
|
-
for data in ensemble_to_data_map.values():
|
|
290
|
-
data = data.T
|
|
291
|
-
if data.le(0).any().any():
|
|
292
|
-
negative_values_in_data = True
|
|
293
|
-
break
|
|
294
|
-
|
|
295
|
-
plot_widget._negative_values_in_data = negative_values_in_data
|
|
296
287
|
observations = None
|
|
297
288
|
if key_def.observations and selected_ensembles:
|
|
298
289
|
try:
|
ert/services/__init__.py
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
from .ert_server import
|
|
2
|
-
|
|
1
|
+
from .ert_server import (
|
|
2
|
+
ErtServer,
|
|
3
|
+
ErtServerExit,
|
|
4
|
+
ServerBootFail,
|
|
5
|
+
create_ertserver_client,
|
|
6
|
+
)
|
|
3
7
|
|
|
4
|
-
__all__ = ["ErtServer", "
|
|
8
|
+
__all__ = ["ErtServer", "ErtServerExit", "ServerBootFail", "create_ertserver_client"]
|
ert/services/_storage_main.py
CHANGED
|
@@ -13,6 +13,7 @@ import sys
|
|
|
13
13
|
import threading
|
|
14
14
|
import time
|
|
15
15
|
import warnings
|
|
16
|
+
from argparse import ArgumentParser
|
|
16
17
|
from base64 import b64encode
|
|
17
18
|
from pathlib import Path
|
|
18
19
|
from typing import Any
|
|
@@ -29,12 +30,11 @@ from uvicorn.supervisors import ChangeReload
|
|
|
29
30
|
|
|
30
31
|
from ert.logging import STORAGE_LOG_CONFIG
|
|
31
32
|
from ert.plugins import setup_site_logging
|
|
32
|
-
from ert.services
|
|
33
|
+
from ert.services import ErtServerExit
|
|
33
34
|
from ert.shared import __file__ as ert_shared_path
|
|
34
35
|
from ert.shared import find_available_socket, get_machine_name
|
|
35
|
-
from ert.shared.storage.command import add_parser_options
|
|
36
36
|
from ert.trace import tracer
|
|
37
|
-
from
|
|
37
|
+
from ert.utils import makedirs_if_needed
|
|
38
38
|
|
|
39
39
|
DARK_STORAGE_APP = "ert.dark_storage.app:app"
|
|
40
40
|
|
|
@@ -82,7 +82,7 @@ def _get_host_list() -> list[str]:
|
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
def _create_connection_info(
|
|
85
|
-
sock: socket.socket, authtoken: str, cert: str | os.PathLike[str]
|
|
85
|
+
sock: socket.socket, authtoken: str, cert: str | os.PathLike[str] | Path
|
|
86
86
|
) -> dict[str, Any]:
|
|
87
87
|
connection_info = {
|
|
88
88
|
"urls": [
|
|
@@ -91,7 +91,7 @@ def _create_connection_info(
|
|
|
91
91
|
"authtoken": authtoken,
|
|
92
92
|
"host": get_machine_name(),
|
|
93
93
|
"port": sock.getsockname()[1],
|
|
94
|
-
"cert": cert,
|
|
94
|
+
"cert": str(cert),
|
|
95
95
|
"auth": authtoken,
|
|
96
96
|
}
|
|
97
97
|
|
|
@@ -102,14 +102,17 @@ def _create_connection_info(
|
|
|
102
102
|
return connection_info
|
|
103
103
|
|
|
104
104
|
|
|
105
|
-
def _generate_certificate(cert_folder:
|
|
105
|
+
def _generate_certificate(cert_folder: Path) -> tuple[Path, Path, bytes]:
|
|
106
106
|
"""Generate a private key and a certificate signed with it
|
|
107
107
|
|
|
108
108
|
Both the certificate and the key are written to files in the folder given
|
|
109
109
|
by `get_certificate_dir(config)`. The key is encrypted before being
|
|
110
110
|
stored.
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
|
|
112
|
+
Returns a 3-tuple with
|
|
113
|
+
* Certificate file path
|
|
114
|
+
* Key file path
|
|
115
|
+
* Password used for encrypting the key
|
|
113
116
|
"""
|
|
114
117
|
# Generate private key
|
|
115
118
|
key = rsa.generate_private_key(
|
|
@@ -150,11 +153,11 @@ def _generate_certificate(cert_folder: str) -> tuple[str, str, bytes]:
|
|
|
150
153
|
|
|
151
154
|
# Write certificate and key to disk
|
|
152
155
|
makedirs_if_needed(cert_folder)
|
|
153
|
-
cert_path =
|
|
154
|
-
|
|
155
|
-
key_path =
|
|
156
|
+
cert_path = cert_folder / f"{dns_name}.crt"
|
|
157
|
+
cert_path.write_bytes(cert.public_bytes(serialization.Encoding.PEM))
|
|
158
|
+
key_path = cert_folder / f"{dns_name}.key"
|
|
156
159
|
pw = bytes(os.urandom(28))
|
|
157
|
-
|
|
160
|
+
key_path.write_bytes(
|
|
158
161
|
key.private_bytes(
|
|
159
162
|
encoding=serialization.Encoding.PEM,
|
|
160
163
|
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
|
@@ -184,16 +187,16 @@ def run_server(
|
|
|
184
187
|
|
|
185
188
|
config_args: dict[str, Any] = {}
|
|
186
189
|
if args.debug or debug:
|
|
187
|
-
config_args.update(reload=True, reload_dirs=[
|
|
190
|
+
config_args.update(reload=True, reload_dirs=[Path(ert_shared_path).parent])
|
|
188
191
|
os.environ["ERT_STORAGE_DEBUG"] = "1"
|
|
189
192
|
|
|
190
|
-
sock = find_available_socket(
|
|
193
|
+
sock: socket.socket = find_available_socket(
|
|
191
194
|
host=get_machine_name(), port_range=range(51850, 51870 + 1)
|
|
192
195
|
)
|
|
193
196
|
|
|
194
197
|
# Appropriated from uvicorn.main:run
|
|
195
198
|
os.environ["ERT_STORAGE_NO_TOKEN"] = "1"
|
|
196
|
-
os.environ["ERT_STORAGE_ENS_PATH"] =
|
|
199
|
+
os.environ["ERT_STORAGE_ENS_PATH"] = str(args.project.absolute())
|
|
197
200
|
config = (
|
|
198
201
|
# uvicorn.Config() resets the logging config (overriding additional
|
|
199
202
|
# handlers added to loggers like e.g. the ert_azurelogger handler
|
|
@@ -253,11 +256,11 @@ def _join_terminate_thread(terminate_on_parent_death_thread: threading.Thread) -
|
|
|
253
256
|
"""Join the terminate thread, handling BaseServiceExit (which is used by Everest)"""
|
|
254
257
|
try:
|
|
255
258
|
terminate_on_parent_death_thread.join()
|
|
256
|
-
except
|
|
259
|
+
except ErtServerExit:
|
|
257
260
|
logger = logging.getLogger("ert.shared.storage.info")
|
|
258
261
|
logger.info(
|
|
259
262
|
"Got BaseServiceExit while joining terminate thread, "
|
|
260
|
-
"as expected from
|
|
263
|
+
"as expected from ert_server.py"
|
|
261
264
|
)
|
|
262
265
|
|
|
263
266
|
|
|
@@ -265,9 +268,7 @@ def main() -> None:
|
|
|
265
268
|
args = parse_args()
|
|
266
269
|
authentication = _generate_authentication()
|
|
267
270
|
os.environ["ERT_STORAGE_TOKEN"] = authentication
|
|
268
|
-
cert_path, key_path, key_pw = _generate_certificate(
|
|
269
|
-
os.path.join(args.project, "cert")
|
|
270
|
-
)
|
|
271
|
+
cert_path, key_path, key_pw = _generate_certificate(args.project / "cert")
|
|
271
272
|
config_args: dict[str, Any] = {
|
|
272
273
|
"ssl_keyfile": key_path,
|
|
273
274
|
"ssl_certfile": cert_path,
|
|
@@ -283,7 +284,7 @@ def main() -> None:
|
|
|
283
284
|
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
|
284
285
|
|
|
285
286
|
if args.debug:
|
|
286
|
-
config_args.update(reload=True, reload_dirs=[
|
|
287
|
+
config_args.update(reload=True, reload_dirs=[Path(ert_shared_path).parent])
|
|
287
288
|
|
|
288
289
|
# Need to run uvicorn.Config before entering the ErtPluginContext because
|
|
289
290
|
# uvicorn.Config overrides the configuration of existing loggers, thus removing
|
|
@@ -310,12 +311,48 @@ def main() -> None:
|
|
|
310
311
|
logger.info("Starting dark storage")
|
|
311
312
|
logger.info(f"Started dark storage with parent {args.parent_pid}")
|
|
312
313
|
run_server(args, debug=False, uvicorn_config=uvicorn_config)
|
|
313
|
-
except (SystemExit,
|
|
314
|
+
except (SystemExit, ErtServerExit):
|
|
314
315
|
logger.info("Stopping dark storage")
|
|
315
316
|
finally:
|
|
316
317
|
stopped.set()
|
|
317
318
|
_join_terminate_thread(terminate_on_parent_death_thread)
|
|
318
319
|
|
|
319
320
|
|
|
321
|
+
def add_parser_options(ap: ArgumentParser) -> None:
|
|
322
|
+
ap.add_argument(
|
|
323
|
+
"config",
|
|
324
|
+
type=str,
|
|
325
|
+
help=("ERT config file to start the server from "),
|
|
326
|
+
nargs="?", # optional
|
|
327
|
+
)
|
|
328
|
+
ap.add_argument(
|
|
329
|
+
"--project",
|
|
330
|
+
"-p",
|
|
331
|
+
type=Path,
|
|
332
|
+
help="Path to directory in which to create storage_server.json",
|
|
333
|
+
default=Path.cwd(),
|
|
334
|
+
)
|
|
335
|
+
ap.add_argument(
|
|
336
|
+
"--traceparent",
|
|
337
|
+
type=str,
|
|
338
|
+
help="Trace parent id to be used by the storage root span",
|
|
339
|
+
default=None,
|
|
340
|
+
)
|
|
341
|
+
ap.add_argument(
|
|
342
|
+
"--parent_pid",
|
|
343
|
+
type=int,
|
|
344
|
+
help="The parent process id",
|
|
345
|
+
default=os.getppid(),
|
|
346
|
+
)
|
|
347
|
+
ap.add_argument(
|
|
348
|
+
"--host", type=str, default=os.environ.get("ERT_STORAGE_HOST", "127.0.0.1")
|
|
349
|
+
)
|
|
350
|
+
ap.add_argument("--logging-config", type=str, default=None)
|
|
351
|
+
ap.add_argument(
|
|
352
|
+
"--verbose", action="store_true", help="Show verbose output.", default=False
|
|
353
|
+
)
|
|
354
|
+
ap.add_argument("--debug", action="store_true", default=False)
|
|
355
|
+
|
|
356
|
+
|
|
320
357
|
if __name__ == "__main__":
|
|
321
358
|
main()
|