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.
Files changed (94) hide show
  1. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/PKG-INFO +1 -1
  2. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_file_reader.py +25 -11
  3. lisainstrument-2.2.1/lisainstrument/instru/instru_file_reader_v2_1_0.py +426 -0
  4. lisainstrument-2.2.1/lisainstrument/instru/instru_store_v2_1_0.py +939 -0
  5. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/pyproject.toml +1 -1
  6. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/LICENSE +0 -0
  7. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/README.md +0 -0
  8. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/__init__.py +0 -0
  9. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/__main__.py +0 -0
  10. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/cli/__init__.py +0 -0
  11. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/cli/run_simulation.py +0 -0
  12. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/freqplan/__init__.py +0 -0
  13. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/freqplan/fplan_file.py +0 -0
  14. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/freqplan/fplan_source.py +0 -0
  15. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/freqplan/fplan_source_interp.py +0 -0
  16. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/glitches/__init__.py +0 -0
  17. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/glitches/glitch_file.py +0 -0
  18. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/glitches/glitch_source.py +0 -0
  19. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/glitches/glitch_source_interp.py +0 -0
  20. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/gwsource/__init__.py +0 -0
  21. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/gwsource/gw_file.py +0 -0
  22. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/gwsource/gw_source.py +0 -0
  23. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/gwsource/hdf5util.py +0 -0
  24. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/__init__.py +0 -0
  25. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_defaults.py +0 -0
  26. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_file_reader_v2_0_0.py +0 -0
  27. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_filter.py +0 -0
  28. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_formulas.py +0 -0
  29. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_fplan.py +0 -0
  30. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_glitchsrc.py +0 -0
  31. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_gwsrc.py +0 -0
  32. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_locking.py +0 -0
  33. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_model.py +0 -0
  34. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_noises.py +0 -0
  35. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_orbsrc.py +0 -0
  36. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_store.py +0 -0
  37. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instru/instru_store_v2_0_0.py +0 -0
  38. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/instrument.py +0 -0
  39. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/__init__.py +0 -0
  40. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/containers.py +0 -0
  41. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/dsp.py +0 -0
  42. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/dynamic_delay_dsp.py +0 -0
  43. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/fixed_shift_dsp.py +0 -0
  44. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/hexagon.py +0 -0
  45. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/legacy_plots.py +0 -0
  46. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/noises.py +0 -0
  47. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/pyplnoise/LICENSE +0 -0
  48. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/pyplnoise/__init__.py +0 -0
  49. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/legacy/pyplnoise/pyplnoise.py +0 -0
  50. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/noisy/__init__.py +0 -0
  51. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/noisy/estimate_psd.py +0 -0
  52. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/noisy/noise_defs.py +0 -0
  53. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/noisy/noise_defs_lisa.py +0 -0
  54. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/noisy/noise_gen_numpy.py +0 -0
  55. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/orbiting/__init__.py +0 -0
  56. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/orbiting/constellation_enums.py +0 -0
  57. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/orbiting/orbit_file.py +0 -0
  58. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/orbiting/orbit_source.py +0 -0
  59. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/orbiting/orbit_source_interp.py +0 -0
  60. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/__init__.py +0 -0
  61. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/adaptive_delay_numpy.py +0 -0
  62. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/chunked_splines.py +0 -0
  63. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/dynamic_delay_numpy.py +0 -0
  64. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/fir_filters_numpy.py +0 -0
  65. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/fixed_shift_numpy.py +0 -0
  66. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/iir_filters_numpy.py +0 -0
  67. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/regular_interpolators.py +0 -0
  68. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/shift_inversion_numpy.py +0 -0
  69. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/sigpro/types_numpy.py +0 -0
  70. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/__init__.py +0 -0
  71. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/analysis.py +0 -0
  72. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/array.py +0 -0
  73. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/delay.py +0 -0
  74. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/derivative.py +0 -0
  75. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/expression.py +0 -0
  76. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/firfilter.py +0 -0
  77. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/graph_util.py +0 -0
  78. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/hdf5_store.py +0 -0
  79. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/iirfilter.py +0 -0
  80. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/integrate.py +0 -0
  81. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/noise.py +0 -0
  82. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/noise_alt.py +0 -0
  83. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/null_store.py +0 -0
  84. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/numpy_store.py +0 -0
  85. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/sampling.py +0 -0
  86. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/scheduler.py +0 -0
  87. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/scheduler_dask.py +0 -0
  88. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/scheduler_serial.py +0 -0
  89. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/segments.py +0 -0
  90. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/shift_inv.py +0 -0
  91. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/store.py +0 -0
  92. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/streams.py +0 -0
  93. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/streams/time.py +0 -0
  94. {lisainstrument-2.2.0 → lisainstrument-2.2.1}/lisainstrument/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: lisainstrument
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Simulates the LISA measurement chain (noise and signals) and generates telemetry data
5
5
  Home-page: https://gitlab.in2p3.fr/lisa-simulation/instrument
6
6
  License: BSD 3-Clause
@@ -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
- self._version: Final = Version(self._h5f.attrs["version_format"])
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
- ds_actual = set(self._h5f.keys()) - {"debug"}
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[type[SimResultFile] | type[SimResultFile_V2_0_0]] = [SimResultFile]
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)