Coverage for tests/test_spectrum.py: 0%

1 from hypothesis import given, strategies as st

2 import numpy as np

3 import pytest

4

5 from swiift.model.model import DiscreteSpectrum

6 from tests.physical_strategies import PHYSICAL_STRATEGIES

7

8# Valid cases: all sizes the same, or some are 1

9 valid_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]

18 valid_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

28 invalid_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

37 def 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

51 def 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

68 def 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))

87 def 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))

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

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

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