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
@@ -10,11 +10,16 @@ import xarray as xr
10
10
  from pydantic import field_serializer
11
11
  from surfio import IrapHeader, IrapSurface
12
12
 
13
+ from ert.field_utils import (
14
+ calc_rho_for_2d_grid_layer,
15
+ transform_local_ellipse_angle_to_local_coords,
16
+ transform_positions_to_local_field_coordinates,
17
+ )
13
18
  from ert.substitutions import substitute_runpath_name
14
19
 
15
20
  from ._str_to_bool import str_to_bool
16
21
  from .field import create_flattened_cube_graph
17
- from .parameter_config import InvalidParameterFile, ParameterConfig, ParameterMetadata
22
+ from .parameter_config import InvalidParameterFile, ParameterConfig
18
23
  from .parsing import ConfigValidationError, ErrorInfo
19
24
 
20
25
  if TYPE_CHECKING:
@@ -46,6 +51,7 @@ class SurfaceMismatchError(InvalidParameterFile):
46
51
 
47
52
  class SurfaceConfig(ParameterConfig):
48
53
  type: Literal["surface"] = "surface"
54
+ dimensionality: Literal[2] = 2
49
55
  ncol: int
50
56
  nrow: int
51
57
  xori: float
@@ -70,25 +76,10 @@ class SurfaceConfig(ParameterConfig):
70
76
  def parameter_keys(self) -> list[str]:
71
77
  return []
72
78
 
73
- @property
74
- def metadata(self) -> list[ParameterMetadata]:
75
- return [
76
- ParameterMetadata(
77
- key=self.name,
78
- dimensionality=2,
79
- transformation=None,
80
- userdata={
81
- "data_origin": "SURFACE",
82
- "nx": self.ncol,
83
- "ny": self.nrow,
84
- },
85
- )
86
- ]
87
-
88
79
  @classmethod
89
- def from_config_list(cls, surface: list[str | dict[str, str]]) -> Self:
90
- name = cast(str, surface[0])
91
- options = cast(dict[str, str], surface[1])
80
+ def from_config_list(cls, config_list: list[str | dict[str, str]]) -> Self:
81
+ name = cast(str, config_list[0])
82
+ options = cast(dict[str, str], config_list[1])
92
83
  init_file = options.get("INIT_FILES")
93
84
  out_file = options.get("OUTPUT_FILE")
94
85
  base_surface = options.get("BASE_SURFACE")
@@ -97,23 +88,27 @@ class SurfaceConfig(ParameterConfig):
97
88
  errors = []
98
89
  if not out_file:
99
90
  errors.append(
100
- ErrorInfo("Missing required OUTPUT_FILE").set_context(surface)
91
+ ErrorInfo("Missing required OUTPUT_FILE").set_context(config_list)
101
92
  )
102
93
  if not init_file:
103
- errors.append(ErrorInfo("Missing required INIT_FILES").set_context(surface))
94
+ errors.append(
95
+ ErrorInfo("Missing required INIT_FILES").set_context(config_list)
96
+ )
104
97
  elif not forward_init and not ("%d" in init_file or "<IENS>" in init_file):
105
98
  errors.append(
106
99
  ErrorInfo(
107
100
  "INIT_FILES must contain %d or <IENS> when FORWARD_INIT:FALSE"
108
- ).set_context(surface)
101
+ ).set_context(config_list)
109
102
  )
110
103
  if not base_surface:
111
104
  errors.append(
112
- ErrorInfo("Missing required BASE_SURFACE").set_context(surface)
105
+ ErrorInfo("Missing required BASE_SURFACE").set_context(config_list)
113
106
  )
114
107
  elif not Path(base_surface).exists():
115
108
  errors.append(
116
- ErrorInfo(f"BASE_SURFACE:{base_surface} not found").set_context(surface)
109
+ ErrorInfo(f"BASE_SURFACE:{base_surface} not found").set_context(
110
+ config_list
111
+ )
117
112
  )
118
113
  if errors:
119
114
  raise ConfigValidationError.from_collected(errors)
@@ -121,11 +116,11 @@ class SurfaceConfig(ParameterConfig):
121
116
  assert out_file is not None
122
117
  assert base_surface is not None
123
118
  try:
124
- surf = IrapSurface.from_ascii_file(base_surface)
119
+ surf = IrapSurface.from_ascii_file(Path(base_surface))
125
120
  yflip = -1 if surf.header.yinc < 0 else 1
126
121
  except Exception as err:
127
122
  raise ConfigValidationError.with_context(
128
- f"Could not load surface {base_surface!r}", surface
123
+ f"Could not load surface {base_surface!r}", config_list
129
124
  ) from err
130
125
  return cls(
131
126
  ncol=surf.header.ncol,
@@ -244,3 +239,55 @@ class SurfaceConfig(ParameterConfig):
244
239
  this flattening process"""
245
240
 
246
241
  return create_flattened_cube_graph(px=self.ncol, py=self.nrow, pz=1)
242
+
243
+ def calc_rho_for_2d_grid_layer(
244
+ self,
245
+ obs_xpos: npt.NDArray[np.float64],
246
+ obs_ypos: npt.NDArray[np.float64],
247
+ obs_main_range: npt.NDArray[np.float64],
248
+ obs_perp_range: npt.NDArray[np.float64],
249
+ obs_anisotropy_angle: npt.NDArray[np.float64],
250
+ ) -> npt.NDArray[np.float64]:
251
+ """Function to calculate scaling values to be used in the RHO matrix
252
+ for distance-based localization.
253
+
254
+ Args:
255
+ obs_xpos: x-coordinates in global coordinates of observations
256
+ obs_ypos: y-coordinates in global coordinates of observations
257
+ obs_main_range: Size of influence ellipse main principal direction.
258
+ obs_perp_range: Size of influence ellipse second principal direction.
259
+ obs_anisotropy_angle: Rotation angle anticlock wise of main principal
260
+ direction of influence ellipse relative to global coordinate
261
+ system's x-axis.
262
+
263
+ Returns:
264
+ Scaling values (elements of the RHO matrix) as a numpy array
265
+ of shape=(nx,ny,nobservations)
266
+
267
+ """
268
+ # Transform observation positions to local surface coordinates
269
+ xpos, ypos = transform_positions_to_local_field_coordinates(
270
+ (self.xori, self.yori), self.rotation, obs_xpos, obs_ypos
271
+ )
272
+ # Transform ellipse orientation to local surface coordinates
273
+ rotation_angle_of_localization_ellipse = (
274
+ transform_local_ellipse_angle_to_local_coords(
275
+ self.rotation, obs_anisotropy_angle
276
+ )
277
+ )
278
+
279
+ # Assume the coordinate system is not flipped.
280
+ # This means the right_handed_grid_indexing is False
281
+ assert self.yflip == 1
282
+ return calc_rho_for_2d_grid_layer(
283
+ self.ncol,
284
+ self.nrow,
285
+ self.xinc,
286
+ self.yinc,
287
+ xpos,
288
+ ypos,
289
+ obs_main_range,
290
+ obs_perp_range,
291
+ rotation_angle_of_localization_ellipse,
292
+ right_handed_grid_indexing=False,
293
+ )
@@ -6,12 +6,13 @@ import typing
6
6
  from dataclasses import dataclass, fields
7
7
  from typing import TYPE_CHECKING, Literal
8
8
 
9
- from PyQt6.QtWidgets import QWidget
10
9
  from typing_extensions import TypedDict
11
10
 
12
11
  from ert.config.parsing.hook_runtime import HookRuntime
13
12
 
14
13
  if TYPE_CHECKING:
14
+ from PyQt6.QtWidgets import QWidget
15
+
15
16
  from ert.config import ESSettings, ObservationSettings
16
17
  from ert.runpaths import Runpaths
17
18
  from ert.storage import Ensemble, Storage
@@ -6,10 +6,23 @@ import os
6
6
  import textwrap
7
7
  from abc import ABC, abstractmethod
8
8
  from dataclasses import field
9
- from typing import TYPE_CHECKING, Self, TypeAlias, cast
9
+ from typing import (
10
+ TYPE_CHECKING,
11
+ Annotated,
12
+ Any,
13
+ Literal,
14
+ Self,
15
+ TypeAlias,
16
+ cast,
17
+ )
10
18
 
11
- from pydantic import field_serializer, field_validator, model_validator
19
+ from pydantic import (
20
+ Field,
21
+ model_serializer,
22
+ model_validator,
23
+ )
12
24
  from pydantic_core.core_schema import ValidationInfo
25
+ from typing_extensions import TypedDict
13
26
 
14
27
  from ert.base_model_context import BaseModelWithContextSupport
15
28
 
@@ -42,7 +55,11 @@ def workflow_job_parser(file: str) -> ConfigDict:
42
55
  return parse(file, schema=schema)
43
56
 
44
57
 
45
- def workflow_job_from_file(config_file: str, name: str | None = None) -> WorkflowJob:
58
+ def workflow_job_from_file(
59
+ config_file: str,
60
+ origin: Literal["user", "site"] = "site",
61
+ name: str | None = None,
62
+ ) -> WorkflowJob:
46
63
  if not name:
47
64
  name = os.path.basename(config_file)
48
65
 
@@ -71,20 +88,32 @@ def workflow_job_from_file(config_file: str, name: str | None = None) -> Workflo
71
88
  )
72
89
  logger.warning(msg)
73
90
  ConfigWarning.deprecation_warn(msg, content_dict["SCRIPT"])
91
+
74
92
  try:
75
93
  ert_script = ErtScript.loadScriptFromFile(script)
76
94
  # Bare Exception here as we have no control
77
95
  # of exceptions in the loaded ErtScript
78
96
  except Exception as err:
79
97
  raise ErtScriptLoadFailure(f"Failed to load {name}: {err}") from err
80
- return ErtScriptWorkflow(
81
- ert_script=ert_script,
82
- name=name,
83
- min_args=min_args,
84
- max_args=max_args,
85
- arg_types=arg_types_list,
86
- stop_on_fail=bool(content_dict.get("STOP_ON_FAIL")),
87
- )
98
+
99
+ if origin == "user":
100
+ return UserInstalledErtScriptWorkflow(
101
+ name=name,
102
+ min_args=min_args,
103
+ max_args=max_args,
104
+ arg_types=arg_types_list,
105
+ source=script,
106
+ stop_on_fail=bool(content_dict.get("STOP_ON_FAIL")),
107
+ )
108
+ else:
109
+ return ErtScriptWorkflow(
110
+ ert_script=ert_script,
111
+ name=name,
112
+ min_args=min_args,
113
+ max_args=max_args,
114
+ arg_types=arg_types_list,
115
+ stop_on_fail=bool(content_dict.get("STOP_ON_FAIL")),
116
+ )
88
117
  else:
89
118
  return ExecutableWorkflow(
90
119
  name=name,
@@ -139,72 +168,124 @@ class _WorkflowJob(BaseModelWithContextSupport, ABC):
139
168
 
140
169
 
141
170
  class ExecutableWorkflow(_WorkflowJob):
171
+ type: Literal["user_installed_executable"] = "user_installed_executable"
142
172
  executable: str | None = None
143
173
 
144
174
  def location(self) -> str | None:
145
175
  return self.executable
146
176
 
147
177
 
148
- class ErtScriptWorkflow(_WorkflowJob):
178
+ class BaseErtScriptWorkflow(_WorkflowJob, ABC):
179
+ @abstractmethod
180
+ def load_ert_script_class(self) -> builtins.type[ErtScript]: ...
181
+
182
+ def location(self) -> str | None:
183
+ return (
184
+ str(self.load_ert_script_class()) if self.load_ert_script_class() else None
185
+ )
186
+
187
+ @model_validator(mode="after")
188
+ def validate_types(self) -> Self:
189
+ ertscript_class = self.load_ert_script_class()
190
+ if not isinstance(ertscript_class, type):
191
+ raise ErtScriptLoadFailure(
192
+ f"Failed to load {self.name}, ert_script is instance, expected "
193
+ f"type, got {ertscript_class}"
194
+ )
195
+ elif not issubclass(ertscript_class, ErtScript):
196
+ raise ErtScriptLoadFailure(
197
+ f"Failed to load {self.name}, script had wrong "
198
+ f"type, expected ErtScript, got {ertscript_class}"
199
+ )
200
+ if ertscript_class.__doc__:
201
+ self.description = textwrap.dedent(ertscript_class.__doc__.strip())
202
+ return self
203
+
204
+ @property
205
+ def source_package(self) -> str:
206
+ return self.load_ert_script_class().__module__.partition(".")[2]
207
+
208
+ def is_plugin(self) -> bool:
209
+ return issubclass(self.load_ert_script_class(), ErtPlugin)
210
+
211
+
212
+ class _SerializedSiteInstalledErtScriptWorkflow(TypedDict):
213
+ type: Literal["site_installed"]
214
+ name: str
215
+
216
+
217
+ class SiteInstalledErtScriptWorkflow(BaseErtScriptWorkflow):
149
218
  """
150
- Single workflow configuration object
219
+ Single workflow configuration object installed from site plugins
151
220
  """
152
221
 
222
+ type: Literal["site_installed"] = "site_installed"
153
223
  ert_script: builtins.type[ErtScript] = None # type: ignore
154
224
  description: str = ""
155
225
  examples: str | None = None
156
226
  category: str = "other"
157
227
 
158
- @field_serializer("ert_script")
159
- def serialize_ert_script(self, _: str | builtins.type[ErtScript]) -> str:
160
- return self.name
228
+ @model_serializer(mode="plain")
229
+ def serialize_model(self) -> _SerializedSiteInstalledErtScriptWorkflow:
230
+ return {"type": "site_installed", "name": self.name}
161
231
 
162
- @field_validator("ert_script", mode="before")
163
- @classmethod
164
- def deserialize_ert_script(
165
- cls, ert_script: str | builtins.type[ErtScript], info: ValidationInfo
166
- ) -> builtins.type[ErtScript]:
167
- if isinstance(ert_script, type) and issubclass(ert_script, ErtScript):
168
- return ert_script
232
+ def load_ert_script_class(self) -> builtins.type[ErtScript]:
233
+ return self.ert_script
169
234
 
235
+ @model_validator(mode="before")
236
+ @classmethod
237
+ def deserialize_model(
238
+ cls, values: dict[str, Any], info: ValidationInfo
239
+ ) -> dict[str, Any]:
170
240
  runtime_plugins = cast("ErtRuntimePlugins", info.context)
171
- ertscript_workflow_job = runtime_plugins.installed_workflow_jobs.get(ert_script)
172
-
173
- if ertscript_workflow_job is None:
241
+ name = values["name"]
242
+
243
+ if runtime_plugins is None:
244
+ if set(values.keys()) == {"name", "type"}:
245
+ raise ValueError(
246
+ f"Cannot resolve workflow job {values},"
247
+ f"as it expects a the workflow job {name}"
248
+ f"to be installed."
249
+ )
250
+ return values
251
+
252
+ if name not in runtime_plugins.installed_workflow_jobs:
174
253
  raise KeyError(
175
- f"Did not find installed workflow job: {ert_script}. "
176
- f"installed workflow jobs are: "
177
- f"{runtime_plugins.installed_workflow_jobs.keys()}"
254
+ f"Expected workflow job {name} to be installed "
255
+ f"via plugins, but it was not found. Please check that "
256
+ f"your python environment has it installed."
178
257
  )
179
- assert isinstance(ertscript_workflow_job, ErtScriptWorkflow)
258
+ site_installed_wfjob = runtime_plugins.installed_workflow_jobs[name]
180
259
 
181
- return ertscript_workflow_job.ert_script
260
+ # Intent: copy the site installed workflow to this instance.
261
+ # bypassing the model_serializer
262
+ return {
263
+ k: getattr(site_installed_wfjob, k)
264
+ for k in SiteInstalledErtScriptWorkflow.model_fields
265
+ }
182
266
 
183
- def location(self) -> str | None:
184
- return str(self.ert_script) if self.ert_script else None
185
267
 
186
- @model_validator(mode="after")
187
- def validate_types(self) -> Self:
188
- if not isinstance(self.ert_script, type):
189
- raise ErtScriptLoadFailure(
190
- f"Failed to load {self.name}, ert_script is instance, expected "
191
- f"type, got {self.ert_script}"
192
- )
193
- elif not issubclass(self.ert_script, ErtScript):
194
- raise ErtScriptLoadFailure(
195
- f"Failed to load {self.name}, script had wrong "
196
- f"type, expected ErtScript, got {self.ert_script}"
197
- )
198
- if self.ert_script.__doc__ is not None:
199
- self.description = textwrap.dedent(self.ert_script.__doc__.strip())
200
- return self
268
+ # We keep the old name for compatability with .legacy_ertscript_workflow
269
+ # all of which add ErtScriptWorkflow (always through plugins, i.e., site-installed)
270
+ ErtScriptWorkflow = SiteInstalledErtScriptWorkflow
201
271
 
202
- @property
203
- def source_package(self) -> str:
204
- return self.ert_script.__module__.partition(".")[2]
205
272
 
206
- def is_plugin(self) -> bool:
207
- return issubclass(self.ert_script, ErtPlugin)
273
+ class UserInstalledErtScriptWorkflow(BaseErtScriptWorkflow):
274
+ type: Literal["user_installed_ertscript"] = "user_installed_ertscript"
275
+ source: str
276
+
277
+ def load_ert_script_class(self) -> builtins.type[ErtScript]:
278
+ try:
279
+ return ErtScript.loadScriptFromFile(self.source)
280
+ except Exception as err:
281
+ raise ErtScriptLoadFailure(f"Failed to load {self.name}: {err}") from err
208
282
 
209
283
 
210
- WorkflowJob: TypeAlias = ErtScriptWorkflow | ExecutableWorkflow
284
+ WorkflowJob: TypeAlias = Annotated[
285
+ (
286
+ SiteInstalledErtScriptWorkflow
287
+ | UserInstalledErtScriptWorkflow
288
+ | ExecutableWorkflow
289
+ ),
290
+ Field(discriminator="type"),
291
+ ]
@@ -1,4 +1,4 @@
1
- from ._session import ConnInfo
1
+ from ._session import ErtClientConnectionInfo
2
2
  from .client import Client
3
3
 
4
- __all__ = ["Client", "ConnInfo"]
4
+ __all__ = ["Client", "ErtClientConnectionInfo"]
@@ -5,7 +5,7 @@ from pathlib import Path
5
5
  from pydantic import BaseModel, ValidationError
6
6
 
7
7
 
8
- class ConnInfo(BaseModel):
8
+ class ErtClientConnectionInfo(BaseModel):
9
9
  base_url: str
10
10
  auth_token: str | None = None
11
11
  cert: str | bool = False
@@ -17,10 +17,10 @@ ENV_VAR = "ERT_STORAGE_CONNECTION_STRING"
17
17
  # that a single client process will only ever want to connect to a single ERT
18
18
  # Storage server during its lifetime, so we don't provide an API for managing
19
19
  # this cache.
20
- _CACHED_CONN_INFO: ConnInfo | None = None
20
+ _CACHED_CONN_INFO: ErtClientConnectionInfo | None = None
21
21
 
22
22
 
23
- def find_conn_info() -> ConnInfo:
23
+ def find_conn_info() -> ErtClientConnectionInfo:
24
24
  """
25
25
  The base url and auth token are read from either:
26
26
  The file `storage_server.json`, starting from the current working directory
@@ -54,7 +54,7 @@ def find_conn_info() -> ConnInfo:
54
54
  raise RuntimeError("No Storage connection configuration found")
55
55
 
56
56
  try:
57
- conn_info = ConnInfo.model_validate_json(conn_str)
57
+ conn_info = ErtClientConnectionInfo.model_validate_json(conn_str)
58
58
  except (json.JSONDecodeError, ValidationError) as e:
59
59
  raise RuntimeError("Invalid storage connection configuration") from e
60
60
  else:
@@ -3,7 +3,7 @@ import ssl
3
3
  import httpx
4
4
  from httpx_retries import Retry, RetryTransport
5
5
 
6
- from ._session import ConnInfo, find_conn_info
6
+ from ._session import ErtClientConnectionInfo, find_conn_info
7
7
 
8
8
 
9
9
  class Client(httpx.Client):
@@ -14,7 +14,7 @@ class Client(httpx.Client):
14
14
  Stores 'conn_info' to bridge the gap to the Everest client setup
15
15
  """
16
16
 
17
- def __init__(self, conn_info: ConnInfo | None = None) -> None:
17
+ def __init__(self, conn_info: ErtClientConnectionInfo | None = None) -> None:
18
18
  if conn_info is None:
19
19
  conn_info = find_conn_info()
20
20
 
@@ -4,7 +4,12 @@ import re
4
4
  from importlib import metadata
5
5
 
6
6
  from ert.dark_storage.exceptions import InternalServerError
7
- from ert.storage import ErtStorageException, Storage, open_storage
7
+ from ert.storage import (
8
+ ErtStorageException,
9
+ ErtStoragePermissionError,
10
+ Storage,
11
+ open_storage,
12
+ )
8
13
 
9
14
  logger = logging.getLogger(__name__)
10
15
 
@@ -17,9 +22,13 @@ def get_storage() -> Storage:
17
22
  if _storage is None:
18
23
  try:
19
24
  return (_storage := open_storage(os.environ["ERT_STORAGE_ENS_PATH"]))
25
+ except ErtStoragePermissionError as err:
26
+ logger.error(f"Permission error accessing storage: {err!s}")
27
+ raise InternalServerError("Permission error accessing storage") from None
20
28
  except ErtStorageException as err:
21
- raise InternalServerError(f"{err!s}") from err
22
- _storage.refresh()
29
+ logger.exception(f"Error accessing storage: {err!s}")
30
+ raise InternalServerError("Error accessing storage") from None
31
+ _storage.reload()
23
32
  return _storage
24
33
 
25
34
 
@@ -5,17 +5,18 @@ import numpy.typing as npt
5
5
  import pandas as pd
6
6
 
7
7
 
8
- def _calculate_misfit(
8
+ def _calculate_signed_chi_squared_misfit(
9
9
  obs_value: npt.NDArray[np.float64],
10
10
  response_value: npt.NDArray[np.float64],
11
11
  obs_std: npt.NDArray[np.float64],
12
12
  ) -> list[float]:
13
- difference = response_value - obs_value
14
- misfit = (difference / obs_std) ** 2
15
- return (misfit * np.sign(difference)).tolist()
13
+ """The signed version is intended for visualization. For data assimiliation one
14
+ would normally use the normal chi-square"""
15
+ residual = response_value - obs_value
16
+ return (np.sign(residual) * residual * residual / (obs_std * obs_std)).tolist()
16
17
 
17
18
 
18
- def calculate_misfits_from_pandas(
19
+ def calculate_signed_chi_squared_misfits(
19
20
  reponses_dict: Mapping[int, pd.DataFrame],
20
21
  observation: pd.DataFrame,
21
22
  summary_misfits: bool = False,
@@ -26,9 +27,12 @@ def calculate_misfits_from_pandas(
26
27
  """
27
28
  misfits_dict = {}
28
29
  for realization_index in reponses_dict:
29
- misfits_dict[realization_index] = _calculate_misfit(
30
+ misfits_dict[realization_index] = _calculate_signed_chi_squared_misfit(
30
31
  observation["values"],
31
- reponses_dict[realization_index].loc[:, observation.index].values.flatten(),
32
+ reponses_dict[realization_index]
33
+ .loc[:, observation.index]
34
+ .to_numpy()
35
+ .flatten(),
32
36
  observation["errors"],
33
37
  )
34
38
 
@@ -1,6 +1,6 @@
1
1
  import json
2
2
  from datetime import datetime
3
- from typing import Any
3
+ from typing import Annotated, Any
4
4
  from uuid import UUID
5
5
 
6
6
  import pandas as pd
@@ -10,7 +10,7 @@ from fastapi.responses import Response
10
10
 
11
11
  from ert.dark_storage import exceptions as exc
12
12
  from ert.dark_storage.common import get_storage
13
- from ert.dark_storage.compute.misfits import calculate_misfits_from_pandas
13
+ from ert.dark_storage.compute.misfits import calculate_signed_chi_squared_misfits
14
14
  from ert.dark_storage.endpoints.observations import (
15
15
  _get_observations,
16
16
  )
@@ -36,7 +36,9 @@ async def get_response_misfits(
36
36
  response_name: str,
37
37
  realization_index: int | None = None,
38
38
  summary_misfits: bool = False,
39
- filter_on: str | None = Query(None, description="JSON string with filters"),
39
+ filter_on: Annotated[
40
+ str | None, Query(description="JSON string with filters")
41
+ ] = None,
40
42
  ) -> Response:
41
43
  ensemble = storage.get_ensemble(ensemble_id)
42
44
  dataframe = data_for_response(
@@ -80,7 +82,7 @@ async def get_response_misfits(
80
82
  index=[parse_index(x) for x in o["x_axis"]],
81
83
  )
82
84
  try:
83
- result_df = calculate_misfits_from_pandas(
85
+ result_df = calculate_signed_chi_squared_misfits(
84
86
  response_dict, observation_df, summary_misfits
85
87
  )
86
88
  except Exception as misfits_exc:
@@ -1,4 +1,5 @@
1
1
  import logging
2
+ from collections import Counter
2
3
  from uuid import UUID
3
4
 
4
5
  from fastapi import APIRouter, Body, Depends, HTTPException
@@ -38,4 +39,7 @@ def get_ensemble(
38
39
  "started_at": ensemble.started_at,
39
40
  },
40
41
  size=ensemble.ensemble_size,
42
+ realization_storage_states=Counter(
43
+ state for states in ensemble.get_ensemble_state() for state in states
44
+ ),
41
45
  )