ert 17.1.7__py3-none-any.whl → 18.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. _ert/events.py +19 -2
  2. ert/__main__.py +8 -7
  3. ert/analysis/_enif_update.py +8 -4
  4. ert/analysis/_update_commons.py +16 -6
  5. ert/cli/main.py +6 -3
  6. ert/cli/monitor.py +7 -0
  7. ert/config/__init__.py +13 -3
  8. ert/config/_create_observation_dataframes.py +60 -12
  9. ert/config/_observations.py +14 -1
  10. ert/config/_read_summary.py +8 -6
  11. ert/config/ensemble_config.py +6 -14
  12. ert/config/ert_config.py +19 -13
  13. ert/config/{everest_objective_config.py → everest_response.py} +23 -12
  14. ert/config/ext_param_config.py +133 -1
  15. ert/config/field.py +12 -8
  16. ert/config/forward_model_step.py +108 -6
  17. ert/config/gen_data_config.py +2 -6
  18. ert/config/gen_kw_config.py +0 -9
  19. ert/config/known_response_types.py +14 -0
  20. ert/config/parameter_config.py +0 -17
  21. ert/config/parsing/config_keywords.py +1 -0
  22. ert/config/parsing/config_schema.py +12 -0
  23. ert/config/parsing/config_schema_deprecations.py +11 -0
  24. ert/config/parsing/config_schema_item.py +1 -1
  25. ert/config/queue_config.py +4 -4
  26. ert/config/response_config.py +0 -7
  27. ert/config/rft_config.py +230 -0
  28. ert/config/summary_config.py +2 -6
  29. ert/config/violations.py +0 -0
  30. ert/config/workflow_fixtures.py +2 -1
  31. ert/dark_storage/client/__init__.py +2 -2
  32. ert/dark_storage/client/_session.py +4 -4
  33. ert/dark_storage/client/client.py +2 -2
  34. ert/dark_storage/compute/misfits.py +7 -6
  35. ert/dark_storage/endpoints/compute/misfits.py +2 -2
  36. ert/dark_storage/endpoints/observations.py +4 -4
  37. ert/dark_storage/endpoints/responses.py +15 -1
  38. ert/ensemble_evaluator/__init__.py +8 -1
  39. ert/ensemble_evaluator/evaluator.py +81 -29
  40. ert/ensemble_evaluator/event.py +6 -0
  41. ert/ensemble_evaluator/snapshot.py +3 -1
  42. ert/ensemble_evaluator/state.py +1 -0
  43. ert/field_utils/__init__.py +8 -0
  44. ert/field_utils/field_utils.py +211 -1
  45. ert/gui/ertwidgets/__init__.py +23 -16
  46. ert/gui/ertwidgets/analysismoduleedit.py +2 -2
  47. ert/gui/ertwidgets/checklist.py +1 -1
  48. ert/gui/ertwidgets/create_experiment_dialog.py +3 -1
  49. ert/gui/ertwidgets/ensembleselector.py +2 -2
  50. ert/gui/ertwidgets/models/__init__.py +2 -0
  51. ert/gui/ertwidgets/models/activerealizationsmodel.py +2 -1
  52. ert/gui/ertwidgets/models/path_model.py +1 -1
  53. ert/gui/ertwidgets/models/targetensemblemodel.py +2 -1
  54. ert/gui/ertwidgets/models/text_model.py +1 -1
  55. ert/gui/ertwidgets/searchbox.py +13 -4
  56. ert/gui/{suggestor → ertwidgets/suggestor}/_suggestor_message.py +13 -4
  57. ert/gui/main.py +11 -6
  58. ert/gui/main_window.py +1 -2
  59. ert/gui/simulation/ensemble_experiment_panel.py +1 -1
  60. ert/gui/simulation/ensemble_information_filter_panel.py +1 -1
  61. ert/gui/simulation/ensemble_smoother_panel.py +1 -1
  62. ert/gui/simulation/evaluate_ensemble_panel.py +1 -1
  63. ert/gui/simulation/experiment_panel.py +1 -1
  64. ert/gui/simulation/manual_update_panel.py +31 -8
  65. ert/gui/simulation/multiple_data_assimilation_panel.py +12 -8
  66. ert/gui/simulation/run_dialog.py +25 -4
  67. ert/gui/simulation/single_test_run_panel.py +2 -2
  68. ert/gui/summarypanel.py +1 -1
  69. ert/gui/tools/load_results/load_results_panel.py +1 -1
  70. ert/gui/tools/manage_experiments/storage_info_widget.py +7 -7
  71. ert/gui/tools/manage_experiments/storage_widget.py +1 -2
  72. ert/gui/tools/plot/plot_api.py +13 -10
  73. ert/gui/tools/plot/plot_window.py +12 -0
  74. ert/gui/tools/plot/plottery/plot_config.py +2 -0
  75. ert/gui/tools/plot/plottery/plot_context.py +14 -0
  76. ert/gui/tools/plot/plottery/plots/ensemble.py +9 -2
  77. ert/gui/tools/plot/plottery/plots/statistics.py +59 -19
  78. ert/mode_definitions.py +2 -0
  79. ert/plugins/__init__.py +0 -1
  80. ert/plugins/hook_implementations/workflows/gen_data_rft_export.py +10 -2
  81. ert/plugins/hook_specifications/__init__.py +0 -2
  82. ert/plugins/hook_specifications/jobs.py +0 -9
  83. ert/plugins/plugin_manager.py +2 -33
  84. ert/resources/shell_scripts/delete_directory.py +2 -2
  85. ert/run_models/__init__.py +18 -5
  86. ert/run_models/_create_run_path.py +56 -23
  87. ert/run_models/ensemble_experiment.py +10 -4
  88. ert/run_models/ensemble_information_filter.py +8 -1
  89. ert/run_models/ensemble_smoother.py +9 -3
  90. ert/run_models/evaluate_ensemble.py +8 -6
  91. ert/run_models/event.py +7 -3
  92. ert/run_models/everest_run_model.py +155 -44
  93. ert/run_models/initial_ensemble_run_model.py +23 -22
  94. ert/run_models/manual_update.py +4 -2
  95. ert/run_models/manual_update_enif.py +37 -0
  96. ert/run_models/model_factory.py +81 -22
  97. ert/run_models/multiple_data_assimilation.py +21 -10
  98. ert/run_models/run_model.py +54 -34
  99. ert/run_models/single_test_run.py +7 -4
  100. ert/run_models/update_run_model.py +4 -2
  101. ert/runpaths.py +5 -6
  102. ert/sample_prior.py +9 -4
  103. ert/scheduler/driver.py +37 -0
  104. ert/scheduler/event.py +3 -1
  105. ert/scheduler/job.py +23 -13
  106. ert/scheduler/lsf_driver.py +6 -2
  107. ert/scheduler/openpbs_driver.py +7 -1
  108. ert/scheduler/scheduler.py +5 -0
  109. ert/scheduler/slurm_driver.py +6 -2
  110. ert/services/__init__.py +2 -2
  111. ert/services/_base_service.py +31 -15
  112. ert/services/ert_server.py +317 -0
  113. ert/shared/_doc_utils/ert_jobs.py +1 -4
  114. ert/shared/storage/connection.py +3 -3
  115. ert/shared/version.py +3 -3
  116. ert/storage/local_ensemble.py +25 -5
  117. ert/storage/local_experiment.py +6 -14
  118. ert/storage/local_storage.py +35 -30
  119. ert/storage/migration/to18.py +12 -0
  120. ert/storage/migration/to8.py +4 -4
  121. ert/substitutions.py +12 -28
  122. ert/validation/active_range.py +7 -7
  123. ert/validation/rangestring.py +16 -16
  124. {ert-17.1.7.dist-info → ert-18.0.0.dist-info}/METADATA +8 -7
  125. {ert-17.1.7.dist-info → ert-18.0.0.dist-info}/RECORD +160 -159
  126. everest/bin/config_branch_script.py +3 -6
  127. everest/bin/everconfigdump_script.py +1 -9
  128. everest/bin/everest_script.py +21 -11
  129. everest/bin/kill_script.py +2 -2
  130. everest/bin/monitor_script.py +2 -2
  131. everest/bin/utils.py +6 -3
  132. everest/config/__init__.py +4 -1
  133. everest/config/control_config.py +61 -2
  134. everest/config/control_variable_config.py +2 -1
  135. everest/config/everest_config.py +38 -16
  136. everest/config/forward_model_config.py +5 -3
  137. everest/config/install_data_config.py +7 -5
  138. everest/config/install_job_config.py +7 -3
  139. everest/config/install_template_config.py +3 -3
  140. everest/config/optimization_config.py +19 -6
  141. everest/config/output_constraint_config.py +8 -2
  142. everest/config/server_config.py +6 -49
  143. everest/config/utils.py +25 -105
  144. everest/config/validation_utils.py +10 -10
  145. everest/config_file_loader.py +13 -2
  146. everest/detached/everserver.py +7 -8
  147. everest/everest_storage.py +6 -10
  148. everest/gui/everest_client.py +0 -1
  149. everest/gui/main_window.py +2 -2
  150. everest/optimizer/everest2ropt.py +59 -32
  151. everest/optimizer/opt_model_transforms.py +12 -13
  152. everest/optimizer/utils.py +0 -29
  153. everest/strings.py +0 -5
  154. ert/config/everest_constraints_config.py +0 -95
  155. ert/services/storage_service.py +0 -127
  156. everest/config/sampler_config.py +0 -103
  157. everest/simulator/__init__.py +0 -88
  158. everest/simulator/everest_to_ert.py +0 -51
  159. /ert/gui/{suggestor → ertwidgets/suggestor}/__init__.py +0 -0
  160. /ert/gui/{suggestor → ertwidgets/suggestor}/_colors.py +0 -0
  161. /ert/gui/{suggestor → ertwidgets/suggestor}/suggestor.py +0 -0
  162. {ert-17.1.7.dist-info → ert-18.0.0.dist-info}/WHEEL +0 -0
  163. {ert-17.1.7.dist-info → ert-18.0.0.dist-info}/entry_points.txt +0 -0
  164. {ert-17.1.7.dist-info → ert-18.0.0.dist-info}/licenses/COPYING +0 -0
  165. {ert-17.1.7.dist-info → ert-18.0.0.dist-info}/top_level.txt +0 -0
@@ -12,6 +12,7 @@ from _ert.events import (
12
12
  EESnapshot,
13
13
  EESnapshotUpdate,
14
14
  EnsembleCancelled,
15
+ EnsembleEvaluationWarning,
15
16
  EnsembleEvent,
16
17
  EnsembleFailed,
17
18
  EnsembleStarted,
@@ -424,7 +425,8 @@ class EnsembleSnapshot:
424
425
 
425
426
  elif e_type in get_args(EnsembleEvent):
426
427
  event = cast(EnsembleEvent, event)
427
- self._ensemble_state = _ENSEMBLE_TYPE_EVENT_TO_STATUS[type(event)]
428
+ if not isinstance(event, EnsembleEvaluationWarning):
429
+ self._ensemble_state = _ENSEMBLE_TYPE_EVENT_TO_STATUS[type(event)]
428
430
  elif type(event) is EESnapshotUpdate:
429
431
  self.merge_snapshot(EnsembleSnapshot.from_nested_dict(event.snapshot))
430
432
  elif type(event) is EESnapshot:
@@ -7,6 +7,7 @@ COLOR_RUNNING: Final = (255, 255, 153)
7
7
  COLOR_UNKNOWN: Final = (128, 128, 128)
8
8
  COLOR_WAITING: Final = (164, 200, 255)
9
9
  COLOR_CANCELLED: Final = (235, 242, 246)
10
+ COLOR_WARNING: Final = (255, 103, 0)
10
11
 
11
12
  ENSEMBLE_STATE_CANCELLED: Final = "Cancelled"
12
13
  ENSEMBLE_STATE_FAILED: Final = "Failed"
@@ -4,20 +4,28 @@ from .field_file_format import FieldFileFormat
4
4
  from .field_utils import (
5
5
  ErtboxParameters,
6
6
  Shape,
7
+ calc_rho_for_2d_grid_layer,
7
8
  calculate_ertbox_parameters,
8
9
  get_shape,
10
+ localization_scaling_function,
9
11
  read_field,
10
12
  read_mask,
11
13
  save_field,
14
+ transform_local_ellipse_angle_to_local_coords,
15
+ transform_positions_to_local_field_coordinates,
12
16
  )
13
17
 
14
18
  __all__ = [
15
19
  "ErtboxParameters",
16
20
  "FieldFileFormat",
17
21
  "Shape",
22
+ "calc_rho_for_2d_grid_layer",
18
23
  "calculate_ertbox_parameters",
19
24
  "get_shape",
25
+ "localization_scaling_function",
20
26
  "read_field",
21
27
  "read_mask",
22
28
  "save_field",
29
+ "transform_local_ellipse_angle_to_local_coords",
30
+ "transform_positions_to_local_field_coordinates",
23
31
  ]
@@ -15,7 +15,7 @@ from .roff_io import export_roff, import_roff
15
15
 
16
16
  if TYPE_CHECKING:
17
17
  import numpy.typing as npt
18
- import xtgeo # type: ignore
18
+ import xtgeo
19
19
 
20
20
  _PathLike: TypeAlias = str | os.PathLike[str]
21
21
 
@@ -262,3 +262,213 @@ def save_field(
262
262
  export_grdecl(field, output_path, field_name, binary=True)
263
263
  else:
264
264
  raise ValueError(f"Cannot export, invalid file format: {file_format}")
265
+
266
+
267
+ def transform_positions_to_local_field_coordinates(
268
+ coordsys_origin: tuple[float, float],
269
+ coordsys_rotation_angle: float,
270
+ utmx: npt.NDArray[np.float64],
271
+ utmy: npt.NDArray[np.float64],
272
+ ) -> tuple[npt.NDArray[np.float64], npt.NDArray[np.float64]]:
273
+ """Calculates coordinate transformation from global to local coordinates.
274
+
275
+ Args:
276
+ coordys_origin: (x,y) coordinate of local coordinate
277
+ origin in global coordinates.
278
+ coordsys_rotation_angle: Angle for how much the local x-axis is rotated
279
+ anti-clockwise relative to the global x-axis in degrees.
280
+ utmx: vector of x-coordinates in global coordinates.
281
+ utmy: vector of y-coordinates in global coordinates.
282
+
283
+ Returns:
284
+ First vector is local x-coordinates and second vector is local y-coordinates.
285
+ """
286
+ # Translate
287
+ x1 = utmx - coordsys_origin[0]
288
+ y1 = utmy - coordsys_origin[1]
289
+ # Rotate
290
+ # Input angle is the local coordinate systems rotation
291
+ # anticlockwise relative to global x-axis in degrees
292
+ rotation_of_ertbox = coordsys_rotation_angle
293
+ rotation_angle = np.deg2rad(rotation_of_ertbox)
294
+ cos_theta = np.cos(rotation_angle)
295
+ sin_theta = np.sin(rotation_angle)
296
+ x2 = x1 * cos_theta + y1 * sin_theta
297
+ y2 = -x1 * sin_theta + y1 * cos_theta
298
+ return x2, y2
299
+
300
+
301
+ def transform_local_ellipse_angle_to_local_coords(
302
+ coordsys_rotation_angle: float,
303
+ ellipse_anisotropy_angle: npt.NDArray[np.float64],
304
+ ) -> npt.NDArray[np.float64]:
305
+ """Calculate angles relative to local coordinate system.
306
+
307
+ Args:
308
+ coordsys_rotation_angle: Local coordinate systems rotation angle
309
+ relative to the global coordinate system.
310
+ ellipse_anisotropy_angle: Vector of input angles in global coordinates.
311
+
312
+ Returns:
313
+ Vector of output angles relative to the local coordinate system.
314
+ """
315
+ # Both angles measured anti-clock from global coordinate systems x-axis in degrees
316
+ return ellipse_anisotropy_angle - coordsys_rotation_angle
317
+
318
+
319
+ def localization_scaling_function(
320
+ distances: npt.NDArray[np.float64],
321
+ ) -> npt.NDArray[np.float64]:
322
+ """Calculate scaling factor to be used as values in
323
+ RHO matrix in distance-based localization.
324
+ The scaling function implements the commonly
325
+ used function published by Gaspari and Cohn.
326
+ For input normalized distance >= 2, the value will be 0.
327
+
328
+ Args:
329
+ distances: Vector of values for normalized distances.
330
+
331
+ Returns:
332
+ Values of scaling factors for each value of input distance.
333
+ """
334
+ # "gaspari-cohn"
335
+ # Commonly used in distance-based localization
336
+ # Is exact 0 for normalized distance > 2.
337
+ scaling_factor = distances
338
+ d2 = distances**2
339
+ d3 = d2 * distances
340
+ d4 = d3 * distances
341
+ d5 = d4 * distances
342
+ s = -1 / 4 * d5 + 1 / 2 * d4 + 5 / 8 * d3 - 5 / 3 * d2 + 1
343
+ scaling_factor[distances <= 1] = s[distances <= 1]
344
+ s = (
345
+ 1 / 12 * d5
346
+ - 1 / 2 * d4
347
+ + 5 / 8 * d3
348
+ + 5 / 3 * d2
349
+ - 5 * distances
350
+ + 4
351
+ - 2 / 3 * 1 / distances
352
+ )
353
+ scaling_factor[(distances > 1) & (distances <= 2)] = s[
354
+ (distances > 1) & (distances <= 2)
355
+ ]
356
+ scaling_factor[distances > 2] = 0.0
357
+
358
+ return scaling_factor
359
+
360
+
361
+ def calc_rho_for_2d_grid_layer(
362
+ nx: int,
363
+ ny: int,
364
+ xinc: float,
365
+ yinc: float,
366
+ obs_xpos: npt.NDArray[np.float64],
367
+ obs_ypos: npt.NDArray[np.float64],
368
+ obs_main_range: npt.NDArray[np.float64],
369
+ obs_perp_range: npt.NDArray[np.float64],
370
+ obs_anisotropy_angle: npt.NDArray[np.float64],
371
+ right_handed_grid_indexing: bool = True,
372
+ ) -> npt.NDArray[np.float64]:
373
+ """Calculate scaling values (RHO matrix elements) for a set of observations
374
+ with associated localization ellipse. The method will first
375
+ calculate the distances from each observation position to each grid cell
376
+ center point of all grid cells for a 2D grid.
377
+ The localization method will only consider lateral distances, and it is
378
+ therefore sufficient to calculate the distances in 2D.
379
+ All input observation positions are in the local grid coordinate system
380
+ to simplify the calculation of the distances.
381
+
382
+ The position: xpos[n], ypos[n] and
383
+ localization ellipse defined by obs_main_range[n],obs_perp_range[n],
384
+ obs_anisotropy_angle[n]) refers to observation[n].
385
+
386
+ The distance between an observation with index n and a grid cell (i,j) is
387
+ d[m,n] = dist((xpos_obs[n],ypos_obs[n]),(xpos_field[i,j],ypos_field[i,j]))
388
+
389
+ RHO[[m,n] = scaling(d)
390
+ where m = j + i * ny for left-handed grid index origo and
391
+ m = (ny - j - 1) + i * ny for right-handed grid index origo
392
+ Note that since d[m,n] does only depend on observation index n and
393
+ grid cell index (i,j). The values for RHO is
394
+ calculated for the combination ((i,j), n) and this covers
395
+ one grid layer in ertbox grid or a 2D surface grid.
396
+
397
+ Args:
398
+ nx: Number of grid cells in x-direction of local coordinate system.
399
+ ny: Number of grid cells in y-direction of local coordinate system.
400
+ xinc: Grid cell size in x-direction.
401
+ yinc: Grid cell size in y-direction.
402
+ obs_xpos: Observations x coordinates in local coordinates
403
+ obs_ypos: Observatiopns y coordinates in local coordinates
404
+ obs_main_range: Localization ellipse first range
405
+ obs_perp_range: Localization ellipse second range
406
+ obs_anisotropy_angle: Localization ellipse orientation relative
407
+ to local coordinate system in degrees
408
+
409
+ Returns:
410
+ Rho matrix values for one layer of the 3D ertbox grid or for a 2D surface grid.
411
+ """
412
+ # Center points of each grid cell in field parameter grid
413
+ x_local = (np.arange(nx, dtype=np.float64) + 0.5) * xinc
414
+ if right_handed_grid_indexing:
415
+ # y coordinate descreases from max to min
416
+ y_local = (np.arange(ny - 1, -1, -1, dtype=np.float64) + 0.5) * yinc
417
+ else:
418
+ # y coordinate increases from min to max
419
+ y_local = (np.arange(ny, dtype=np.float64) + 0.5) * yinc
420
+ mesh_x_coord, mesh_y_coord = np.meshgrid(x_local, y_local, indexing="ij")
421
+
422
+ # Number of observations
423
+ nobs = len(obs_xpos)
424
+ assert nobs == len(obs_ypos), (
425
+ "Number of coordinates must match number of observations"
426
+ )
427
+ assert nobs == len(obs_anisotropy_angle), (
428
+ "Number of ellipse orientation angles must match number of observations"
429
+ )
430
+ assert nobs == len(obs_main_range), (
431
+ "Number of ellipse main range values must match number of observations"
432
+ )
433
+ assert nobs == len(obs_perp_range), (
434
+ "Number of ellipse second range values must match number of observations"
435
+ )
436
+ assert np.all(obs_main_range > 0.0), (
437
+ "All range values for all observations must be positive"
438
+ )
439
+ assert np.all(obs_perp_range > 0.0), (
440
+ "All range values for all observations must be positive"
441
+ )
442
+
443
+ # Expand grid coordinates to match observations
444
+ mesh_x_coord_flat = mesh_x_coord.flatten()[:, np.newaxis] # (nx * ny, 1)
445
+ mesh_y_coord_flat = mesh_y_coord.flatten()[:, np.newaxis] # (nx * ny, 1)
446
+
447
+ # Observation coordinates and parameters
448
+ obs_xpos = obs_xpos[np.newaxis, :] # (1, nobs)
449
+ obs_ypos = obs_ypos[np.newaxis, :] # (1, nobs)
450
+ obs_main_range = obs_main_range[np.newaxis, :] # (1, nobs)
451
+ obs_perp_range = obs_perp_range[np.newaxis, :] # (1, nobs)
452
+ obs_anisotropy_angle = obs_anisotropy_angle[np.newaxis, :] # (1, nobs)
453
+
454
+ # Compute displacement between grid points and observations
455
+ dX = mesh_x_coord_flat - obs_xpos # (nx * ny, nobs)
456
+ dY = mesh_y_coord_flat - obs_ypos # (nx * ny, nobs)
457
+
458
+ # Compute rotation parameters
459
+ rotation = np.deg2rad(obs_anisotropy_angle)
460
+ cos_angle = np.cos(rotation) # (1, nobs)
461
+ sin_angle = np.sin(rotation) # (1, nobs)
462
+
463
+ # Rotate and scale displacements to local coordinate system defined
464
+ # by the two half axes of the influence ellipse. First coordinate (local x) is in
465
+ # direction defined by anisotropy angle and local y is perpendicular to that.
466
+ # Scale the distance by the ranges to get a normalized distance
467
+ # (with value 1 at the edge of the ellipse)
468
+ dX_ellipse = (dX * cos_angle + dY * sin_angle) / obs_main_range # (nx * ny, nobs)
469
+ dY_ellipse = (-dX * sin_angle + dY * cos_angle) / obs_perp_range # (nx * ny, nobs)
470
+
471
+ # Compute distances in the elliptical coordinate system
472
+ distances = np.hypot(dX_ellipse, dY_ellipse) # (nx * ny, nobs)
473
+ # Apply the scaling function
474
+ return localization_scaling_function(distances).reshape((nx, ny, nobs))
@@ -1,31 +1,34 @@
1
- # isort: skip_file
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
2
4
  from PyQt6.QtCore import Qt
3
5
  from PyQt6.QtGui import QCursor
4
6
  from PyQt6.QtWidgets import QApplication
5
- from typing import Any
6
- from collections.abc import Callable
7
-
8
7
 
9
- from .closabledialog import ClosableDialog
10
8
  from .analysismoduleedit import AnalysisModuleEdit
11
- from .searchbox import SearchBox
12
- from .ensembleselector import EnsembleSelector
13
9
  from .checklist import CheckList
14
- from .stringbox import StringBox
15
- from .textbox import TextBox
16
- from .listeditbox import ListEditBox
10
+ from .closabledialog import ClosableDialog
11
+ from .copy_button import CopyButton
12
+ from .copyablelabel import CopyableLabel
13
+ from .create_experiment_dialog import CreateExperimentDialog
17
14
  from .customdialog import CustomDialog
18
- from .pathchooser import PathChooser
15
+ from .ensembleselector import EnsembleSelector
16
+ from .listeditbox import ListEditBox
19
17
  from .models import (
20
- TextModel,
21
18
  ActiveRealizationsModel,
19
+ ErtSummary,
20
+ PathModel,
21
+ SelectableListModel,
22
22
  TargetEnsembleModel,
23
+ TextModel,
23
24
  ValueModel,
24
- SelectableListModel,
25
- PathModel,
26
25
  )
27
- from .copyablelabel import CopyableLabel
28
- from .copy_button import CopyButton
26
+ from .parameterviewer import get_parameters_button
27
+ from .pathchooser import PathChooser
28
+ from .searchbox import SearchBox
29
+ from .stringbox import StringBox
30
+ from .suggestor import Suggestor
31
+ from .textbox import TextBox
29
32
 
30
33
 
31
34
  def showWaitCursorWhileWaiting(func: Callable[..., Any]) -> Callable[..., Any]:
@@ -49,17 +52,21 @@ __all__ = [
49
52
  "ClosableDialog",
50
53
  "CopyButton",
51
54
  "CopyableLabel",
55
+ "CreateExperimentDialog",
52
56
  "CustomDialog",
53
57
  "EnsembleSelector",
58
+ "ErtSummary",
54
59
  "ListEditBox",
55
60
  "PathChooser",
56
61
  "PathModel",
57
62
  "SearchBox",
58
63
  "SelectableListModel",
59
64
  "StringBox",
65
+ "Suggestor",
60
66
  "TargetEnsembleModel",
61
67
  "TextBox",
62
68
  "TextModel",
63
69
  "ValueModel",
70
+ "get_parameters_button",
64
71
  "showWaitCursorWhileWaiting",
65
72
  ]
@@ -6,8 +6,8 @@ from PyQt6.QtCore import QMargins, Qt
6
6
  from PyQt6.QtGui import QIcon
7
7
  from PyQt6.QtWidgets import QHBoxLayout, QToolButton, QWidget
8
8
 
9
- from ert.gui.ertwidgets import ClosableDialog
10
- from ert.gui.ertwidgets.analysismodulevariablespanel import AnalysisModuleVariablesPanel
9
+ from .analysismodulevariablespanel import AnalysisModuleVariablesPanel
10
+ from .closabledialog import ClosableDialog
11
11
 
12
12
  if TYPE_CHECKING:
13
13
  from ert.config import AnalysisModule
@@ -16,7 +16,7 @@ from PyQt6.QtWidgets import (
16
16
  QWidget,
17
17
  )
18
18
 
19
- from ert.gui.ertwidgets import SearchBox
19
+ from .searchbox import SearchBox
20
20
 
21
21
  if TYPE_CHECKING:
22
22
  from .models.selectable_list_model import SelectableListModel
@@ -9,9 +9,11 @@ from PyQt6.QtWidgets import (
9
9
  )
10
10
 
11
11
  from ert.gui.ertnotifier import ErtNotifier
12
- from ert.gui.ertwidgets import StringBox, TextModel, ValueModel
13
12
  from ert.validation import ExperimentValidation, IntegerArgument, ProperNameArgument
14
13
 
14
+ from .models import TextModel, ValueModel
15
+ from .stringbox import StringBox
16
+
15
17
 
16
18
  class CreateExperimentDialog(QDialog):
17
19
  onDone = Signal(str, str, int)
@@ -9,11 +9,11 @@ from PyQt6.QtCore import Qt
9
9
  from PyQt6.QtCore import pyqtSignal as Signal
10
10
  from PyQt6.QtWidgets import QComboBox
11
11
 
12
+ from ert.config import ErrorInfo
12
13
  from ert.gui.ertnotifier import ErtNotifier
13
14
  from ert.storage import RealizationStorageState
14
15
 
15
- from ...config import ErrorInfo
16
- from ..suggestor import Suggestor
16
+ from .suggestor import Suggestor
17
17
 
18
18
  if TYPE_CHECKING:
19
19
  from ert.storage import Ensemble
@@ -1,4 +1,5 @@
1
1
  from .activerealizationsmodel import ActiveRealizationsModel
2
+ from .ertsummary import ErtSummary
2
3
  from .path_model import PathModel
3
4
  from .selectable_list_model import SelectableListModel
4
5
  from .targetensemblemodel import TargetEnsembleModel
@@ -7,6 +8,7 @@ from .valuemodel import ValueModel
7
8
 
8
9
  __all__ = [
9
10
  "ActiveRealizationsModel",
11
+ "ErtSummary",
10
12
  "PathModel",
11
13
  "SelectableListModel",
12
14
  "TargetEnsembleModel",
@@ -2,9 +2,10 @@ from collections.abc import Collection
2
2
 
3
3
  from typing_extensions import override
4
4
 
5
- from ert.gui.ertwidgets.models.valuemodel import ValueModel
6
5
  from ert.validation import ActiveRange, mask_to_rangestring
7
6
 
7
+ from .valuemodel import ValueModel
8
+
8
9
 
9
10
  class ActiveRealizationsModel(ValueModel):
10
11
  def __init__(self, ensemble_size: int, show_default: bool = True) -> None:
@@ -1,4 +1,4 @@
1
- from ert.gui.ertwidgets.models.valuemodel import ValueModel
1
+ from .valuemodel import ValueModel
2
2
 
3
3
 
4
4
  class PathModel(ValueModel):
@@ -4,7 +4,8 @@ from typing_extensions import override
4
4
 
5
5
  from ert.config import AnalysisConfig
6
6
  from ert.gui.ertnotifier import ErtNotifier
7
- from ert.gui.ertwidgets.models.valuemodel import ValueModel
7
+
8
+ from .valuemodel import ValueModel
8
9
 
9
10
 
10
11
  class TargetEnsembleModel(ValueModel):
@@ -1,6 +1,6 @@
1
1
  from typing_extensions import override
2
2
 
3
- from ert.gui.ertwidgets.models.valuemodel import ValueModel
3
+ from .valuemodel import ValueModel
4
4
 
5
5
 
6
6
  class TextModel(ValueModel):
@@ -1,6 +1,6 @@
1
1
  from typing import Any
2
2
 
3
- from PyQt6.QtCore import Qt
3
+ from PyQt6.QtCore import Qt, QTimer
4
4
  from PyQt6.QtCore import pyqtSignal as Signal
5
5
  from PyQt6.QtGui import QColor, QFocusEvent, QKeyEvent
6
6
  from PyQt6.QtWidgets import QLineEdit
@@ -12,16 +12,25 @@ class SearchBox(QLineEdit):
12
12
 
13
13
  filterChanged = Signal(object)
14
14
 
15
- def __init__(self) -> None:
15
+ def __init__(self, debounce_timeout: int = 1000) -> None:
16
16
  QLineEdit.__init__(self)
17
17
 
18
18
  self.setToolTip("Type to search!")
19
19
  self.active_color = self.palette().color(self.foregroundRole())
20
20
  self.disable_search = True
21
21
  self.presentSearch()
22
- self.textChanged.connect(self.__emitFilterChanged)
22
+ self.textChanged.connect(self._start_debounce_timer)
23
+ self._debounce_timout = debounce_timeout
24
+ self._debounce_timer = QTimer(self)
25
+ self._debounce_timer.setSingleShot(True)
26
+ self._debounce_timer.timeout.connect(self._emit_filter_changed)
23
27
 
24
- def __emitFilterChanged(self, _filter: Any) -> None:
28
+ def _start_debounce_timer(self, _filter: Any) -> None:
29
+ if not self._debounce_timer.isActive():
30
+ self._debounce_timer.start(self._debounce_timout)
31
+ self._debounce_timer.setInterval(self._debounce_timout)
32
+
33
+ def _emit_filter_changed(self) -> None:
25
34
  self.filterChanged.emit(self.filter())
26
35
 
27
36
  def filter(self) -> str:
@@ -13,10 +13,11 @@ from PyQt6.QtWidgets import (
13
13
  QVBoxLayout,
14
14
  QWidget,
15
15
  )
16
+ from typing_extensions import override
16
17
 
17
18
  from ert.gui import is_dark_mode
18
19
 
19
- from ..ertwidgets.copyablelabel import _CopyButton
20
+ from ..copy_button import CopyButton
20
21
  from ._colors import (
21
22
  BLUE_BACKGROUND,
22
23
  BLUE_TEXT,
@@ -33,6 +34,16 @@ def _svg_icon(image_name: str) -> QSvgWidget:
33
34
  return widget
34
35
 
35
36
 
37
+ class _CopyButton(CopyButton):
38
+ def __init__(self, message: str) -> None:
39
+ super().__init__()
40
+ self.message = message
41
+
42
+ @override
43
+ def copy(self) -> None:
44
+ self.copy_text(self.message)
45
+
46
+
36
47
  class SuggestorMessage(QWidget):
37
48
  def __init__(
38
49
  self,
@@ -100,9 +111,7 @@ class SuggestorMessage(QWidget):
100
111
  else:
101
112
  self._expand_collapse_label = QLabel()
102
113
  self._hbox.addWidget(self.lbl, alignment=Qt.AlignmentFlag.AlignTop)
103
- self._hbox.addWidget(
104
- _CopyButton(QLabel(message)), alignment=Qt.AlignmentFlag.AlignTop
105
- )
114
+ self._hbox.addWidget(_CopyButton(message), alignment=Qt.AlignmentFlag.AlignTop)
106
115
 
107
116
  def _toggle_expand(self, _link: Any) -> None:
108
117
  if self._expanded:
ert/gui/main.py CHANGED
@@ -8,6 +8,7 @@ import traceback
8
8
  import types
9
9
  from collections import Counter
10
10
  from importlib.resources import files
11
+ from pathlib import Path
11
12
  from signal import SIG_DFL, SIGINT, signal
12
13
 
13
14
  from opentelemetry.trace import Status, StatusCode
@@ -27,11 +28,11 @@ from ert.gui.tools.event_viewer import (
27
28
  )
28
29
  from ert.namespace import Namespace
29
30
  from ert.plugins import ErtRuntimePlugins, get_site_plugins
30
- from ert.services import StorageService
31
+ from ert.services import ErtServer
31
32
  from ert.storage import ErtStorageException, local_storage_set_ert_config, open_storage
32
33
  from ert.trace import trace, tracer
33
34
 
34
- from .suggestor import Suggestor
35
+ from .ertwidgets import Suggestor
35
36
 
36
37
  logger = logging.getLogger(__name__)
37
38
 
@@ -113,7 +114,7 @@ def run_gui(args: Namespace, plugins: ErtRuntimePlugins | None = None) -> int:
113
114
  return show_window()
114
115
 
115
116
  try:
116
- with StorageService.init_service(project=os.path.abspath(ens_path)):
117
+ with ErtServer.init_service(project=Path(ens_path).absolute()):
117
118
  return show_window()
118
119
  except PermissionError as pe:
119
120
  print(f"Error: {pe}", file=sys.stderr)
@@ -157,9 +158,13 @@ def _start_initial_gui_window(
157
158
  storage_path = None
158
159
  if ert_config is not None:
159
160
  try:
160
- # Open write to initialize the storage,so that
161
- # dark storage can be mounted onto it
162
- open_storage(ert_config.ens_path, mode="w").close()
161
+ with open_storage(ert_config.ens_path) as read_storage:
162
+ should_migrate = read_storage.check_migration_needed()
163
+
164
+ if should_migrate:
165
+ # Open in write mode to initialize the storage, so that
166
+ # dark storage can be mounted onto it
167
+ open_storage(ert_config.ens_path, mode="w").close()
163
168
  storage_path = ert_config.ens_path
164
169
  except ErtStorageException as err:
165
170
  validation_messages.errors.append(
ert/gui/main_window.py CHANGED
@@ -9,7 +9,7 @@ from pathlib import Path
9
9
  from PyQt6.QtCore import QCoreApplication, QEvent, QSize, Qt
10
10
  from PyQt6.QtCore import pyqtSignal as Signal
11
11
  from PyQt6.QtCore import pyqtSlot as Slot
12
- from PyQt6.QtGui import QAction, QCloseEvent, QCursor, QFontDatabase, QIcon, QMouseEvent
12
+ from PyQt6.QtGui import QAction, QCloseEvent, QCursor, QIcon, QMouseEvent
13
13
  from PyQt6.QtWidgets import (
14
14
  QButtonGroup,
15
15
  QFrame,
@@ -96,7 +96,6 @@ class ErtMainWindow(QMainWindow):
96
96
  log_handler: GUILogHandler | None = None,
97
97
  ) -> None:
98
98
  QMainWindow.__init__(self)
99
- self.available_fonts = QFontDatabase.families()
100
99
  self.notifier = ErtNotifier()
101
100
  self.plugins_tool: PluginsTool | None = None
102
101
  self.ert_config = ert_config
@@ -17,6 +17,7 @@ from ert.gui.ertwidgets import (
17
17
  CopyableLabel,
18
18
  StringBox,
19
19
  TextModel,
20
+ get_parameters_button,
20
21
  )
21
22
  from ert.mode_definitions import ENSEMBLE_EXPERIMENT_MODE
22
23
  from ert.run_models import EnsembleExperiment
@@ -24,7 +25,6 @@ from ert.validation import ExperimentValidation, ProperNameArgument
24
25
  from ert.validation.active_range import ActiveRange
25
26
  from ert.validation.range_string_argument import RangeSubsetStringArgument
26
27
 
27
- from ..ertwidgets.parameterviewer import get_parameters_button
28
28
  from ._design_matrix_panel import DesignMatrixPanel
29
29
  from .experiment_config_panel import ExperimentConfigPanel
30
30
 
@@ -14,6 +14,7 @@ from ert.gui.ertwidgets import (
14
14
  StringBox,
15
15
  TargetEnsembleModel,
16
16
  TextModel,
17
+ get_parameters_button,
17
18
  )
18
19
  from ert.mode_definitions import ENIF_MODE
19
20
  from ert.run_models import EnsembleInformationFilter
@@ -24,7 +25,6 @@ from ert.validation import (
24
25
  from ert.validation.active_range import ActiveRange
25
26
  from ert.validation.range_string_argument import RangeSubsetStringArgument
26
27
 
27
- from ..ertwidgets.parameterviewer import get_parameters_button
28
28
  from ._design_matrix_panel import DesignMatrixPanel
29
29
  from .experiment_config_panel import ExperimentConfigPanel
30
30
 
@@ -16,6 +16,7 @@ from ert.gui.ertwidgets import (
16
16
  StringBox,
17
17
  TargetEnsembleModel,
18
18
  TextModel,
19
+ get_parameters_button,
19
20
  )
20
21
  from ert.mode_definitions import ENSEMBLE_SMOOTHER_MODE
21
22
  from ert.run_models import EnsembleSmoother
@@ -26,7 +27,6 @@ from ert.validation import (
26
27
  from ert.validation.active_range import ActiveRange
27
28
  from ert.validation.range_string_argument import RangeSubsetStringArgument
28
29
 
29
- from ..ertwidgets.parameterviewer import get_parameters_button
30
30
  from ._design_matrix_panel import DesignMatrixPanel
31
31
  from .experiment_config_panel import ExperimentConfigPanel
32
32
 
@@ -14,9 +14,9 @@ from ert.gui.ertwidgets import (
14
14
  CopyableLabel,
15
15
  EnsembleSelector,
16
16
  StringBox,
17
+ Suggestor,
17
18
  )
18
19
  from ert.gui.simulation.experiment_config_panel import ExperimentConfigPanel
19
- from ert.gui.suggestor import Suggestor
20
20
  from ert.mode_definitions import EVALUATE_ENSEMBLE_MODE
21
21
  from ert.run_models.evaluate_ensemble import EvaluateEnsemble
22
22
  from ert.storage import RealizationStorageState
@@ -364,7 +364,7 @@ class ExperimentPanel(QWidget):
364
364
  run_path=Path(self.config.runpath_config.runpath_format_string),
365
365
  storage_path=self._notifier.storage.path,
366
366
  )
367
- self._dialog.set_queue_system_name(model.queue_system)
367
+ self._dialog.set_queue_system_name(model.queue_config.queue_system)
368
368
  self.experiment_started.emit(self._dialog)
369
369
  self._simulation_done = False
370
370
  self.run_button.setEnabled(self._simulation_done)