Coverage for tests / tests_config / tests_simulation / test_config_simulation.py: 98%
106 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 pathlib import Path
29from unittest.mock import MagicMock, PropertyMock, patch
31import pytest
33from physioblocks.computing.models import (
34 BlockMetaClass,
35 Expression,
36 ExpressionDefinition,
37 TermDefinition,
38)
39from physioblocks.computing.quantities import Quantity
40from physioblocks.configuration.constants import NET_ID
41from physioblocks.configuration.simulation.simulations import (
42 PARAMETERS_ID,
43 load_simulation_config,
44 save_simulation_config,
45)
46from physioblocks.description.blocks import (
47 BLOCK_DESCRIPTION_TYPE_ID,
48 ID_SEPARATOR,
49 Block,
50 BlockDescription,
51)
52from physioblocks.description.flux import (
53 FLUX_TYPE_REGISTER_ID,
54 FluxDofTypesRegister,
55)
56from physioblocks.description.nets import BOUNDARY_CONDITION_ID, BoundaryCondition, Net
57from physioblocks.io.configuration import read_json
58from physioblocks.simulation.functions import AbstractFunction
59from physioblocks.simulation.runtime import AbstractSimulation, ForwardSimulation
60from physioblocks.simulation.setup import SimulationFactory
61from physioblocks.simulation.solvers import AbstractSolver
62from physioblocks.simulation.time_manager import TIME_MANAGER_ID, TimeManager
63from tests.helpers.assertion_helpers import (
64 assert_net_equals,
65 assert_solvers_equals,
66 assert_states_equals,
67 assert_time_manager_equals,
68)
70ref_config_file_path = Path(
71 "./tests/tests_config/tests_simulation/simulation_reference.json"
72)
74DOF_ID = "dof"
75SCALAR_ID = "scalar"
76VECTOR_ID = "vector"
77NODE_A_ID = "node_a"
78NODE_B_ID = "node_b"
79BLOCK_A_ID = "block_a"
80BLOCK_LOCAL_POTENTIAL = "potential_id"
81INLET_FLUX_CONDITION_ID = "inlet_flux_condition"
82OUTPUT_ID = "output_id"
83FLUX_TYPE_ID = "flux_type"
84DOF_TYPE_ID = "potential_type"
85FUNC_TYPE_ID = "FunctionType"
86SIM_TYPE_ID = "SimulationType"
87BLOCK_TYPE_ID = "BlockType"
88SOLVER_TYPE_ID = "SolverType"
89OUTLET_POTENTIAL_CONDITION_ID = ID_SEPARATOR.join([BLOCK_A_ID, BLOCK_LOCAL_POTENTIAL])
92@dataclass
93class BlockTest(Block):
94 potential_id: Quantity
97def ref_func():
98 pass
101@pytest.fixture
102def ref_term() -> TermDefinition:
103 return TermDefinition(1, DOF_ID)
106@pytest.fixture
107def ref_expression():
108 return Expression(1, ref_func)
111@pytest.fixture
112def ref_expression_definition(
113 ref_expression: Expression, ref_term: TermDefinition
114) -> ExpressionDefinition:
115 return ExpressionDefinition(ref_expression, [ref_term])
118@pytest.fixture
119def ref_nodes():
120 return {0: [FLUX_TYPE_ID], 1: [FLUX_TYPE_ID]}
123@pytest.fixture
124def ref_flux_expressions(ref_expression_definition: ExpressionDefinition):
125 return {
126 0: ref_expression_definition,
127 1: ref_expression_definition,
128 }
131@pytest.fixture
132@patch.multiple(
133 "physioblocks.description.nets._flux_type_register",
134 create=True,
135 _fluxes_types={FLUX_TYPE_ID: DOF_TYPE_ID},
136 _dof_types={DOF_TYPE_ID: FLUX_TYPE_ID},
137)
138def net_reference(ref_nodes, ref_flux_expressions):
139 with patch.multiple(
140 BlockMetaClass,
141 fluxes_expressions=PropertyMock(return_value=ref_flux_expressions),
142 nodes=PropertyMock(return_value=ref_nodes),
143 local_ids=PropertyMock(return_value=[BLOCK_LOCAL_POTENTIAL]),
144 ):
145 net = Net()
146 net.add_node(NODE_A_ID)
147 net.add_node(NODE_B_ID)
148 net.add_block(
149 BLOCK_A_ID,
150 BlockDescription(BLOCK_A_ID, BlockTest, FLUX_TYPE_ID),
151 {0: NODE_A_ID, 1: NODE_B_ID},
152 )
154 net.set_boundary(NODE_A_ID, FLUX_TYPE_ID, INLET_FLUX_CONDITION_ID)
155 net.set_boundary(NODE_B_ID, DOF_TYPE_ID, OUTLET_POTENTIAL_CONDITION_ID)
157 return net
160def time_func(self, time):
161 return 0.0
164def func_init(self):
165 pass
168@pytest.fixture
169@patch.multiple(
170 "physioblocks.description.nets._flux_type_register",
171 create=True,
172 _fluxes_types={FLUX_TYPE_ID: DOF_TYPE_ID},
173 _dof_types={DOF_TYPE_ID: FLUX_TYPE_ID},
174)
175def simulation_reference(net_reference: Net, ref_flux_expressions, ref_nodes):
176 with (
177 patch.multiple(AbstractSimulation, __abstractmethods__=set()),
178 patch.multiple(AbstractSolver, __abstractmethods__=set()),
179 patch.multiple(AbstractFunction, __abstractmethods__=set(), eval=time_func),
180 patch.multiple(
181 BlockMetaClass,
182 fluxes_expressions=PropertyMock(return_value=ref_flux_expressions),
183 nodes=PropertyMock(return_value=ref_nodes),
184 __init__=MagicMock(return_value=None),
185 ),
186 ):
187 factory = SimulationFactory(AbstractSimulation, AbstractSolver(), net_reference)
188 sim = factory.create_simulation()
189 sim.magnitudes = {str.format("{0}.{1}", NODE_A_ID, DOF_TYPE_ID): 1.1}
190 sim.register_timed_parameter_update(INLET_FLUX_CONDITION_ID, AbstractFunction())
191 sim.register_output_function(OUTPUT_ID, AbstractFunction())
192 sim.parameters[VECTOR_ID] = 3 * [0.0]
193 sim.parameters[SCALAR_ID] = 0.0
194 return sim
197@patch(
198 "physioblocks.registers.type_register.__type_register",
199 new={
200 FUNC_TYPE_ID: AbstractFunction,
201 SIM_TYPE_ID: AbstractSimulation,
202 BLOCK_DESCRIPTION_TYPE_ID: BlockDescription,
203 BLOCK_TYPE_ID: BlockTest,
204 SOLVER_TYPE_ID: AbstractSolver,
205 TIME_MANAGER_ID: TimeManager,
206 NET_ID: Net,
207 BOUNDARY_CONDITION_ID: BoundaryCondition,
208 FLUX_TYPE_REGISTER_ID: FluxDofTypesRegister,
209 AbstractFunction: FUNC_TYPE_ID,
210 AbstractSimulation: SIM_TYPE_ID,
211 BlockDescription: BLOCK_DESCRIPTION_TYPE_ID,
212 BlockTest: BLOCK_TYPE_ID,
213 AbstractSolver: SOLVER_TYPE_ID,
214 Net: NET_ID,
215 TimeManager: TIME_MANAGER_ID,
216 BoundaryCondition: BOUNDARY_CONDITION_ID,
217 FluxDofTypesRegister: FLUX_TYPE_REGISTER_ID,
218 },
219)
220@patch.multiple(
221 "physioblocks.description.nets._flux_type_register",
222 create=True,
223 _fluxes_types={FLUX_TYPE_ID: DOF_TYPE_ID},
224 _dof_types={DOF_TYPE_ID: FLUX_TYPE_ID},
225)
226def test_simulation_save_config(
227 simulation_reference: ForwardSimulation, ref_flux_expressions, ref_nodes
228):
229 with (
230 patch.multiple(
231 BlockMetaClass,
232 fluxes_expressions=PropertyMock(return_value=ref_flux_expressions),
233 nodes=PropertyMock(return_value=ref_nodes),
234 ),
235 patch.multiple(
236 AbstractFunction,
237 __abstractmethods__=set(),
238 eval=time_func,
239 __init__=func_init,
240 ),
241 ):
242 configuration = save_simulation_config(simulation_reference)
243 ref_config = read_json(ref_config_file_path)
244 assert configuration == ref_config
247@patch.multiple(
248 "physioblocks.description.nets._flux_type_register",
249 create=True,
250 _fluxes_types={FLUX_TYPE_ID: DOF_TYPE_ID},
251 _dof_types={DOF_TYPE_ID: FLUX_TYPE_ID},
252)
253@patch(
254 "physioblocks.registers.type_register.__type_register",
255 new={
256 FUNC_TYPE_ID: AbstractFunction,
257 SIM_TYPE_ID: AbstractSimulation,
258 BLOCK_DESCRIPTION_TYPE_ID: BlockDescription,
259 BLOCK_TYPE_ID: BlockTest,
260 SOLVER_TYPE_ID: AbstractSolver,
261 TIME_MANAGER_ID: TimeManager,
262 NET_ID: Net,
263 BOUNDARY_CONDITION_ID: BoundaryCondition,
264 AbstractFunction: FUNC_TYPE_ID,
265 AbstractSimulation: SIM_TYPE_ID,
266 BlockDescription: BLOCK_DESCRIPTION_TYPE_ID,
267 BlockTest: BLOCK_TYPE_ID,
268 AbstractSolver: SOLVER_TYPE_ID,
269 Net: NET_ID,
270 TimeManager: TIME_MANAGER_ID,
271 BoundaryCondition: BOUNDARY_CONDITION_ID,
272 },
273)
274@patch.multiple(AbstractSimulation, __abstractmethods__=set())
275@patch.multiple(AbstractSolver, __abstractmethods__=set())
276@patch.multiple(AbstractFunction, __abstractmethods__=set(), eval=time_func)
277def test_simulation_load_config(
278 simulation_reference: ForwardSimulation, ref_flux_expressions, ref_nodes
279):
280 with patch.multiple(
281 BlockMetaClass,
282 fluxes_expressions=PropertyMock(return_value=ref_flux_expressions),
283 nodes=PropertyMock(return_value=ref_nodes),
284 ):
285 ref_config = read_json(ref_config_file_path)
286 sim = load_simulation_config(ref_config, ForwardSimulation)
288 assert sim.parameters == simulation_reference.parameters
289 assert_net_equals(sim.factory.net, simulation_reference.factory.net)
290 assert_solvers_equals(sim.solver, simulation_reference.solver)
291 assert_states_equals(sim.state, simulation_reference.state)
292 assert_time_manager_equals(sim.time_manager, simulation_reference.time_manager)
294 # load raises exceptions correctly:
295 ref_config[PARAMETERS_ID]["wrong_type_id"] = object()
296 with pytest.raises(TypeError):
297 load_simulation_config(ref_config)