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.
Files changed (82) hide show
  1. ert/__main__.py +94 -63
  2. ert/analysis/_es_update.py +11 -14
  3. ert/config/__init__.py +3 -2
  4. ert/config/_create_observation_dataframes.py +51 -375
  5. ert/config/_observations.py +483 -200
  6. ert/config/_read_summary.py +4 -5
  7. ert/config/ert_config.py +53 -80
  8. ert/config/everest_control.py +40 -39
  9. ert/config/everest_response.py +1 -13
  10. ert/config/field.py +0 -72
  11. ert/config/forward_model_step.py +17 -1
  12. ert/config/gen_data_config.py +14 -17
  13. ert/config/observation_config_migrations.py +821 -0
  14. ert/config/parameter_config.py +18 -28
  15. ert/config/parsing/__init__.py +0 -1
  16. ert/config/parsing/_parse_zonemap.py +45 -0
  17. ert/config/parsing/config_keywords.py +1 -1
  18. ert/config/parsing/config_schema.py +2 -8
  19. ert/config/parsing/observations_parser.py +2 -0
  20. ert/config/response_config.py +5 -23
  21. ert/config/rft_config.py +44 -19
  22. ert/config/summary_config.py +1 -13
  23. ert/config/surface_config.py +0 -57
  24. ert/dark_storage/compute/misfits.py +0 -42
  25. ert/dark_storage/endpoints/__init__.py +0 -2
  26. ert/dark_storage/endpoints/experiments.py +2 -5
  27. ert/dark_storage/json_schema/experiment.py +1 -2
  28. ert/field_utils/__init__.py +0 -2
  29. ert/field_utils/field_utils.py +1 -117
  30. ert/gui/ertwidgets/listeditbox.py +9 -1
  31. ert/gui/ertwidgets/models/ertsummary.py +20 -6
  32. ert/gui/ertwidgets/pathchooser.py +9 -1
  33. ert/gui/ertwidgets/stringbox.py +11 -3
  34. ert/gui/ertwidgets/textbox.py +10 -3
  35. ert/gui/ertwidgets/validationsupport.py +19 -1
  36. ert/gui/main_window.py +11 -6
  37. ert/gui/simulation/experiment_panel.py +1 -1
  38. ert/gui/simulation/run_dialog.py +11 -1
  39. ert/gui/tools/manage_experiments/export_dialog.py +4 -0
  40. ert/gui/tools/manage_experiments/manage_experiments_panel.py +1 -0
  41. ert/gui/tools/manage_experiments/storage_info_widget.py +5 -2
  42. ert/gui/tools/manage_experiments/storage_widget.py +18 -3
  43. ert/gui/tools/plot/data_type_proxy_model.py +1 -1
  44. ert/gui/tools/plot/plot_api.py +35 -27
  45. ert/gui/tools/plot/plot_widget.py +5 -0
  46. ert/gui/tools/plot/plot_window.py +4 -7
  47. ert/run_models/ensemble_experiment.py +1 -3
  48. ert/run_models/ensemble_smoother.py +1 -3
  49. ert/run_models/everest_run_model.py +12 -13
  50. ert/run_models/initial_ensemble_run_model.py +19 -22
  51. ert/run_models/model_factory.py +7 -7
  52. ert/run_models/multiple_data_assimilation.py +1 -3
  53. ert/sample_prior.py +12 -14
  54. ert/services/__init__.py +7 -3
  55. ert/services/_storage_main.py +59 -22
  56. ert/services/ert_server.py +186 -24
  57. ert/shared/version.py +3 -3
  58. ert/storage/local_ensemble.py +46 -115
  59. ert/storage/local_experiment.py +0 -16
  60. ert/utils/__init__.py +20 -0
  61. ert/warnings/specific_warning_handler.py +3 -2
  62. {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/METADATA +4 -51
  63. {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/RECORD +75 -80
  64. everest/bin/everest_script.py +5 -5
  65. everest/bin/kill_script.py +2 -2
  66. everest/bin/monitor_script.py +2 -2
  67. everest/bin/utils.py +4 -4
  68. everest/detached/everserver.py +6 -6
  69. everest/gui/everest_client.py +0 -6
  70. everest/gui/main_window.py +2 -2
  71. everest/util/__init__.py +1 -19
  72. ert/dark_storage/compute/__init__.py +0 -0
  73. ert/dark_storage/endpoints/compute/__init__.py +0 -0
  74. ert/dark_storage/endpoints/compute/misfits.py +0 -95
  75. ert/services/_base_service.py +0 -387
  76. ert/services/webviz_ert_service.py +0 -20
  77. ert/shared/storage/command.py +0 -38
  78. ert/shared/storage/extraction.py +0 -42
  79. {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/WHEEL +0 -0
  80. {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/entry_points.txt +0 -0
  81. {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/licenses/COPYING +0 -0
  82. {ert-19.0.0rc4.dist-info → ert-20.0.0b0.dist-info}/top_level.txt +0 -0
@@ -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
- str(Path(__file__).parent / "_storage_main.py"),
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=120,
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 / "storage_server.json"
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 / "storage_server.json").open() as f:
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}/storage_server.json"
429
+ path = f"{self._storage_path}/{_ERT_SERVER_CONNECTION_INFO_FILE}"
281
430
  else:
282
- path = "storage_server.json"
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 = '19.0.0rc4'
32
- __version_tuple__ = version_tuple = (19, 0, 0, 'rc4')
31
+ __version__ = version = '20.0.0b0'
32
+ __version_tuple__ = version_tuple = (20, 0, 0, 'b0')
33
33
 
34
- __commit_id__ = commit_id = 'g2518c4485'
34
+ __commit_id__ = commit_id = 'g23469e640'
@@ -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 ParameterCardinality, ParameterConfig, SummaryConfig
26
- from ert.config.response_config import InvalidResponseFile
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
- df_lazy = df_lazy.select(["realization", *keys])
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
- df_lazy = df_lazy.filter(pl.col("realization").is_in(realizations))
583
- df = df_lazy.collect(engine="streaming")
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
- self.experiment.parameter_configuration[col].transform_data(),
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
- real_nr: int,
766
+ active_realizations: list[int],
745
767
  random_seed: int,
768
+ num_realizations: int,
746
769
  ) -> pl.DataFrame:
747
- parameter_value = parameter.sample_value(
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
- parameter_dict = {parameter.name: parameter_value[0]}
753
- parameter_dict["realization"] = real_nr
754
- return pl.DataFrame(
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( # noqa: PD010
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,
@@ -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(message)
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: 19.0.0rc4
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<1.35,>=1.32.3
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-extras
140
+ uv sync --all-groups
188
141
  ```
189
142
 
190
143
  ### Test setup