Coverage for tests / tests_computing / test_assembling.py: 100%

92 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 

27from dataclasses import dataclass 

28 

29import numpy as np 

30import pytest 

31from numpy.typing import NDArray 

32 

33from physioblocks.computing.assembling import EqSystem 

34 

35 

36@dataclass 

37class ParamsF1: 

38 a: float 

39 x0: float 

40 

41 

42def f1(params: ParamsF1) -> float: 

43 return params.a * params.x0 

44 

45 

46def df1_dx0(params: ParamsF1) -> float: 

47 return params.a 

48 

49 

50@dataclass 

51class ParamsF2: 

52 a: float 

53 b: float 

54 x0: float 

55 x1: float 

56 

57 

58def f2(params: ParamsF2) -> NDArray[np.float64]: 

59 return np.array( 

60 [params.a * params.x0 + params.x1, params.b * params.x1 + params.x0], 

61 ) 

62 

63 

64def df2_dx0(params: ParamsF2) -> NDArray[np.float64]: 

65 return np.array( 

66 [params.a, 1.0], 

67 ) 

68 

69 

70def df2_dx1(params: ParamsF2) -> NDArray[np.float64]: 

71 return np.array( 

72 [1.0, params.b], 

73 ) 

74 

75 

76@dataclass 

77class ParamsF3: 

78 a: float 

79 b: float 

80 c: float 

81 x1: float 

82 x2: float 

83 x3: float 

84 

85 

86def f3(params: ParamsF3): 

87 return np.array( 

88 [ 

89 params.a * params.x1, 

90 params.b * params.x2 + params.x3, 

91 params.c * params.x3 + params.x2, 

92 ] 

93 ) 

94 

95 

96def df3_dx1(params: ParamsF3): 

97 return np.array( 

98 [ 

99 params.a, 

100 0.0, 

101 0.0, 

102 ] 

103 ) 

104 

105 

106def df3_dx2(params: ParamsF3): 

107 return np.array([0.0, params.b, 1.0]) 

108 

109 

110def df3_dx3(params: ParamsF3): 

111 return np.array([0.0, 1.0, params.c]) 

112 

113 

114@pytest.fixture 

115def params_f1(): 

116 return ParamsF1(1.0, 0.1) 

117 

118 

119@pytest.fixture 

120def params_f2(): 

121 return ParamsF2(a=1.0, b=2.0, x0=0.1, x1=0.2) 

122 

123 

124@pytest.fixture 

125def res_ref(): 

126 return np.array([0.4, 0.5, 0.7, 0.7]) 

127 

128 

129@pytest.fixture 

130def grad_ref(): 

131 return np.array( 

132 [ 

133 [2.0, 1.0, 0.0, 0.0], 

134 [1.0, 2.0, 0.0, 0.0], 

135 [0.0, 0.0, 1.0, 1.0], 

136 [0.0, 0.0, 1.0, 1.0], 

137 ] 

138 ) 

139 

140 

141@pytest.fixture 

142def eq_system_ref(): 

143 eq_system = EqSystem(4) 

144 x0 = 0.1 

145 x1 = 0.2 

146 x2 = 0.3 

147 x3 = 0.4 

148 a = 1.0 

149 

150 eq_system.add_system_part(0, 1, f1, {0: df1_dx0}, ParamsF1(a, x0)) 

151 

152 eq_system.add_system_part( 

153 0, 2, f2, {0: df2_dx0, 1: df2_dx1}, ParamsF2(a, a, x0, x1) 

154 ) 

155 

156 eq_system.add_system_part( 

157 1, 

158 3, 

159 f3, 

160 {1: df3_dx1, 2: df3_dx2, 3: df3_dx3}, 

161 ParamsF3(a, a, a, x1, x2, x3), 

162 ) 

163 return eq_system 

164 

165 

166class TestEqSystem: 

167 def test_constructor(self): 

168 eq_system = EqSystem(0) 

169 assert eq_system.system_size == 0 

170 

171 def test_set(self): 

172 eq_system = EqSystem(0) 

173 with pytest.raises(AttributeError): 

174 eq_system.system_size = 1 

175 

176 def test_add_part(self, params_f1): 

177 eq_system = EqSystem(1) 

178 

179 grads = {0: df1_dx0} 

180 eq_system.add_system_part(0, 1, f1, grads, params_f1) 

181 

182 def test_add_part_exceed_residual_size(self, params_f2): 

183 eq_system = EqSystem(1) 

184 

185 grads = {0: df2_dx0, 1: df2_dx1} 

186 with pytest.raises(ValueError): 

187 eq_system.add_system_part(0, 2, f2, grads, params_f2) 

188 

189 def test_add_part_exceed_gradient_size(self, params_f2): 

190 eq_system = EqSystem(2) 

191 grads = {0: df2_dx0, 2: df2_dx1} 

192 with pytest.raises(ValueError): 

193 eq_system.add_system_part(0, 2, f2, grads, params_f2) 

194 

195 def test_residual_gradient(self, eq_system_ref: EqSystem, res_ref, grad_ref): 

196 res = eq_system_ref.compute_residual() 

197 grad = eq_system_ref.compute_gradient() 

198 

199 assert res == pytest.approx(res_ref) 

200 assert grad == pytest.approx(grad_ref)