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
« 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/>.
27import re
29import numpy as np
30import pytest
32from physioblocks.library.functions.piecewise import (
33 PiecewiseLinear,
34 PiecewiseLinearPeriodic,
35 RescaleTwoPhasesFunction,
36)
39@pytest.fixture
40def piecewise_func_absissas():
41 return np.array([0.0, 0.5, 1.0])
44@pytest.fixture
45def piecewise_func_ordinates():
46 return np.array([0.0, 0.5, 1.5])
49@pytest.fixture
50def period():
51 return 2.0
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)
66@pytest.fixture
67def left_value():
68 return -2.0
71@pytest.fixture
72def right_value():
73 return 2.3
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)
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)
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 ]
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 ]
124@pytest.fixture
125def func_phases():
126 return [0, 0, 1, 1, 0]
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)
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)
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)
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)
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)
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)