ert 19.0.0__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 +11 -14
- ert/config/_create_observation_dataframes.py +12 -228
- ert/config/_observations.py +164 -152
- ert/config/_read_summary.py +4 -5
- ert/config/ert_config.py +1 -56
- ert/config/observation_config_migrations.py +793 -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/json_schema/experiment.py +0 -1
- ert/field_utils/grdecl_io.py +9 -26
- ert/gui/main_window.py +2 -0
- ert/gui/tools/manage_experiments/export_dialog.py +4 -0
- ert/gui/tools/manage_experiments/storage_info_widget.py +1 -5
- ert/gui/tools/plot/plot_api.py +10 -10
- ert/gui/tools/plot/plot_widget.py +12 -14
- ert/gui/tools/plot/plot_window.py +1 -10
- 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 +3 -107
- ert/storage/local_experiment.py +0 -16
- ert/storage/local_storage.py +1 -3
- ert/utils/__init__.py +20 -0
- {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/METADATA +2 -2
- {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/RECORD +40 -47
- {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/WHEEL +1 -1
- 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/storage/migration/to23.py +0 -49
- {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/entry_points.txt +0 -0
- {ert-19.0.0.dist-info → ert-19.0.0rc1.dist-info}/licenses/COPYING +0 -0
- {ert-19.0.0.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
|
@@ -323,16 +323,13 @@ def analysis_ES(
|
|
|
323
323
|
logger.info(log_msg)
|
|
324
324
|
progress_callback(AnalysisStatusEvent(msg=log_msg))
|
|
325
325
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
)
|
|
334
|
-
logger.info(log_msg)
|
|
335
|
-
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))
|
|
336
333
|
|
|
337
334
|
if module.localization:
|
|
338
335
|
config_node = source_ensemble.experiment.parameter_configuration[
|
|
@@ -382,16 +379,16 @@ def analysis_ES(
|
|
|
382
379
|
)
|
|
383
380
|
|
|
384
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
|
+
|
|
385
386
|
# In-place multiplication is not yet supported, therefore avoiding @=
|
|
386
387
|
param_ensemble_array[non_zero_variance_mask] = param_ensemble_array[ # noqa: PLR6104
|
|
387
388
|
non_zero_variance_mask
|
|
388
389
|
] @ T.astype(param_ensemble_array.dtype)
|
|
389
390
|
|
|
390
|
-
log_msg = f"Storing data for {param_group}.."
|
|
391
|
-
logger.info(log_msg)
|
|
392
|
-
progress_callback(AnalysisStatusEvent(msg=log_msg))
|
|
393
391
|
start = time.time()
|
|
394
|
-
|
|
395
392
|
target_ensemble.save_parameters_numpy(
|
|
396
393
|
param_ensemble_array, param_group, iens_active_index
|
|
397
394
|
)
|
|
@@ -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
|
-
|
|
35
|
-
import numpy.typing as npt
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
DEFAULT_TIME_DELTA = timedelta(seconds=30)
|
|
39
|
-
DEFAULT_LOCALIZATION_RADIUS = 3000
|
|
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,95 +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
|
-
"east": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
209
|
-
"north": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
210
|
-
"radius": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
211
|
-
}
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
def _get_time(
|
|
216
|
-
date_dict: ObservationDate, start_time: datetime, context: Any = None
|
|
217
|
-
) -> tuple[datetime, str]:
|
|
218
|
-
if date_dict.date is not None:
|
|
219
|
-
return _parse_date(date_dict.date), f"DATE={date_dict.date}"
|
|
220
|
-
if date_dict.days is not None:
|
|
221
|
-
days = date_dict.days
|
|
222
|
-
return start_time + timedelta(days=days), f"DAYS={days}"
|
|
223
|
-
if date_dict.hours is not None:
|
|
224
|
-
hours = date_dict.hours
|
|
225
|
-
return start_time + timedelta(hours=hours), f"HOURS={hours}"
|
|
226
|
-
raise ObservationConfigError.with_context("Missing time specifier", context=context)
|
|
227
|
-
|
|
228
|
-
|
|
229
108
|
def _parse_date(date_str: str) -> datetime:
|
|
230
109
|
try:
|
|
231
110
|
return datetime.fromisoformat(date_str)
|
|
@@ -246,102 +125,26 @@ def _parse_date(date_str: str) -> datetime:
|
|
|
246
125
|
return date
|
|
247
126
|
|
|
248
127
|
|
|
249
|
-
def _find_nearest(
|
|
250
|
-
time_map: list[datetime],
|
|
251
|
-
time: datetime,
|
|
252
|
-
threshold: timedelta = DEFAULT_TIME_DELTA,
|
|
253
|
-
) -> int:
|
|
254
|
-
nearest_index = -1
|
|
255
|
-
nearest_diff = None
|
|
256
|
-
for i, t in enumerate(time_map):
|
|
257
|
-
diff = abs(time - t)
|
|
258
|
-
if diff < threshold and (nearest_diff is None or nearest_diff > diff):
|
|
259
|
-
nearest_diff = diff
|
|
260
|
-
nearest_index = i
|
|
261
|
-
if nearest_diff is None:
|
|
262
|
-
raise IndexError(f"{time} is not in the time map")
|
|
263
|
-
return nearest_index
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def _get_restart(
|
|
267
|
-
date_dict: ObservationDate,
|
|
268
|
-
obs_name: str,
|
|
269
|
-
time_map: list[datetime],
|
|
270
|
-
has_refcase: bool,
|
|
271
|
-
) -> int:
|
|
272
|
-
if date_dict.restart is not None:
|
|
273
|
-
return date_dict.restart
|
|
274
|
-
if not time_map:
|
|
275
|
-
raise ObservationConfigError.with_context(
|
|
276
|
-
f"Missing REFCASE or TIME_MAP for observations: {obs_name}",
|
|
277
|
-
obs_name,
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
time, date_str = _get_time(date_dict, time_map[0], context=obs_name)
|
|
281
|
-
|
|
282
|
-
try:
|
|
283
|
-
return _find_nearest(time_map, time)
|
|
284
|
-
except IndexError as err:
|
|
285
|
-
raise ObservationConfigError.with_context(
|
|
286
|
-
f"Could not find {time} ({date_str}) in "
|
|
287
|
-
f"the time map for observations {obs_name}. "
|
|
288
|
-
+ (
|
|
289
|
-
"The time map is set from the REFCASE keyword. Either "
|
|
290
|
-
"the REFCASE has an incorrect/missing date, or the observation "
|
|
291
|
-
"is given an incorrect date.)"
|
|
292
|
-
if has_refcase
|
|
293
|
-
else "(The time map is set from the TIME_MAP "
|
|
294
|
-
"keyword. Either the time map file has an "
|
|
295
|
-
"incorrect/missing date, or the observation is given an "
|
|
296
|
-
"incorrect date."
|
|
297
|
-
),
|
|
298
|
-
obs_name,
|
|
299
|
-
) from err
|
|
300
|
-
|
|
301
|
-
|
|
302
128
|
def _has_localization(summary_dict: SummaryObservation) -> bool:
|
|
303
|
-
return summary_dict.
|
|
129
|
+
return summary_dict.location_x is not None and summary_dict.location_y is not None
|
|
304
130
|
|
|
305
131
|
|
|
306
132
|
def _handle_summary_observation(
|
|
307
133
|
summary_dict: SummaryObservation,
|
|
308
134
|
obs_key: str,
|
|
309
|
-
time_map: list[datetime],
|
|
310
|
-
has_refcase: bool,
|
|
311
135
|
) -> pl.DataFrame:
|
|
312
136
|
summary_key = summary_dict.key
|
|
313
137
|
value = summary_dict.value
|
|
314
138
|
std_dev = float(_handle_error_mode(np.array(value), summary_dict))
|
|
139
|
+
date = _parse_date(summary_dict.date)
|
|
315
140
|
|
|
316
|
-
if summary_dict.restart and not (time_map or has_refcase):
|
|
317
|
-
raise ObservationConfigError.with_context(
|
|
318
|
-
"Keyword 'RESTART' requires either TIME_MAP or REFCASE", context=obs_key
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
if summary_dict.date is not None and not time_map:
|
|
322
|
-
# We special case when the user has provided date in SUMMARY_OBS
|
|
323
|
-
# and not REFCASE or time_map so that we don't change current behavior.
|
|
324
|
-
date = _parse_date(summary_dict.date)
|
|
325
|
-
restart = None
|
|
326
|
-
else:
|
|
327
|
-
restart = _get_restart(summary_dict, obs_key, time_map, has_refcase)
|
|
328
|
-
date = time_map[restart]
|
|
329
|
-
|
|
330
|
-
if restart == 0:
|
|
331
|
-
raise ObservationConfigError.with_context(
|
|
332
|
-
"It is unfortunately not possible to use summary "
|
|
333
|
-
"observations from the start of the simulation. "
|
|
334
|
-
f"Problem with observation {obs_key}"
|
|
335
|
-
f"{' at ' + str(_get_time(summary_dict, time_map[0], obs_key)) if summary_dict.restart is None else ''}", # noqa: E501
|
|
336
|
-
obs_key,
|
|
337
|
-
)
|
|
338
141
|
if std_dev <= 0:
|
|
339
142
|
raise ObservationConfigError.with_context(
|
|
340
143
|
"Observation uncertainty must be strictly > 0", summary_key
|
|
341
144
|
) from None
|
|
342
145
|
|
|
343
|
-
|
|
344
|
-
summary_dict.
|
|
146
|
+
location_range = (
|
|
147
|
+
summary_dict.location_range or DEFAULT_LOCATION_RANGE_M
|
|
345
148
|
if _has_localization(summary_dict)
|
|
346
149
|
else None
|
|
347
150
|
)
|
|
@@ -353,9 +156,9 @@ def _handle_summary_observation(
|
|
|
353
156
|
"time": pl.Series([date]).dt.cast_time_unit("ms"),
|
|
354
157
|
"observations": pl.Series([value], dtype=pl.Float32),
|
|
355
158
|
"std": pl.Series([std_dev], dtype=pl.Float32),
|
|
356
|
-
"
|
|
357
|
-
"
|
|
358
|
-
"
|
|
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),
|
|
359
162
|
}
|
|
360
163
|
)
|
|
361
164
|
|
|
@@ -364,21 +167,8 @@ def _handle_general_observation(
|
|
|
364
167
|
gen_data_config: GenDataConfig | None,
|
|
365
168
|
general_observation: GeneralObservation,
|
|
366
169
|
obs_key: str,
|
|
367
|
-
time_map: list[datetime],
|
|
368
|
-
has_refcase: bool,
|
|
369
170
|
) -> pl.DataFrame:
|
|
370
171
|
response_key = general_observation.data
|
|
371
|
-
|
|
372
|
-
if all(
|
|
373
|
-
getattr(general_observation, key) is None
|
|
374
|
-
for key in ["restart", "date", "days", "hours"]
|
|
375
|
-
):
|
|
376
|
-
# The user has not provided RESTART or DATE, this is legal
|
|
377
|
-
# for GEN_DATA, so we default it to None
|
|
378
|
-
restart = None
|
|
379
|
-
else:
|
|
380
|
-
restart = _get_restart(general_observation, obs_key, time_map, has_refcase)
|
|
381
|
-
|
|
382
172
|
if gen_data_config is None or response_key not in gen_data_config.keys:
|
|
383
173
|
raise ObservationConfigError.with_context(
|
|
384
174
|
f"Problem with GENERAL_OBSERVATION {obs_key}:"
|
|
@@ -386,7 +176,7 @@ def _handle_general_observation(
|
|
|
386
176
|
response_key,
|
|
387
177
|
)
|
|
388
178
|
assert isinstance(gen_data_config, GenDataConfig)
|
|
389
|
-
|
|
179
|
+
restart = general_observation.restart
|
|
390
180
|
_, report_steps = gen_data_config.get_args_for_key(response_key)
|
|
391
181
|
|
|
392
182
|
response_report_steps = [] if report_steps is None else report_steps
|
|
@@ -485,11 +275,6 @@ def _handle_general_observation(
|
|
|
485
275
|
"index": pl.Series(indices, dtype=pl.UInt16),
|
|
486
276
|
"observations": pl.Series(values, dtype=pl.Float32),
|
|
487
277
|
"std": pl.Series(stds, dtype=pl.Float32),
|
|
488
|
-
# Location attributes will always be None for general observations, but are
|
|
489
|
-
# necessary to concatenate with other observation dataframes.
|
|
490
|
-
"east": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
491
|
-
"north": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
492
|
-
"radius": pl.Series([None] * len(values), dtype=pl.Float32),
|
|
493
278
|
}
|
|
494
279
|
)
|
|
495
280
|
|
|
@@ -532,6 +317,5 @@ def _handle_rft_observation(
|
|
|
532
317
|
"tvd": pl.Series([location[2]], dtype=pl.Float32),
|
|
533
318
|
"observations": pl.Series([rft_observation.value], dtype=pl.Float32),
|
|
534
319
|
"std": pl.Series([rft_observation.error], dtype=pl.Float32),
|
|
535
|
-
"radius": pl.Series([None], dtype=pl.Float32),
|
|
536
320
|
}
|
|
537
321
|
)
|