Coverage for tests / tests_library / tests_functions / test_piecewise_functions.py: 99%

86 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-09 16:40 +0100

1# SPDX-FileCopyrightText: Copyright INRIA 

2# 

3# SPDX-License-Identifier: LGPL-3.0-only 

4# 

5# Copyright INRIA 

6# 

7# This file is part of PhysioBlocks, a library mostly developed by the 

8# [Ananke project-team](https://team.inria.fr/ananke) at INRIA. 

9# 

10# Authors: 

11# - Colin Drieu 

12# - Dominique Chapelle 

13# - François Kimmig 

14# - Philippe Moireau 

15# 

16# PhysioBlocks is free software: you can redistribute it and/or modify it under the 

17# terms of the GNU Lesser General Public License as published by the Free Software 

18# Foundation, version 3 of the License. 

19# 

20# PhysioBlocks is distributed in the hope that it will be useful, but WITHOUT ANY 

21# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 

22# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 

23# 

24# You should have received a copy of the GNU Lesser General Public License along with 

25# PhysioBlocks. If not, see <https://www.gnu.org/licenses/>. 

26 

27import re 

28 

29import numpy as np 

30import pytest 

31 

32from physioblocks.library.functions.piecewise import ( 

33 PiecewiseLinear, 

34 PiecewiseLinearPeriodic, 

35 RescaleTwoPhasesFunction, 

36) 

37 

38 

39@pytest.fixture 

40def piecewise_func_absissas(): 

41 return np.array([0.0, 0.5, 1.0]) 

42 

43 

44@pytest.fixture 

45def piecewise_func_ordinates(): 

46 return np.array([0.0, 0.5, 1.5]) 

47 

48 

49@pytest.fixture 

50def period(): 

51 return 2.0 

52 

53 

54class TestPiecewiseLinearPeriodic: 

55 def test_eval(self, period, piecewise_func_absissas, piecewise_func_ordinates): 

56 function_ref = PiecewiseLinearPeriodic( 

57 period, piecewise_func_absissas, piecewise_func_ordinates 

58 ) 

59 assert function_ref.eval(0.5) == pytest.approx(0.5) 

60 assert function_ref.eval(0.25) == pytest.approx(0.25) 

61 assert function_ref.eval(0.75) == pytest.approx(1.0) 

62 assert function_ref.eval(1.5) == pytest.approx(0.75) 

63 assert function_ref.eval(2.25) == pytest.approx(0.25) 

64 

65 

66@pytest.fixture 

67def left_value(): 

68 return -2.0 

69 

70 

71@pytest.fixture 

72def right_value(): 

73 return 2.3 

74 

75 

76class TestPiecewiseLinear: 

77 def test_eval_left_right_value( 

78 self, left_value, right_value, piecewise_func_absissas, piecewise_func_ordinates 

79 ): 

80 function_ref = PiecewiseLinear( 

81 piecewise_func_absissas, piecewise_func_ordinates, left_value, right_value 

82 ) 

83 assert function_ref.eval(0.0) == pytest.approx(0.0) 

84 assert function_ref.eval(0.5) == pytest.approx(0.5) 

85 assert function_ref.eval(0.25) == pytest.approx(0.25) 

86 assert function_ref.eval(0.75) == pytest.approx(1.0) 

87 assert function_ref.eval(1.5) == pytest.approx(right_value) 

88 assert function_ref.eval(-0.3) == pytest.approx(left_value) 

89 

90 def test_eval_no_left_right_value( 

91 self, piecewise_func_absissas, piecewise_func_ordinates 

92 ): 

93 piecewise_function = PiecewiseLinear( 

94 piecewise_func_absissas, piecewise_func_ordinates 

95 ) 

96 assert piecewise_function.eval(1.5) == pytest.approx(1.5) 

97 assert piecewise_function.eval(-1.0) == pytest.approx(0.0) 

98 

99 

100@pytest.fixture 

101def piecewise_func(): 

102 return [ 

103 [0.0, -1.0], 

104 [0.249, -1.0], 

105 [0.25, 1.0], 

106 [0.749, 1.0], 

107 [0.75, -1.0], 

108 [1.0, -1.0], 

109 ] 

110 

111 

112@pytest.fixture 

113def piecewise_func_shifted(): 

114 return [ 

115 [-0.5, -1.0], 

116 [-0.251, -1.0], 

117 [-0.25, 1.0], 

118 [0.249, 1.0], 

119 [0.25, -1.0], 

120 [0.5, -1.0], 

121 ] 

122 

123 

124@pytest.fixture 

125def func_phases(): 

126 return [0, 0, 1, 1, 0] 

127 

128 

129class TestRescaleFunction: 

130 def test_mid_scaling_factor(self, piecewise_func, func_phases): 

131 function_reduction_alpha_mid = RescaleTwoPhasesFunction( 

132 0.5, piecewise_func, 0.5, func_phases 

133 ) 

134 assert function_reduction_alpha_mid.rescaled_period == pytest.approx(0.5) 

135 assert function_reduction_alpha_mid.eval(0.0) == pytest.approx(-1.0) 

136 assert function_reduction_alpha_mid.eval(0.1245) == pytest.approx(-1.0) 

137 assert function_reduction_alpha_mid.eval(0.125) == pytest.approx(1.0) 

138 assert function_reduction_alpha_mid.eval(0.5) == pytest.approx(-1.0) 

139 

140 function_reduction_alpha_mid_shifted = RescaleTwoPhasesFunction( 

141 0.5, piecewise_func, 0.5, func_phases 

142 ) 

143 assert function_reduction_alpha_mid_shifted.rescaled_period == pytest.approx( 

144 0.5 

145 ) 

146 assert function_reduction_alpha_mid_shifted.eval(-0.5) == pytest.approx(-1.0) 

147 assert function_reduction_alpha_mid_shifted.eval(-0.3755) == pytest.approx(-1.0) 

148 assert function_reduction_alpha_mid_shifted.eval(-0.375) == pytest.approx(1.0) 

149 assert function_reduction_alpha_mid_shifted.eval(0.0) == pytest.approx(-1.0) 

150 

151 def test_big_scaling_factor(self, piecewise_func, func_phases): 

152 function_reduction_alpha_mid = RescaleTwoPhasesFunction( 

153 0.5, piecewise_func, 0.90, func_phases 

154 ) 

155 assert function_reduction_alpha_mid.rescaled_period == pytest.approx(0.5) 

156 assert function_reduction_alpha_mid.eval(0.0) == pytest.approx(-1.0) 

157 assert function_reduction_alpha_mid.eval(0.0249) == pytest.approx(-1.0) 

158 assert function_reduction_alpha_mid.eval(0.025) == pytest.approx(1.0) 

159 assert function_reduction_alpha_mid.eval(0.474) == pytest.approx(1.0) 

160 assert function_reduction_alpha_mid.eval(0.475) == pytest.approx(-1.0) 

161 assert function_reduction_alpha_mid.eval(0.5) == pytest.approx(-1.0) 

162 

163 def test_exceptions(self, piecewise_func: list[tuple], func_phases: list[int]): 

164 with pytest.raises( 

165 ValueError, 

166 match=re.escape( 

167 "A phase should be defined for each interval defined in the " 

168 "reference function." 

169 ), 

170 ): 

171 RescaleTwoPhasesFunction(0.5, piecewise_func, 0.90, [0.0, 1.0]) 

172 wrong_phases_neg = [0, -1, 1, 1, 0] 

173 wrong_phases_sup = [0, 1, 1, 1, 2] 

174 with pytest.raises( 

175 ValueError, 

176 match=re.escape( 

177 str.format( 

178 "There are only two phases allowed: 0 or 1, got {0}.", 

179 wrong_phases_neg, 

180 ) 

181 ), 

182 ): 

183 RescaleTwoPhasesFunction(0.5, piecewise_func, 0.90, wrong_phases_neg) 

184 with pytest.raises( 

185 ValueError, 

186 match=re.escape( 

187 str.format( 

188 "There are only two phases allowed: 0 or 1, got {0}", 

189 wrong_phases_sup, 

190 ) 

191 ), 

192 ): 

193 RescaleTwoPhasesFunction(0.5, piecewise_func, 0.90, wrong_phases_sup) 

194 

195 with pytest.raises( 

196 ValueError, 

197 match=re.escape( 

198 str.format( 

199 "The proportion of the variation of phase 0 should be in ]0, 1[, " 

200 "got {0}", 

201 -0.9, 

202 ) 

203 ), 

204 ): 

205 RescaleTwoPhasesFunction(0.5, piecewise_func, -0.90, func_phases) 

206 

207 unsorted_abscissas_func = [ 

208 [0.0, 0.0], 

209 [0.1, 0.0], 

210 [0.05, 0.0], 

211 [0.2, 0.0], 

212 [0.3, 0.0], 

213 [0.4, 0.0], 

214 ] 

215 with pytest.raises( 

216 ValueError, 

217 match=re.escape( 

218 str.format( 

219 "Reference function abscissas should be sorted, got {0}", 

220 [0.0, 0.1, 0.05, 0.2, 0.3, 0.4], 

221 ) 

222 ), 

223 ): 

224 RescaleTwoPhasesFunction(0.5, unsorted_abscissas_func, 0.5, func_phases)