Coverage for tests/test_spectrum.py: 0%
73 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-09-11 16:23 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-09-11 16:23 +0200
1from hypothesis import given, strategies as st
2import numpy as np
3import pytest
5from swiift.model.model import DiscreteSpectrum
6from tests.physical_strategies import PHYSICAL_STRATEGIES
8# Valid cases: all sizes the same, or some are 1
9valid_cases = [
10 # (amplitude, period, phase)
11 (1, 1, 7), # (1, 1, 1)
12 ([1], [1], [7]), # (1, 1, 1)
13 ([1, 2, 9, 6, 1], [1], [7]), # (5, 1, 1)
14 ([1, 3, 4, 2, 1], [1, 1, 1, 1, 1], 7), # (5, 5, 1)
15 ([1, 3, 1, 4, 1], [1, 8, 0.1, 1, 1], [7]), # (5, 5, 1)
16 ([1, 1, 2, 1, 1], [1.5, 1, 0.2, 1, 1], [7, 1, 7, 1, 1]), # (5, 5, 5)
17]
18valid_shapes = [
19 tuple(
20 [
21 len(subsequence) if isinstance(subsequence, list) else 0
22 for subsequence in sequence
23 ]
24 )
25 for sequence in valid_cases
26]
28invalid_cases = [
29 ([1, 1, 1], [1, 1], 1), # (3, 2, 1)
30 ([1, 1, 1], [1, 1], [1]), # (3, 2, 1)
31 ([1, 1, 1], [1, 1, 1, 1], [1]), # (3, 4, 1)
32 ([1, 1, 1], [1, 1], [1, 1, 1]), # (3, 2, 3)
33 ([1, 1, 1], [1, 1], [1, 1, 1, 1]), # (3, 2, 4)
34]
37def parse_shape(
38 size: int, strategy: st.SearchStrategy[float]
39) -> st.SearchStrategy[float | list[float]]:
40 if size == 0:
41 return strategy
42 else:
43 return st.lists(
44 strategy,
45 min_size=size,
46 max_size=size,
47 )
50@st.composite
51def spectrum_arguments(
52 draw: st.DrawFn, sizes: tuple[int, int, int]
53) -> list[float | list[float]]:
54 return [
55 draw(parse_shape(size, strategy))
56 for size, strategy in zip(
57 sizes,
58 (
59 PHYSICAL_STRATEGIES[("wave", "amplitude")],
60 PHYSICAL_STRATEGIES[("wave", "frequency")],
61 PHYSICAL_STRATEGIES[("wave", "phase")],
62 ),
63 )
64 ]
67@st.composite
68def spectrum_arguments_periods(
69 draw: st.DrawFn, sizes: tuple[int, int, int]
70) -> list[float | list[float]]:
71 return [
72 draw(parse_shape(size, strategy))
73 for size, strategy in zip(
74 sizes,
75 (
76 PHYSICAL_STRATEGIES[("wave", "amplitude")],
77 PHYSICAL_STRATEGIES[("wave", "period")],
78 PHYSICAL_STRATEGIES[("wave", "phase")],
79 ),
80 )
81 ]
84@pytest.mark.parametrize("args", valid_cases)
85@pytest.mark.parametrize("with_shape", (True, False))
86@pytest.mark.parametrize("as_array", (True, False))
87def test_valid_shapes(args, with_shape: bool, as_array: bool):
88 passed_args = args[:-1] if not with_shape else args
89 if as_array:
90 passed_args = (np.asarray(_a) for _a in passed_args)
91 spectrum = DiscreteSpectrum(*passed_args)
92 assert spectrum.amplitudes.size > 0
93 assert len(spectrum.amplitudes.shape) == 1
94 assert spectrum.amplitudes.shape == spectrum.frequencies.shape
95 assert spectrum.amplitudes.shape == spectrum.phases.shape
96 assert np.all(spectrum.phases >= 0) and np.all(spectrum.phases < 2 * np.pi)
99@pytest.mark.parametrize("args", invalid_cases)
100@pytest.mark.parametrize("with_phase", (True, False))
101@pytest.mark.parametrize("as_array", (True, False))
102def test_invalid_shapes(args, with_phase: bool, as_array: bool):
103 passed_args = args if with_phase else args[:-1]
104 if as_array:
105 passed_args = (np.asarray(_a) for _a in passed_args)
106 with pytest.raises(ValueError):
107 DiscreteSpectrum(*passed_args)
110@pytest.mark.parametrize("shapes", valid_shapes)
111@pytest.mark.parametrize("with_phase", (True, False))
112@given(data=st.data())
113def test_from_periods(
114 data: st.DataObject, shapes: tuple[int, int, int], with_phase: bool
115):
116 amplitudes, periods, phases = data.draw(spectrum_arguments_periods(shapes))
117 if with_phase:
118 spectrum = DiscreteSpectrum.from_periods(amplitudes, periods, phases)
119 else:
120 spectrum = DiscreteSpectrum.from_periods(amplitudes, periods)
121 assert np.allclose(1 / spectrum.frequencies, spectrum.periods)
122 # Test frequencies rather than periods, because of floating point errors
123 assert np.isin(
124 1 / np.array(periods), spectrum.frequencies, assume_unique=False
125 ).all()
128@pytest.mark.parametrize("shapes", valid_shapes)
129@pytest.mark.parametrize("with_phase", (True, False))
130@pytest.mark.parametrize(
131 "property", ("periods", "angular_frequencies", "_ang_freqs_pow2", "nf", "energy")
132)
133@given(data=st.data())
134def test_properties(
135 data: st.DataObject,
136 shapes: tuple[int, int, int],
137 with_phase: bool,
138 property: str,
139):
140 amplitudes, frequencies, phases = data.draw(spectrum_arguments(shapes))
141 if with_phase:
142 spectrum = DiscreteSpectrum(amplitudes, frequencies, phases)
143 else:
144 spectrum = DiscreteSpectrum(amplitudes, frequencies)
146 if property == "periods":
147 assert np.allclose(1 / spectrum.frequencies, spectrum.periods)
148 elif property == "angular_frequencies":
149 assert np.allclose(
150 2 * np.pi * spectrum.frequencies, spectrum.angular_frequencies
151 )
152 elif property == "_ang_freqs_pow2":
153 assert np.allclose(
154 (2 * np.pi * spectrum.frequencies) ** 2, spectrum._ang_freqs_pow2
155 )
156 elif property == "nf":
157 nf = max(np.ravel(amplitudes).size, np.ravel(frequencies).size)
158 if with_phase:
159 nf = max(nf, np.ravel(phases).size)
160 assert nf == spectrum.nf
161 elif property == "energy":
162 assert np.allclose(np.sum(spectrum.amplitudes**2) / 2, spectrum.energy)
163 else:
164 raise ValueError(f"DiscreteSpectrum objects have no property {property}.")