ert 17.0.0__py3-none-any.whl → 19.0.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. _ert/events.py +19 -2
  2. _ert/forward_model_runner/client.py +6 -2
  3. ert/__main__.py +28 -13
  4. ert/analysis/_enif_update.py +8 -4
  5. ert/analysis/_es_update.py +19 -6
  6. ert/analysis/_update_commons.py +16 -6
  7. ert/cli/main.py +13 -6
  8. ert/cli/monitor.py +7 -0
  9. ert/config/__init__.py +15 -6
  10. ert/config/_create_observation_dataframes.py +117 -20
  11. ert/config/_get_num_cpu.py +1 -1
  12. ert/config/_observations.py +91 -2
  13. ert/config/_read_summary.py +8 -6
  14. ert/config/design_matrix.py +51 -24
  15. ert/config/distribution.py +1 -1
  16. ert/config/ensemble_config.py +9 -17
  17. ert/config/ert_config.py +103 -19
  18. ert/config/everest_control.py +234 -0
  19. ert/config/{everest_objective_config.py → everest_response.py} +24 -15
  20. ert/config/field.py +96 -84
  21. ert/config/forward_model_step.py +122 -17
  22. ert/config/gen_data_config.py +5 -10
  23. ert/config/gen_kw_config.py +5 -35
  24. ert/config/known_response_types.py +14 -0
  25. ert/config/parameter_config.py +1 -33
  26. ert/config/parsing/_option_dict.py +10 -2
  27. ert/config/parsing/config_keywords.py +2 -0
  28. ert/config/parsing/config_schema.py +23 -3
  29. ert/config/parsing/config_schema_deprecations.py +3 -14
  30. ert/config/parsing/config_schema_item.py +26 -11
  31. ert/config/parsing/context_values.py +3 -3
  32. ert/config/parsing/file_context_token.py +1 -1
  33. ert/config/parsing/observations_parser.py +6 -2
  34. ert/config/parsing/queue_system.py +9 -0
  35. ert/config/parsing/schema_item_type.py +1 -0
  36. ert/config/queue_config.py +4 -5
  37. ert/config/response_config.py +0 -8
  38. ert/config/rft_config.py +275 -0
  39. ert/config/summary_config.py +3 -8
  40. ert/config/surface_config.py +59 -16
  41. ert/config/workflow_fixtures.py +2 -1
  42. ert/dark_storage/client/__init__.py +2 -2
  43. ert/dark_storage/client/_session.py +4 -4
  44. ert/dark_storage/client/client.py +2 -2
  45. ert/dark_storage/common.py +1 -1
  46. ert/dark_storage/compute/misfits.py +11 -7
  47. ert/dark_storage/endpoints/compute/misfits.py +6 -4
  48. ert/dark_storage/endpoints/experiment_server.py +12 -9
  49. ert/dark_storage/endpoints/experiments.py +2 -2
  50. ert/dark_storage/endpoints/observations.py +8 -6
  51. ert/dark_storage/endpoints/parameters.py +2 -18
  52. ert/dark_storage/endpoints/responses.py +24 -5
  53. ert/dark_storage/json_schema/experiment.py +1 -1
  54. ert/data/_measured_data.py +6 -5
  55. ert/ensemble_evaluator/__init__.py +8 -1
  56. ert/ensemble_evaluator/config.py +2 -1
  57. ert/ensemble_evaluator/evaluator.py +81 -29
  58. ert/ensemble_evaluator/event.py +6 -0
  59. ert/ensemble_evaluator/snapshot.py +3 -1
  60. ert/ensemble_evaluator/state.py +1 -0
  61. ert/field_utils/__init__.py +8 -0
  62. ert/field_utils/field_utils.py +212 -3
  63. ert/field_utils/roff_io.py +1 -1
  64. ert/gui/__init__.py +5 -2
  65. ert/gui/ertnotifier.py +1 -1
  66. ert/gui/ertwidgets/__init__.py +23 -16
  67. ert/gui/ertwidgets/analysismoduleedit.py +2 -2
  68. ert/gui/ertwidgets/checklist.py +1 -1
  69. ert/gui/ertwidgets/create_experiment_dialog.py +3 -1
  70. ert/gui/ertwidgets/ensembleselector.py +2 -2
  71. ert/gui/ertwidgets/models/__init__.py +2 -0
  72. ert/gui/ertwidgets/models/activerealizationsmodel.py +2 -1
  73. ert/gui/ertwidgets/models/path_model.py +1 -1
  74. ert/gui/ertwidgets/models/targetensemblemodel.py +2 -1
  75. ert/gui/ertwidgets/models/text_model.py +1 -1
  76. ert/gui/ertwidgets/pathchooser.py +0 -3
  77. ert/gui/ertwidgets/searchbox.py +13 -4
  78. ert/gui/{suggestor → ertwidgets/suggestor}/_suggestor_message.py +13 -4
  79. ert/gui/{suggestor → ertwidgets/suggestor}/suggestor.py +63 -30
  80. ert/gui/main.py +37 -8
  81. ert/gui/main_window.py +1 -7
  82. ert/gui/simulation/ensemble_experiment_panel.py +1 -1
  83. ert/gui/simulation/ensemble_information_filter_panel.py +1 -1
  84. ert/gui/simulation/ensemble_smoother_panel.py +1 -1
  85. ert/gui/simulation/evaluate_ensemble_panel.py +1 -1
  86. ert/gui/simulation/experiment_panel.py +16 -3
  87. ert/gui/simulation/manual_update_panel.py +31 -8
  88. ert/gui/simulation/multiple_data_assimilation_panel.py +12 -8
  89. ert/gui/simulation/run_dialog.py +27 -20
  90. ert/gui/simulation/single_test_run_panel.py +2 -2
  91. ert/gui/summarypanel.py +20 -1
  92. ert/gui/tools/load_results/load_results_panel.py +1 -1
  93. ert/gui/tools/manage_experiments/export_dialog.py +136 -0
  94. ert/gui/tools/manage_experiments/storage_info_widget.py +121 -16
  95. ert/gui/tools/manage_experiments/storage_widget.py +1 -2
  96. ert/gui/tools/plot/plot_api.py +37 -25
  97. ert/gui/tools/plot/plot_widget.py +10 -2
  98. ert/gui/tools/plot/plot_window.py +38 -18
  99. ert/gui/tools/plot/plottery/plot_config.py +2 -0
  100. ert/gui/tools/plot/plottery/plot_context.py +14 -0
  101. ert/gui/tools/plot/plottery/plots/__init__.py +2 -0
  102. ert/gui/tools/plot/plottery/plots/cesp.py +3 -1
  103. ert/gui/tools/plot/plottery/plots/distribution.py +6 -1
  104. ert/gui/tools/plot/plottery/plots/ensemble.py +12 -3
  105. ert/gui/tools/plot/plottery/plots/gaussian_kde.py +12 -2
  106. ert/gui/tools/plot/plottery/plots/histogram.py +3 -1
  107. ert/gui/tools/plot/plottery/plots/misfits.py +436 -0
  108. ert/gui/tools/plot/plottery/plots/observations.py +18 -4
  109. ert/gui/tools/plot/plottery/plots/statistics.py +62 -20
  110. ert/gui/tools/plot/plottery/plots/std_dev.py +3 -1
  111. ert/mode_definitions.py +2 -0
  112. ert/plugins/__init__.py +0 -1
  113. ert/plugins/hook_implementations/workflows/csv_export.py +2 -3
  114. ert/plugins/hook_implementations/workflows/gen_data_rft_export.py +10 -2
  115. ert/plugins/hook_specifications/__init__.py +0 -2
  116. ert/plugins/hook_specifications/jobs.py +0 -9
  117. ert/plugins/plugin_manager.py +6 -33
  118. ert/resources/forward_models/run_reservoirsimulator.py +8 -3
  119. ert/resources/shell_scripts/delete_directory.py +2 -2
  120. ert/run_models/__init__.py +18 -5
  121. ert/run_models/_create_run_path.py +131 -37
  122. ert/run_models/ensemble_experiment.py +10 -4
  123. ert/run_models/ensemble_information_filter.py +8 -1
  124. ert/run_models/ensemble_smoother.py +9 -3
  125. ert/run_models/evaluate_ensemble.py +8 -6
  126. ert/run_models/event.py +7 -3
  127. ert/run_models/everest_run_model.py +159 -46
  128. ert/run_models/initial_ensemble_run_model.py +25 -24
  129. ert/run_models/manual_update.py +6 -3
  130. ert/run_models/manual_update_enif.py +37 -0
  131. ert/run_models/model_factory.py +81 -21
  132. ert/run_models/multiple_data_assimilation.py +22 -11
  133. ert/run_models/run_model.py +64 -55
  134. ert/run_models/single_test_run.py +7 -4
  135. ert/run_models/update_run_model.py +4 -2
  136. ert/runpaths.py +5 -6
  137. ert/sample_prior.py +9 -4
  138. ert/scheduler/driver.py +37 -0
  139. ert/scheduler/event.py +3 -1
  140. ert/scheduler/job.py +23 -13
  141. ert/scheduler/lsf_driver.py +6 -2
  142. ert/scheduler/openpbs_driver.py +7 -1
  143. ert/scheduler/scheduler.py +5 -0
  144. ert/scheduler/slurm_driver.py +6 -2
  145. ert/services/__init__.py +2 -2
  146. ert/services/_base_service.py +37 -20
  147. ert/services/ert_server.py +317 -0
  148. ert/shared/_doc_utils/__init__.py +4 -2
  149. ert/shared/_doc_utils/ert_jobs.py +1 -4
  150. ert/shared/net_utils.py +43 -18
  151. ert/shared/storage/connection.py +3 -3
  152. ert/shared/version.py +3 -3
  153. ert/storage/__init__.py +2 -0
  154. ert/storage/local_ensemble.py +38 -12
  155. ert/storage/local_experiment.py +8 -16
  156. ert/storage/local_storage.py +68 -42
  157. ert/storage/migration/to11.py +1 -1
  158. ert/storage/migration/to16.py +38 -0
  159. ert/storage/migration/to17.py +42 -0
  160. ert/storage/migration/to18.py +11 -0
  161. ert/storage/migration/to19.py +34 -0
  162. ert/storage/migration/to20.py +23 -0
  163. ert/storage/migration/to21.py +25 -0
  164. ert/storage/migration/to8.py +4 -4
  165. ert/substitutions.py +12 -28
  166. ert/validation/active_range.py +7 -7
  167. ert/validation/rangestring.py +16 -16
  168. ert/workflow_runner.py +2 -1
  169. {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/METADATA +9 -8
  170. {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/RECORD +208 -205
  171. {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/WHEEL +1 -1
  172. everest/api/everest_data_api.py +14 -1
  173. everest/bin/config_branch_script.py +3 -6
  174. everest/bin/everconfigdump_script.py +1 -9
  175. everest/bin/everest_script.py +21 -11
  176. everest/bin/everlint_script.py +0 -2
  177. everest/bin/kill_script.py +2 -2
  178. everest/bin/monitor_script.py +2 -2
  179. everest/bin/utils.py +8 -4
  180. everest/bin/visualization_script.py +6 -14
  181. everest/config/__init__.py +4 -1
  182. everest/config/control_config.py +81 -6
  183. everest/config/control_variable_config.py +4 -3
  184. everest/config/everest_config.py +75 -42
  185. everest/config/forward_model_config.py +5 -3
  186. everest/config/install_data_config.py +7 -5
  187. everest/config/install_job_config.py +7 -3
  188. everest/config/install_template_config.py +3 -3
  189. everest/config/optimization_config.py +19 -6
  190. everest/config/output_constraint_config.py +8 -2
  191. everest/config/server_config.py +6 -49
  192. everest/config/utils.py +25 -105
  193. everest/config/validation_utils.py +17 -11
  194. everest/config_file_loader.py +13 -4
  195. everest/detached/client.py +3 -3
  196. everest/detached/everserver.py +7 -8
  197. everest/everest_storage.py +6 -12
  198. everest/gui/everest_client.py +2 -3
  199. everest/gui/main_window.py +2 -2
  200. everest/optimizer/everest2ropt.py +59 -32
  201. everest/optimizer/opt_model_transforms.py +12 -13
  202. everest/optimizer/utils.py +0 -29
  203. everest/strings.py +0 -5
  204. ert/config/everest_constraints_config.py +0 -95
  205. ert/config/ext_param_config.py +0 -106
  206. ert/gui/tools/export/__init__.py +0 -3
  207. ert/gui/tools/export/export_panel.py +0 -83
  208. ert/gui/tools/export/export_tool.py +0 -69
  209. ert/gui/tools/export/exporter.py +0 -36
  210. ert/services/storage_service.py +0 -127
  211. everest/config/sampler_config.py +0 -103
  212. everest/simulator/__init__.py +0 -88
  213. everest/simulator/everest_to_ert.py +0 -51
  214. /ert/gui/{suggestor → ertwidgets/suggestor}/__init__.py +0 -0
  215. /ert/gui/{suggestor → ertwidgets/suggestor}/_colors.py +0 -0
  216. {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/entry_points.txt +0 -0
  217. {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/licenses/COPYING +0 -0
  218. {ert-17.0.0.dist-info → ert-19.0.0rc2.dist-info}/top_level.txt +0 -0
ert/config/ert_config.py CHANGED
@@ -25,6 +25,7 @@ from ._design_matrix_validator import DesignMatrixValidator
25
25
  from ._observations import (
26
26
  HistoryObservation,
27
27
  Observation,
28
+ RFTObservation,
28
29
  SummaryObservation,
29
30
  make_observations,
30
31
  )
@@ -36,9 +37,12 @@ from .forward_model_step import (
36
37
  ForwardModelStepJSON,
37
38
  ForwardModelStepValidationError,
38
39
  ForwardModelStepWarning,
40
+ SiteInstalledForwardModelStep,
41
+ SiteOrUserForwardModelStep,
42
+ UserInstalledForwardModelStep,
39
43
  )
40
44
  from .gen_data_config import GenDataConfig
41
- from .gen_kw_config import GenKwConfig
45
+ from .gen_kw_config import DataSource, GenKwConfig
42
46
  from .model_config import ModelConfig
43
47
  from .parse_arg_types_list import parse_arg_types_list
44
48
  from .parsing import (
@@ -59,6 +63,7 @@ from .parsing import (
59
63
  from .parsing.observations_parser import ObservationDict
60
64
  from .queue_config import KnownQueueOptions, QueueConfig
61
65
  from .refcase import Refcase
66
+ from .rft_config import RFTConfig
62
67
  from .workflow import Workflow
63
68
  from .workflow_fixtures import fixtures_per_hook
64
69
  from .workflow_job import (
@@ -133,6 +138,7 @@ def create_forward_model_json(
133
138
  env_pr_fm_step = {}
134
139
 
135
140
  context_substitutions = Substitutions(context)
141
+ real_iter_substituter = context_substitutions.real_iter_substituter(iens, itr)
136
142
 
137
143
  class Substituter:
138
144
  def __init__(self, fm_step: ForwardModelStep) -> None:
@@ -146,7 +152,7 @@ def create_forward_model_json(
146
152
  )
147
153
  self.copy_private_args = Substitutions(
148
154
  {
149
- key: context_substitutions.substitute_real_iter(val, iens, itr)
155
+ key: real_iter_substituter.substitute(val)
150
156
  for key, val in fm_step.private_args.items()
151
157
  }
152
158
  )
@@ -163,7 +169,7 @@ def create_forward_model_json(
163
169
  string = self.copy_private_args.substitute(
164
170
  string, self.substitution_context_hint, 1, warn_max_iter=False
165
171
  )
166
- return context_substitutions.substitute_real_iter(string, iens, itr)
172
+ return real_iter_substituter.substitute(string)
167
173
 
168
174
  def filter_env_dict(self, env_dict: dict[str, str]) -> dict[str, str] | None:
169
175
  substituted_dict = {}
@@ -518,18 +524,16 @@ def workflows_from_dict(
518
524
 
519
525
  def installed_forward_model_steps_from_dict(
520
526
  config_dict: ConfigDict,
521
- ) -> dict[str, ForwardModelStep]:
527
+ ) -> dict[str, UserInstalledForwardModelStep]:
522
528
  errors: list[ErrorInfo | ConfigValidationError] = []
523
- fm_steps: dict[str, ForwardModelStep] = {}
529
+ fm_steps: dict[str, UserInstalledForwardModelStep] = {}
524
530
  for name, (fm_step_config_file, config_contents) in config_dict.get(
525
531
  ConfigKeys.INSTALL_JOB, []
526
532
  ):
527
533
  fm_step_config_file = path.abspath(fm_step_config_file)
528
534
  try:
529
535
  new_fm_step = forward_model_step_from_config_contents(
530
- config_contents,
531
- name=name,
532
- config_file=fm_step_config_file,
536
+ config_contents, name=name, config_file=fm_step_config_file
533
537
  )
534
538
  except ConfigValidationError as e:
535
539
  errors.append(e)
@@ -692,6 +696,8 @@ def log_observation_keys(
692
696
 
693
697
  RESERVED_KEYWORDS = ["realization", "IENS", "ITER"]
694
698
 
699
+ USER_CONFIG_SCHEMA = init_user_config_schema()
700
+
695
701
 
696
702
  class ErtConfig(BaseModel):
697
703
  DEFAULT_ENSPATH: ClassVar[str] = "storage"
@@ -703,6 +709,7 @@ class ErtConfig(BaseModel):
703
709
  QUEUE_OPTIONS: ClassVar[KnownQueueOptions | None] = None
704
710
  RESERVED_KEYWORDS: ClassVar[list[str]] = RESERVED_KEYWORDS
705
711
  ENV_VARS: ClassVar[dict[str, str]] = {}
712
+ PRIORITIZE_PRIVATE_IP_ADDRESS: ClassVar[bool] = False
706
713
 
707
714
  substitutions: dict[str, str] = Field(default_factory=dict)
708
715
  ensemble_config: EnsembleConfig = Field(default_factory=EnsembleConfig)
@@ -717,10 +724,11 @@ class ErtConfig(BaseModel):
717
724
  default_factory=lambda: defaultdict(lambda: cast(list[Workflow], []))
718
725
  )
719
726
  runpath_file: Path = Path(DEFAULT_RUNPATH_FILE)
727
+ prioritize_private_ip_address: bool = False
720
728
 
721
729
  ert_templates: list[tuple[str, str]] = Field(default_factory=list)
722
730
 
723
- forward_model_steps: list[ForwardModelStep] = Field(default_factory=list)
731
+ forward_model_steps: list[SiteOrUserForwardModelStep] = Field(default_factory=list)
724
732
  runpath_config: ModelConfig = Field(default_factory=ModelConfig)
725
733
  user_config_file: str = "no_config"
726
734
  config_path: str = Field(init=False, default="")
@@ -733,6 +741,18 @@ class ErtConfig(BaseModel):
733
741
  @property
734
742
  def observations(self) -> dict[str, pl.DataFrame]:
735
743
  if self._observations is None:
744
+ has_rft_observations = any(
745
+ isinstance(o, RFTObservation) for o in self.observation_declarations
746
+ )
747
+ if (
748
+ has_rft_observations
749
+ and "rft" not in self.ensemble_config.response_configs
750
+ ):
751
+ self.ensemble_config.response_configs["rft"] = RFTConfig(
752
+ input_files=[self.runpath_config.eclbase_format_string],
753
+ data_to_read={},
754
+ locations=[],
755
+ )
736
756
  computed = create_observation_dataframes(
737
757
  self.observation_declarations,
738
758
  self.refcase,
@@ -740,6 +760,10 @@ class ErtConfig(BaseModel):
740
760
  GenDataConfig | None,
741
761
  self.ensemble_config.response_configs.get("gen_data", None),
742
762
  ),
763
+ cast(
764
+ RFTConfig | None,
765
+ self.ensemble_config.response_configs.get("rft", None),
766
+ ),
743
767
  self.time_map,
744
768
  self.history_source,
745
769
  )
@@ -791,6 +815,15 @@ class ErtConfig(BaseModel):
791
815
  )
792
816
  return self
793
817
 
818
+ @model_validator(mode="after")
819
+ def log_ensemble_config_contents(self) -> Self:
820
+ all_parameters = self.parameter_configurations_with_design_matrix
821
+ parameter_type_count = Counter(parameter.type for parameter in all_parameters)
822
+ logger.info(
823
+ f"EnsembleConfig contains parameters of type {dict(parameter_type_count)}"
824
+ )
825
+ return self
826
+
794
827
  def __eq__(self, other: object) -> bool:
795
828
  if not isinstance(other, ErtConfig):
796
829
  return False
@@ -817,7 +850,7 @@ class ErtConfig(BaseModel):
817
850
  def with_plugins(runtime_plugins: ErtRuntimePlugins) -> type[ErtConfig]:
818
851
  class ErtConfigWithPlugins(ErtConfig):
819
852
  PREINSTALLED_FORWARD_MODEL_STEPS: ClassVar[
820
- Mapping[str, ForwardModelStep]
853
+ Mapping[str, SiteInstalledForwardModelStep]
821
854
  ] = runtime_plugins.installed_forward_model_steps
822
855
  PREINSTALLED_WORKFLOWS = dict(runtime_plugins.installed_workflow_jobs)
823
856
  ENV_PR_FM_STEP: ClassVar[dict[str, dict[str, Any]]] = (
@@ -827,6 +860,9 @@ class ErtConfig(BaseModel):
827
860
  )
828
861
  ENV_VARS = dict(runtime_plugins.environment_variables)
829
862
  QUEUE_OPTIONS = runtime_plugins.queue_options
863
+ PRIORITIZE_PRIVATE_IP_ADDRESS = (
864
+ runtime_plugins.prioritize_private_ip_address
865
+ )
830
866
 
831
867
  ErtConfigWithPlugins.model_rebuild()
832
868
  assert issubclass(ErtConfigWithPlugins, ErtConfig)
@@ -1025,11 +1061,30 @@ class ErtConfig(BaseModel):
1025
1061
  if isinstance(cfg, GenKwConfig) and cfg.name in dm_params
1026
1062
  ]
1027
1063
  if overwrite_params:
1028
- ConfigWarning.warn(
1029
- f"Parameters {overwrite_params} "
1030
- "will be overridden by design matrix. This will cause "
1031
- "updates to be turned off for these parameters."
1032
- )
1064
+ param_sampled = [
1065
+ k
1066
+ for k in overwrite_params
1067
+ if analysis_config.design_matrix.parameter_priority[k]
1068
+ == DataSource.SAMPLED
1069
+ ]
1070
+ param_design = [
1071
+ k
1072
+ for k in overwrite_params
1073
+ if analysis_config.design_matrix.parameter_priority[k]
1074
+ == DataSource.DESIGN_MATRIX
1075
+ ]
1076
+ if param_sampled:
1077
+ ConfigWarning.warn(
1078
+ f"Parameters {param_sampled} "
1079
+ "are also defined in design matrix, but due to the sampled"
1080
+ " priority they will remain as such."
1081
+ )
1082
+ if param_design:
1083
+ ConfigWarning.warn(
1084
+ f"Parameters {param_design} "
1085
+ "will be overridden by design matrix. This will cause "
1086
+ "updates to be turned off for these parameters."
1087
+ )
1033
1088
 
1034
1089
  if dm_errors:
1035
1090
  raise ConfigValidationError.from_collected(dm_errors)
@@ -1060,6 +1115,19 @@ class ErtConfig(BaseModel):
1060
1115
  user_configured_.add(key)
1061
1116
  env_vars[key] = substituter.substitute(val)
1062
1117
 
1118
+ prioritize_private_ip_address: bool = cls.PRIORITIZE_PRIVATE_IP_ADDRESS
1119
+ if ConfigKeys.PRIORITIZE_PRIVATE_IP_ADDRESS in config_dict:
1120
+ user_prioritize_private_ip_address = bool(
1121
+ config_dict[ConfigKeys.PRIORITIZE_PRIVATE_IP_ADDRESS]
1122
+ )
1123
+ if prioritize_private_ip_address != user_prioritize_private_ip_address:
1124
+ logger.warning(
1125
+ "PRIORITIZE_PRIVATE_IP_ADDRESS was overwritten by user: "
1126
+ f"{prioritize_private_ip_address} -> "
1127
+ f"{user_prioritize_private_ip_address}"
1128
+ )
1129
+ prioritize_private_ip_address = user_prioritize_private_ip_address
1130
+
1063
1131
  try:
1064
1132
  refcase = Refcase.from_config_dict(config_dict)
1065
1133
  cls_config = cls(
@@ -1086,11 +1154,21 @@ class ErtConfig(BaseModel):
1086
1154
  time_map=time_map,
1087
1155
  history_source=history_source,
1088
1156
  refcase=refcase,
1157
+ prioritize_private_ip_address=prioritize_private_ip_address,
1089
1158
  )
1090
1159
 
1091
1160
  # The observations are created here because create_observation_dataframes
1092
1161
  # will perform additonal validation which needs the context in
1093
1162
  # obs_configs which is stripped by pydantic
1163
+ has_rft_observations = any(
1164
+ isinstance(o, RFTObservation) for o in obs_configs
1165
+ )
1166
+ if has_rft_observations and "rft" not in ensemble_config.response_configs:
1167
+ ensemble_config.response_configs["rft"] = RFTConfig(
1168
+ input_files=[eclbase],
1169
+ data_to_read={},
1170
+ locations=[],
1171
+ )
1094
1172
  cls_config._observations = create_observation_dataframes(
1095
1173
  obs_configs,
1096
1174
  refcase,
@@ -1098,6 +1176,10 @@ class ErtConfig(BaseModel):
1098
1176
  GenDataConfig | None,
1099
1177
  ensemble_config.response_configs.get("gen_data", None),
1100
1178
  ),
1179
+ cast(
1180
+ RFTConfig | None,
1181
+ ensemble_config.response_configs.get("rft", None),
1182
+ ),
1101
1183
  time_map,
1102
1184
  history_source,
1103
1185
  )
@@ -1275,7 +1357,7 @@ class ErtConfig(BaseModel):
1275
1357
  @classmethod
1276
1358
  def _read_user_config_contents(cls, user_config: str, file_name: str) -> ConfigDict:
1277
1359
  return parse_contents(
1278
- user_config, file_name=file_name, schema=init_user_config_schema()
1360
+ user_config, file_name=file_name, schema=USER_CONFIG_SCHEMA
1279
1361
  )
1280
1362
 
1281
1363
  @classmethod
@@ -1365,8 +1447,10 @@ def uppercase_subkeys_and_stringify_subvalues(
1365
1447
 
1366
1448
 
1367
1449
  def forward_model_step_from_config_contents(
1368
- config_contents: str, config_file: str, name: str | None = None
1369
- ) -> ForwardModelStep:
1450
+ config_contents: str,
1451
+ config_file: str,
1452
+ name: str | None = None,
1453
+ ) -> UserInstalledForwardModelStep:
1370
1454
  if name is None:
1371
1455
  name = os.path.basename(config_file)
1372
1456
 
@@ -1390,7 +1474,7 @@ def forward_model_step_from_config_contents(
1390
1474
  environment = {k: v for [k, v] in content_dict.get("ENV", [])}
1391
1475
  default_mapping = {k: v for [k, v] in content_dict.get("DEFAULT", [])}
1392
1476
 
1393
- return ForwardModelStep(
1477
+ return UserInstalledForwardModelStep(
1394
1478
  name=name,
1395
1479
  executable=content_dict["EXECUTABLE"],
1396
1480
  stdin_file=content_dict.get("STDIN"),
@@ -0,0 +1,234 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ import json
5
+ import logging
6
+ from collections.abc import Iterator, Mapping, MutableMapping
7
+ from dataclasses import field
8
+ from pathlib import Path
9
+ from textwrap import dedent
10
+ from typing import TYPE_CHECKING, Any, Literal, Self
11
+
12
+ import networkx as nx
13
+ import numpy as np
14
+ import xarray as xr
15
+ from pydantic import BaseModel, ConfigDict, Field, model_validator
16
+ from ropt.workflow import find_sampler_plugin
17
+
18
+ from ert.substitutions import substitute_runpath_name
19
+
20
+ from .parameter_config import ParameterConfig
21
+
22
+ if TYPE_CHECKING:
23
+ import numpy.typing as npt
24
+
25
+ from ert.storage import Ensemble
26
+
27
+ Number = int | float
28
+ DataType = Mapping[str, Number | Mapping[str, Number]]
29
+ MutableDataType = MutableMapping[str, Number | MutableMapping[str, Number]]
30
+
31
+
32
+ class SamplerConfig(BaseModel):
33
+ backend: str | None = Field(
34
+ default=None,
35
+ description=dedent(
36
+ """
37
+ [Deprecated]
38
+
39
+ The correct backend will be inferred by the method. If several backends
40
+ have a method named `A`, pick a specific backend `B` by putting `B/A` in
41
+ the `method` field.
42
+ """
43
+ ),
44
+ )
45
+ method: str = Field(
46
+ default="norm",
47
+ description=dedent(
48
+ """
49
+ The sampling method or distribution used by the sampler backend.
50
+
51
+ The set of available methods depends on the sampler backend used. By
52
+ default a plugin based on `scipy.stats` is used, implementing the
53
+ following methods:
54
+
55
+ - From Probability Distributions:
56
+ - `norm`: Samples from a standard normal distribution (mean 0,
57
+ standard deviation 1).
58
+ - `truncnorm`: Samples from a truncated normal distribution
59
+ (mean 0, std. dev. 1), truncated to the range `[-1, 1]`.
60
+ - `uniform`: Samples from a uniform distribution in the range
61
+ `[-1, 1]`.
62
+
63
+ - From Quasi-Monte Carlo Sequences:
64
+ - `sobol`: Uses Sobol' sequences.
65
+ - `halton`: Uses Halton sequences.
66
+ - `lhs`: Uses Latin Hypercube Sampling.
67
+
68
+ Note: QMC samples are generated in the unit hypercube `[0, 1]^d`
69
+ and then scaled to the hypercube `[-1, 1]^d`.
70
+ """
71
+ ),
72
+ )
73
+ options: dict[str, Any] | None = Field(
74
+ default=None,
75
+ description=dedent(
76
+ """
77
+ Specifies a dict of optional parameters for the sampler backend.
78
+
79
+ This dict of values is passed unchanged to the selected method in
80
+ the backend.
81
+ """
82
+ ),
83
+ )
84
+ shared: bool | None = Field(
85
+ default=None,
86
+ description=dedent(
87
+ """
88
+ Whether to share perturbations between realizations.
89
+ """
90
+ ),
91
+ )
92
+ model_config = ConfigDict(extra="forbid")
93
+
94
+ @model_validator(mode="after")
95
+ def validate_backend_and_method(self) -> Self:
96
+ if self.backend is not None:
97
+ message = (
98
+ "sampler.backend is deprecated. "
99
+ "The correct backend will be inferred by the method. "
100
+ "If several backends have a method named A, you need to pick "
101
+ "a specific backend B by putting B/A in sampler.method."
102
+ )
103
+ print(message)
104
+ # Note: Importing EVEREST.everest
105
+ # leads to circular import, but we still wish to log
106
+ # from "everest" here as per old behavior.
107
+ # Can consider logging this as if from ERT,
108
+ # which is valid if we store SamplerConfig as part of
109
+ # EverestControl configs.
110
+ logging.getLogger("everest").warning(message)
111
+
112
+ # Update the default for backends that are not scipy:
113
+ if (
114
+ self.backend not in {None, "scipy"}
115
+ and "method" not in self.model_fields_set
116
+ ):
117
+ self.method = "default"
118
+
119
+ if self.backend is not None:
120
+ self.method = f"{self.backend}/{self.method}"
121
+
122
+ try:
123
+ plugin = find_sampler_plugin(f"{self.method}")
124
+ except ValueError:
125
+ raise
126
+ except Exception as exc:
127
+ ert_version = importlib.metadata.version("ert")
128
+ ropt_version = importlib.metadata.version("ropt")
129
+ msg = (
130
+ f"Error while initializing ropt:\n\n{exc}.\n\n"
131
+ "There may a be version mismatch between "
132
+ f"ERT ({ert_version}) and ropt ({ropt_version})\n"
133
+ "If the installation is correct, please report this as a bug."
134
+ )
135
+ raise RuntimeError(msg) from exc
136
+
137
+ if plugin is None:
138
+ raise ValueError(f"Sampler method '{self.method}' not found")
139
+
140
+ self.backend = None
141
+
142
+ return self
143
+
144
+
145
+ class EverestControl(ParameterConfig):
146
+ """Create an EverestControl for @key with the given @input_keys
147
+
148
+ @input_keys can be either a list of keys as strings or a dict with
149
+ keys as strings and a list of suffixes for each key.
150
+ If a list of strings is given, the order is preserved.
151
+ """
152
+
153
+ type: Literal["everest_parameters"] = "everest_parameters"
154
+ input_keys: list[str] = field(default_factory=list)
155
+ forward_init: bool = False
156
+ output_file: str = ""
157
+ forward_init_file: str = ""
158
+ update: bool = False
159
+ types: list[Literal["well_control", "generic_control"]]
160
+ initial_guesses: list[float]
161
+ control_types: list[Literal["real", "integer"]]
162
+ enabled: list[bool]
163
+ min: list[float]
164
+ max: list[float]
165
+ perturbation_types: list[Literal["absolute", "relative"]]
166
+ perturbation_magnitudes: list[float]
167
+ scaled_ranges: list[tuple[float, float]]
168
+ samplers: list[SamplerConfig | None]
169
+
170
+ # Set up for deprecation, but has to live here until support for the
171
+ # "dotdash" notation is removed for everest controls via everest config.
172
+ input_keys_dotdash: list[str] = field(default_factory=list)
173
+
174
+ @property
175
+ def parameter_keys(self) -> list[str]:
176
+ return self.input_keys
177
+
178
+ def read_from_runpath(
179
+ self, run_path: Path, real_nr: int, iteration: int
180
+ ) -> xr.Dataset:
181
+ raise NotImplementedError
182
+
183
+ def write_to_runpath(
184
+ self, run_path: Path, real_nr: int, ensemble: Ensemble
185
+ ) -> None:
186
+ file_path = run_path / substitute_runpath_name(
187
+ self.output_file, real_nr, ensemble.iteration
188
+ )
189
+ Path.mkdir(file_path.parent, exist_ok=True, parents=True)
190
+
191
+ data: MutableDataType = {}
192
+ for da in ensemble.load_parameters(self.name, real_nr)["values"]:
193
+ assert isinstance(da, xr.DataArray)
194
+ name = str(da.names.values)
195
+ try:
196
+ outer, inner = name.split("\0")
197
+
198
+ if outer not in data:
199
+ data[outer] = {}
200
+ data[outer][inner] = float(da) # type: ignore
201
+ except ValueError:
202
+ data[name] = float(da)
203
+
204
+ file_path.write_text(json.dumps(data), encoding="utf-8")
205
+
206
+ def create_storage_datasets(
207
+ self,
208
+ from_data: npt.NDArray[np.float64],
209
+ iens_active_index: npt.NDArray[np.int_],
210
+ ) -> Iterator[tuple[int, xr.Dataset]]:
211
+ for i, realization in enumerate(iens_active_index):
212
+ yield (
213
+ int(realization),
214
+ xr.Dataset(
215
+ {
216
+ "values": ("names", from_data[:, i]),
217
+ "names": [
218
+ x.split(f"{self.name}.")[1].replace(".", "\0")
219
+ for x in self.parameter_keys
220
+ ],
221
+ }
222
+ ),
223
+ )
224
+
225
+ def load_parameters(
226
+ self, ensemble: Ensemble, realizations: npt.NDArray[np.int_]
227
+ ) -> npt.NDArray[np.float64]:
228
+ raise NotImplementedError
229
+
230
+ def load_parameter_graph(self) -> nx.Graph[int]:
231
+ raise NotImplementedError
232
+
233
+ def __len__(self) -> int:
234
+ return len(self.input_keys)
@@ -11,19 +11,21 @@ from .response_config import InvalidResponseFile, ResponseConfig, ResponseMetada
11
11
  from .responses_index import responses_index
12
12
 
13
13
 
14
- class EverestObjectivesConfig(ResponseConfig):
15
- type: Literal["everest_objectives"] = "everest_objectives"
16
- name: str = "everest_objectives"
14
+ class EverestResponse(ResponseConfig):
15
+ """Base class for Everest response configurations."""
16
+
17
17
  has_finalized_keys: bool = True
18
- weights: list[float | None]
19
18
  scales: list[float | None]
20
- objective_types: list[Literal["mean", "stddev"]]
19
+
20
+ @property
21
+ def primary_key(self) -> list[str]:
22
+ return []
21
23
 
22
24
  @property
23
25
  def metadata(self) -> list[ResponseMetadata]:
24
26
  return [
25
27
  ResponseMetadata(
26
- response_type=self.name,
28
+ response_type=self.type,
27
29
  response_key=response_key,
28
30
  finalized=self.has_finalized_keys,
29
31
  filter_on=None,
@@ -52,7 +54,6 @@ class EverestObjectivesConfig(ResponseConfig):
52
54
  )
53
55
 
54
56
  errors = []
55
-
56
57
  run_path_ = Path(run_path)
57
58
  datasets_per_name = []
58
59
 
@@ -75,24 +76,32 @@ class EverestObjectivesConfig(ResponseConfig):
75
76
  if all(isinstance(err, FileNotFoundError) for err in errors):
76
77
  raise FileNotFoundError(
77
78
  "Could not find one or more files/directories while reading "
78
- f"{self.name}: {','.join([str(err) for err in errors])}"
79
+ f"{self.type}: {','.join([str(err) for err in errors])}"
79
80
  )
80
81
  else:
81
82
  raise InvalidResponseFile(
82
83
  "Error reading "
83
- f"{self.name}, errors: {','.join([str(err) for err in errors])}"
84
+ f"{self.type}, errors: {','.join([str(err) for err in errors])}"
84
85
  )
85
86
 
86
87
  combined = pl.concat(datasets_per_name)
87
88
  return combined
88
89
 
89
- @property
90
- def response_type(self) -> str:
91
- return "everest_objectives"
92
90
 
93
- @property
94
- def primary_key(self) -> list[str]:
95
- return []
91
+ class EverestConstraintsConfig(EverestResponse):
92
+ type: Literal["everest_constraints"] = "everest_constraints"
93
+ targets: list[float | None]
94
+ upper_bounds: list[float]
95
+ lower_bounds: list[float]
96
+
97
+
98
+ responses_index.add_response_type(EverestConstraintsConfig)
99
+
100
+
101
+ class EverestObjectivesConfig(EverestResponse):
102
+ type: Literal["everest_objectives"] = "everest_objectives"
103
+ weights: list[float | None]
104
+ objective_types: list[Literal["mean", "stddev"]]
96
105
 
97
106
 
98
107
  responses_index.add_response_type(EverestObjectivesConfig)