Skip to content

Commit e747034

Browse files
authored
Introducing delta ensembles - pilot module 'SimulationTimeSeries' (#812)
1 parent 5bd371e commit e747034

File tree

146 files changed

+4447
-1137
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

146 files changed

+4447
-1137
lines changed

backend_py/primary/primary/routers/timeseries/converters.py

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,7 @@ def to_api_vector_statistic_data(
3131
"""
3232
Create API VectorStatisticData from service layer VectorStatistics
3333
"""
34-
value_objects: List[schemas.StatisticValueObject] = []
35-
for api_func_enum in schemas.StatisticFunction:
36-
service_func_enum = StatisticFunction.from_string_value(api_func_enum.value)
37-
if service_func_enum is not None:
38-
value_arr = vector_statistics.values_dict.get(service_func_enum)
39-
if value_arr is not None:
40-
value_objects.append(schemas.StatisticValueObject(statistic_function=api_func_enum, values=value_arr))
41-
34+
value_objects = _create_statistic_value_object_list(vector_statistics)
4235
ret_data = schemas.VectorStatisticData(
4336
realizations=vector_statistics.realizations,
4437
timestamps_utc_ms=vector_statistics.timestamps_utc_ms,
@@ -48,3 +41,36 @@ def to_api_vector_statistic_data(
4841
)
4942

5043
return ret_data
44+
45+
46+
def to_api_delta_ensemble_vector_statistic_data(
47+
vector_statistics: VectorStatistics, is_rate: bool, unit: str
48+
) -> schemas.VectorStatisticData:
49+
"""
50+
Create API VectorStatisticData from service layer VectorStatistics
51+
"""
52+
value_objects = _create_statistic_value_object_list(vector_statistics)
53+
ret_data = schemas.VectorStatisticData(
54+
realizations=vector_statistics.realizations,
55+
timestamps_utc_ms=vector_statistics.timestamps_utc_ms,
56+
value_objects=value_objects,
57+
unit=unit,
58+
is_rate=is_rate,
59+
)
60+
61+
return ret_data
62+
63+
64+
def _create_statistic_value_object_list(vector_statistics: VectorStatistics) -> list[schemas.StatisticValueObject]:
65+
"""
66+
Create list of statistic value objects from vector statistics object
67+
"""
68+
value_objects: list[schemas.StatisticValueObject] = []
69+
for api_func_enum in schemas.StatisticFunction:
70+
service_func_enum = StatisticFunction.from_string_value(api_func_enum.value)
71+
if service_func_enum is not None:
72+
value_arr = vector_statistics.values_dict.get(service_func_enum)
73+
if value_arr is not None:
74+
value_objects.append(schemas.StatisticValueObject(statistic_function=api_func_enum, values=value_arr))
75+
76+
return value_objects

backend_py/primary/primary/routers/timeseries/router.py

Lines changed: 247 additions & 19 deletions
Large diffs are not rendered by default.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from dataclasses import dataclass
2+
3+
import pyarrow as pa
4+
import pyarrow.compute as pc
5+
import numpy as np
6+
7+
from primary.services.service_exceptions import InvalidDataError, Service
8+
9+
10+
@dataclass
11+
class RealizationDeltaVector:
12+
realization: int
13+
timestamps_utc_ms: list[int]
14+
values: list[float]
15+
is_rate: bool
16+
unit: str
17+
18+
19+
def _validate_summary_vector_table_pa(
20+
vector_table: pa.Table, vector_name: str, service: Service = Service.GENERAL
21+
) -> None:
22+
"""
23+
Check if the pyarrow vector table is valid.
24+
25+
Expect the pyarrow single vector table to only contain the following columns: DATE, REAL, vector_name.
26+
27+
Raises InvalidDataError if the table does not contain the expected columns.
28+
"""
29+
expected_columns = {"DATE", "REAL", vector_name}
30+
actual_columns = set(vector_table.column_names)
31+
if not expected_columns.issubset(actual_columns) or len(expected_columns) != len(actual_columns):
32+
unexpected_columns = actual_columns - expected_columns
33+
raise InvalidDataError(f"Unexpected columns in table {unexpected_columns}", service)
34+
35+
# Validate table column types
36+
if vector_table.field("DATE").type != pa.timestamp("ms"):
37+
raise InvalidDataError(
38+
f'DATE column must be of type timestamp(ms), but got {vector_table.field("DATE").type}', service
39+
)
40+
if vector_table.field("REAL").type != pa.int16():
41+
raise InvalidDataError("REAL column must be of type int16", service)
42+
if vector_table.field(vector_name).type != pa.float32():
43+
raise InvalidDataError(f"{vector_name} column must be of type float32", service)
44+
45+
46+
def create_delta_vector_table(
47+
comparison_vector_table: pa.Table, reference_vector_table: pa.Table, vector_name: str
48+
) -> pa.Table:
49+
"""
50+
Create a table with delta values of the requested vector name between the two input tables.
51+
52+
Definition:
53+
54+
delta_vector = comparison_vector - reference_vector
55+
56+
Performs "inner join". Only obtain matching index ["DATE", "REAL"] - i.e "DATE"-"REAL" combination
57+
present in only one vector is neglected.
58+
59+
Returns: A table with columns ["DATE", "REAL", vector_name] where vector_name contains the delta values.
60+
61+
`Note`: Pre-processing of DATE-columns, e.g. resampling, should be done before calling this function.
62+
"""
63+
_validate_summary_vector_table_pa(comparison_vector_table, vector_name)
64+
_validate_summary_vector_table_pa(reference_vector_table, vector_name)
65+
66+
joined_vector_table = comparison_vector_table.join(
67+
reference_vector_table, keys=["DATE", "REAL"], join_type="inner", right_suffix="_reference"
68+
)
69+
delta_vector = pc.subtract(
70+
joined_vector_table.column(vector_name), joined_vector_table.column(f"{vector_name}_reference")
71+
)
72+
73+
delta_table = pa.table(
74+
{
75+
"DATE": joined_vector_table.column("DATE"),
76+
"REAL": joined_vector_table.column("REAL"),
77+
vector_name: delta_vector,
78+
}
79+
)
80+
81+
return delta_table
82+
83+
84+
def create_realization_delta_vector_list(
85+
delta_vector_table: pa.Table, vector_name: str, is_rate: bool, unit: str
86+
) -> list[RealizationDeltaVector]:
87+
"""
88+
Create a list of RealizationDeltaVector from the delta vector table.
89+
"""
90+
_validate_summary_vector_table_pa(delta_vector_table, vector_name)
91+
92+
real_arr_np = delta_vector_table.column("REAL").to_numpy()
93+
unique_reals, first_occurrence_idx, real_counts = np.unique(real_arr_np, return_index=True, return_counts=True)
94+
95+
whole_date_np_arr = delta_vector_table.column("DATE").to_numpy()
96+
whole_value_np_arr = delta_vector_table.column(vector_name).to_numpy()
97+
98+
ret_arr: list[RealizationDeltaVector] = []
99+
for i, real in enumerate(unique_reals):
100+
start_row_idx = first_occurrence_idx[i]
101+
row_count = real_counts[i]
102+
date_np_arr = whole_date_np_arr[start_row_idx : start_row_idx + row_count]
103+
value_np_arr = whole_value_np_arr[start_row_idx : start_row_idx + row_count]
104+
105+
ret_arr.append(
106+
RealizationDeltaVector(
107+
realization=real,
108+
timestamps_utc_ms=date_np_arr.astype(int).tolist(),
109+
values=value_np_arr.tolist(),
110+
is_rate=is_rate,
111+
unit=unit,
112+
)
113+
)
114+
115+
return ret_arr

backend_py/primary/primary/services/sumo_access/summary_access.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@
1111
from fmu.sumo.explorer.objects import TableCollection, Table
1212
from webviz_pkg.core_utils.perf_timer import PerfTimer
1313

14-
from primary.services.utils.arrow_helpers import sort_table_on_real_then_date, is_date_column_monotonically_increasing
15-
from primary.services.utils.arrow_helpers import find_first_non_increasing_date_pair
14+
from primary.services.utils.arrow_helpers import (
15+
find_first_non_increasing_date_pair,
16+
sort_table_on_real_then_date,
17+
is_date_column_monotonically_increasing,
18+
)
1619
from primary.services.service_exceptions import (
1720
Service,
1821
NoDataError,
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import pytest
2+
import pyarrow as pa
3+
4+
from primary.services.service_exceptions import InvalidDataError, Service
5+
from primary.services.summary_delta_vectors import (
6+
create_delta_vector_table,
7+
create_realization_delta_vector_list,
8+
RealizationDeltaVector,
9+
_validate_summary_vector_table_pa,
10+
)
11+
12+
13+
VECTOR_TABLE_SCHEMA = pa.schema([("DATE", pa.timestamp("ms")), ("REAL", pa.int16()), ("vector", pa.float32())])
14+
15+
16+
def test_create_delta_vector_table() -> None:
17+
# Create sample data for comparison_vector_table
18+
comparison_data = {"DATE": [1, 2, 3, 4], "REAL": [1, 1, 2, 2], "vector": [10.0, 20.0, 30.0, 40.0]}
19+
comparison_vector_table = pa.table(comparison_data, schema=VECTOR_TABLE_SCHEMA)
20+
21+
# Create sample data for reference_vector_table
22+
reference_data = {"DATE": [1, 2, 3, 4], "REAL": [1, 1, 2, 2], "vector": [5.0, 15.0, 25.0, 35.0]}
23+
reference_vector_table = pa.table(reference_data, schema=VECTOR_TABLE_SCHEMA)
24+
25+
# Expected delta values
26+
expected_delta_data = {"DATE": [1, 2, 3, 4], "REAL": [1, 1, 2, 2], "vector": [5.0, 5.0, 5.0, 5.0]}
27+
expected_delta_table = pa.table(expected_delta_data, schema=VECTOR_TABLE_SCHEMA)
28+
29+
# Call the function
30+
result_table = create_delta_vector_table(comparison_vector_table, reference_vector_table, "vector")
31+
32+
# Validate the result
33+
assert result_table.equals(expected_delta_table)
34+
35+
36+
def test_create_delta_vector_table_with_missing_dates() -> None:
37+
# Create sample data for comparison_vector_table
38+
comparison_data = {"DATE": [1, 2, 4], "REAL": [1, 1, 2], "vector": [10.0, 20.0, 40.0]}
39+
comparison_vector_table = pa.table(comparison_data, schema=VECTOR_TABLE_SCHEMA)
40+
41+
# Create sample data for reference_vector_table
42+
reference_data = {"DATE": [1, 2, 3], "REAL": [1, 1, 2], "vector": [5.0, 15.0, 25.0]}
43+
reference_vector_table = pa.table(reference_data, schema=VECTOR_TABLE_SCHEMA)
44+
45+
# Expected delta values
46+
expected_delta_data = {"DATE": [1, 2], "REAL": [1, 1], "vector": [5.0, 5.0]}
47+
expected_delta_table = pa.table(expected_delta_data, schema=VECTOR_TABLE_SCHEMA)
48+
49+
# Call the function
50+
result_table = create_delta_vector_table(comparison_vector_table, reference_vector_table, "vector")
51+
52+
# Validate the result
53+
assert result_table.equals(expected_delta_table)
54+
55+
56+
def test_create_delta_vector_table_with_different_reals() -> None:
57+
# Create sample data for comparison_vector_table
58+
comparison_data = {"DATE": [1, 2, 3, 4], "REAL": [1, 1, 2, 3], "vector": [10.0, 20.0, 30.0, 40.0]}
59+
comparison_vector_table = pa.table(comparison_data, schema=VECTOR_TABLE_SCHEMA)
60+
61+
# Create sample data for reference_vector_table
62+
reference_data = {"DATE": [1, 2, 3, 4], "REAL": [1, 1, 2, 2], "vector": [5.0, 15.0, 25.0, 35.0]}
63+
reference_vector_table = pa.table(reference_data, schema=VECTOR_TABLE_SCHEMA)
64+
65+
# Expected delta values
66+
expected_delta_data = {"DATE": [1, 2, 3], "REAL": [1, 1, 2], "vector": [5.0, 5.0, 5.0]}
67+
expected_delta_table = pa.table(expected_delta_data, schema=VECTOR_TABLE_SCHEMA)
68+
69+
# Call the function
70+
result_table = create_delta_vector_table(comparison_vector_table, reference_vector_table, "vector")
71+
72+
# Validate the result
73+
assert result_table.equals(expected_delta_table)
74+
75+
76+
def test_create_realization_delta_vector_list() -> None:
77+
# Create sample data for delta_vector_table
78+
delta_data = {"DATE": [1, 2, 3, 4], "REAL": [1, 1, 2, 2], "vector": [5.0, 10.0, 15.0, 20.0]}
79+
delta_vector_table = pa.table(delta_data, schema=VECTOR_TABLE_SCHEMA)
80+
81+
# Expected result
82+
expected_result = [
83+
RealizationDeltaVector(realization=1, timestamps_utc_ms=[1, 2], values=[5.0, 10.0], is_rate=True, unit="unit"),
84+
RealizationDeltaVector(realization=2, timestamps_utc_ms=[3, 4], values=[15.0, 20.0], is_rate=True, unit="unit"),
85+
]
86+
87+
# Call the function
88+
result = create_realization_delta_vector_list(delta_vector_table, "vector", is_rate=True, unit="unit")
89+
90+
# Validate the result
91+
assert result == expected_result
92+
93+
94+
def test_create_realization_delta_vector_list_with_single_real() -> None:
95+
# Create sample data for delta_vector_table
96+
delta_data = {"DATE": [1, 2, 3, 4], "REAL": [1, 1, 1, 1], "vector": [5.0, 10.0, 15.0, 20.0]}
97+
delta_vector_table = pa.table(delta_data, schema=VECTOR_TABLE_SCHEMA)
98+
99+
# Expected result
100+
expected_result = [
101+
RealizationDeltaVector(
102+
realization=1, timestamps_utc_ms=[1, 2, 3, 4], values=[5.0, 10.0, 15.0, 20.0], is_rate=False, unit="unit"
103+
)
104+
]
105+
106+
# Call the function
107+
result = create_realization_delta_vector_list(delta_vector_table, "vector", is_rate=False, unit="unit")
108+
109+
# Validate the result
110+
assert result == expected_result
111+
112+
113+
def test_create_realization_delta_vector_list_with_empty_table() -> None:
114+
# Create an empty delta_vector_table
115+
delta_vector_table = pa.table({"DATE": [], "REAL": [], "vector": []}, schema=VECTOR_TABLE_SCHEMA)
116+
117+
# Expected result
118+
expected_result: list[RealizationDeltaVector] = []
119+
120+
# Call the function
121+
result = create_realization_delta_vector_list(delta_vector_table, "vector", is_rate=True, unit="unit")
122+
123+
# Validate the result
124+
assert result == expected_result
125+
126+
127+
def test_validate_summary_vector_table_pa_valid() -> None:
128+
vector_name = "VECTOR"
129+
data = {"DATE": [1, 2, 3], "REAL": [4, 5, 6], vector_name: [7.0, 8.0, 9.0]}
130+
schema = pa.schema([("DATE", pa.timestamp("ms")), ("REAL", pa.int16()), (vector_name, pa.float32())])
131+
table = pa.Table.from_pydict(data, schema=schema)
132+
try:
133+
_validate_summary_vector_table_pa(table, vector_name)
134+
except InvalidDataError:
135+
pytest.fail("validate_summary_vector_table_pa raised InvalidDataError unexpectedly!")
136+
137+
138+
def test_validate_summary_vector_table_pa_missing_column() -> None:
139+
vector_name = "VECTOR"
140+
data = {"DATE": [1, 2, 3], "REAL": [4, 5, 6]}
141+
schema = pa.schema([("DATE", pa.timestamp("ms")), ("REAL", pa.int16())])
142+
table = pa.Table.from_pydict(data, schema=schema)
143+
with pytest.raises(InvalidDataError):
144+
_validate_summary_vector_table_pa(table, vector_name)
145+
146+
147+
def test_validate_summary_vector_table_pa_unexpected_column() -> None:
148+
vector_name = "VECTOR"
149+
data = {"DATE": [1, 2, 3], "REAL": [4, 5, 6], vector_name: [7.0, 8.0, 9.0], "EXTRA": [10.0, 11.0, 12.0]}
150+
schema = pa.schema(
151+
[("DATE", pa.timestamp("ms")), ("REAL", pa.int16()), (vector_name, pa.float32()), ("EXTRA", pa.float32())]
152+
)
153+
table = pa.Table.from_pydict(data, schema=schema)
154+
with pytest.raises(InvalidDataError):
155+
_validate_summary_vector_table_pa(table, vector_name)
156+
157+
158+
def test_validate_summary_vector_table_pa_invalid_date_type() -> None:
159+
vector_name = "VECTOR"
160+
data = {"DATE": [1, 2, 3], "REAL": [4, 5, 6], vector_name: [7.0, 8.0, 9.0]}
161+
schema = pa.schema([("DATE", pa.int32()), ("REAL", pa.int16()), (vector_name, pa.float32())])
162+
table = pa.Table.from_pydict(data, schema=schema)
163+
with pytest.raises(InvalidDataError):
164+
_validate_summary_vector_table_pa(table, vector_name)
165+
166+
167+
def test_validate_summary_vector_table_pa_invalid_real_type() -> None:
168+
vector_name = "VECTOR"
169+
data = {"DATE": [1, 2, 3], "REAL": [4.0, 5.0, 6.0], vector_name: [7.0, 8.0, 9.0]}
170+
schema = pa.schema([("DATE", pa.timestamp("ms")), ("REAL", pa.float32()), (vector_name, pa.float32())])
171+
table = pa.Table.from_pydict(data, schema=schema)
172+
with pytest.raises(InvalidDataError):
173+
_validate_summary_vector_table_pa(table, vector_name)
174+
175+
176+
def test_validate_summary_vector_table_pa_invalid_vector_type() -> None:
177+
vector_name = "VECTOR"
178+
data = {"DATE": [1, 2, 3], "REAL": [4, 5, 6], vector_name: [7, 8, 9]}
179+
schema = pa.schema([("DATE", pa.timestamp("ms")), ("REAL", pa.int16()), (vector_name, pa.int32())])
180+
table = pa.Table.from_pydict(data, schema=schema)
181+
with pytest.raises(InvalidDataError):
182+
_validate_summary_vector_table_pa(table, vector_name)
183+
184+
185+
def test_validate_summary_vector_table_pa_sumo_service() -> None:
186+
vector_name = "VECTOR"
187+
data = {"DATE": [1, 2, 3], "REAL": [4, 5, 6]}
188+
schema = pa.schema([("DATE", pa.timestamp("ms")), ("REAL", pa.int16())])
189+
table = pa.Table.from_pydict(data, schema=schema)
190+
with pytest.raises(InvalidDataError) as excinfo:
191+
_validate_summary_vector_table_pa(table, vector_name, Service.SUMO)
192+
assert excinfo.value.service == Service.SUMO

frontend/src/App.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,10 @@ function App() {
8484

8585
setIsMounted(true);
8686

87-
const storedEnsembleIdents = workbench.maybeLoadEnsembleSettingsFromLocalStorage();
88-
if (storedEnsembleIdents) {
89-
setInitAppState(InitAppState.LoadingEnsembles);
90-
workbench.loadAndSetupEnsembleSetInSession(queryClient, storedEnsembleIdents).finally(() => {
91-
initApp();
92-
});
93-
} else {
87+
// Initialize the workbench
88+
workbench.initWorkbenchFromLocalStorage(queryClient).finally(() => {
9489
initApp();
95-
}
90+
});
9691

9792
return function handleUnmount() {
9893
workbench.clearLayout();

0 commit comments

Comments
 (0)