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

1from hypothesis import given, strategies as st 

2import numpy as np 

3import pytest 

4 

5from swiift.model.model import DiscreteSpectrum 

6from tests.physical_strategies import PHYSICAL_STRATEGIES 

7 

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] 

27 

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] 

35 

36 

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 ) 

48 

49 

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 ] 

65 

66 

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 ] 

82 

83 

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) 

97 

98 

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) 

108 

109 

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() 

126 

127 

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) 

145 

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}.")