ert 19.0.0rc0__py3-none-any.whl → 19.0.0rc1__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.
- ert/__main__.py +94 -63
- ert/analysis/_es_update.py +30 -20
- ert/config/_create_observation_dataframes.py +23 -262
- ert/config/_observations.py +181 -170
- ert/config/_read_summary.py +4 -5
- ert/config/ert_config.py +1 -48
- ert/config/observation_config_migrations.py +793 -0
- ert/config/parsing/config_schema_deprecations.py +0 -11
- ert/config/parsing/observations_parser.py +6 -0
- ert/config/rft_config.py +1 -1
- ert/dark_storage/compute/misfits.py +0 -42
- ert/dark_storage/endpoints/__init__.py +0 -2
- ert/dark_storage/endpoints/experiments.py +0 -3
- ert/dark_storage/endpoints/observations.py +10 -2
- ert/dark_storage/json_schema/experiment.py +0 -1
- ert/gui/main.py +4 -4
- ert/gui/main_window.py +2 -0
- ert/gui/simulation/experiment_panel.py +4 -0
- ert/gui/summarypanel.py +19 -0
- ert/gui/tools/manage_experiments/export_dialog.py +4 -0
- ert/gui/tools/manage_experiments/storage_info_widget.py +20 -20
- ert/gui/tools/plot/plot_api.py +10 -10
- ert/gui/tools/plot/plot_widget.py +5 -0
- ert/gui/tools/plot/plot_window.py +1 -1
- ert/run_models/run_model.py +1 -21
- ert/services/__init__.py +7 -3
- ert/services/_storage_main.py +59 -22
- ert/services/ert_server.py +186 -24
- ert/shared/version.py +3 -3
- ert/storage/local_ensemble.py +2 -95
- ert/storage/local_experiment.py +0 -16
- ert/storage/local_storage.py +3 -1
- ert/storage/migration/to22.py +18 -0
- ert/utils/__init__.py +20 -0
- {ert-19.0.0rc0.dist-info → ert-19.0.0rc1.dist-info}/METADATA +2 -2
- {ert-19.0.0rc0.dist-info → ert-19.0.0rc1.dist-info}/RECORD +47 -52
- everest/bin/everest_script.py +5 -5
- everest/bin/kill_script.py +2 -2
- everest/bin/monitor_script.py +2 -2
- everest/bin/utils.py +4 -4
- everest/detached/everserver.py +6 -6
- everest/gui/main_window.py +2 -2
- everest/util/__init__.py +1 -19
- ert/dark_storage/compute/__init__.py +0 -0
- ert/dark_storage/endpoints/compute/__init__.py +0 -0
- ert/dark_storage/endpoints/compute/misfits.py +0 -95
- ert/services/_base_service.py +0 -387
- ert/services/webviz_ert_service.py +0 -20
- ert/shared/storage/command.py +0 -38
- ert/shared/storage/extraction.py +0 -42
- {ert-19.0.0rc0.dist-info → ert-19.0.0rc1.dist-info}/WHEEL +0 -0
- {ert-19.0.0rc0.dist-info → ert-19.0.0rc1.dist-info}/entry_points.txt +0 -0
- {ert-19.0.0rc0.dist-info → ert-19.0.0rc1.dist-info}/licenses/COPYING +0 -0
- {ert-19.0.0rc0.dist-info → ert-19.0.0rc1.dist-info}/top_level.txt +0 -0
ert/__main__.py
CHANGED
|
@@ -8,10 +8,12 @@ import multiprocessing
|
|
|
8
8
|
import os
|
|
9
9
|
import re
|
|
10
10
|
import resource
|
|
11
|
+
import shutil
|
|
11
12
|
import sys
|
|
12
13
|
import warnings
|
|
13
14
|
from argparse import ArgumentParser, ArgumentTypeError
|
|
14
15
|
from collections.abc import Sequence
|
|
16
|
+
from datetime import datetime
|
|
15
17
|
from pathlib import Path
|
|
16
18
|
from typing import Any
|
|
17
19
|
from uuid import UUID
|
|
@@ -24,6 +26,9 @@ from _ert.threading import set_signal_handler
|
|
|
24
26
|
from ert.base_model_context import use_runtime_plugins
|
|
25
27
|
from ert.cli.main import ErtCliError, run_cli
|
|
26
28
|
from ert.config import ConfigValidationError, ErtConfig, lint_file
|
|
29
|
+
from ert.config.observation_config_migrations import (
|
|
30
|
+
remove_refcase_and_time_map_dependence_from_obs_config,
|
|
31
|
+
)
|
|
27
32
|
from ert.logging import LOGGING_CONFIG
|
|
28
33
|
from ert.mode_definitions import (
|
|
29
34
|
ENIF_MODE,
|
|
@@ -36,9 +41,9 @@ from ert.mode_definitions import (
|
|
|
36
41
|
from ert.namespace import Namespace
|
|
37
42
|
from ert.plugins import ErtRuntimePlugins, get_site_plugins, setup_site_logging
|
|
38
43
|
from ert.run_models.multiple_data_assimilation import MultipleDataAssimilationConfig
|
|
39
|
-
from ert.services import ErtServer
|
|
44
|
+
from ert.services import ErtServer
|
|
45
|
+
from ert.services._storage_main import add_parser_options as ert_api_add_parser_options
|
|
40
46
|
from ert.shared.status.utils import get_ert_memory_usage
|
|
41
|
-
from ert.shared.storage.command import add_parser_options as ert_api_add_parser_options
|
|
42
47
|
from ert.storage import ErtStorageException, ErtStoragePermissionError
|
|
43
48
|
from ert.trace import trace, tracer
|
|
44
49
|
from ert.validation import (
|
|
@@ -53,6 +58,66 @@ from ert.validation import (
|
|
|
53
58
|
logger = logging.getLogger(__name__)
|
|
54
59
|
|
|
55
60
|
|
|
61
|
+
def run_convert_observations(
|
|
62
|
+
args: Namespace, _: ErtRuntimePlugins | None = None
|
|
63
|
+
) -> None:
|
|
64
|
+
changes = remove_refcase_and_time_map_dependence_from_obs_config(args.config)
|
|
65
|
+
|
|
66
|
+
if changes is None or changes.is_empty():
|
|
67
|
+
logger.info("convert_observations did not make any changes")
|
|
68
|
+
print(
|
|
69
|
+
"No observations dependent on TIME_MAP / REFCASE found, you can "
|
|
70
|
+
"safely remove TIME_MAP / REFCASE and the "
|
|
71
|
+
"corresponding files from ERT config."
|
|
72
|
+
)
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
obs_config_to_edit_path = changes.obs_config_path + ".updated"
|
|
76
|
+
print(
|
|
77
|
+
f"Making copy of obs config "
|
|
78
|
+
f"@ {changes.obs_config_path} -> {obs_config_to_edit_path}"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
shutil.copy(changes.obs_config_path, obs_config_to_edit_path)
|
|
82
|
+
print(f"Applying change to obs config @ {obs_config_to_edit_path}...")
|
|
83
|
+
changes.apply_to_file(Path(obs_config_to_edit_path))
|
|
84
|
+
convert_observations_trace = ""
|
|
85
|
+
for history_change in changes.history_changes:
|
|
86
|
+
convert_observations_trace += (
|
|
87
|
+
f"History obs {history_change.source_observation.name} "
|
|
88
|
+
f"-> {len(history_change.summary_obs_declarations)} summary observations\n"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
for gen_obs_change in changes.general_obs_changes:
|
|
92
|
+
convert_observations_trace += (
|
|
93
|
+
f"General obs {gen_obs_change.source_observation.name}, changing "
|
|
94
|
+
f"DATE {gen_obs_change.source_observation.date} "
|
|
95
|
+
f"to RESTART={gen_obs_change.restart}\n"
|
|
96
|
+
)
|
|
97
|
+
for summary_change in changes.summary_obs_changes:
|
|
98
|
+
convert_observations_trace += (
|
|
99
|
+
f"Summary obs {summary_change.source_observation.name}, changing "
|
|
100
|
+
f"RESTART {summary_change.source_observation.restart} "
|
|
101
|
+
f"to DATE={summary_change.date}\n"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
logger.info(f"convert_observations trace: \n {convert_observations_trace}")
|
|
105
|
+
print(convert_observations_trace)
|
|
106
|
+
|
|
107
|
+
os.rename(
|
|
108
|
+
changes.obs_config_path,
|
|
109
|
+
f"{changes.obs_config_path}-{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.old",
|
|
110
|
+
)
|
|
111
|
+
os.rename(obs_config_to_edit_path, changes.obs_config_path)
|
|
112
|
+
msg = (
|
|
113
|
+
f"Observation changes applied to {changes.obs_config_path}. The old "
|
|
114
|
+
f"observations file is now at {changes.obs_config_path}.old and can be "
|
|
115
|
+
f"safely deleted if the new one works."
|
|
116
|
+
)
|
|
117
|
+
print(msg)
|
|
118
|
+
logger.info(msg)
|
|
119
|
+
|
|
120
|
+
|
|
56
121
|
def run_ert_storage(args: Namespace, _: ErtRuntimePlugins | None = None) -> None:
|
|
57
122
|
with ErtServer.start_server(
|
|
58
123
|
verbose=True,
|
|
@@ -63,67 +128,24 @@ def run_ert_storage(args: Namespace, _: ErtRuntimePlugins | None = None) -> None
|
|
|
63
128
|
|
|
64
129
|
|
|
65
130
|
def run_webviz_ert(args: Namespace, _: ErtRuntimePlugins | None = None) -> None:
|
|
66
|
-
try:
|
|
67
|
-
import webviz_ert # type: ignore # noqa
|
|
68
|
-
except ImportError as err:
|
|
69
|
-
raise ValueError(
|
|
70
|
-
"Running `ert vis` requires that webviz_ert is installed"
|
|
71
|
-
) from err
|
|
72
|
-
|
|
73
|
-
kwargs: dict[str, Any] = {"verbose": args.verbose}
|
|
74
|
-
ert_config = ErtConfig.with_plugins(get_site_plugins()).from_file(args.config)
|
|
75
|
-
|
|
76
|
-
os.chdir(ert_config.config_path)
|
|
77
|
-
ens_path = ert_config.ens_path
|
|
78
|
-
|
|
79
|
-
# Changing current working directory means we need to
|
|
80
|
-
# only use the base name of the config file path
|
|
81
|
-
kwargs["ert_config"] = os.path.basename(args.config)
|
|
82
|
-
kwargs["project"] = os.path.abspath(ens_path)
|
|
83
|
-
|
|
84
131
|
yellow = "\x1b[33m"
|
|
85
132
|
green = "\x1b[32m"
|
|
86
133
|
bold = "\x1b[1m"
|
|
87
134
|
reset = "\x1b[0m"
|
|
88
135
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
storage.wait_until_ready()
|
|
92
|
-
print(
|
|
93
|
-
f"""
|
|
136
|
+
print(
|
|
137
|
+
f"""
|
|
94
138
|
---------------------------------------------------------------
|
|
95
139
|
|
|
96
|
-
{yellow}{bold}Webviz-ERT is
|
|
140
|
+
{yellow}{bold}Webviz-ERT is removed.
|
|
97
141
|
|
|
98
142
|
{green}{bold}Plotting capabilities provided by Webviz-ERT are now available
|
|
99
143
|
using the ERT plotter{reset}
|
|
100
144
|
|
|
101
|
-
---------------------------------------------------------------
|
|
102
|
-
|
|
103
|
-
Starting up Webviz-ERT. This might take more than a minute.
|
|
104
|
-
|
|
105
145
|
---------------------------------------------------------------
|
|
106
146
|
"""
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
webviz_kwargs = {
|
|
110
|
-
"experimental_mode": args.experimental_mode,
|
|
111
|
-
"verbose": args.verbose,
|
|
112
|
-
"title": kwargs.get("ert_config", "ERT - Visualization tool"),
|
|
113
|
-
"project": kwargs.get("project", os.getcwd()),
|
|
114
|
-
}
|
|
115
|
-
with WebvizErt.start_server(**webviz_kwargs) as webviz_ert_server:
|
|
116
|
-
webviz_ert_server.wait()
|
|
117
|
-
except PermissionError as pe:
|
|
118
|
-
print(f"Error: {pe}", file=sys.stderr)
|
|
119
|
-
print(
|
|
120
|
-
"Cannot start or connect to storage service due to permission issues.",
|
|
121
|
-
file=sys.stderr,
|
|
122
|
-
)
|
|
123
|
-
print(
|
|
124
|
-
"This is most likely due to another user starting ERT using this storage",
|
|
125
|
-
file=sys.stderr,
|
|
126
|
-
)
|
|
147
|
+
)
|
|
148
|
+
logger.info("Show Webviz-ert removal warning")
|
|
127
149
|
|
|
128
150
|
|
|
129
151
|
def strip_error_message_and_raise_exception(validated: ValidationStatus) -> None:
|
|
@@ -317,19 +339,6 @@ def get_ert_parser(parser: ArgumentParser | None = None) -> ArgumentParser:
|
|
|
317
339
|
ert_api_parser.set_defaults(func=run_ert_storage)
|
|
318
340
|
ert_api_add_parser_options(ert_api_parser)
|
|
319
341
|
|
|
320
|
-
ert_vis_parser = subparsers.add_parser(
|
|
321
|
-
"vis",
|
|
322
|
-
description="Launch webviz-driven visualization tool.",
|
|
323
|
-
)
|
|
324
|
-
ert_vis_parser.set_defaults(func=run_webviz_ert)
|
|
325
|
-
ert_vis_parser.add_argument("--name", "-n", type=str, default="Webviz-ERT")
|
|
326
|
-
ert_vis_parser.add_argument(
|
|
327
|
-
"--experimental-mode",
|
|
328
|
-
action="store_true",
|
|
329
|
-
help="Feature flag for enabling experimental plugins",
|
|
330
|
-
)
|
|
331
|
-
ert_api_add_parser_options(ert_vis_parser) # ert vis shares args with ert api
|
|
332
|
-
|
|
333
342
|
# test_run_parser
|
|
334
343
|
test_run_description = f"Run '{TEST_RUN_MODE}' in cli"
|
|
335
344
|
test_run_parser = subparsers.add_parser(
|
|
@@ -564,6 +573,28 @@ def get_ert_parser(parser: ArgumentParser | None = None) -> ArgumentParser:
|
|
|
564
573
|
"--ensemble", help="Which ensemble to use", default=None
|
|
565
574
|
)
|
|
566
575
|
|
|
576
|
+
# convert_observations_parser
|
|
577
|
+
convert_obs_parser = subparsers.add_parser(
|
|
578
|
+
"convert_observations",
|
|
579
|
+
help=(
|
|
580
|
+
"Convert HISTORY_OBSERVATION to SUMMARY_OBSERVATION and "
|
|
581
|
+
"remove REFCASE and TIME_MAP from ERT config."
|
|
582
|
+
),
|
|
583
|
+
description=(
|
|
584
|
+
"Convert HISTORY_OBSERVATION to SUMMARY_OBSERVATION, "
|
|
585
|
+
"and embed REFCASE and TIME_MAP into observations"
|
|
586
|
+
),
|
|
587
|
+
)
|
|
588
|
+
convert_obs_parser.set_defaults(func=run_convert_observations)
|
|
589
|
+
convert_obs_parser.add_argument(
|
|
590
|
+
"config",
|
|
591
|
+
type=valid_file,
|
|
592
|
+
help="Path to ERT config file",
|
|
593
|
+
)
|
|
594
|
+
convert_obs_parser.add_argument(
|
|
595
|
+
"--verbose", action="store_true", help="Show verbose output.", default=False
|
|
596
|
+
)
|
|
597
|
+
|
|
567
598
|
# Common arguments/defaults for all non-gui modes
|
|
568
599
|
for cli_parser in [
|
|
569
600
|
test_run_parser,
|
ert/analysis/_es_update.py
CHANGED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import functools
|
|
4
4
|
import logging
|
|
5
|
+
import re
|
|
5
6
|
import time
|
|
6
7
|
import warnings
|
|
7
8
|
from collections.abc import Callable, Iterable, Sequence
|
|
@@ -322,16 +323,13 @@ def analysis_ES(
|
|
|
322
323
|
logger.info(log_msg)
|
|
323
324
|
progress_callback(AnalysisStatusEvent(msg=log_msg))
|
|
324
325
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
)
|
|
333
|
-
logger.info(log_msg)
|
|
334
|
-
progress_callback(AnalysisStatusEvent(msg=log_msg))
|
|
326
|
+
if (param_count := (~non_zero_variance_mask).sum()) > 0:
|
|
327
|
+
log_msg = (
|
|
328
|
+
f"There are {param_count} parameters with 0 variance "
|
|
329
|
+
f"that will not be updated."
|
|
330
|
+
)
|
|
331
|
+
logger.info(log_msg)
|
|
332
|
+
progress_callback(AnalysisStatusEvent(msg=log_msg))
|
|
335
333
|
|
|
336
334
|
if module.localization:
|
|
337
335
|
config_node = source_ensemble.experiment.parameter_configuration[
|
|
@@ -381,16 +379,16 @@ def analysis_ES(
|
|
|
381
379
|
)
|
|
382
380
|
|
|
383
381
|
else:
|
|
382
|
+
log_msg = f"There are {num_obs} responses and {ensemble_size} realizations."
|
|
383
|
+
logger.info(log_msg)
|
|
384
|
+
progress_callback(AnalysisStatusEvent(msg=log_msg))
|
|
385
|
+
|
|
384
386
|
# In-place multiplication is not yet supported, therefore avoiding @=
|
|
385
387
|
param_ensemble_array[non_zero_variance_mask] = param_ensemble_array[ # noqa: PLR6104
|
|
386
388
|
non_zero_variance_mask
|
|
387
389
|
] @ T.astype(param_ensemble_array.dtype)
|
|
388
390
|
|
|
389
|
-
log_msg = f"Storing data for {param_group}.."
|
|
390
|
-
logger.info(log_msg)
|
|
391
|
-
progress_callback(AnalysisStatusEvent(msg=log_msg))
|
|
392
391
|
start = time.time()
|
|
393
|
-
|
|
394
392
|
target_ensemble.save_parameters_numpy(
|
|
395
393
|
param_ensemble_array, param_group, iens_active_index
|
|
396
394
|
)
|
|
@@ -441,6 +439,12 @@ def smoother_update(
|
|
|
441
439
|
with warnings.catch_warnings():
|
|
442
440
|
original_showwarning = warnings.showwarning
|
|
443
441
|
|
|
442
|
+
ILL_CONDITIONED_RE = re.compile(
|
|
443
|
+
r"^LinAlgWarning:.*ill[- ]?conditioned\s+matrix", re.IGNORECASE
|
|
444
|
+
)
|
|
445
|
+
LIMIT_ILL_CONDITIONED_WARNING = 1000
|
|
446
|
+
illconditioned_warn_counter = 0
|
|
447
|
+
|
|
444
448
|
def log_warning(
|
|
445
449
|
message: Warning | str,
|
|
446
450
|
category: type[Warning],
|
|
@@ -449,12 +453,18 @@ def smoother_update(
|
|
|
449
453
|
file: TextIO | None = None,
|
|
450
454
|
line: str | None = None,
|
|
451
455
|
) -> None:
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
456
|
+
nonlocal illconditioned_warn_counter
|
|
457
|
+
|
|
458
|
+
if ILL_CONDITIONED_RE.search(str(message)):
|
|
459
|
+
illconditioned_warn_counter += 1
|
|
460
|
+
|
|
461
|
+
if illconditioned_warn_counter < LIMIT_ILL_CONDITIONED_WARNING:
|
|
462
|
+
logger.warning(
|
|
463
|
+
f"{category.__name__}: {message} (from {filename}:{lineno})"
|
|
464
|
+
)
|
|
465
|
+
original_showwarning(
|
|
466
|
+
message, category, filename, lineno, file=file, line=line
|
|
467
|
+
)
|
|
458
468
|
|
|
459
469
|
warnings.showwarning = log_warning
|
|
460
470
|
analysis_ES(
|
|
@@ -2,21 +2,19 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections import defaultdict
|
|
4
4
|
from collections.abc import Sequence
|
|
5
|
-
from datetime import datetime
|
|
6
|
-
from typing import
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import assert_never
|
|
7
7
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
import polars as pl
|
|
10
|
-
from
|
|
10
|
+
from numpy import typing as npt
|
|
11
11
|
|
|
12
12
|
from ert.validation import rangestring_to_list
|
|
13
13
|
|
|
14
14
|
from ._observations import (
|
|
15
15
|
ErrorModes,
|
|
16
16
|
GeneralObservation,
|
|
17
|
-
HistoryObservation,
|
|
18
17
|
Observation,
|
|
19
|
-
ObservationDate,
|
|
20
18
|
ObservationError,
|
|
21
19
|
RFTObservation,
|
|
22
20
|
SummaryObservation,
|
|
@@ -25,59 +23,31 @@ from .gen_data_config import GenDataConfig
|
|
|
25
23
|
from .parsing import (
|
|
26
24
|
ConfigWarning,
|
|
27
25
|
ErrorInfo,
|
|
28
|
-
HistorySource,
|
|
29
26
|
ObservationConfigError,
|
|
30
27
|
)
|
|
31
|
-
from .refcase import Refcase
|
|
32
28
|
from .rft_config import RFTConfig
|
|
33
29
|
|
|
34
|
-
if TYPE_CHECKING:
|
|
35
|
-
import numpy.typing as npt
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
DEFAULT_TIME_DELTA = timedelta(seconds=30)
|
|
39
30
|
DEFAULT_LOCATION_RANGE_M = 3000
|
|
40
31
|
|
|
41
32
|
|
|
42
33
|
def create_observation_dataframes(
|
|
43
34
|
observations: Sequence[Observation],
|
|
44
|
-
refcase: Refcase | None,
|
|
45
35
|
gen_data_config: GenDataConfig | None,
|
|
46
36
|
rft_config: RFTConfig | None,
|
|
47
|
-
time_map: list[datetime] | None,
|
|
48
|
-
history: HistorySource,
|
|
49
37
|
) -> dict[str, pl.DataFrame]:
|
|
50
38
|
if not observations:
|
|
51
39
|
return {}
|
|
52
|
-
obs_time_list: list[datetime] = []
|
|
53
|
-
if refcase is not None:
|
|
54
|
-
obs_time_list = refcase.all_dates
|
|
55
|
-
elif time_map is not None:
|
|
56
|
-
obs_time_list = time_map
|
|
57
40
|
|
|
58
|
-
time_len = len(obs_time_list)
|
|
59
41
|
config_errors: list[ErrorInfo] = []
|
|
60
42
|
grouped: dict[str, list[pl.DataFrame]] = defaultdict(list)
|
|
61
43
|
for obs in observations:
|
|
62
44
|
try:
|
|
63
45
|
match obs:
|
|
64
|
-
case HistoryObservation():
|
|
65
|
-
grouped["summary"].append(
|
|
66
|
-
_handle_history_observation(
|
|
67
|
-
refcase,
|
|
68
|
-
obs,
|
|
69
|
-
obs.name,
|
|
70
|
-
history,
|
|
71
|
-
time_len,
|
|
72
|
-
)
|
|
73
|
-
)
|
|
74
46
|
case SummaryObservation():
|
|
75
47
|
grouped["summary"].append(
|
|
76
48
|
_handle_summary_observation(
|
|
77
49
|
obs,
|
|
78
50
|
obs.name,
|
|
79
|
-
obs_time_list,
|
|
80
|
-
bool(refcase),
|
|
81
51
|
)
|
|
82
52
|
)
|
|
83
53
|
case GeneralObservation():
|
|
@@ -86,8 +56,6 @@ def create_observation_dataframes(
|
|
|
86
56
|
gen_data_config,
|
|
87
57
|
obs,
|
|
88
58
|
obs.name,
|
|
89
|
-
obs_time_list,
|
|
90
|
-
bool(refcase),
|
|
91
59
|
)
|
|
92
60
|
)
|
|
93
61
|
case RFTObservation():
|
|
@@ -137,92 +105,6 @@ def _handle_error_mode(
|
|
|
137
105
|
assert_never(default)
|
|
138
106
|
|
|
139
107
|
|
|
140
|
-
def _handle_history_observation(
|
|
141
|
-
refcase: Refcase | None,
|
|
142
|
-
history_observation: HistoryObservation,
|
|
143
|
-
summary_key: str,
|
|
144
|
-
history_type: HistorySource,
|
|
145
|
-
time_len: int,
|
|
146
|
-
) -> pl.DataFrame:
|
|
147
|
-
if refcase is None:
|
|
148
|
-
raise ObservationConfigError.with_context(
|
|
149
|
-
"REFCASE is required for HISTORY_OBSERVATION", summary_key
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
if history_type == HistorySource.REFCASE_HISTORY:
|
|
153
|
-
local_key = history_key(summary_key)
|
|
154
|
-
else:
|
|
155
|
-
local_key = summary_key
|
|
156
|
-
if local_key not in refcase.keys:
|
|
157
|
-
raise ObservationConfigError.with_context(
|
|
158
|
-
f"Key {local_key!r} is not present in refcase", summary_key
|
|
159
|
-
)
|
|
160
|
-
values = refcase.values[refcase.keys.index(local_key)]
|
|
161
|
-
std_dev = _handle_error_mode(values, history_observation)
|
|
162
|
-
for segment in history_observation.segments:
|
|
163
|
-
start = segment.start
|
|
164
|
-
stop = segment.stop
|
|
165
|
-
if start < 0:
|
|
166
|
-
ConfigWarning.warn(
|
|
167
|
-
f"Segment {segment.name} out of bounds."
|
|
168
|
-
" Truncating start of segment to 0.",
|
|
169
|
-
segment.name,
|
|
170
|
-
)
|
|
171
|
-
start = 0
|
|
172
|
-
if stop >= time_len:
|
|
173
|
-
ConfigWarning.warn(
|
|
174
|
-
f"Segment {segment.name} out of bounds. Truncating"
|
|
175
|
-
f" end of segment to {time_len - 1}.",
|
|
176
|
-
segment.name,
|
|
177
|
-
)
|
|
178
|
-
stop = time_len - 1
|
|
179
|
-
if start > stop:
|
|
180
|
-
ConfigWarning.warn(
|
|
181
|
-
f"Segment {segment.name} start after stop. Truncating"
|
|
182
|
-
f" end of segment to {start}.",
|
|
183
|
-
segment.name,
|
|
184
|
-
)
|
|
185
|
-
stop = start
|
|
186
|
-
if np.size(std_dev[start:stop]) == 0:
|
|
187
|
-
ConfigWarning.warn(
|
|
188
|
-
f"Segment {segment.name} does not"
|
|
189
|
-
" contain any time steps. The interval "
|
|
190
|
-
f"[{start}, {stop}) does not intersect with steps in the"
|
|
191
|
-
"time map.",
|
|
192
|
-
segment.name,
|
|
193
|
-
)
|
|
194
|
-
std_dev[start:stop] = _handle_error_mode(values[start:stop], segment)
|
|
195
|
-
dates_series = pl.Series(refcase.dates).dt.cast_time_unit("ms")
|
|
196
|
-
if (std_dev <= 0).any():
|
|
197
|
-
raise ObservationConfigError.with_context(
|
|
198
|
-
"Observation uncertainty must be strictly > 0", summary_key
|
|
199
|
-
) from None
|
|
200
|
-
|
|
201
|
-
return pl.DataFrame(
|
|
202
|
-
{
|
|
203
|
-
"response_key": summary_key,
|
|
204
|
-
"observation_key": summary_key,
|
|
205
|
-
"time": dates_series,
|
|
206
|
-
"observations": pl.Series(values, dtype=pl.Float32),
|
|
207
|
-
"std": pl.Series(std_dev, dtype=pl.Float32),
|
|
208
|
-
}
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
def _get_time(
|
|
213
|
-
date_dict: ObservationDate, start_time: datetime, context: Any = None
|
|
214
|
-
) -> tuple[datetime, str]:
|
|
215
|
-
if date_dict.date is not None:
|
|
216
|
-
return _parse_date(date_dict.date), f"DATE={date_dict.date}"
|
|
217
|
-
if date_dict.days is not None:
|
|
218
|
-
days = date_dict.days
|
|
219
|
-
return start_time + timedelta(days=days), f"DAYS={days}"
|
|
220
|
-
if date_dict.hours is not None:
|
|
221
|
-
hours = date_dict.hours
|
|
222
|
-
return start_time + timedelta(hours=hours), f"HOURS={hours}"
|
|
223
|
-
raise ObservationConfigError.with_context("Missing time specifier", context=context)
|
|
224
|
-
|
|
225
|
-
|
|
226
108
|
def _parse_date(date_str: str) -> datetime:
|
|
227
109
|
try:
|
|
228
110
|
return datetime.fromisoformat(date_str)
|
|
@@ -243,171 +125,50 @@ def _parse_date(date_str: str) -> datetime:
|
|
|
243
125
|
return date
|
|
244
126
|
|
|
245
127
|
|
|
246
|
-
def _find_nearest(
|
|
247
|
-
time_map: list[datetime],
|
|
248
|
-
time: datetime,
|
|
249
|
-
threshold: timedelta = DEFAULT_TIME_DELTA,
|
|
250
|
-
) -> int:
|
|
251
|
-
nearest_index = -1
|
|
252
|
-
nearest_diff = None
|
|
253
|
-
for i, t in enumerate(time_map):
|
|
254
|
-
diff = abs(time - t)
|
|
255
|
-
if diff < threshold and (nearest_diff is None or nearest_diff > diff):
|
|
256
|
-
nearest_diff = diff
|
|
257
|
-
nearest_index = i
|
|
258
|
-
if nearest_diff is None:
|
|
259
|
-
raise IndexError(f"{time} is not in the time map")
|
|
260
|
-
return nearest_index
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def _get_restart(
|
|
264
|
-
date_dict: ObservationDate,
|
|
265
|
-
obs_name: str,
|
|
266
|
-
time_map: list[datetime],
|
|
267
|
-
has_refcase: bool,
|
|
268
|
-
) -> int:
|
|
269
|
-
if date_dict.restart is not None:
|
|
270
|
-
return date_dict.restart
|
|
271
|
-
if not time_map:
|
|
272
|
-
raise ObservationConfigError.with_context(
|
|
273
|
-
f"Missing REFCASE or TIME_MAP for observations: {obs_name}",
|
|
274
|
-
obs_name,
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
time, date_str = _get_time(date_dict, time_map[0], context=obs_name)
|
|
278
|
-
|
|
279
|
-
try:
|
|
280
|
-
return _find_nearest(time_map, time)
|
|
281
|
-
except IndexError as err:
|
|
282
|
-
raise ObservationConfigError.with_context(
|
|
283
|
-
f"Could not find {time} ({date_str}) in "
|
|
284
|
-
f"the time map for observations {obs_name}. "
|
|
285
|
-
+ (
|
|
286
|
-
"The time map is set from the REFCASE keyword. Either "
|
|
287
|
-
"the REFCASE has an incorrect/missing date, or the observation "
|
|
288
|
-
"is given an incorrect date.)"
|
|
289
|
-
if has_refcase
|
|
290
|
-
else "(The time map is set from the TIME_MAP "
|
|
291
|
-
"keyword. Either the time map file has an "
|
|
292
|
-
"incorrect/missing date, or the observation is given an "
|
|
293
|
-
"incorrect date."
|
|
294
|
-
),
|
|
295
|
-
obs_name,
|
|
296
|
-
) from err
|
|
297
|
-
|
|
298
|
-
|
|
299
128
|
def _has_localization(summary_dict: SummaryObservation) -> bool:
|
|
300
|
-
return
|
|
301
|
-
[
|
|
302
|
-
summary_dict.location_x is not None,
|
|
303
|
-
summary_dict.location_y is not None,
|
|
304
|
-
summary_dict.location_range is not None,
|
|
305
|
-
]
|
|
306
|
-
)
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
def _validate_localization_values(summary_dict: SummaryObservation) -> None:
|
|
310
|
-
"""The user must provide LOCATION_X and LOCATION_Y to use localization, while
|
|
311
|
-
unprovided LOCATION_RANGE should default to some value.
|
|
312
|
-
|
|
313
|
-
This method assumes the summary dict contains at least one LOCATION key.
|
|
314
|
-
"""
|
|
315
|
-
if summary_dict.location_x is None or summary_dict.location_y is None:
|
|
316
|
-
loc_values = {
|
|
317
|
-
"LOCATION_X": summary_dict.location_x,
|
|
318
|
-
"LOCATION_Y": summary_dict.location_y,
|
|
319
|
-
"LOCATION_RANGE": summary_dict.location_range,
|
|
320
|
-
}
|
|
321
|
-
provided_loc_values = {k: v for k, v in loc_values.items() if v is not None}
|
|
322
|
-
|
|
323
|
-
provided_loc_values_string = ", ".join(
|
|
324
|
-
key.upper() for key in provided_loc_values
|
|
325
|
-
)
|
|
326
|
-
raise ObservationConfigError.with_context(
|
|
327
|
-
f"Localization for observation {summary_dict.name} is misconfigured.\n"
|
|
328
|
-
f"Only {provided_loc_values_string} were provided. To enable "
|
|
329
|
-
f"localization for an observation, ensure that both LOCATION_X and "
|
|
330
|
-
f"LOCATION_Y are defined - or remove LOCATION keywords to disable "
|
|
331
|
-
f"localization.",
|
|
332
|
-
summary_dict,
|
|
333
|
-
)
|
|
129
|
+
return summary_dict.location_x is not None and summary_dict.location_y is not None
|
|
334
130
|
|
|
335
131
|
|
|
336
132
|
def _handle_summary_observation(
|
|
337
133
|
summary_dict: SummaryObservation,
|
|
338
134
|
obs_key: str,
|
|
339
|
-
time_map: list[datetime],
|
|
340
|
-
has_refcase: bool,
|
|
341
135
|
) -> pl.DataFrame:
|
|
342
136
|
summary_key = summary_dict.key
|
|
343
137
|
value = summary_dict.value
|
|
344
138
|
std_dev = float(_handle_error_mode(np.array(value), summary_dict))
|
|
139
|
+
date = _parse_date(summary_dict.date)
|
|
345
140
|
|
|
346
|
-
if summary_dict.restart and not (time_map or has_refcase):
|
|
347
|
-
raise ObservationConfigError.with_context(
|
|
348
|
-
"Keyword 'RESTART' requires either TIME_MAP or REFCASE", context=obs_key
|
|
349
|
-
)
|
|
350
|
-
|
|
351
|
-
if summary_dict.date is not None and not time_map:
|
|
352
|
-
# We special case when the user has provided date in SUMMARY_OBS
|
|
353
|
-
# and not REFCASE or time_map so that we don't change current behavior.
|
|
354
|
-
date = _parse_date(summary_dict.date)
|
|
355
|
-
restart = None
|
|
356
|
-
else:
|
|
357
|
-
restart = _get_restart(summary_dict, obs_key, time_map, has_refcase)
|
|
358
|
-
date = time_map[restart]
|
|
359
|
-
|
|
360
|
-
if restart == 0:
|
|
361
|
-
raise ObservationConfigError.with_context(
|
|
362
|
-
"It is unfortunately not possible to use summary "
|
|
363
|
-
"observations from the start of the simulation. "
|
|
364
|
-
f"Problem with observation {obs_key}"
|
|
365
|
-
f"{' at ' + str(_get_time(summary_dict, time_map[0], obs_key)) if summary_dict.restart is None else ''}", # noqa: E501
|
|
366
|
-
obs_key,
|
|
367
|
-
)
|
|
368
141
|
if std_dev <= 0:
|
|
369
142
|
raise ObservationConfigError.with_context(
|
|
370
143
|
"Observation uncertainty must be strictly > 0", summary_key
|
|
371
144
|
) from None
|
|
372
145
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
"std": pl.Series([std_dev], dtype=pl.Float32),
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
if _has_localization(summary_dict):
|
|
382
|
-
_validate_localization_values(summary_dict)
|
|
383
|
-
data_dict["location_x"] = summary_dict.location_x
|
|
384
|
-
data_dict["location_y"] = summary_dict.location_y
|
|
385
|
-
data_dict["location_range"] = (
|
|
386
|
-
summary_dict.location_range or DEFAULT_LOCATION_RANGE_M
|
|
387
|
-
)
|
|
146
|
+
location_range = (
|
|
147
|
+
summary_dict.location_range or DEFAULT_LOCATION_RANGE_M
|
|
148
|
+
if _has_localization(summary_dict)
|
|
149
|
+
else None
|
|
150
|
+
)
|
|
388
151
|
|
|
389
|
-
return pl.DataFrame(
|
|
152
|
+
return pl.DataFrame(
|
|
153
|
+
{
|
|
154
|
+
"response_key": [summary_key],
|
|
155
|
+
"observation_key": [obs_key],
|
|
156
|
+
"time": pl.Series([date]).dt.cast_time_unit("ms"),
|
|
157
|
+
"observations": pl.Series([value], dtype=pl.Float32),
|
|
158
|
+
"std": pl.Series([std_dev], dtype=pl.Float32),
|
|
159
|
+
"location_x": pl.Series([summary_dict.location_x], dtype=pl.Float32),
|
|
160
|
+
"location_y": pl.Series([summary_dict.location_y], dtype=pl.Float32),
|
|
161
|
+
"location_range": pl.Series([location_range], dtype=pl.Float32),
|
|
162
|
+
}
|
|
163
|
+
)
|
|
390
164
|
|
|
391
165
|
|
|
392
166
|
def _handle_general_observation(
|
|
393
167
|
gen_data_config: GenDataConfig | None,
|
|
394
168
|
general_observation: GeneralObservation,
|
|
395
169
|
obs_key: str,
|
|
396
|
-
time_map: list[datetime],
|
|
397
|
-
has_refcase: bool,
|
|
398
170
|
) -> pl.DataFrame:
|
|
399
171
|
response_key = general_observation.data
|
|
400
|
-
|
|
401
|
-
if all(
|
|
402
|
-
getattr(general_observation, key) is None
|
|
403
|
-
for key in ["restart", "date", "days", "hours"]
|
|
404
|
-
):
|
|
405
|
-
# The user has not provided RESTART or DATE, this is legal
|
|
406
|
-
# for GEN_DATA, so we default it to None
|
|
407
|
-
restart = None
|
|
408
|
-
else:
|
|
409
|
-
restart = _get_restart(general_observation, obs_key, time_map, has_refcase)
|
|
410
|
-
|
|
411
172
|
if gen_data_config is None or response_key not in gen_data_config.keys:
|
|
412
173
|
raise ObservationConfigError.with_context(
|
|
413
174
|
f"Problem with GENERAL_OBSERVATION {obs_key}:"
|
|
@@ -415,7 +176,7 @@ def _handle_general_observation(
|
|
|
415
176
|
response_key,
|
|
416
177
|
)
|
|
417
178
|
assert isinstance(gen_data_config, GenDataConfig)
|
|
418
|
-
|
|
179
|
+
restart = general_observation.restart
|
|
419
180
|
_, report_steps = gen_data_config.get_args_for_key(response_key)
|
|
420
181
|
|
|
421
182
|
response_report_steps = [] if report_steps is None else report_steps
|