Coverage for tests / tests_config / tests_simulation / test_functions.py: 96%

125 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 

28from typing import Any 

29from unittest.mock import patch 

30 

31import numpy as np 

32import pytest 

33from numpy.typing import NDArray 

34 

35from physioblocks.computing.quantities import Quantity 

36from physioblocks.configuration.base import Configuration, ConfigurationError 

37from physioblocks.configuration.functions import load, save 

38from physioblocks.description.blocks import Block 

39from physioblocks.simulation.functions import AbstractFunction 

40 

41FUNC_ID = "func_id" 

42 

43 

44class BlockTest(Block): 

45 param: float 

46 

47 def __init__(self, param: float): 

48 self.param = param 

49 

50 

51@dataclass 

52class FunctionValues(AbstractFunction): 

53 a: float 

54 b: int 

55 c: str 

56 d: NDArray[np.float64] 

57 e: np.float64 

58 f: Quantity[np.float64] 

59 g: Quantity[NDArray[np.float64]] 

60 h: list[Quantity] 

61 i: dict[str, Quantity] 

62 

63 

64@pytest.fixture 

65def ref_function_values(): 

66 with patch.multiple(FunctionValues, __abstractmethods__=set()): 

67 return FunctionValues( 

68 0.1, 

69 2, 

70 "three", 

71 np.array([0.4, 0.5, 0.6]), 

72 0.7, 

73 Quantity(0.8), 

74 Quantity([0.9, 1.0, 1.1]), 

75 [ 

76 Quantity(1.2), 

77 Quantity(1.3), 

78 Quantity(1.4), 

79 ], 

80 { 

81 "q1": Quantity(1.5), 

82 "q2": Quantity(1.6), 

83 "q3": Quantity(1.7), 

84 }, 

85 ) 

86 

87 

88@pytest.fixture 

89def ref_config_values(): 

90 config = Configuration(FUNC_ID) 

91 config.configuration_items = { 

92 "a": 0.1, 

93 "b": 2, 

94 "c": "three", 

95 "d": [0.4, 0.5, 0.6], 

96 "e": 0.7, 

97 "f": 0.8, 

98 "g": [0.9, 1.0, 1.1], 

99 "h": [1.2, 1.3, 1.4], 

100 "i": {"q1": 1.5, "q2": 1.6, "q3": 1.7}, 

101 } 

102 return config 

103 

104 

105@dataclass 

106class FunctionReferences(AbstractFunction): 

107 a: Quantity[np.float64] 

108 b: Quantity[NDArray[np.float64]] 

109 c: list[Quantity] 

110 d: dict[str, Quantity] 

111 e: BlockTest 

112 f: list[BlockTest] 

113 g: dict[str, BlockTest] 

114 

115 

116@pytest.fixture 

117def ref_config_references(): 

118 config = Configuration(FUNC_ID) 

119 config.configuration_items = { 

120 "a": "qa", 

121 "b": "qb", 

122 "c": ["qc1", "qc2", "qc3"], 

123 "d": {"q1": "qd1", "q2": "qd2", "q3": "qd3"}, 

124 "e": "ba", 

125 "f": ["bb1", "bb2"], 

126 "g": {"b1": "bc1", "b2": "bc2"}, 

127 } 

128 

129 return config 

130 

131 

132@pytest.fixture 

133def quantities(): 

134 return { 

135 "qa": Quantity(0.1), 

136 "qb": Quantity(np.array([0.2, 0.3, 0.4])), 

137 "qc1": Quantity(0.5), 

138 "qc2": Quantity(0.6), 

139 "qc3": Quantity(0.7), 

140 "qd1": Quantity(0.8), 

141 "qd2": Quantity(0.9), 

142 "qd3": Quantity(1.0), 

143 } 

144 

145 

146@pytest.fixture 

147def blocks(): 

148 return { 

149 "ba": BlockTest(1.1), 

150 "bb1": BlockTest(1.2), 

151 "bb2": BlockTest(1.3), 

152 "bc1": BlockTest(1.4), 

153 "bc2": BlockTest(1.5), 

154 } 

155 

156 

157@pytest.fixture 

158def ref_function_references(quantities, blocks): 

159 with patch.multiple(FunctionReferences, __abstractmethods__=set()): 

160 return FunctionReferences( 

161 quantities["qa"], 

162 quantities["qb"], 

163 [ 

164 quantities["qc1"], 

165 quantities["qc2"], 

166 quantities["qc3"], 

167 ], 

168 { 

169 "q1": quantities["qd1"], 

170 "q2": quantities["qd2"], 

171 "q3": quantities["qd3"], 

172 }, 

173 blocks["ba"], 

174 [blocks["bb1"], blocks["bb2"]], 

175 {"b1": blocks["bc1"], "b2": blocks["bc2"]}, 

176 ) 

177 

178 

179@dataclass 

180class CanNotConvertFunction(AbstractFunction): 

181 can_not_convert_float: float 

182 can_not_convert_object: object 

183 

184 

185@pytest.fixture 

186def ref_config_can_not_convert(): 

187 config = Configuration(FUNC_ID) 

188 config.configuration_items = { 

189 "can_not_convert_float": "0.1", 

190 "can_not_convert_object": "some_id", 

191 } 

192 return config 

193 

194 

195@pytest.fixture 

196def ref_can_not_convert_values(): 

197 with patch.multiple(CanNotConvertFunction, __abstractmethods__=set()): 

198 return CanNotConvertFunction(object(), object()) 

199 

200 

201@patch( 

202 "physioblocks.registers.type_register.__type_register", 

203 new={FunctionValues: FUNC_ID}, 

204) 

205def test_to_config_values(ref_function_values, ref_config_values): 

206 config = save(ref_function_values) 

207 assert config == ref_config_values 

208 

209 

210@patch( 

211 "physioblocks.registers.type_register.__type_register", 

212 new={FunctionReferences: FUNC_ID}, 

213) 

214def test_to_config_references( 

215 ref_function_references, 

216 ref_config_references, 

217 quantities: dict[str, Any], 

218 blocks: dict[str, Any], 

219): 

220 references = quantities.copy() 

221 references.update(blocks) 

222 config = save(ref_function_references, configuration_references=references) 

223 assert config == ref_config_references 

224 

225 

226@patch( 

227 "physioblocks.registers.type_register.__type_register", 

228 new={FunctionReferences: FUNC_ID}, 

229) 

230def test_to_config_can_not_configure_block_exception(ref_function_references): 

231 message = str.format("Can not configure object {0}.", ref_function_references.e) 

232 with pytest.raises(ConfigurationError, match=message): 

233 save(ref_function_references) 

234 

235 

236@patch( 

237 "physioblocks.registers.type_register.__type_register", 

238 new={FUNC_ID: FunctionValues}, 

239) 

240@patch.multiple(FunctionValues, __abstractmethods__=set()) 

241def test_from_config_values(ref_config_values, ref_function_values: FunctionValues): 

242 func: FunctionValues = load(ref_config_values) 

243 assert func.a == pytest.approx(ref_function_values.a) 

244 assert func.b == ref_function_values.b 

245 assert func.c == ref_function_values.c 

246 assert func.d == pytest.approx(ref_function_values.d) 

247 assert func.e == pytest.approx(ref_function_values.e) 

248 assert func.f == pytest.approx(ref_function_values.f) 

249 assert func.g == pytest.approx(ref_function_values.g) 

250 

251 for index in range(0, len(func.h)): 

252 assert func.h[index] == pytest.approx(ref_function_values.h[index]) 

253 

254 for key in func.i: 

255 assert func.i[key] == pytest.approx(ref_function_values.i[key]) 

256 

257 

258@patch( 

259 "physioblocks.registers.type_register.__type_register", 

260 new={FUNC_ID: FunctionReferences}, 

261) 

262@patch.multiple(FunctionReferences, __abstractmethods__=set()) 

263def test_from_config_references( 

264 ref_config_references, 

265 ref_function_references: FunctionReferences, 

266 quantities, 

267 blocks, 

268): 

269 references = {} 

270 references.update(quantities) 

271 references.update(blocks) 

272 

273 func: FunctionReferences = load( 

274 ref_config_references, configuration_references=references 

275 ) 

276 assert func.a.current == pytest.approx(ref_function_references.a.current) 

277 assert func.a.new == pytest.approx(ref_function_references.a.new) 

278 

279 assert func.b.current == pytest.approx(ref_function_references.b.current) 

280 assert func.b.new == pytest.approx(ref_function_references.b.new) 

281 

282 for index in range(0, len(func.c)): 

283 assert func.c[index].current == pytest.approx( 

284 ref_function_references.c[index].current 

285 ) 

286 assert func.c[index].new == pytest.approx(ref_function_references.c[index].new) 

287 

288 for key in func.d: 

289 assert func.d[key].current == pytest.approx( 

290 ref_function_references.d[key].current 

291 ) 

292 assert func.d[key].new == pytest.approx(ref_function_references.d[key].new) 

293 

294 assert func.e.param == pytest.approx(ref_function_references.e.param) 

295 

296 for index in range(0, len(func.f)): 

297 assert func.f[index].param == pytest.approx( 

298 ref_function_references.f[index].param 

299 ) 

300 

301 for key in func.g: 

302 assert func.g[key].param == pytest.approx(ref_function_references.g[key].param)