ert 16.0.9__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 (286) hide show
  1. _ert/events.py +19 -2
  2. _ert/forward_model_runner/client.py +6 -2
  3. _ert/forward_model_runner/fm_dispatch.py +9 -6
  4. _ert/forward_model_runner/reporting/event.py +1 -0
  5. _ert/forward_model_runner/runner.py +1 -2
  6. _ert/utils.py +12 -0
  7. ert/__main__.py +58 -38
  8. ert/analysis/_enif_update.py +8 -4
  9. ert/analysis/_es_update.py +19 -6
  10. ert/analysis/_update_commons.py +16 -6
  11. ert/base_model_context.py +1 -1
  12. ert/cli/main.py +17 -12
  13. ert/cli/monitor.py +7 -0
  14. ert/config/__init__.py +17 -6
  15. ert/config/_create_observation_dataframes.py +118 -21
  16. ert/config/_get_num_cpu.py +1 -1
  17. ert/config/_observations.py +91 -2
  18. ert/config/_read_summary.py +74 -328
  19. ert/config/design_matrix.py +62 -23
  20. ert/config/distribution.py +1 -1
  21. ert/config/ensemble_config.py +9 -17
  22. ert/config/ert_config.py +155 -58
  23. ert/config/everest_control.py +234 -0
  24. ert/config/{everest_constraints_config.py → everest_response.py} +27 -15
  25. ert/config/field.py +99 -90
  26. ert/config/forward_model_step.py +122 -17
  27. ert/config/gen_data_config.py +5 -10
  28. ert/config/gen_kw_config.py +11 -41
  29. ert/config/known_response_types.py +14 -0
  30. ert/config/parameter_config.py +1 -33
  31. ert/config/parsing/_option_dict.py +10 -2
  32. ert/config/parsing/config_errors.py +1 -1
  33. ert/config/parsing/config_keywords.py +2 -1
  34. ert/config/parsing/config_schema.py +23 -11
  35. ert/config/parsing/config_schema_deprecations.py +3 -3
  36. ert/config/parsing/config_schema_item.py +26 -11
  37. ert/config/parsing/context_values.py +3 -3
  38. ert/config/parsing/file_context_token.py +1 -1
  39. ert/config/parsing/observations_parser.py +6 -2
  40. ert/config/parsing/queue_system.py +9 -0
  41. ert/config/parsing/schema_item_type.py +1 -0
  42. ert/config/queue_config.py +42 -50
  43. ert/config/response_config.py +0 -8
  44. ert/config/rft_config.py +275 -0
  45. ert/config/summary_config.py +3 -8
  46. ert/config/surface_config.py +73 -26
  47. ert/config/workflow_fixtures.py +2 -1
  48. ert/config/workflow_job.py +135 -54
  49. ert/dark_storage/client/__init__.py +2 -2
  50. ert/dark_storage/client/_session.py +4 -4
  51. ert/dark_storage/client/client.py +2 -2
  52. ert/dark_storage/common.py +12 -3
  53. ert/dark_storage/compute/misfits.py +11 -7
  54. ert/dark_storage/endpoints/compute/misfits.py +6 -4
  55. ert/dark_storage/endpoints/ensembles.py +4 -0
  56. ert/dark_storage/endpoints/experiment_server.py +30 -24
  57. ert/dark_storage/endpoints/experiments.py +2 -2
  58. ert/dark_storage/endpoints/observations.py +8 -6
  59. ert/dark_storage/endpoints/parameters.py +4 -12
  60. ert/dark_storage/endpoints/responses.py +24 -5
  61. ert/dark_storage/json_schema/ensemble.py +3 -0
  62. ert/dark_storage/json_schema/experiment.py +1 -1
  63. ert/data/_measured_data.py +6 -5
  64. ert/ensemble_evaluator/__init__.py +8 -1
  65. ert/ensemble_evaluator/config.py +2 -1
  66. ert/ensemble_evaluator/evaluator.py +81 -29
  67. ert/ensemble_evaluator/event.py +6 -0
  68. ert/ensemble_evaluator/snapshot.py +3 -1
  69. ert/ensemble_evaluator/state.py +1 -0
  70. ert/field_utils/__init__.py +8 -0
  71. ert/field_utils/field_utils.py +228 -15
  72. ert/field_utils/grdecl_io.py +1 -1
  73. ert/field_utils/roff_io.py +1 -1
  74. ert/gui/__init__.py +5 -2
  75. ert/gui/ertnotifier.py +1 -1
  76. ert/gui/ertwidgets/__init__.py +23 -16
  77. ert/gui/ertwidgets/analysismoduleedit.py +2 -2
  78. ert/gui/ertwidgets/checklist.py +1 -1
  79. ert/gui/ertwidgets/closabledialog.py +2 -0
  80. ert/gui/ertwidgets/copyablelabel.py +2 -0
  81. ert/gui/ertwidgets/create_experiment_dialog.py +3 -1
  82. ert/gui/ertwidgets/ensembleselector.py +2 -2
  83. ert/gui/ertwidgets/listeditbox.py +2 -0
  84. ert/gui/ertwidgets/models/__init__.py +2 -0
  85. ert/gui/ertwidgets/models/activerealizationsmodel.py +5 -1
  86. ert/gui/ertwidgets/models/path_model.py +1 -1
  87. ert/gui/ertwidgets/models/targetensemblemodel.py +5 -1
  88. ert/gui/ertwidgets/models/text_model.py +4 -1
  89. ert/gui/ertwidgets/pathchooser.py +0 -3
  90. ert/gui/ertwidgets/searchbox.py +17 -4
  91. ert/gui/ertwidgets/stringbox.py +2 -0
  92. ert/gui/{suggestor → ertwidgets/suggestor}/_suggestor_message.py +13 -4
  93. ert/gui/{suggestor → ertwidgets/suggestor}/suggestor.py +63 -30
  94. ert/gui/main.py +41 -13
  95. ert/gui/main_window.py +3 -7
  96. ert/gui/model/fm_step_list.py +3 -0
  97. ert/gui/model/real_list.py +1 -0
  98. ert/gui/model/snapshot.py +1 -0
  99. ert/gui/simulation/combobox_with_description.py +3 -0
  100. ert/gui/simulation/ensemble_experiment_panel.py +8 -2
  101. ert/gui/simulation/ensemble_information_filter_panel.py +7 -2
  102. ert/gui/simulation/ensemble_smoother_panel.py +8 -2
  103. ert/gui/simulation/evaluate_ensemble_panel.py +17 -7
  104. ert/gui/simulation/experiment_panel.py +18 -6
  105. ert/gui/simulation/manual_update_panel.py +35 -10
  106. ert/gui/simulation/multiple_data_assimilation_panel.py +13 -9
  107. ert/gui/simulation/run_dialog.py +47 -20
  108. ert/gui/simulation/single_test_run_panel.py +6 -3
  109. ert/gui/simulation/view/progress_widget.py +2 -0
  110. ert/gui/simulation/view/realization.py +5 -1
  111. ert/gui/simulation/view/update.py +2 -0
  112. ert/gui/summarypanel.py +20 -1
  113. ert/gui/tools/event_viewer/panel.py +3 -4
  114. ert/gui/tools/event_viewer/tool.py +2 -0
  115. ert/gui/tools/load_results/load_results_panel.py +1 -1
  116. ert/gui/tools/load_results/load_results_tool.py +2 -0
  117. ert/gui/tools/manage_experiments/export_dialog.py +136 -0
  118. ert/gui/tools/manage_experiments/manage_experiments_panel.py +2 -0
  119. ert/gui/tools/manage_experiments/storage_info_widget.py +121 -16
  120. ert/gui/tools/manage_experiments/storage_widget.py +4 -3
  121. ert/gui/tools/plot/customize/color_chooser.py +5 -2
  122. ert/gui/tools/plot/customize/customize_plot_dialog.py +2 -0
  123. ert/gui/tools/plot/customize/default_customization_view.py +4 -0
  124. ert/gui/tools/plot/customize/limits_customization_view.py +3 -0
  125. ert/gui/tools/plot/customize/statistics_customization_view.py +3 -0
  126. ert/gui/tools/plot/customize/style_chooser.py +2 -0
  127. ert/gui/tools/plot/customize/style_customization_view.py +3 -0
  128. ert/gui/tools/plot/data_type_keys_widget.py +2 -0
  129. ert/gui/tools/plot/data_type_proxy_model.py +3 -0
  130. ert/gui/tools/plot/plot_api.py +50 -28
  131. ert/gui/tools/plot/plot_ensemble_selection_widget.py +17 -10
  132. ert/gui/tools/plot/plot_widget.py +15 -2
  133. ert/gui/tools/plot/plot_window.py +41 -19
  134. ert/gui/tools/plot/plottery/plot_config.py +2 -0
  135. ert/gui/tools/plot/plottery/plot_context.py +14 -0
  136. ert/gui/tools/plot/plottery/plots/__init__.py +2 -0
  137. ert/gui/tools/plot/plottery/plots/cesp.py +3 -1
  138. ert/gui/tools/plot/plottery/plots/distribution.py +6 -1
  139. ert/gui/tools/plot/plottery/plots/ensemble.py +13 -5
  140. ert/gui/tools/plot/plottery/plots/gaussian_kde.py +12 -2
  141. ert/gui/tools/plot/plottery/plots/histogram.py +3 -1
  142. ert/gui/tools/plot/plottery/plots/misfits.py +436 -0
  143. ert/gui/tools/plot/plottery/plots/observations.py +18 -4
  144. ert/gui/tools/plot/plottery/plots/statistics.py +62 -20
  145. ert/gui/tools/plot/plottery/plots/std_dev.py +3 -1
  146. ert/gui/tools/plot/widgets/clearable_line_edit.py +9 -0
  147. ert/gui/tools/plot/widgets/filter_popup.py +2 -0
  148. ert/gui/tools/plot/widgets/filterable_kw_list_model.py +3 -0
  149. ert/gui/tools/plugins/plugin.py +1 -1
  150. ert/gui/tools/plugins/plugins_tool.py +2 -0
  151. ert/gui/tools/plugins/process_job_dialog.py +3 -0
  152. ert/gui/tools/workflows/workflow_dialog.py +2 -0
  153. ert/gui/tools/workflows/workflows_tool.py +2 -0
  154. ert/libres_facade.py +5 -7
  155. ert/logging/__init__.py +4 -1
  156. ert/mode_definitions.py +2 -0
  157. ert/plugins/__init__.py +4 -6
  158. ert/plugins/hook_implementations/workflows/csv_export.py +2 -3
  159. ert/plugins/hook_implementations/workflows/gen_data_rft_export.py +10 -2
  160. ert/plugins/hook_specifications/__init__.py +0 -10
  161. ert/plugins/hook_specifications/jobs.py +0 -9
  162. ert/plugins/plugin_manager.py +53 -124
  163. ert/resources/forward_models/run_reservoirsimulator.py +8 -4
  164. ert/resources/forward_models/template_render.py +10 -10
  165. ert/resources/shell_scripts/delete_directory.py +2 -2
  166. ert/run_models/__init__.py +24 -6
  167. ert/run_models/_create_run_path.py +133 -38
  168. ert/run_models/ensemble_experiment.py +10 -4
  169. ert/run_models/ensemble_information_filter.py +8 -1
  170. ert/run_models/ensemble_smoother.py +9 -3
  171. ert/run_models/evaluate_ensemble.py +8 -6
  172. ert/run_models/event.py +7 -3
  173. ert/run_models/everest_run_model.py +337 -113
  174. ert/run_models/initial_ensemble_run_model.py +25 -24
  175. ert/run_models/manual_update.py +6 -3
  176. ert/run_models/manual_update_enif.py +37 -0
  177. ert/run_models/model_factory.py +78 -18
  178. ert/run_models/multiple_data_assimilation.py +22 -11
  179. ert/run_models/run_model.py +72 -73
  180. ert/run_models/single_test_run.py +7 -4
  181. ert/run_models/update_run_model.py +4 -2
  182. ert/runpaths.py +5 -6
  183. ert/sample_prior.py +9 -4
  184. ert/scheduler/__init__.py +10 -5
  185. ert/scheduler/driver.py +40 -0
  186. ert/scheduler/event.py +3 -1
  187. ert/scheduler/job.py +23 -13
  188. ert/scheduler/lsf_driver.py +15 -5
  189. ert/scheduler/openpbs_driver.py +10 -4
  190. ert/scheduler/scheduler.py +5 -0
  191. ert/scheduler/slurm_driver.py +20 -5
  192. ert/services/__init__.py +2 -2
  193. ert/services/_base_service.py +37 -20
  194. ert/services/_storage_main.py +20 -18
  195. ert/services/ert_server.py +317 -0
  196. ert/shared/_doc_utils/__init__.py +4 -2
  197. ert/shared/_doc_utils/ert_jobs.py +1 -4
  198. ert/shared/net_utils.py +43 -18
  199. ert/shared/storage/connection.py +3 -3
  200. ert/shared/version.py +3 -3
  201. ert/storage/__init__.py +14 -1
  202. ert/storage/local_ensemble.py +44 -13
  203. ert/storage/local_experiment.py +54 -34
  204. ert/storage/local_storage.py +90 -58
  205. ert/storage/migration/to10.py +3 -2
  206. ert/storage/migration/to11.py +9 -10
  207. ert/storage/migration/to12.py +19 -20
  208. ert/storage/migration/to13.py +28 -27
  209. ert/storage/migration/to14.py +3 -3
  210. ert/storage/migration/to15.py +25 -0
  211. ert/storage/migration/to16.py +38 -0
  212. ert/storage/migration/to17.py +42 -0
  213. ert/storage/migration/to18.py +11 -0
  214. ert/storage/migration/to19.py +34 -0
  215. ert/storage/migration/to20.py +23 -0
  216. ert/storage/migration/to21.py +25 -0
  217. ert/storage/migration/to6.py +3 -2
  218. ert/storage/migration/to7.py +12 -13
  219. ert/storage/migration/to8.py +9 -11
  220. ert/storage/migration/to9.py +5 -4
  221. ert/storage/realization_storage_state.py +7 -7
  222. ert/substitutions.py +12 -28
  223. ert/validation/active_range.py +7 -7
  224. ert/validation/ensemble_realizations_argument.py +4 -2
  225. ert/validation/rangestring.py +16 -16
  226. ert/workflow_runner.py +6 -3
  227. {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/METADATA +21 -15
  228. ert-19.0.0rc2.dist-info/RECORD +524 -0
  229. {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/WHEEL +1 -1
  230. everest/api/everest_data_api.py +14 -1
  231. everest/assets/everest_logo.svg +406 -0
  232. everest/bin/config_branch_script.py +30 -14
  233. everest/bin/everconfigdump_script.py +2 -10
  234. everest/bin/everest_script.py +53 -33
  235. everest/bin/everlint_script.py +3 -5
  236. everest/bin/kill_script.py +7 -5
  237. everest/bin/main.py +11 -24
  238. everest/bin/monitor_script.py +64 -35
  239. everest/bin/utils.py +58 -43
  240. everest/bin/visualization_script.py +23 -13
  241. everest/config/__init__.py +4 -1
  242. everest/config/control_config.py +81 -6
  243. everest/config/control_variable_config.py +4 -3
  244. everest/config/everest_config.py +102 -79
  245. everest/config/forward_model_config.py +5 -3
  246. everest/config/install_data_config.py +7 -5
  247. everest/config/install_job_config.py +45 -3
  248. everest/config/install_template_config.py +3 -3
  249. everest/config/optimization_config.py +19 -6
  250. everest/config/output_constraint_config.py +8 -2
  251. everest/config/server_config.py +6 -55
  252. everest/config/simulator_config.py +62 -17
  253. everest/config/utils.py +25 -105
  254. everest/config/validation_utils.py +34 -15
  255. everest/config_file_loader.py +30 -21
  256. everest/detached/__init__.py +0 -6
  257. everest/detached/client.py +7 -52
  258. everest/detached/everserver.py +19 -45
  259. everest/everest_storage.py +24 -40
  260. everest/gui/everest_client.py +2 -3
  261. everest/gui/main_window.py +2 -2
  262. everest/optimizer/everest2ropt.py +68 -42
  263. everest/optimizer/opt_model_transforms.py +15 -20
  264. everest/optimizer/utils.py +0 -29
  265. everest/plugins/hook_specs.py +0 -24
  266. everest/strings.py +1 -6
  267. everest/util/__init__.py +3 -1
  268. ert/config/everest_objective_config.py +0 -95
  269. ert/config/ext_param_config.py +0 -107
  270. ert/gui/tools/export/__init__.py +0 -3
  271. ert/gui/tools/export/export_panel.py +0 -83
  272. ert/gui/tools/export/export_tool.py +0 -67
  273. ert/gui/tools/export/exporter.py +0 -36
  274. ert/plugins/hook_specifications/ecl_config.py +0 -29
  275. ert/services/storage_service.py +0 -127
  276. ert/summary_key_type.py +0 -234
  277. ert-16.0.9.dist-info/RECORD +0 -521
  278. everest/bin/everexport_script.py +0 -53
  279. everest/config/sampler_config.py +0 -103
  280. everest/simulator/__init__.py +0 -88
  281. everest/simulator/everest_to_ert.py +0 -252
  282. /ert/gui/{suggestor → ertwidgets/suggestor}/__init__.py +0 -0
  283. /ert/gui/{suggestor → ertwidgets/suggestor}/_colors.py +0 -0
  284. {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/entry_points.txt +0 -0
  285. {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/licenses/COPYING +0 -0
  286. {ert-16.0.9.dist-info → ert-19.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -12,13 +12,12 @@ from typing import (
12
12
  from pydantic import AfterValidator, BaseModel, ConfigDict, Field, model_validator
13
13
  from ropt.enums import PerturbationType, VariableType
14
14
 
15
- from ert.config import ExtParamConfig
15
+ from ert.config import EverestControl, SamplerConfig
16
16
 
17
17
  from .control_variable_config import (
18
18
  ControlVariableConfig,
19
19
  ControlVariableGuessListConfig,
20
20
  )
21
- from .sampler_config import SamplerConfig
22
21
  from .validation_utils import (
23
22
  control_variables_validation,
24
23
  no_dots_in_string,
@@ -167,7 +166,8 @@ class ControlConfig(BaseModel):
167
166
  """
168
167
  ),
169
168
  )
170
- perturbation_magnitude: float = Field(
169
+ perturbation_magnitude: float | None = Field(
170
+ default=None,
171
171
  description=dedent(
172
172
  """
173
173
  The magnitude of the perturbation of all control variables in the
@@ -186,6 +186,8 @@ class ControlConfig(BaseModel):
186
186
  and `min` fields.
187
187
 
188
188
  This default value can be overridden at the variable level.
189
+
190
+ This field is required unless it is explicitly set for all variables.
189
191
  """
190
192
  ),
191
193
  )
@@ -248,6 +250,19 @@ class ControlConfig(BaseModel):
248
250
  )
249
251
  return values
250
252
 
253
+ @model_validator(mode="after")
254
+ def validate_perturbation_magnitude(self) -> Self:
255
+ if self.perturbation_magnitude is None and any(
256
+ variable
257
+ for variable in self.variables
258
+ if variable.perturbation_magnitude is None
259
+ ):
260
+ raise ValueError(
261
+ "A default perturbation_magnitude for all variables must be set "
262
+ "when not all variables define it explicitly."
263
+ )
264
+ return self
265
+
251
266
  @model_validator(mode="after")
252
267
  def validate_variables(self) -> Self:
253
268
  if self.variables is None:
@@ -274,7 +289,7 @@ class ControlConfig(BaseModel):
274
289
  def __hash__(self) -> int:
275
290
  return hash(self.name)
276
291
 
277
- def __eq__(self, other: Any) -> bool:
292
+ def __eq__(self, other: object) -> bool:
278
293
  return isinstance(other, self.__class__) and other.name == self.name
279
294
 
280
295
  @property
@@ -313,9 +328,69 @@ class ControlConfig(BaseModel):
313
328
  extra="forbid",
314
329
  )
315
330
 
316
- def to_ert_parameter_config(self) -> ExtParamConfig:
317
- return ExtParamConfig(
331
+ def to_ert_parameter_config(self) -> EverestControl:
332
+ types = []
333
+ initial_guesses = []
334
+ control_types = []
335
+ enabled = []
336
+ mins = []
337
+ maxs = []
338
+ perturbation_types = []
339
+ perturbation_magnitudes = []
340
+ scaled_ranges = []
341
+ samplers = []
342
+
343
+ def _parse_variable(
344
+ variable: ControlVariableConfig | ControlVariableGuessListConfig,
345
+ initial_guess: float | None = None,
346
+ ) -> None:
347
+ types.append(self.type)
348
+ initial_guesses.append(
349
+ initial_guess
350
+ if initial_guess is not None
351
+ else variable.initial_guess
352
+ if variable.initial_guess is not None
353
+ else self.initial_guess
354
+ )
355
+ control_types.append(self.control_type)
356
+ enabled.append(
357
+ variable.enabled if variable.enabled is not None else self.enabled
358
+ )
359
+ mins.append(variable.min if variable.min is not None else self.min)
360
+ maxs.append(variable.max if variable.max is not None else self.max)
361
+ perturbation_types.append(variable.perturbation_type)
362
+ perturbation_magnitudes.append(
363
+ variable.perturbation_magnitude
364
+ if variable.perturbation_magnitude is not None
365
+ else self.perturbation_magnitude
366
+ )
367
+ scaled_ranges.append(
368
+ variable.scaled_range
369
+ if variable.scaled_range is not None
370
+ else self.scaled_range
371
+ )
372
+ samplers.append(variable.sampler or self.sampler)
373
+
374
+ for variable in self.variables:
375
+ if isinstance(variable, ControlVariableConfig):
376
+ _parse_variable(variable)
377
+ else:
378
+ for initial_guess in variable.initial_guess:
379
+ _parse_variable(variable, initial_guess=initial_guess)
380
+
381
+ return EverestControl(
318
382
  name=self.name,
319
383
  input_keys=self.formatted_control_names,
384
+ input_keys_dotdash=self.formatted_control_names_dotdash,
320
385
  output_file=self.name + ".json",
386
+ types=types,
387
+ initial_guesses=initial_guesses,
388
+ control_types=control_types,
389
+ enabled=enabled,
390
+ min=mins,
391
+ max=maxs,
392
+ perturbation_types=perturbation_types,
393
+ perturbation_magnitudes=perturbation_magnitudes,
394
+ scaled_ranges=scaled_ranges,
395
+ samplers=samplers,
321
396
  )
@@ -12,7 +12,8 @@ from pydantic import (
12
12
  )
13
13
  from ropt.enums import VariableType
14
14
 
15
- from .sampler_config import SamplerConfig
15
+ from ert.config import SamplerConfig
16
+
16
17
  from .validation_utils import no_dots_in_string, valid_range
17
18
 
18
19
 
@@ -200,7 +201,7 @@ class ControlVariableConfig(_ControlVariable):
200
201
  def __hash__(self) -> int:
201
202
  return hash(self.name) + hash(self.index)
202
203
 
203
- def __eq__(self, other: Any) -> bool:
204
+ def __eq__(self, other: object) -> bool:
204
205
  return (
205
206
  isinstance(other, self.__class__)
206
207
  and other.name == self.name
@@ -233,7 +234,7 @@ class ControlVariableGuessListConfig(_ControlVariable):
233
234
  def __hash__(self) -> int:
234
235
  return hash(self.name)
235
236
 
236
- def __eq__(self, other: Any) -> bool:
237
+ def __eq__(self, other: object) -> bool:
237
238
  return isinstance(other, self.__class__) and other.name == self.name
238
239
 
239
240
  @property
@@ -2,7 +2,7 @@ import logging
2
2
  import os
3
3
  from argparse import ArgumentParser
4
4
  from copy import copy
5
- from io import StringIO
5
+ from enum import StrEnum
6
6
  from itertools import chain
7
7
  from pathlib import Path
8
8
  from sys import float_info
@@ -12,6 +12,8 @@ from typing import (
12
12
  Any,
13
13
  Optional,
14
14
  Self,
15
+ TextIO,
16
+ TypeVar,
15
17
  no_type_check,
16
18
  )
17
19
 
@@ -27,10 +29,17 @@ from pydantic.json_schema import SkipJsonSchema
27
29
  from pydantic_core import ErrorDetails
28
30
  from pydantic_core.core_schema import ValidationInfo
29
31
  from ruamel.yaml import YAML, YAMLError
30
-
31
- from ert.base_model_context import BaseModelWithContextSupport
32
- from ert.config import ConfigWarning, QueueSystem
33
- from ert.plugins import ErtPluginContext
32
+ from ruamel.yaml.nodes import ScalarNode
33
+ from ruamel.yaml.representer import Representer
34
+
35
+ from ert.base_model_context import BaseModelWithContextSupport, use_runtime_plugins
36
+ from ert.config import (
37
+ ConfigWarning,
38
+ EverestConstraintsConfig,
39
+ EverestObjectivesConfig,
40
+ QueueSystem,
41
+ )
42
+ from ert.plugins import get_site_plugins
34
43
  from everest.config.install_template_config import InstallTemplateConfig
35
44
  from everest.config.server_config import ServerConfig
36
45
  from everest.config.validation_utils import (
@@ -60,7 +69,11 @@ from .forward_model_config import (
60
69
  )
61
70
  from .input_constraint_config import InputConstraintConfig
62
71
  from .install_data_config import InstallDataConfig
63
- from .install_job_config import InstallJobConfig
72
+ from .install_job_config import (
73
+ InstallForwardModelStepConfig,
74
+ InstallJobConfig,
75
+ InstallWorkflowJobConfig,
76
+ )
64
77
  from .model_config import ModelConfig
65
78
  from .objective_function_config import ObjectiveFunctionConfig
66
79
  from .optimization_config import OptimizationConfig
@@ -125,9 +138,7 @@ def _convert_to_everest_validation_error(
125
138
  """
126
139
  everest_validation_error = EverestValidationError()
127
140
 
128
- file_content = []
129
- with open(config_path, encoding="utf-8") as f:
130
- file_content = f.readlines()
141
+ file_content = Path(config_path).read_text(encoding="utf-8").splitlines()
131
142
 
132
143
  for error in validation_error.errors(
133
144
  include_context=True, include_input=True, include_url=False
@@ -164,6 +175,9 @@ def _find_loc(loc: tuple[int | str, ...] | None, file_content: list[str]) -> int
164
175
  return None
165
176
 
166
177
 
178
+ InstallJobConfigSubclass = TypeVar("InstallJobConfigSubclass", bound=InstallJobConfig)
179
+
180
+
167
181
  class EverestConfig(BaseModelWithContextSupport):
168
182
  controls: Annotated[list[ControlConfig], AfterValidator(unique_items)] = Field(
169
183
  description=dedent(
@@ -177,18 +191,18 @@ class EverestConfig(BaseModelWithContextSupport):
177
191
  ```yaml
178
192
  controls:
179
193
  - name: point
180
- type: generic_control
181
- min: -1.0
194
+ type: generic_control
195
+ min: -1.0
196
+ max: 1.0
197
+ initial_guess: 0.1
198
+ perturbation_magnitude: 0.001
199
+ variables:
200
+ - name: x
201
+ initial_guess: 0.5
202
+ - name: y
203
+ min: 0.0
182
204
  max: 1.0
183
- initial_guess: 0.1
184
- perturbation_magnitude: 0.001
185
- variables:
186
- - name: x
187
- initial_guess: 0.5
188
- - name: y
189
- min: 0.0
190
- max: 1.0
191
- - name: z
205
+ - name: z
192
206
  ```
193
207
 
194
208
  This defines a group of controls names `point`, with variables `x`,
@@ -210,17 +224,18 @@ class EverestConfig(BaseModelWithContextSupport):
210
224
  ```yaml
211
225
  controls:
212
226
  - name: point
213
- max: 1.0
214
- min: -1.0
215
- initial_guess: 0.25
216
- type: generic_control
217
- variables:
218
- - name: x
219
- index: 1
220
- - name: x
221
- index: 2
222
- - name: x
223
- index: 3
227
+ max: 1.0
228
+ min: -1.0
229
+ initial_guess: 0.25
230
+ perturbation_magnitude: 0.001
231
+ type: generic_control
232
+ variables:
233
+ - name: x
234
+ index: 1
235
+ - name: x
236
+ index: 2
237
+ - name: x
238
+ index: 3
224
239
  ```
225
240
 
226
241
  This defines three variables `point.x.1`, `point.x.2` and
@@ -236,12 +251,13 @@ class EverestConfig(BaseModelWithContextSupport):
236
251
  ```yaml
237
252
  controls:
238
253
  - name: point
239
- max: 1.0
240
- min: -1.0
241
- type: generic_control
242
- variables:
243
- - name: x
244
- initial_guess: [0.25, 0.25, 0.25]
254
+ max: 1.0
255
+ min: -1.0
256
+ perturbation_magnitude: 0.001
257
+ type: generic_control
258
+ variables:
259
+ - name: x
260
+ initial_guess: [0.25, 0.25, 0.25]
245
261
  ```
246
262
  """
247
263
  ),
@@ -365,7 +381,7 @@ class EverestConfig(BaseModelWithContextSupport):
365
381
  """
366
382
  ),
367
383
  )
368
- install_jobs: list[InstallJobConfig] = Field(
384
+ install_jobs: list[InstallForwardModelStepConfig] = Field(
369
385
  default_factory=list,
370
386
  description=dedent(
371
387
  """
@@ -377,7 +393,7 @@ class EverestConfig(BaseModelWithContextSupport):
377
393
  ),
378
394
  validate_default=True,
379
395
  )
380
- install_workflow_jobs: list[InstallJobConfig] = Field(
396
+ install_workflow_jobs: list[InstallWorkflowJobConfig] = Field(
381
397
  default_factory=list,
382
398
  description=dedent(
383
399
  """
@@ -624,7 +640,7 @@ class EverestConfig(BaseModelWithContextSupport):
624
640
 
625
641
  @model_validator(mode="after")
626
642
  def validate_job_executables(self) -> Self: # pylint: disable=E0213
627
- def _check_jobs(jobs: list[InstallJobConfig]) -> list[str]:
643
+ def _check_jobs(jobs: list[InstallJobConfigSubclass]) -> list[str]:
628
644
  errors = []
629
645
  for job in jobs:
630
646
  if job.executable is None:
@@ -1017,31 +1033,6 @@ to read summary data from forward model, do:
1017
1033
 
1018
1034
  return the_dict
1019
1035
 
1020
- @classmethod
1021
- def with_defaults(cls, **kwargs: Any) -> Self:
1022
- """
1023
- Creates an Everest config with default values. Useful for initializing a config
1024
- without having to provide empty defaults.
1025
- """
1026
- defaults = {
1027
- "controls": [
1028
- {
1029
- "name": "default_group",
1030
- "type": "generic_control",
1031
- "initial_guess": 0.5,
1032
- "perturbation_magnitude": 0.01,
1033
- "variables": [
1034
- {"name": "default_name", "min": 0, "max": 1},
1035
- ],
1036
- }
1037
- ],
1038
- "objective_functions": [{"name": "default"}],
1039
- "config_path": ".",
1040
- "model": {"realizations": [0]},
1041
- }
1042
-
1043
- return cls.with_plugins({**defaults, **kwargs}) # type: ignore
1044
-
1045
1036
  @staticmethod
1046
1037
  def lint_config_dict(config: ConfigDict) -> list[ErrorDetails]:
1047
1038
  try:
@@ -1073,7 +1064,7 @@ to read summary data from forward model, do:
1073
1064
 
1074
1065
  @classmethod
1075
1066
  def with_plugins(cls, config_dict: dict[str, Any] | ConfigDict) -> Self:
1076
- with ErtPluginContext():
1067
+ with use_runtime_plugins(get_site_plugins()):
1077
1068
  return cls(**config_dict)
1078
1069
 
1079
1070
  @staticmethod
@@ -1095,26 +1086,58 @@ to read summary data from forward model, do:
1095
1086
  except ValueError as e:
1096
1087
  parser.error(f"Loading config file <{config_path}> failed with: {e}")
1097
1088
 
1098
- def dump(self, fname: str | None = None) -> str | None:
1099
- """Write a config dict to file or return it if fname is None."""
1100
- stripped_conf = self.to_dict()
1089
+ def write_to_file(
1090
+ self, output_file: str | Path | TextIO, drop_config_path: bool = False
1091
+ ) -> None:
1092
+ dictconf = self.to_dict()
1093
+ if drop_config_path:
1094
+ del dictconf["config_path"]
1095
+ if isinstance(output_file, str):
1096
+ output_file = Path(output_file)
1097
+ self.write_dict_to_file(dictconf, output_file)
1101
1098
 
1102
- del stripped_conf["config_path"]
1099
+ @staticmethod
1100
+ def write_dict_to_file(
1101
+ config: dict[str, Any], output_file: Path | TextIO, safe_and_pure: bool = True
1102
+ ) -> None:
1103
+ yaml = YAML(typ="safe", pure=True) if safe_and_pure else YAML()
1104
+ yaml.indent(mapping=2, sequence=4, offset=2)
1105
+ yaml.preserve_quotes = True
1103
1106
 
1104
- yaml = YAML(typ="safe", pure=True)
1105
- yaml.default_flow_style = False
1106
- if fname is None:
1107
- with StringIO() as sio:
1108
- yaml.dump(stripped_conf, sio)
1109
- return sio.getvalue()
1107
+ def strenum_representer(dumper: Representer, data: StrEnum) -> ScalarNode:
1108
+ return dumper.represent_str(data.value)
1110
1109
 
1111
- with open(fname, "w", encoding="utf-8") as out:
1112
- yaml.dump(stripped_conf, out)
1110
+ yaml.representer.add_multi_representer(StrEnum, strenum_representer)
1113
1111
 
1114
- return None
1112
+ yaml.default_flow_style = False
1113
+ yaml.dump(config, output_file)
1115
1114
 
1116
1115
  @property
1117
1116
  def server_queue_system(self) -> QueueSystem:
1118
1117
  assert self.server is not None
1119
1118
  assert self.server.queue_system is not None
1120
1119
  return self.server.queue_system.name
1120
+
1121
+ def create_ert_objectives_config(self) -> EverestObjectivesConfig:
1122
+ objective_names = [o.name for o in self.objective_functions]
1123
+ return EverestObjectivesConfig(
1124
+ keys=objective_names,
1125
+ input_files=objective_names,
1126
+ weights=[o.weight for o in self.objective_functions],
1127
+ scales=[o.scale for o in self.objective_functions],
1128
+ objective_types=[o.type for o in self.objective_functions],
1129
+ )
1130
+
1131
+ def create_ert_output_constraints_config(self) -> EverestConstraintsConfig | None:
1132
+ if len(self.output_constraints) == 0:
1133
+ return None
1134
+
1135
+ constraint_names = [c.name for c in self.output_constraints]
1136
+ return EverestConstraintsConfig(
1137
+ keys=constraint_names,
1138
+ input_files=constraint_names,
1139
+ scales=[c.scale for c in self.output_constraints],
1140
+ targets=[c.target for c in self.output_constraints],
1141
+ upper_bounds=[c.upper_bound for c in self.output_constraints],
1142
+ lower_bounds=[c.lower_bound for c in self.output_constraints],
1143
+ )
@@ -9,7 +9,9 @@ from pydantic import (
9
9
  )
10
10
 
11
11
  from ert.base_model_context import BaseModelWithContextSupport
12
- from ert.config import ForwardModelStep
12
+ from ert.config import (
13
+ SiteOrUserForwardModelStep,
14
+ )
13
15
 
14
16
 
15
17
  class ForwardModelResult(BaseModelWithContextSupport):
@@ -74,8 +76,8 @@ class ForwardModelStepConfig(BaseModelWithContextSupport):
74
76
  return values
75
77
 
76
78
  def to_ert_forward_model_step(
77
- self, installed_fm_steps: dict[str, ForwardModelStep]
78
- ) -> ForwardModelStep:
79
+ self, installed_fm_steps: dict[str, SiteOrUserForwardModelStep]
80
+ ) -> SiteOrUserForwardModelStep:
79
81
  fm_name, *arglist = self.job.split()
80
82
  match fm_name:
81
83
  # All three reservoir simulator fm_steps map to
@@ -9,11 +9,13 @@ from typing import Any
9
9
 
10
10
  from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
11
11
 
12
- from ert.config import ForwardModelStep
12
+ from ert.config.forward_model_step import (
13
+ SiteOrUserForwardModelStep,
14
+ )
13
15
 
14
16
 
15
17
  def _is_dir_all_model(source: str, model_realizations: list[int]) -> bool:
16
- """Expands <GEO_ID> for all realizations and if:
18
+ """Expands <REALIZATION_ID> for all realizations and if:
17
19
  - all are directories, returns True,
18
20
  - all are files, returns False,
19
21
  - some are non-existing, raises an AssertionError
@@ -21,7 +23,7 @@ def _is_dir_all_model(source: str, model_realizations: list[int]) -> bool:
21
23
 
22
24
  is_dir = []
23
25
  for model_realization in model_realizations:
24
- model_source = source.replace("<GEO_ID>", str(model_realization))
26
+ model_source = source.replace("<REALIZATION_ID>", str(model_realization))
25
27
  if not os.path.exists(model_source):
26
28
  msg = (
27
29
  "Expected source to exist for data installation, "
@@ -126,8 +128,8 @@ class InstallDataConfig(BaseModel):
126
128
  config_directory: str,
127
129
  output_directory: str,
128
130
  model_realizations: list[int],
129
- installed_fm_steps: dict[str, ForwardModelStep],
130
- ) -> ForwardModelStep:
131
+ installed_fm_steps: dict[str, SiteOrUserForwardModelStep],
132
+ ) -> SiteOrUserForwardModelStep:
131
133
  target = self.target
132
134
 
133
135
  def _missing_fm_msg(fm_name: str) -> str:
@@ -5,7 +5,12 @@ from textwrap import dedent
5
5
 
6
6
  from pydantic import BaseModel, Field, model_validator
7
7
 
8
- from ert.config import ForwardModelStep, forward_model_step_from_config_contents
8
+ from ert.config import (
9
+ ExecutableWorkflow,
10
+ UserInstalledForwardModelStep,
11
+ forward_model_step_from_config_contents,
12
+ workflow_job_from_file,
13
+ )
9
14
 
10
15
 
11
16
  class InstallJobConfig(BaseModel, extra="forbid"):
@@ -44,12 +49,24 @@ class InstallJobConfig(BaseModel, extra="forbid"):
44
49
  raise ValueError("Either source or executable must be provided")
45
50
  return self
46
51
 
47
- def to_ert_forward_model_step(self, config_directory: str) -> ForwardModelStep:
52
+ @model_validator(mode="after")
53
+ def validate_source_exists(self) -> InstallJobConfig:
54
+ if self.source is not None and not Path(self.source).is_file():
55
+ raise ValueError(f"No such file or directory: {self.source}")
56
+ return self
57
+
58
+
59
+ class InstallForwardModelStepConfig(InstallJobConfig):
60
+ def to_ert_forward_model_step(
61
+ self, config_directory: str
62
+ ) -> UserInstalledForwardModelStep:
48
63
  if self.executable is not None:
49
64
  executable = Path(self.executable)
50
65
  if not executable.is_absolute():
51
66
  executable = Path(config_directory) / executable
52
- return ForwardModelStep(name=self.name, executable=str(executable))
67
+ return UserInstalledForwardModelStep(
68
+ name=self.name, executable=str(executable)
69
+ )
53
70
  else:
54
71
  assert (
55
72
  self.source is not None
@@ -59,3 +76,28 @@ class InstallJobConfig(BaseModel, extra="forbid"):
59
76
  config_file=self.source,
60
77
  name=self.name,
61
78
  )
79
+
80
+
81
+ class InstallWorkflowJobConfig(InstallJobConfig):
82
+ def to_ert_executable_workflow(self, config_directory: str) -> ExecutableWorkflow:
83
+ if self.executable is not None:
84
+ executable = Path(self.executable)
85
+ if not executable.is_absolute():
86
+ executable = Path(config_directory) / executable
87
+ return ExecutableWorkflow(
88
+ name=self.name,
89
+ min_args=None,
90
+ max_args=None,
91
+ arg_types=[],
92
+ executable=str(executable),
93
+ )
94
+ else:
95
+ assert self.source is not None
96
+ workflow = workflow_job_from_file(
97
+ config_file=str(Path(config_directory) / self.source),
98
+ name=self.name,
99
+ origin="user",
100
+ )
101
+ if not isinstance(workflow, ExecutableWorkflow):
102
+ raise ValueError(f"Workflow must be an executable: {self.source}")
103
+ return workflow
@@ -4,7 +4,7 @@ from textwrap import dedent
4
4
 
5
5
  from pydantic import BaseModel, Field
6
6
 
7
- from ert.config import ForwardModelStep
7
+ from ert.config import SiteOrUserForwardModelStep
8
8
  from everest.strings import EVEREST
9
9
 
10
10
 
@@ -38,9 +38,9 @@ class InstallTemplateConfig(BaseModel, extra="forbid"):
38
38
  def to_ert_forward_model_step(
39
39
  self,
40
40
  control_names: list[str],
41
- installed_fm_steps: dict[str, ForwardModelStep],
41
+ installed_fm_steps: dict[str, SiteOrUserForwardModelStep],
42
42
  well_path: str,
43
- ) -> ForwardModelStep:
43
+ ) -> SiteOrUserForwardModelStep:
44
44
  fm_step_instance = copy.deepcopy(installed_fm_steps.get("template_render"))
45
45
  if fm_step_instance is None:
46
46
  raise KeyError(
@@ -1,11 +1,12 @@
1
+ import importlib
1
2
  import logging
2
3
  from textwrap import dedent
3
4
  from typing import Any, Self
4
5
 
5
6
  from pydantic import BaseModel, Field, model_validator
7
+ from ropt.workflow import find_optimizer_plugin, validate_optimizer_options
6
8
 
7
9
  from everest.config.cvar_config import CVaRConfig
8
- from everest.optimizer.utils import get_ropt_plugin_manager
9
10
  from everest.strings import EVEREST
10
11
 
11
12
 
@@ -346,15 +347,27 @@ class OptimizationConfig(BaseModel, extra="forbid"):
346
347
  else f"{self.backend}/{self.algorithm}"
347
348
  )
348
349
 
349
- plugin_manager = get_ropt_plugin_manager()
350
- plugin_name = plugin_manager.get_plugin_name("optimizer", algorithm)
350
+ try:
351
+ plugin_name = find_optimizer_plugin(algorithm)
352
+ except ValueError:
353
+ raise
354
+ except Exception as exc:
355
+ ert_version = importlib.metadata.version("ert")
356
+ ropt_version = importlib.metadata.version("ropt")
357
+ msg = (
358
+ f"Error while initializing ropt:\n\n{exc}.\n\n"
359
+ "There may a be version mismatch between "
360
+ f"ERT ({ert_version}) and ropt ({ropt_version})\n"
361
+ "If the installation is correct, please report this as a bug."
362
+ )
363
+ raise RuntimeError(msg) from exc
351
364
  if plugin_name is None:
352
365
  raise ValueError(f"Optimizer algorithm '{algorithm}' not found")
353
366
  self._optimization_plugin_name = plugin_name
354
367
 
355
- plugin_manager.get_plugin("optimizer", algorithm).validate_options(
356
- self.algorithm, self.options or self.backend_options
357
- )
368
+ options = self.options or self.backend_options
369
+ if options:
370
+ validate_optimizer_options(algorithm, options)
358
371
 
359
372
  self.backend = None
360
373
  self.algorithm = algorithm
@@ -74,10 +74,16 @@ class OutputConstraintConfig(BaseModel, extra="forbid"):
74
74
  @model_validator(mode="before")
75
75
  @classmethod
76
76
  def validate_target_or_bounds(cls, values: dict[str, Any]) -> dict[str, Any]:
77
- if "target" in values and ("lower_bound" in values or "upper_bound" in values):
77
+ if (values.get("target") is not None) and (
78
+ "lower_bound" in values or "upper_bound" in values
79
+ ):
78
80
  raise ValueError("Can not combine target and bounds")
79
81
  elif not any(
80
- ("target" in values, "lower_bound" in values, "upper_bound" in values)
82
+ (
83
+ (values.get("target") is not None),
84
+ "lower_bound" in values,
85
+ "upper_bound" in values,
86
+ )
81
87
  ):
82
88
  raise ValueError("Must provide target or lower_bound/upper_bound")
83
89
  return values