lisainstrument 2.2.0__tar.gz → 2.2.1__tar.gz
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.
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/PKG-INFO +1 -1
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_file_reader.py +25 -11
- lisainstrument-2.2.1/lisainstrument/instru/instru_file_reader_v2_1_0.py +426 -0
- lisainstrument-2.2.1/lisainstrument/instru/instru_store_v2_1_0.py +939 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/pyproject.toml +1 -1
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/LICENSE +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/README.md +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/__main__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/cli/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/cli/run_simulation.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/freqplan/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/freqplan/fplan_file.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/freqplan/fplan_source.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/freqplan/fplan_source_interp.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/glitches/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/glitches/glitch_file.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/glitches/glitch_source.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/glitches/glitch_source_interp.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/gwsource/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/gwsource/gw_file.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/gwsource/gw_source.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/gwsource/hdf5util.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_defaults.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_file_reader_v2_0_0.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_filter.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_formulas.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_fplan.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_glitchsrc.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_gwsrc.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_locking.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_model.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_noises.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_orbsrc.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_store.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_store_v2_0_0.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instrument.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/containers.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/dsp.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/dynamic_delay_dsp.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/fixed_shift_dsp.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/hexagon.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/legacy_plots.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/noises.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/pyplnoise/LICENSE +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/pyplnoise/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/pyplnoise/pyplnoise.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/noisy/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/noisy/estimate_psd.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/noisy/noise_defs.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/noisy/noise_defs_lisa.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/noisy/noise_gen_numpy.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/orbiting/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/orbiting/constellation_enums.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/orbiting/orbit_file.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/orbiting/orbit_source.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/orbiting/orbit_source_interp.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/adaptive_delay_numpy.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/chunked_splines.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/dynamic_delay_numpy.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/fir_filters_numpy.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/fixed_shift_numpy.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/iir_filters_numpy.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/regular_interpolators.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/shift_inversion_numpy.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/types_numpy.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/__init__.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/analysis.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/array.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/delay.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/derivative.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/expression.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/firfilter.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/graph_util.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/hdf5_store.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/iirfilter.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/integrate.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/noise.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/noise_alt.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/null_store.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/numpy_store.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/sampling.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/scheduler.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/scheduler_dask.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/scheduler_serial.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/segments.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/shift_inv.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/store.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/streams.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/time.py +0 -0
- {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/version.py +0 -0
|
@@ -27,6 +27,9 @@ from packaging.version import Version
|
|
|
27
27
|
from lisainstrument.instru.instru_file_reader_v2_0_0 import (
|
|
28
28
|
SimResultFile as SimResultFile_V2_0_0,
|
|
29
29
|
)
|
|
30
|
+
from lisainstrument.instru.instru_file_reader_v2_1_0 import (
|
|
31
|
+
SimResultFile as SimResultFile_V2_1_0,
|
|
32
|
+
)
|
|
30
33
|
from lisainstrument.instru.instru_store import (
|
|
31
34
|
IdxSpace,
|
|
32
35
|
SimFullDatasetsMOSA,
|
|
@@ -80,16 +83,29 @@ class SimResultFile:
|
|
|
80
83
|
path: Path of the a HDF5 file created by store_instru_hdf5
|
|
81
84
|
"""
|
|
82
85
|
self._h5f_raw = h5py.File(str(path), "r")
|
|
83
|
-
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
|
|
89
|
+
self._version: Final = Version(self._h5f.attrs["version_format"])
|
|
90
|
+
|
|
91
|
+
ds_actual = set(self._h5f.keys()) - {"debug"}
|
|
92
|
+
ds_debug = set(self._h5f["debug"].keys())
|
|
93
|
+
|
|
94
|
+
md = json.loads(self._h5f.attrs["metadata_json"])
|
|
95
|
+
|
|
96
|
+
self._description = str(self._h5f.attrs.get("description", None))
|
|
97
|
+
|
|
98
|
+
except:
|
|
99
|
+
self.close()
|
|
100
|
+
raise
|
|
84
101
|
|
|
85
102
|
if not SimResultFile.format_specifier().contains(self._version):
|
|
86
103
|
msg = f"File {path} version {self._version} not compatible with file reader"
|
|
87
104
|
raise RuntimeError(msg)
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
ds_debug = set(self._h5f["debug"].keys())
|
|
91
|
-
ds_all = ds_actual | ds_debug
|
|
106
|
+
self._metadata = SimMetaData(**md)
|
|
92
107
|
|
|
108
|
+
ds_all = ds_actual | ds_debug
|
|
93
109
|
if ds_all == SimResultsNumpyFull.all_dataset_names:
|
|
94
110
|
self._extended = True
|
|
95
111
|
elif ds_all == SimResultsNumpyCore.all_dataset_names:
|
|
@@ -98,11 +114,6 @@ class SimResultFile:
|
|
|
98
114
|
msg = f"File {path} contains invalid set of quantities"
|
|
99
115
|
raise RuntimeError(msg)
|
|
100
116
|
|
|
101
|
-
md = json.loads(self._h5f.attrs["metadata_json"])
|
|
102
|
-
self._metadata = SimMetaData(**md)
|
|
103
|
-
|
|
104
|
-
self._description = str(self._h5f.attrs.get("description", None))
|
|
105
|
-
|
|
106
117
|
self._t0 = self._metadata.t0
|
|
107
118
|
self._sample_periods = {
|
|
108
119
|
IdxSpace.PHYSICS: self._metadata.physics_dt,
|
|
@@ -437,7 +448,7 @@ class SimResultFile:
|
|
|
437
448
|
|
|
438
449
|
def sim_result_file(
|
|
439
450
|
path: pathlib.Path | str, only_compatible: bool = False
|
|
440
|
-
) -> SimResultFile | SimResultFile_V2_0_0:
|
|
451
|
+
) -> SimResultFile | SimResultFile_V2_0_0 | SimResultFile_V2_1_0:
|
|
441
452
|
"""Open simulation result file
|
|
442
453
|
|
|
443
454
|
This provides an interface with methods for reading the file.
|
|
@@ -454,9 +465,12 @@ def sim_result_file(
|
|
|
454
465
|
SimResultFile instance for reading file data
|
|
455
466
|
"""
|
|
456
467
|
|
|
457
|
-
readers: list[
|
|
468
|
+
readers: list[
|
|
469
|
+
type[SimResultFile] | type[SimResultFile_V2_0_0] | type[SimResultFile_V2_1_0]
|
|
470
|
+
] = [SimResultFile]
|
|
458
471
|
if not only_compatible:
|
|
459
472
|
readers.append(SimResultFile_V2_0_0)
|
|
473
|
+
readers.append(SimResultFile_V2_1_0)
|
|
460
474
|
|
|
461
475
|
version: Version
|
|
462
476
|
for reader in readers:
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# pylint: disable = duplicate-code
|
|
2
|
+
# The duplication is on purpose, we do not want to entangle readers for
|
|
3
|
+
# different formats, even though they are very similar.
|
|
4
|
+
"""File reader for old 2.1.0 instrument file format
|
|
5
|
+
|
|
6
|
+
Files created by version 2.0.1 cannot be represented by the current interface
|
|
7
|
+
and data structures because they are missing some metadata items. To support
|
|
8
|
+
reading them, we keep the old interface here, which can be used by
|
|
9
|
+
`instru_file_reader.sim_results_file` to read the old format.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import pathlib
|
|
14
|
+
from collections import defaultdict
|
|
15
|
+
from typing import Final, TypeVar
|
|
16
|
+
|
|
17
|
+
import h5py
|
|
18
|
+
import numpy as np
|
|
19
|
+
from packaging.specifiers import SpecifierSet
|
|
20
|
+
from packaging.version import Version
|
|
21
|
+
|
|
22
|
+
from lisainstrument.instru.instru_store_v2_1_0 import (
|
|
23
|
+
IdxSpace,
|
|
24
|
+
SimFullDatasetsMOSA,
|
|
25
|
+
SimFullDatasetsSat,
|
|
26
|
+
SimMetaData,
|
|
27
|
+
SimResultsNumpyCore,
|
|
28
|
+
SimResultsNumpyFull,
|
|
29
|
+
datasets_metadata_dict,
|
|
30
|
+
make_dataset_id,
|
|
31
|
+
)
|
|
32
|
+
from lisainstrument.orbiting.constellation_enums import MosaID, SatID
|
|
33
|
+
from lisainstrument.streams import DatasetIdentifier, StreamBundle
|
|
34
|
+
from lisainstrument.streams.hdf5_store import (
|
|
35
|
+
DataStorageHDF5,
|
|
36
|
+
instru_hdf5_file_as_stream_bundle,
|
|
37
|
+
)
|
|
38
|
+
from lisainstrument.streams.numpy_store import store_bundle_numpy
|
|
39
|
+
|
|
40
|
+
_T = TypeVar("_T", SimResultsNumpyFull, SimResultsNumpyCore)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _unique_index_range(ranges: list[tuple[int, int]]) -> tuple[int, int]:
|
|
44
|
+
"""Ensure index ranges in list are identical and return that range"""
|
|
45
|
+
s = set(ranges)
|
|
46
|
+
if len(s) != 1:
|
|
47
|
+
msg = f"unique_index_range: ranges not identical ({ranges=})"
|
|
48
|
+
raise RuntimeError(msg)
|
|
49
|
+
(uni,) = list(s)
|
|
50
|
+
return uni
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class SimResultFile:
|
|
54
|
+
"""Represents a simulation result file in HDF5 format.
|
|
55
|
+
|
|
56
|
+
There are low-level methods for direct reading of datasets identified either
|
|
57
|
+
by name and MOSA or SC index, or by a DatasetIdentifier. For use cases where the
|
|
58
|
+
results fit into memory, there are methods to create a SimResultsNumpyCore or
|
|
59
|
+
SimResultsNumpyFull instance with the data and metadata.
|
|
60
|
+
|
|
61
|
+
For use cases dealing with large data sets, there is a method representing
|
|
62
|
+
the datasets as streams in a StreamBundle for use with chunked processing.
|
|
63
|
+
For all those methods, it is possible to restrict the data to a given time
|
|
64
|
+
interval.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
def __init__(self, path: pathlib.Path | str):
|
|
68
|
+
"""Constructor
|
|
69
|
+
|
|
70
|
+
Arguments:
|
|
71
|
+
path: Path of the a HDF5 file created by store_instru_hdf5
|
|
72
|
+
"""
|
|
73
|
+
self._h5f_raw = h5py.File(str(path), "r")
|
|
74
|
+
self._version: Final = Version(self._h5f.attrs["version_format"])
|
|
75
|
+
|
|
76
|
+
if not SimResultFile.format_specifier().contains(self._version):
|
|
77
|
+
msg = f"File {path} version {self._version} not compatible with file reader"
|
|
78
|
+
raise RuntimeError(msg)
|
|
79
|
+
|
|
80
|
+
ds_actual = set(self._h5f.keys()) - {"debug"}
|
|
81
|
+
ds_debug = set(self._h5f["debug"].keys())
|
|
82
|
+
ds_all = ds_actual | ds_debug
|
|
83
|
+
|
|
84
|
+
if ds_all == SimResultsNumpyFull.all_dataset_names:
|
|
85
|
+
self._extended = True
|
|
86
|
+
elif ds_all == SimResultsNumpyCore.all_dataset_names:
|
|
87
|
+
self._extended = False
|
|
88
|
+
else:
|
|
89
|
+
msg = f"File {path} contains invalid set of quantities"
|
|
90
|
+
raise RuntimeError(msg)
|
|
91
|
+
|
|
92
|
+
md = json.loads(self._h5f.attrs["metadata_json"])
|
|
93
|
+
self._metadata = SimMetaData(**md)
|
|
94
|
+
|
|
95
|
+
self._description = str(self._h5f.attrs.get("description", None))
|
|
96
|
+
|
|
97
|
+
self._t0 = self._metadata.t0
|
|
98
|
+
self._sample_periods = {
|
|
99
|
+
IdxSpace.PHYSICS: self._metadata.physics_dt,
|
|
100
|
+
IdxSpace.PHYSICS_EXT: self._metadata.physics_dt,
|
|
101
|
+
IdxSpace.REGULAR: self._metadata.dt,
|
|
102
|
+
IdxSpace.TELEMETRY: self._metadata.telemetry_dt,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
categories = datasets_metadata_dict()
|
|
106
|
+
|
|
107
|
+
isps: dict[DatasetIdentifier, IdxSpace] = {}
|
|
108
|
+
ranges: dict[IdxSpace, list[tuple[int, int]]] = defaultdict(list)
|
|
109
|
+
for dsid in self.dataset_identifier_set():
|
|
110
|
+
n = dsid[-2]
|
|
111
|
+
cat = categories[n]
|
|
112
|
+
rg = self._read_range(dsid)
|
|
113
|
+
ranges[cat.idxspace].append(rg)
|
|
114
|
+
isps[dsid] = cat.idxspace
|
|
115
|
+
self._isp_by_dsid: Final = isps
|
|
116
|
+
self._range_by_isp: Final = {
|
|
117
|
+
i: _unique_index_range(r) for i, r in ranges.items()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def _h5f(self) -> h5py.File:
|
|
122
|
+
if self._h5f_raw is None:
|
|
123
|
+
msg = "SimResultFile used after closing it"
|
|
124
|
+
raise RuntimeError(msg)
|
|
125
|
+
return self._h5f_raw
|
|
126
|
+
|
|
127
|
+
def close(self) -> None:
|
|
128
|
+
"""Close file
|
|
129
|
+
|
|
130
|
+
Note the interface provides a context manager, consider using `with`
|
|
131
|
+
mechanism instead
|
|
132
|
+
"""
|
|
133
|
+
if self._h5f_raw is not None:
|
|
134
|
+
self._h5f_raw.close()
|
|
135
|
+
self._h5f_raw = None
|
|
136
|
+
|
|
137
|
+
def __del__(self):
|
|
138
|
+
"""HDF5 file is automatically closed"""
|
|
139
|
+
self.close()
|
|
140
|
+
|
|
141
|
+
def __enter__(self):
|
|
142
|
+
"""Enter context"""
|
|
143
|
+
return self
|
|
144
|
+
|
|
145
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
146
|
+
"""Exit context"""
|
|
147
|
+
self.close()
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def format_version(self) -> Version:
|
|
151
|
+
"""Version number of the file format"""
|
|
152
|
+
return self._version
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
def format_specifier() -> SpecifierSet:
|
|
156
|
+
"""Version specifier set compatible with file reader"""
|
|
157
|
+
v = Version("2.1.0")
|
|
158
|
+
return SpecifierSet(f"=={v}")
|
|
159
|
+
|
|
160
|
+
@classmethod
|
|
161
|
+
def check_file_version(cls, path: str | pathlib.Path) -> tuple[bool, Version]:
|
|
162
|
+
"""Test if file version is compatible with file reader class
|
|
163
|
+
|
|
164
|
+
Arguments:
|
|
165
|
+
path: Path of file to check
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Whether file can be read and the file format version
|
|
169
|
+
"""
|
|
170
|
+
with h5py.File(str(path), "r") as gwf:
|
|
171
|
+
version = Version(gwf.attrs["version_format"])
|
|
172
|
+
return cls.format_specifier().contains(version), version
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def is_extended(self) -> bool:
|
|
176
|
+
"""Whether file contains extended set of quantities"""
|
|
177
|
+
return self._extended
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def metadata(self) -> SimMetaData:
|
|
181
|
+
"""Simulation metadata"""
|
|
182
|
+
return self._metadata
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def description(self) -> str | None:
|
|
186
|
+
"""Description text"""
|
|
187
|
+
return self._description
|
|
188
|
+
|
|
189
|
+
def dataset_identifier_set(self) -> set[DatasetIdentifier]:
|
|
190
|
+
"""Set of all available dataset identifiers"""
|
|
191
|
+
if self.is_extended:
|
|
192
|
+
return SimResultsNumpyFull.dataset_identifier_set()
|
|
193
|
+
return SimResultsNumpyCore.dataset_identifier_set()
|
|
194
|
+
|
|
195
|
+
def idxspace_by_dataset_id(self, dsid: DatasetIdentifier) -> IdxSpace:
|
|
196
|
+
"""Get index space for dataset"""
|
|
197
|
+
return self._isp_by_dsid[dsid]
|
|
198
|
+
|
|
199
|
+
def range_by_idxspace(self, isp: IdxSpace) -> tuple[int, int]:
|
|
200
|
+
"""Get range for given index space"""
|
|
201
|
+
return self._range_by_isp[isp]
|
|
202
|
+
|
|
203
|
+
def range_by_dataset_id(self, dsid: DatasetIdentifier) -> tuple[int, int]:
|
|
204
|
+
"""Get range for given dataset"""
|
|
205
|
+
return self.range_by_idxspace(self.idxspace_by_dataset_id(dsid))
|
|
206
|
+
|
|
207
|
+
def dt_by_idxspace(self, isp: IdxSpace) -> float:
|
|
208
|
+
"""Get sample period for given index space"""
|
|
209
|
+
return self._sample_periods[isp]
|
|
210
|
+
|
|
211
|
+
def dt_by_dataset_id(self, dsid: DatasetIdentifier) -> float:
|
|
212
|
+
"""Get sample period for given dataset"""
|
|
213
|
+
return self.dt_by_idxspace(self.idxspace_by_dataset_id(dsid))
|
|
214
|
+
|
|
215
|
+
def read_by_datset_id(
|
|
216
|
+
self,
|
|
217
|
+
dsid: DatasetIdentifier,
|
|
218
|
+
istart: int | None = None,
|
|
219
|
+
istop: int | None = None,
|
|
220
|
+
) -> np.ndarray:
|
|
221
|
+
"""Read data identified by a `DatasetIdentifier`
|
|
222
|
+
|
|
223
|
+
Optionally, one can restrict the index range. This refers to the logical
|
|
224
|
+
index range given returned `range_by_dataset_id()`, not necessarily starting
|
|
225
|
+
at zero. The returned data will contain indices `istart <= i < istop`
|
|
226
|
+
|
|
227
|
+
Arguments:
|
|
228
|
+
dsid: `DatasetIdentifier` specifying dataset
|
|
229
|
+
istart: Optionally, exclude lower indices
|
|
230
|
+
istop: Optionally, first index to exclude
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
1D numpy array with data
|
|
234
|
+
"""
|
|
235
|
+
dspth = "/".join(dsid)
|
|
236
|
+
ds: h5py.Dataset = self._h5f[dspth]
|
|
237
|
+
aistart, aistop = self.range_by_dataset_id(dsid)
|
|
238
|
+
istart = aistart if istart is None else int(istart)
|
|
239
|
+
istop = aistop if istop is None else int(istop)
|
|
240
|
+
if not aistart <= istart <= istop <= aistop:
|
|
241
|
+
msg = (
|
|
242
|
+
f"SimResultFile: index range {istart}, {istop} not "
|
|
243
|
+
f"available for dataset {dspth}"
|
|
244
|
+
)
|
|
245
|
+
raise RuntimeError(msg)
|
|
246
|
+
if len(ds.shape) == 0: # pylint: disable = no-member
|
|
247
|
+
dat = np.empty(
|
|
248
|
+
istop - istart, dtype=ds.dtype # pylint: disable = no-member
|
|
249
|
+
)
|
|
250
|
+
dat[:] = ds[()]
|
|
251
|
+
return dat
|
|
252
|
+
return ds[istart - aistart : istop - aistart]
|
|
253
|
+
|
|
254
|
+
def read_by_name_and_mosa(
|
|
255
|
+
self,
|
|
256
|
+
name: str,
|
|
257
|
+
mosa: MosaID | str,
|
|
258
|
+
istart: int | None = None,
|
|
259
|
+
istop: int | None = None,
|
|
260
|
+
) -> np.ndarray:
|
|
261
|
+
"""Like `read_by_datset_id` but dataset is specified by name and `MosaID`
|
|
262
|
+
|
|
263
|
+
Arguments:
|
|
264
|
+
name: Dataset name
|
|
265
|
+
mosa: Read dataset for MOSA specified by `MosaID` or MOSA name
|
|
266
|
+
istart: Optionally, exclude lower indices
|
|
267
|
+
istop: Optionally, first index to exclude
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
1D numpy array with data
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
if name not in SimFullDatasetsMOSA.dataset_names():
|
|
274
|
+
msg = f"SimResultFile: invalid per-MOSA dataset {name}"
|
|
275
|
+
raise RuntimeError(msg)
|
|
276
|
+
cat = SimFullDatasetsMOSA.dataset_metadata()[name]
|
|
277
|
+
dsid = make_dataset_id(cat.actual, name, MosaID(mosa).value)
|
|
278
|
+
return self.read_by_datset_id(dsid, istart, istop)
|
|
279
|
+
|
|
280
|
+
def read_by_name_and_sat(
|
|
281
|
+
self,
|
|
282
|
+
name: str,
|
|
283
|
+
sc: SatID | str,
|
|
284
|
+
istart: int | None = None,
|
|
285
|
+
istop: int | None = None,
|
|
286
|
+
) -> np.ndarray:
|
|
287
|
+
"""Like `read_by_datset_id` but dataset is specified by name and `SatID`
|
|
288
|
+
|
|
289
|
+
Arguments:
|
|
290
|
+
name: Dataset name
|
|
291
|
+
sc: Read dataset for spacecraft specified by `SatID` or spacecraft name
|
|
292
|
+
istart: Optionally, exclude lower indices
|
|
293
|
+
istop: Optionally, first index to exclude
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
1D numpy array with data
|
|
297
|
+
"""
|
|
298
|
+
if name not in SimFullDatasetsSat.dataset_names():
|
|
299
|
+
msg = f"SimResultFile: invalid per-spacecraft dataset {name}"
|
|
300
|
+
raise RuntimeError(msg)
|
|
301
|
+
cat = SimFullDatasetsSat.dataset_metadata()[name]
|
|
302
|
+
dsid = make_dataset_id(cat.actual, name, SatID(sc).value)
|
|
303
|
+
return self.read_by_datset_id(dsid, istart, istop)
|
|
304
|
+
|
|
305
|
+
def _read_range(self, dsid: DatasetIdentifier) -> tuple[int, int]:
|
|
306
|
+
"""Get the index range available for a given dataset
|
|
307
|
+
|
|
308
|
+
The available indices `i` are in the range `istart <= i < istop`.
|
|
309
|
+
|
|
310
|
+
Arguments:
|
|
311
|
+
dsid: `DatasetIdentifier` specifying the dataset
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
Tuple `(istart, istop)`
|
|
315
|
+
"""
|
|
316
|
+
dspth = "/".join(dsid)
|
|
317
|
+
ds: h5py.Dataset = self._h5f[dspth]
|
|
318
|
+
istart = int(ds.attrs[DataStorageHDF5.attr_name_index_start])
|
|
319
|
+
istop = int(ds.attrs[DataStorageHDF5.attr_name_index_stop])
|
|
320
|
+
return (istart, istop)
|
|
321
|
+
|
|
322
|
+
def _restrict_range_isp(
|
|
323
|
+
self,
|
|
324
|
+
isp: IdxSpace,
|
|
325
|
+
t_min: float | None = None,
|
|
326
|
+
t_max: float | None = None,
|
|
327
|
+
) -> tuple[int, int]:
|
|
328
|
+
"""Compute index range within given time interval for a given dataset"""
|
|
329
|
+
dt = self.dt_by_idxspace(isp)
|
|
330
|
+
istart, istop = self.range_by_idxspace(isp)
|
|
331
|
+
if t_min is None:
|
|
332
|
+
i0 = istart
|
|
333
|
+
else:
|
|
334
|
+
i0 = int(np.ceil((t_min - self._t0) / dt))
|
|
335
|
+
|
|
336
|
+
if t_max is None:
|
|
337
|
+
i1 = istop
|
|
338
|
+
else:
|
|
339
|
+
i1 = int(np.ceil((t_max - self._t0) / dt))
|
|
340
|
+
|
|
341
|
+
return i0, i1
|
|
342
|
+
|
|
343
|
+
def _restrict_ranges(
|
|
344
|
+
self,
|
|
345
|
+
t_min: float | None = None,
|
|
346
|
+
t_max: float | None = None,
|
|
347
|
+
) -> dict[IdxSpace, tuple[int, int]]:
|
|
348
|
+
"""Compute index ranges within given time interval for datasets"""
|
|
349
|
+
return {
|
|
350
|
+
isp: self._restrict_range_isp(isp, t_min, t_max)
|
|
351
|
+
for isp in self._range_by_isp
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
def _read_datasets(
|
|
355
|
+
self,
|
|
356
|
+
cls: type[_T],
|
|
357
|
+
t_min: float | None = None,
|
|
358
|
+
t_max: float | None = None,
|
|
359
|
+
) -> _T:
|
|
360
|
+
"""Read datasets into a `DataStorageNumpy` instance"""
|
|
361
|
+
datasets = cls.dataset_identifier_set()
|
|
362
|
+
ranges_isp = self._restrict_ranges(t_min, t_max)
|
|
363
|
+
ranges_dsid = {
|
|
364
|
+
dsid: ranges_isp[self.idxspace_by_dataset_id(dsid)] for dsid in datasets
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
stb = self.as_stream_bundle(datasets, ranges_dsid)
|
|
368
|
+
store = store_bundle_numpy(stb)
|
|
369
|
+
return cls(store.as_dict(), ranges_isp, self.metadata.asdict())
|
|
370
|
+
|
|
371
|
+
def read_full(
|
|
372
|
+
self, t_min: float | None = None, t_max: float | None = None
|
|
373
|
+
) -> SimResultsNumpyFull:
|
|
374
|
+
"""Read extended set of quantities into memory as `SimResultsNumpyFull` instance
|
|
375
|
+
|
|
376
|
+
If the file does not contain the extended set, an RuntimeError is raised.
|
|
377
|
+
|
|
378
|
+
Optionally, on can restrict the time range for which the data samples are
|
|
379
|
+
read. This does not change the index space, i.e. which indices refer to which
|
|
380
|
+
times. It only changes the index range of the datasets, available through
|
|
381
|
+
the `sat_ranges` and `mosa_ranges` attributes of `SimResultsNumpyFull`.
|
|
382
|
+
|
|
383
|
+
Arguments:
|
|
384
|
+
t_min: Optionally, only read samples at later times
|
|
385
|
+
t_max: Optionally, only read samples at earlier times
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
`SimResultsNumpyFull` instance with data.
|
|
389
|
+
"""
|
|
390
|
+
|
|
391
|
+
if not self.is_extended:
|
|
392
|
+
msg = "SimResultFile: cannot read extended results from basic result file"
|
|
393
|
+
raise RuntimeError(msg)
|
|
394
|
+
return self._read_datasets(SimResultsNumpyFull, t_min, t_max)
|
|
395
|
+
|
|
396
|
+
def read_core(
|
|
397
|
+
self, t_min: float | None = None, t_max: float | None = None
|
|
398
|
+
) -> SimResultsNumpyCore:
|
|
399
|
+
"""Same as `read_full` but restricted to basic set of quantities
|
|
400
|
+
|
|
401
|
+
Arguments:
|
|
402
|
+
t_min: Optionally, only read samples at later times
|
|
403
|
+
t_max: Optionally, only read samples at earlier times
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
`SimResultsNumpyCore` instance with data.
|
|
407
|
+
"""
|
|
408
|
+
return self._read_datasets(SimResultsNumpyCore, t_min, t_max)
|
|
409
|
+
|
|
410
|
+
def as_stream_bundle(
|
|
411
|
+
self,
|
|
412
|
+
datasets: set[DatasetIdentifier] | None = None,
|
|
413
|
+
ranges: dict[DatasetIdentifier, tuple[int, int]] | None = None,
|
|
414
|
+
) -> StreamBundle:
|
|
415
|
+
"""Represent datasets in the file as `StreamBundle`
|
|
416
|
+
|
|
417
|
+
Arguments:
|
|
418
|
+
datasets: Set of quantities to include
|
|
419
|
+
ranges: Dictionary with optional entris restricting dataset index range
|
|
420
|
+
|
|
421
|
+
Returns:
|
|
422
|
+
StreamBundle with specified datasets as outputs.
|
|
423
|
+
"""
|
|
424
|
+
if datasets is None:
|
|
425
|
+
datasets = self.dataset_identifier_set()
|
|
426
|
+
return instru_hdf5_file_as_stream_bundle(self._h5f, datasets, ranges=ranges)
|