ert 18.0.8__py3-none-any.whl → 19.0.0__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 (117) hide show
  1. _ert/forward_model_runner/client.py +6 -2
  2. ert/__main__.py +20 -6
  3. ert/cli/main.py +7 -3
  4. ert/config/__init__.py +3 -4
  5. ert/config/_create_observation_dataframes.py +85 -59
  6. ert/config/_get_num_cpu.py +1 -1
  7. ert/config/_observations.py +106 -31
  8. ert/config/distribution.py +1 -1
  9. ert/config/ensemble_config.py +3 -3
  10. ert/config/ert_config.py +50 -0
  11. ert/config/{ext_param_config.py → everest_control.py} +8 -12
  12. ert/config/everest_response.py +3 -5
  13. ert/config/field.py +76 -14
  14. ert/config/forward_model_step.py +12 -9
  15. ert/config/gen_data_config.py +3 -4
  16. ert/config/gen_kw_config.py +2 -12
  17. ert/config/parameter_config.py +1 -16
  18. ert/config/parsing/_option_dict.py +10 -2
  19. ert/config/parsing/config_keywords.py +1 -0
  20. ert/config/parsing/config_schema.py +8 -0
  21. ert/config/parsing/config_schema_deprecations.py +3 -3
  22. ert/config/parsing/config_schema_item.py +12 -3
  23. ert/config/parsing/context_values.py +3 -3
  24. ert/config/parsing/file_context_token.py +1 -1
  25. ert/config/parsing/observations_parser.py +12 -2
  26. ert/config/parsing/queue_system.py +9 -0
  27. ert/config/queue_config.py +0 -1
  28. ert/config/response_config.py +0 -1
  29. ert/config/rft_config.py +78 -33
  30. ert/config/summary_config.py +1 -2
  31. ert/config/surface_config.py +59 -16
  32. ert/dark_storage/common.py +1 -1
  33. ert/dark_storage/compute/misfits.py +4 -1
  34. ert/dark_storage/endpoints/compute/misfits.py +4 -2
  35. ert/dark_storage/endpoints/experiment_server.py +12 -9
  36. ert/dark_storage/endpoints/experiments.py +2 -2
  37. ert/dark_storage/endpoints/observations.py +14 -4
  38. ert/dark_storage/endpoints/parameters.py +2 -18
  39. ert/dark_storage/endpoints/responses.py +10 -5
  40. ert/dark_storage/json_schema/experiment.py +1 -1
  41. ert/data/_measured_data.py +6 -5
  42. ert/ensemble_evaluator/config.py +2 -1
  43. ert/field_utils/field_utils.py +1 -1
  44. ert/field_utils/grdecl_io.py +26 -9
  45. ert/field_utils/roff_io.py +1 -1
  46. ert/gui/__init__.py +5 -2
  47. ert/gui/ertnotifier.py +1 -1
  48. ert/gui/ertwidgets/pathchooser.py +0 -3
  49. ert/gui/ertwidgets/suggestor/suggestor.py +63 -30
  50. ert/gui/main.py +27 -5
  51. ert/gui/main_window.py +0 -5
  52. ert/gui/simulation/experiment_panel.py +12 -3
  53. ert/gui/simulation/run_dialog.py +2 -16
  54. ert/gui/tools/manage_experiments/export_dialog.py +136 -0
  55. ert/gui/tools/manage_experiments/storage_info_widget.py +133 -28
  56. ert/gui/tools/plot/plot_api.py +24 -15
  57. ert/gui/tools/plot/plot_widget.py +19 -4
  58. ert/gui/tools/plot/plot_window.py +35 -18
  59. ert/gui/tools/plot/plottery/plots/__init__.py +2 -0
  60. ert/gui/tools/plot/plottery/plots/cesp.py +3 -1
  61. ert/gui/tools/plot/plottery/plots/distribution.py +6 -1
  62. ert/gui/tools/plot/plottery/plots/ensemble.py +3 -1
  63. ert/gui/tools/plot/plottery/plots/gaussian_kde.py +12 -2
  64. ert/gui/tools/plot/plottery/plots/histogram.py +3 -1
  65. ert/gui/tools/plot/plottery/plots/misfits.py +436 -0
  66. ert/gui/tools/plot/plottery/plots/observations.py +18 -4
  67. ert/gui/tools/plot/plottery/plots/statistics.py +3 -1
  68. ert/gui/tools/plot/plottery/plots/std_dev.py +3 -1
  69. ert/plugins/hook_implementations/workflows/csv_export.py +2 -3
  70. ert/plugins/plugin_manager.py +4 -0
  71. ert/resources/forward_models/run_reservoirsimulator.py +8 -3
  72. ert/run_models/_create_run_path.py +3 -3
  73. ert/run_models/everest_run_model.py +13 -11
  74. ert/run_models/initial_ensemble_run_model.py +2 -2
  75. ert/run_models/run_model.py +9 -0
  76. ert/services/_base_service.py +6 -5
  77. ert/services/ert_server.py +4 -4
  78. ert/shared/_doc_utils/__init__.py +4 -2
  79. ert/shared/net_utils.py +43 -18
  80. ert/shared/version.py +3 -3
  81. ert/storage/__init__.py +2 -0
  82. ert/storage/local_ensemble.py +25 -8
  83. ert/storage/local_experiment.py +2 -2
  84. ert/storage/local_storage.py +45 -25
  85. ert/storage/migration/to11.py +1 -1
  86. ert/storage/migration/to18.py +0 -1
  87. ert/storage/migration/to19.py +34 -0
  88. ert/storage/migration/to20.py +23 -0
  89. ert/storage/migration/to21.py +25 -0
  90. ert/storage/migration/to22.py +18 -0
  91. ert/storage/migration/to23.py +49 -0
  92. ert/workflow_runner.py +2 -1
  93. {ert-18.0.8.dist-info → ert-19.0.0.dist-info}/METADATA +1 -1
  94. {ert-18.0.8.dist-info → ert-19.0.0.dist-info}/RECORD +112 -110
  95. {ert-18.0.8.dist-info → ert-19.0.0.dist-info}/WHEEL +1 -1
  96. everest/bin/everlint_script.py +0 -2
  97. everest/bin/utils.py +2 -1
  98. everest/bin/visualization_script.py +4 -11
  99. everest/config/control_config.py +4 -4
  100. everest/config/control_variable_config.py +2 -2
  101. everest/config/everest_config.py +9 -0
  102. everest/config/utils.py +2 -2
  103. everest/config/validation_utils.py +7 -1
  104. everest/config_file_loader.py +0 -2
  105. everest/detached/client.py +3 -3
  106. everest/everest_storage.py +0 -2
  107. everest/gui/everest_client.py +2 -2
  108. everest/optimizer/everest2ropt.py +4 -4
  109. everest/optimizer/opt_model_transforms.py +2 -2
  110. ert/config/violations.py +0 -0
  111. ert/gui/tools/export/__init__.py +0 -3
  112. ert/gui/tools/export/export_panel.py +0 -83
  113. ert/gui/tools/export/export_tool.py +0 -69
  114. ert/gui/tools/export/exporter.py +0 -36
  115. {ert-18.0.8.dist-info → ert-19.0.0.dist-info}/entry_points.txt +0 -0
  116. {ert-18.0.8.dist-info → ert-19.0.0.dist-info}/licenses/COPYING +0 -0
  117. {ert-18.0.8.dist-info → ert-19.0.0.dist-info}/top_level.txt +0 -0
@@ -2,8 +2,9 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import logging
5
+ import types
5
6
  import uuid
6
- from typing import Any, Self
7
+ from typing import Self
7
8
 
8
9
  import zmq
9
10
  import zmq.asyncio
@@ -67,7 +68,10 @@ class Client:
67
68
  return self
68
69
 
69
70
  async def __aexit__(
70
- self, exc_type: Any, exc_value: Any, exc_traceback: Any
71
+ self,
72
+ exc_type: type[BaseException] | None,
73
+ exc_value: BaseException | None,
74
+ exc_traceback: types.TracebackType | None,
71
75
  ) -> None:
72
76
  try:
73
77
  await self.send(DISCONNECT_MSG)
ert/__main__.py CHANGED
@@ -80,18 +80,32 @@ def run_webviz_ert(args: Namespace, _: ErtRuntimePlugins | None = None) -> None:
80
80
  # only use the base name of the config file path
81
81
  kwargs["ert_config"] = os.path.basename(args.config)
82
82
  kwargs["project"] = os.path.abspath(ens_path)
83
+
84
+ yellow = "\x1b[33m"
85
+ green = "\x1b[32m"
86
+ bold = "\x1b[1m"
87
+ reset = "\x1b[0m"
88
+
83
89
  try:
84
90
  with ErtServer.init_service(project=Path(ens_path).absolute()) as storage:
85
91
  storage.wait_until_ready()
86
92
  print(
87
- """
88
- -----------------------------------------------------------
93
+ f"""
94
+ ---------------------------------------------------------------
95
+
96
+ {yellow}{bold}Webviz-ERT is deprecated and will be removed in the near future{reset}
97
+
98
+ {green}{bold}Plotting capabilities provided by Webviz-ERT are now available
99
+ using the ERT plotter{reset}
100
+
101
+ ---------------------------------------------------------------
89
102
 
90
103
  Starting up Webviz-ERT. This might take more than a minute.
91
104
 
92
- -----------------------------------------------------------
105
+ ---------------------------------------------------------------
93
106
  """
94
107
  )
108
+ logger.info("Show Webviz-ert deprecation warning")
95
109
  webviz_kwargs = {
96
110
  "experimental_mode": args.experimental_mode,
97
111
  "verbose": args.verbose,
@@ -567,15 +581,15 @@ def get_ert_parser(parser: ArgumentParser | None = None) -> ArgumentParser:
567
581
  "--color-always",
568
582
  action="store_true",
569
583
  help="Force coloring of monitor output, which is automatically"
570
- + " disabled if the output stream is not a terminal.",
584
+ " disabled if the output stream is not a terminal.",
571
585
  default=False,
572
586
  )
573
587
  cli_parser.add_argument(
574
588
  "--disable-monitoring",
575
589
  action="store_true",
576
590
  help="Monitoring will continuously print the status of the realisations"
577
- + " classified into Waiting, Pending, Running, Failed, Finished"
578
- + " and Unknown.",
591
+ " classified into Waiting, Pending, Running, Failed, Finished"
592
+ " and Unknown.",
579
593
  default=False,
580
594
  )
581
595
  cli_parser.add_argument(
ert/cli/main.py CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env python
2
1
  from __future__ import annotations
3
2
 
4
3
  import contextlib
@@ -7,6 +6,7 @@ import os
7
6
  import queue
8
7
  import sys
9
8
  from collections import Counter
9
+ from pathlib import Path
10
10
  from typing import TextIO
11
11
 
12
12
  from _ert.threading import ErtThread
@@ -27,7 +27,7 @@ from ert.namespace import Namespace
27
27
  from ert.plugins import ErtRuntimePlugins, get_site_plugins
28
28
  from ert.run_models.event import StatusEvents
29
29
  from ert.run_models.model_factory import create_model
30
- from ert.storage import open_storage
30
+ from ert.storage import LocalStorage, open_storage
31
31
  from ert.storage.local_storage import local_storage_set_ert_config
32
32
 
33
33
 
@@ -88,7 +88,10 @@ def run_cli(args: Namespace, runtime_plugins: ErtRuntimePlugins | None = None) -
88
88
  )
89
89
 
90
90
  if args.mode == WORKFLOW_MODE:
91
- with open_storage(ert_config.ens_path, "w") as storage:
91
+ path = Path(ert_config.ens_path)
92
+ if LocalStorage.check_migration_needed(path):
93
+ LocalStorage.perform_migration(path)
94
+ with open_storage(path, "w") as storage:
92
95
  execute_workflow(ert_config, storage, args.name)
93
96
  return
94
97
 
@@ -130,6 +133,7 @@ def run_cli(args: Namespace, runtime_plugins: ErtRuntimePlugins | None = None) -
130
133
  if args.port_range is None
131
134
  else (min(args.port_range), max(args.port_range) + 1),
132
135
  use_ipc_protocol=using_local_queuesystem,
136
+ prioritize_private_ip_address=ert_config.prioritize_private_ip_address,
133
137
  )
134
138
 
135
139
  if model.check_if_runpath_exists():
ert/config/__init__.py CHANGED
@@ -11,8 +11,8 @@ from .ensemble_config import EnsembleConfig
11
11
  from .ert_config import ErtConfig, forward_model_step_from_config_contents
12
12
  from .ert_plugin import ErtPlugin
13
13
  from .ert_script import ErtScript
14
+ from .everest_control import EverestControl, SamplerConfig
14
15
  from .everest_response import EverestConstraintsConfig, EverestObjectivesConfig
15
- from .ext_param_config import ExtParamConfig, SamplerConfig
16
16
  from .external_ert_script import ExternalErtScript
17
17
  from .field import Field, field_transform
18
18
  from .forward_model_step import (
@@ -31,7 +31,7 @@ from .gen_kw_config import DataSource, GenKwConfig, PriorDict
31
31
  from .known_response_types import KnownResponseTypes
32
32
  from .lint_file import lint_file
33
33
  from .model_config import ModelConfig
34
- from .parameter_config import ParameterCardinality, ParameterConfig, ParameterMetadata
34
+ from .parameter_config import ParameterCardinality, ParameterConfig
35
35
  from .parsing import (
36
36
  ConfigValidationError,
37
37
  ConfigWarning,
@@ -90,9 +90,9 @@ __all__ = [
90
90
  "ErtScript",
91
91
  "ErtScriptWorkflow",
92
92
  "EverestConstraintsConfig",
93
+ "EverestControl",
93
94
  "EverestObjectivesConfig",
94
95
  "ExecutableWorkflow",
95
- "ExtParamConfig",
96
96
  "ExternalErtScript",
97
97
  "Field",
98
98
  "ForwardModelStep",
@@ -119,7 +119,6 @@ __all__ = [
119
119
  "OutlierSettings",
120
120
  "ParameterCardinality",
121
121
  "ParameterConfig",
122
- "ParameterMetadata",
123
122
  "PostExperimentFixtures",
124
123
  "PostSimulationFixtures",
125
124
  "PostUpdateFixtures",
@@ -18,6 +18,7 @@ from ._observations import (
18
18
  Observation,
19
19
  ObservationDate,
20
20
  ObservationError,
21
+ RFTObservation,
21
22
  SummaryObservation,
22
23
  )
23
24
  from .gen_data_config import GenDataConfig
@@ -28,19 +29,21 @@ from .parsing import (
28
29
  ObservationConfigError,
29
30
  )
30
31
  from .refcase import Refcase
32
+ from .rft_config import RFTConfig
31
33
 
32
34
  if TYPE_CHECKING:
33
35
  import numpy.typing as npt
34
36
 
35
37
 
36
38
  DEFAULT_TIME_DELTA = timedelta(seconds=30)
37
- DEFAULT_LOCATION_RANGE_M = 3000
39
+ DEFAULT_LOCALIZATION_RADIUS = 3000
38
40
 
39
41
 
40
42
  def create_observation_dataframes(
41
43
  observations: Sequence[Observation],
42
44
  refcase: Refcase | None,
43
45
  gen_data_config: GenDataConfig | None,
46
+ rft_config: RFTConfig | None,
44
47
  time_map: list[datetime] | None,
45
48
  history: HistorySource,
46
49
  ) -> dict[str, pl.DataFrame]:
@@ -56,7 +59,6 @@ def create_observation_dataframes(
56
59
  config_errors: list[ErrorInfo] = []
57
60
  grouped: dict[str, list[pl.DataFrame]] = defaultdict(list)
58
61
  for obs in observations:
59
- obs_name = obs.name
60
62
  try:
61
63
  match obs:
62
64
  case HistoryObservation():
@@ -64,7 +66,7 @@ def create_observation_dataframes(
64
66
  _handle_history_observation(
65
67
  refcase,
66
68
  obs,
67
- obs_name,
69
+ obs.name,
68
70
  history,
69
71
  time_len,
70
72
  )
@@ -73,7 +75,7 @@ def create_observation_dataframes(
73
75
  grouped["summary"].append(
74
76
  _handle_summary_observation(
75
77
  obs,
76
- obs_name,
78
+ obs.name,
77
79
  obs_time_list,
78
80
  bool(refcase),
79
81
  )
@@ -83,11 +85,18 @@ def create_observation_dataframes(
83
85
  _handle_general_observation(
84
86
  gen_data_config,
85
87
  obs,
86
- obs_name,
88
+ obs.name,
87
89
  obs_time_list,
88
90
  bool(refcase),
89
91
  )
90
92
  )
93
+ case RFTObservation():
94
+ if rft_config is None:
95
+ raise TypeError(
96
+ "create_observation_dataframes requires "
97
+ "rft_config is not None when using RFTObservation"
98
+ )
99
+ grouped["rft"].append(_handle_rft_observation(rft_config, obs))
91
100
  case default:
92
101
  assert_never(default)
93
102
  except ObservationConfigError as err:
@@ -196,6 +205,9 @@ def _handle_history_observation(
196
205
  "time": dates_series,
197
206
  "observations": pl.Series(values, dtype=pl.Float32),
198
207
  "std": pl.Series(std_dev, dtype=pl.Float32),
208
+ "east": pl.Series([None] * len(values), dtype=pl.Float32),
209
+ "north": pl.Series([None] * len(values), dtype=pl.Float32),
210
+ "radius": pl.Series([None] * len(values), dtype=pl.Float32),
199
211
  }
200
212
  )
201
213
 
@@ -288,40 +300,7 @@ def _get_restart(
288
300
 
289
301
 
290
302
  def _has_localization(summary_dict: SummaryObservation) -> bool:
291
- return any(
292
- [
293
- summary_dict.location_x is not None,
294
- summary_dict.location_y is not None,
295
- summary_dict.location_range is not None,
296
- ]
297
- )
298
-
299
-
300
- def _validate_localization_values(summary_dict: SummaryObservation) -> None:
301
- """The user must provide LOCATION_X and LOCATION_Y to use localization, while
302
- unprovided LOCATION_RANGE should default to some value.
303
-
304
- This method assumes the summary dict contains at least one LOCATION key.
305
- """
306
- if summary_dict.location_x is None or summary_dict.location_y is None:
307
- loc_values = {
308
- "LOCATION_X": summary_dict.location_x,
309
- "LOCATION_Y": summary_dict.location_y,
310
- "LOCATION_RANGE": summary_dict.location_range,
311
- }
312
- provided_loc_values = {k: v for k, v in loc_values.items() if v is not None}
313
-
314
- provided_loc_values_string = ", ".join(
315
- key.upper() for key in provided_loc_values
316
- )
317
- raise ObservationConfigError.with_context(
318
- f"Localization for observation {summary_dict.name} is misconfigured.\n"
319
- f"Only {provided_loc_values_string} were provided. To enable "
320
- f"localization for an observation, ensure that both LOCATION_X and "
321
- f"LOCATION_Y are defined - or remove LOCATION keywords to disable "
322
- f"localization.",
323
- summary_dict,
324
- )
303
+ return summary_dict.east is not None and summary_dict.north is not None
325
304
 
326
305
 
327
306
  def _handle_summary_observation(
@@ -361,23 +340,24 @@ def _handle_summary_observation(
361
340
  "Observation uncertainty must be strictly > 0", summary_key
362
341
  ) from None
363
342
 
364
- data_dict = {
365
- "response_key": [summary_key],
366
- "observation_key": [obs_key],
367
- "time": pl.Series([date]).dt.cast_time_unit("ms"),
368
- "observations": pl.Series([value], dtype=pl.Float32),
369
- "std": pl.Series([std_dev], dtype=pl.Float32),
370
- }
371
-
372
- if _has_localization(summary_dict):
373
- _validate_localization_values(summary_dict)
374
- data_dict["location_x"] = summary_dict.location_x
375
- data_dict["location_y"] = summary_dict.location_y
376
- data_dict["location_range"] = (
377
- summary_dict.location_range or DEFAULT_LOCATION_RANGE_M
378
- )
343
+ localization_radius = (
344
+ summary_dict.radius or DEFAULT_LOCALIZATION_RADIUS
345
+ if _has_localization(summary_dict)
346
+ else None
347
+ )
379
348
 
380
- return pl.DataFrame(data_dict)
349
+ return pl.DataFrame(
350
+ {
351
+ "response_key": [summary_key],
352
+ "observation_key": [obs_key],
353
+ "time": pl.Series([date]).dt.cast_time_unit("ms"),
354
+ "observations": pl.Series([value], dtype=pl.Float32),
355
+ "std": pl.Series([std_dev], dtype=pl.Float32),
356
+ "east": pl.Series([summary_dict.east], dtype=pl.Float32),
357
+ "north": pl.Series([summary_dict.north], dtype=pl.Float32),
358
+ "radius": pl.Series([localization_radius], dtype=pl.Float32),
359
+ }
360
+ )
381
361
 
382
362
 
383
363
  def _handle_general_observation(
@@ -460,10 +440,8 @@ def _handle_general_observation(
460
440
  stds = file_values[1::2]
461
441
 
462
442
  else:
463
- assert (
464
- general_observation.value is not None
465
- and general_observation.error is not None
466
- )
443
+ assert general_observation.value is not None
444
+ assert general_observation.error is not None
467
445
  values = np.array([general_observation.value])
468
446
  stds = np.array([general_observation.error])
469
447
 
@@ -507,5 +485,53 @@ def _handle_general_observation(
507
485
  "index": pl.Series(indices, dtype=pl.UInt16),
508
486
  "observations": pl.Series(values, dtype=pl.Float32),
509
487
  "std": pl.Series(stds, dtype=pl.Float32),
488
+ # Location attributes will always be None for general observations, but are
489
+ # necessary to concatenate with other observation dataframes.
490
+ "east": pl.Series([None] * len(values), dtype=pl.Float32),
491
+ "north": pl.Series([None] * len(values), dtype=pl.Float32),
492
+ "radius": pl.Series([None] * len(values), dtype=pl.Float32),
493
+ }
494
+ )
495
+
496
+
497
+ def _handle_rft_observation(
498
+ rft_config: RFTConfig,
499
+ rft_observation: RFTObservation,
500
+ ) -> pl.DataFrame:
501
+ location = (rft_observation.east, rft_observation.north, rft_observation.tvd)
502
+ if location not in rft_config.locations:
503
+ rft_config.locations.append(location)
504
+
505
+ data_to_read = rft_config.data_to_read
506
+ if rft_observation.well not in data_to_read:
507
+ rft_config.data_to_read[rft_observation.well] = {}
508
+
509
+ well_dict = data_to_read[rft_observation.well]
510
+ if rft_observation.date not in well_dict:
511
+ well_dict[rft_observation.date] = []
512
+
513
+ property_list = well_dict[rft_observation.date]
514
+ if rft_observation.property not in property_list:
515
+ property_list.append(rft_observation.property)
516
+
517
+ if rft_observation.error <= 0.0:
518
+ raise ObservationConfigError.with_context(
519
+ "Observation uncertainty must be strictly > 0", rft_observation.well
520
+ )
521
+
522
+ return pl.DataFrame(
523
+ {
524
+ "response_key": (
525
+ f"{rft_observation.well}:"
526
+ f"{rft_observation.date}:"
527
+ f"{rft_observation.property}"
528
+ ),
529
+ "observation_key": rft_observation.name,
530
+ "east": pl.Series([location[0]], dtype=pl.Float32),
531
+ "north": pl.Series([location[1]], dtype=pl.Float32),
532
+ "tvd": pl.Series([location[2]], dtype=pl.Float32),
533
+ "observations": pl.Series([rft_observation.value], dtype=pl.Float32),
534
+ "std": pl.Series([rft_observation.error], dtype=pl.Float32),
535
+ "radius": pl.Series([None], dtype=pl.Float32),
510
536
  }
511
537
  )
@@ -156,7 +156,7 @@ class _Parser:
156
156
  def next_line(self) -> Iterator[str]: ...
157
157
 
158
158
  @overload
159
- def next_line(self, __default: T) -> Iterator[str] | T: ...
159
+ def next_line(self, __default: T, /) -> Iterator[str] | T: ...
160
160
 
161
161
  def next_line(self, *args: T) -> Iterator[str] | T:
162
162
  self.line_number += 1
@@ -1,5 +1,4 @@
1
1
  import os
2
- from collections import Counter
3
2
  from collections.abc import Sequence
4
3
  from dataclasses import dataclass
5
4
  from enum import StrEnum
@@ -90,9 +89,9 @@ class _SummaryValues:
90
89
  name: str
91
90
  value: float
92
91
  key: str #: The :term:`summary key` in the summary response
93
- location_x: float | None = None
94
- location_y: float | None = None
95
- location_range: float | None = None
92
+ east: float | None = None
93
+ north: float | None = None
94
+ radius: float | None = None
96
95
 
97
96
 
98
97
  @dataclass
@@ -104,7 +103,7 @@ class SummaryObservation(ObservationDate, _SummaryValues, ObservationError):
104
103
 
105
104
  date_dict: ObservationDate = ObservationDate()
106
105
  float_values: dict[str, float] = {"ERROR_MIN": 0.1}
107
- localization_values: dict[str, float] = {}
106
+ localization_values: dict[str, float | None] = {}
108
107
  for key, value in observation_dict.items():
109
108
  match key:
110
109
  case "type" | "name":
@@ -125,12 +124,15 @@ class SummaryObservation(ObservationDate, _SummaryValues, ObservationError):
125
124
  summary_key = value
126
125
  case "DATE":
127
126
  date_dict.date = value
128
- case "LOCATION_X":
129
- localization_values["x"] = validate_float(value, key)
130
- case "LOCATION_Y":
131
- localization_values["y"] = validate_float(value, key)
132
- case "LOCATION_RANGE":
133
- localization_values["range"] = validate_float(value, key)
127
+ case "LOCALIZATION":
128
+ validate_localization(value, observation_dict["name"])
129
+ localization_values["east"] = validate_float(value["EAST"], key)
130
+ localization_values["north"] = validate_float(value["NORTH"], key)
131
+ localization_values["radius"] = (
132
+ validate_float(value["RADIUS"], key)
133
+ if "RADIUS" in value
134
+ else None
135
+ )
134
136
  case _:
135
137
  raise _unknown_key_error(str(key), observation_dict["name"])
136
138
  if "VALUE" not in float_values:
@@ -147,9 +149,9 @@ class SummaryObservation(ObservationDate, _SummaryValues, ObservationError):
147
149
  error_min=float_values["ERROR_MIN"],
148
150
  key=summary_key,
149
151
  value=float_values["VALUE"],
150
- location_x=localization_values.get("x"),
151
- location_y=localization_values.get("y"),
152
- location_range=localization_values.get("range"),
152
+ east=localization_values.get("east"),
153
+ north=localization_values.get("north"),
154
+ radius=localization_values.get("radius"),
153
155
  **date_dict.__dict__,
154
156
  )
155
157
 
@@ -214,12 +216,88 @@ class GeneralObservation(ObservationDate, _GeneralObservation):
214
216
  return output
215
217
 
216
218
 
217
- Observation = HistoryObservation | SummaryObservation | GeneralObservation
219
+ @dataclass
220
+ class RFTObservation:
221
+ name: str
222
+ well: str
223
+ date: str
224
+ property: str
225
+ value: float
226
+ error: float
227
+ north: float
228
+ east: float
229
+ tvd: float
230
+
231
+ @classmethod
232
+ def from_obs_dict(cls, directory: str, observation_dict: ObservationDict) -> Self:
233
+ well = None
234
+ observed_property = None
235
+ observed_value = None
236
+ error = None
237
+ date = None
238
+ north = None
239
+ east = None
240
+ tvd = None
241
+ for key, value in observation_dict.items():
242
+ match key:
243
+ case "type" | "name":
244
+ pass
245
+ case "WELL":
246
+ well = value
247
+ case "PROPERTY":
248
+ observed_property = value
249
+ case "VALUE":
250
+ observed_value = validate_float(value, key)
251
+ case "ERROR":
252
+ error = validate_float(value, key)
253
+ case "DATE":
254
+ date = value
255
+ case "NORTH":
256
+ north = validate_float(value, key)
257
+ case "EAST":
258
+ east = validate_float(value, key)
259
+ case "TVD":
260
+ tvd = validate_float(value, key)
261
+ case _:
262
+ raise _unknown_key_error(str(key), observation_dict["name"])
263
+ if well is None:
264
+ raise _missing_value_error(observation_dict["name"], "WELL")
265
+ if observed_value is None:
266
+ raise _missing_value_error(observation_dict["name"], "VALUE")
267
+ if observed_property is None:
268
+ raise _missing_value_error(observation_dict["name"], "PROPERTY")
269
+ if error is None:
270
+ raise _missing_value_error(observation_dict["name"], "ERROR")
271
+ if date is None:
272
+ raise _missing_value_error(observation_dict["name"], "DATE")
273
+ if north is None:
274
+ raise _missing_value_error(observation_dict["name"], "NORTH")
275
+ if east is None:
276
+ raise _missing_value_error(observation_dict["name"], "EAST")
277
+ if tvd is None:
278
+ raise _missing_value_error(observation_dict["name"], "TVD")
279
+ return cls(
280
+ observation_dict["name"],
281
+ well,
282
+ date,
283
+ observed_property,
284
+ observed_value,
285
+ error,
286
+ north,
287
+ east,
288
+ tvd,
289
+ )
290
+
291
+
292
+ Observation = (
293
+ HistoryObservation | SummaryObservation | GeneralObservation | RFTObservation
294
+ )
218
295
 
219
296
  _TYPE_TO_CLASS: dict[ObservationType, type[Observation]] = {
220
297
  ObservationType.HISTORY: HistoryObservation,
221
298
  ObservationType.SUMMARY: SummaryObservation,
222
299
  ObservationType.GENERAL: GeneralObservation,
300
+ ObservationType.RFT: RFTObservation,
223
301
  }
224
302
 
225
303
 
@@ -248,25 +326,9 @@ def make_observations(
248
326
  if error_list:
249
327
  raise ObservationConfigError.from_collected(error_list)
250
328
 
251
- _validate_unique_names(result)
252
329
  return result
253
330
 
254
331
 
255
- def _validate_unique_names(
256
- observations: Sequence[Observation],
257
- ) -> None:
258
- names_counter = Counter(d.name for d in observations)
259
- duplicate_names = [n for n, c in names_counter.items() if c > 1]
260
- errors = [
261
- ErrorInfo(
262
- f"Duplicate observation name {n}",
263
- ).set_context(n)
264
- for n in duplicate_names
265
- ]
266
- if errors:
267
- raise ObservationConfigError.from_collected(errors)
268
-
269
-
270
332
  def _validate_segment_dict(name_token: str, inp: dict[str, Any]) -> Segment:
271
333
  start = None
272
334
  stop = None
@@ -339,6 +401,19 @@ def validate_positive_float(val: str, key: str) -> float:
339
401
  return v
340
402
 
341
403
 
404
+ def validate_localization(val: dict[str, Any], obs_name: str) -> None:
405
+ errors = []
406
+ if "EAST" not in val:
407
+ errors.append(_missing_value_error(f"LOCALIZATION for {obs_name}", "EAST"))
408
+ if "NORTH" not in val:
409
+ errors.append(_missing_value_error(f"LOCALIZATION for {obs_name}", "NORTH"))
410
+ for key in val:
411
+ if key not in {"EAST", "NORTH", "RADIUS"}:
412
+ errors.append(_unknown_key_error(key, f"LOCALIZATION for {obs_name}"))
413
+ if errors:
414
+ raise ObservationConfigError.from_collected(errors)
415
+
416
+
342
417
  def validate_positive_int(val: str, key: str) -> int:
343
418
  try:
344
419
  v = int(val)
@@ -17,7 +17,7 @@ class TransSettingsValidation(BaseModel):
17
17
  model_config = {"extra": "forbid"}
18
18
 
19
19
  @classmethod
20
- def create(cls: type[T], *args: Any, **kwargs: Any) -> T:
20
+ def create(cls, *args: Any, **kwargs: Any) -> Self:
21
21
  return cls(*args, **kwargs)
22
22
 
23
23
  @classmethod
@@ -7,7 +7,7 @@ from typing import Self
7
7
 
8
8
  from pydantic import BaseModel, Field, model_validator
9
9
 
10
- from .ext_param_config import ExtParamConfig
10
+ from .everest_control import EverestControl
11
11
  from .field import Field as FieldConfig
12
12
  from .gen_kw_config import GenKwConfig
13
13
  from .known_response_types import KNOWN_ERT_RESPONSE_TYPES, KnownErtResponseTypes
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
22
22
  class EnsembleConfig(BaseModel):
23
23
  response_configs: dict[str, KnownErtResponseTypes] = Field(default_factory=dict)
24
24
  parameter_configs: dict[
25
- str, GenKwConfig | FieldConfig | SurfaceConfig | ExtParamConfig
25
+ str, GenKwConfig | FieldConfig | SurfaceConfig | EverestControl
26
26
  ] = Field(default_factory=dict)
27
27
 
28
28
  @model_validator(mode="after")
@@ -132,7 +132,7 @@ class EnsembleConfig(BaseModel):
132
132
  response_configs.append(instance)
133
133
 
134
134
  return cls(
135
- response_configs={response.name: response for response in response_configs},
135
+ response_configs={response.type: response for response in response_configs},
136
136
  parameter_configs={
137
137
  parameter.name: parameter for parameter in parameter_configs
138
138
  },