Skip to content

Commit

Permalink
Add workaround for problems with microseconds
Browse files Browse the repository at this point in the history
Workaround for storage not handling datetimes
with microseconds due to index overflow in netcdf3.
#6952
  • Loading branch information
eivindjahren committed Jan 23, 2024
1 parent bfb7547 commit fa0125d
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 66 deletions.
20 changes: 13 additions & 7 deletions src/ert/analysis/_es_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,22 +311,28 @@ def _get_obs_and_measure_data(
name: list(set(index.get_level_values(name))) for name in index.names
}
observation = observation.sel(sub_selection)
ds = source_fs.load_responses(group, tuple(iens_active_index))
response = source_fs.load_responses(group, tuple(iens_active_index))
if "time" in observation.coords:
response = response.reindex(
time=observation.time, method="nearest", tolerance="1s" # type: ignore
)
try:
filtered_ds = observation.merge(ds, join="left")
filtered_response = observation.merge(response, join="left")
except KeyError as e:
raise ErtAnalysisError(
f"Mismatched index for: "
f"Observation: {obs_key} attached to response: {group}"
) from e

observation_keys.append([obs_key] * len(filtered_ds.observations.data.ravel()))
observation_values.append(filtered_ds["observations"].data.ravel())
observation_errors.append(filtered_ds["std"].data.ravel())
observation_keys.append(
[obs_key] * len(filtered_response.observations.data.ravel())
)
observation_values.append(filtered_response["observations"].data.ravel())
observation_errors.append(filtered_response["std"].data.ravel())
measured_data.append(
filtered_ds["values"]
filtered_response["values"]
.transpose(..., "realization")
.values.reshape((-1, len(filtered_ds.realization)))
.values.reshape((-1, len(filtered_response.realization)))
)
source_fs.load_responses.cache_clear()
return (
Expand Down
25 changes: 23 additions & 2 deletions src/ert/config/_read_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,8 @@ def _read_spec(
hour=hour,
minute=minute,
second=microsecond // 10**6,
microsecond=microsecond % 10**6,
# Due to https://github.com/equinor/ert/issues/6952
# microsecond=self.micro_seconds % 10**6,
)
except Exception as err:
raise ValueError(
Expand Down Expand Up @@ -404,6 +405,19 @@ def optional_get(arr: Optional[npt.NDArray[Any]], idx: int) -> Any:
)


def _round_to_seconds(dt: datetime) -> datetime:
"""
>>> _round_to_seconds(datetime(2000, 1, 1, 1, 0, 1, 1))
datetime.datetime(2000, 1, 1, 1, 0, 1)
>>> _round_to_seconds(datetime(2000, 1, 1, 1, 0, 1, 500001))
datetime.datetime(2000, 1, 1, 1, 0, 2)
>>> _round_to_seconds(datetime(2000, 1, 1, 1, 0, 1, 499999))
datetime.datetime(2000, 1, 1, 1, 0, 1)
"""
extra_sec = round(dt.microsecond / 10**6)
return dt.replace(microsecond=0) + timedelta(seconds=extra_sec)


def _read_summary(
summary: str,
start_date: datetime,
Expand All @@ -427,7 +441,14 @@ def read_params() -> None:
if last_params is not None:
vals = _check_vals("PARAMS", summary, last_params.read_array())
values.append(vals[indices])
dates.append(start_date + unit.make_delta(float(vals[date_index])))

dates.append(
_round_to_seconds(
start_date + unit.make_delta(float(vals[date_index]))
),
)
# Due to https://github.com/equinor/ert/issues/6952
# dates.append(start_date + unit.make_delta(float(vals[date_index])))
last_params = None

with open(summary, mode) as fp:
Expand Down
13 changes: 0 additions & 13 deletions src/ert/config/summary_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,6 @@ def __post_init__(self) -> None:
def read_from_file(self, run_path: str, iens: int) -> xr.Dataset:
filename = self.input_file.replace("<IENS>", str(iens))
_, keys, time_map, data = read_summary(f"{run_path}/{filename}", self.keys)

if self.refcase:
assert isinstance(self.refcase, set)
missing = self.refcase.difference(time_map)
if missing:
first, last = min(missing), max(missing)
logger.warning(
f"Realization: {iens}, load warning: {len(missing)} "
f"inconsistencies in time map, first: Time mismatch for response "
f"time: {first}, last: Time mismatch for response time: "
f"{last} from: {run_path}/{filename}.UNSMRY"
)

ds = xr.Dataset(
{"values": (["name", "time"], data)},
coords={"time": time_map, "name": keys},
Expand Down
7 changes: 5 additions & 2 deletions tests/unit_tests/config/summary_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ def to_datetime(self) -> datetime:
hour=self.hour,
minute=self.minutes,
second=self.micro_seconds // 10**6,
microsecond=self.micro_seconds % 10**6,
# Due to https://github.com/equinor/ert/issues/6952
# microsecond=self.micro_seconds % 10**6,
)

@classmethod
Expand All @@ -216,7 +217,9 @@ def from_datetime(cls, dt: datetime) -> Self:
day=dt.day,
hour=dt.hour,
minutes=dt.minute,
micro_seconds=dt.second * 10**6 + dt.microsecond,
# Due to https://github.com/equinor/ert/issues/6952
micro_seconds=dt.second * 10**6,
# micro_seconds=dt.second * 10**6 + dt.microsecond,
)


Expand Down
42 changes: 0 additions & 42 deletions tests/unit_tests/test_load_forward_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,48 +69,6 @@ def run_simulator(time_step_count, start_date) -> Summary:
return summary


@pytest.mark.usefixtures("copy_snake_oil_case_storage")
def test_load_inconsistent_time_map_summary(caplog):
"""
Checking that we dont util_abort, we fail the forward model instead
"""
cwd = os.getcwd()

# Get rid of GEN_DATA as we are only interested in SUMMARY
with fileinput.input("snake_oil.ert", inplace=True) as fin:
for line in fin:
if line.startswith("GEN_DATA"):
continue
print(line, end="")

facade = LibresFacade.from_config_file("snake_oil.ert")
realisation_number = 0
storage = open_storage(facade.enspath, mode="w")
ensemble = storage.get_ensemble_by_name("default_0")
assert ensemble.get_realization_mask_with_responses()[
realisation_number
] # Check prior state

# Create a result that is incompatible with the refcase
run_path = Path("storage") / "snake_oil" / "runpath" / "realization-0" / "iter-0"
os.chdir(run_path)
ecl_sum = run_simulator(1, datetime(2000, 1, 1))
ecl_sum.fwrite()
os.chdir(cwd)

realizations = [False] * facade.get_ensemble_size()
realizations[realisation_number] = True
with caplog.at_level(logging.WARNING):
loaded = facade.load_from_forward_model(ensemble, realizations, 0)
assert (
"Realization: 0, load warning: 200 inconsistencies in time map, first: "
"Time mismatch for response time: 2010-01-10 00:00:00, last: Time mismatch "
f"for response time: 2015-06-23 00:00:00 from: {run_path.absolute()}"
f"/SNAKE_OIL_FIELD.UNSMRY"
) in caplog.messages
assert loaded == 1


@pytest.mark.usefixtures("copy_snake_oil_case_storage")
def test_load_forward_model(snake_oil_default_storage):
"""
Expand Down

0 comments on commit fa0125d

Please sign in to comment.