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
« 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/>.
27from dataclasses import dataclass
28from typing import Any
29from unittest.mock import patch
31import numpy as np
32import pytest
33from numpy.typing import NDArray
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
41FUNC_ID = "func_id"
44class BlockTest(Block):
45 param: float
47 def __init__(self, param: float):
48 self.param = param
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]
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 )
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
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]
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 }
129 return config
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 }
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 }
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 )
179@dataclass
180class CanNotConvertFunction(AbstractFunction):
181 can_not_convert_float: float
182 can_not_convert_object: object
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
195@pytest.fixture
196def ref_can_not_convert_values():
197 with patch.multiple(CanNotConvertFunction, __abstractmethods__=set()):
198 return CanNotConvertFunction(object(), object())
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
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
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)
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)
251 for index in range(0, len(func.h)):
252 assert func.h[index] == pytest.approx(ref_function_values.h[index])
254 for key in func.i:
255 assert func.i[key] == pytest.approx(ref_function_values.i[key])
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)
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)
279 assert func.b.current == pytest.approx(ref_function_references.b.current)
280 assert func.b.new == pytest.approx(ref_function_references.b.new)
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)
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)
294 assert func.e.param == pytest.approx(ref_function_references.e.param)
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 )
301 for key in func.g:
302 assert func.g[key].param == pytest.approx(ref_function_references.g[key].param)