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