Coverage for tests / tests_description / test_nets.py: 100%
206 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 unittest.mock import patch
29import pytest
31from physioblocks.computing.models import (
32 Block,
33 Expression,
34 ExpressionDefinition,
35 TermDefinition,
36)
37from physioblocks.description.blocks import BlockDescription
38from physioblocks.description.flux import Dof
39from physioblocks.description.nets import Net, Node
41NODE_ID = "node"
43FLUX_TYPE = "flux_type"
44FLUX_TYPE_A = "flux_type_a"
45FLUX_TYPE_B = "flux_type_b"
47DOF_TYPE = "dof_type"
48DOF_TYPE_A = "dof_type_a"
49DOF_TYPE_B = "dof_type_b"
51POTENTIAL_A = "potential_a"
52POTENTIAL_B = "potential_b"
55@pytest.fixture
56def expression():
57 return Expression(1, None, {})
60@pytest.fixture
61def flux_definition_type_a(expression):
62 return ExpressionDefinition(expression, [TermDefinition(POTENTIAL_A, 1)])
65@pytest.fixture
66def flux_definition_type_b(expression):
67 return ExpressionDefinition(expression, [TermDefinition(POTENTIAL_B, 1)])
70class BlockA(Block):
71 pass
74class BlockB(Block):
75 pass
78@patch.multiple(
79 "physioblocks.description.nets._flux_type_register",
80 create=True,
81 _fluxes_types={
82 FLUX_TYPE: DOF_TYPE,
83 FLUX_TYPE_A: DOF_TYPE_A,
84 FLUX_TYPE_B: DOF_TYPE_B,
85 },
86 _dof_types={DOF_TYPE: FLUX_TYPE, DOF_TYPE_A: FLUX_TYPE_A, DOF_TYPE_B: FLUX_TYPE_B},
87)
88class TestNode:
89 def test_constructor(self):
90 node = Node(NODE_ID)
91 assert node.name == NODE_ID
92 assert node.dofs == []
93 assert node.is_boundary is False
94 assert node.boundary_conditions == []
95 assert node.local_nodes == []
97 def test_set(self):
98 node = Node(NODE_ID)
100 with pytest.raises(AttributeError):
101 node.name = ""
103 with pytest.raises(AttributeError):
104 node.dofs = []
105 dof = Dof("id", DOF_TYPE)
106 node.dofs.append(dof)
107 assert node.dofs == []
109 with pytest.raises(AttributeError):
110 node.local_nodes = []
111 node.local_nodes.append((0, 0))
112 assert node.local_nodes == []
114 with pytest.raises(AttributeError):
115 node.is_boundary = True
117 with pytest.raises(AttributeError):
118 node.boundary_conditions = []
120 node.boundary_conditions.append(DOF_TYPE)
121 assert node.boundary_conditions == []
123 def test_add_remove_dof(self):
124 node = Node(NODE_ID)
125 node.add_dof("dof_type_id", DOF_TYPE)
126 assert node.dofs[0].dof_id == "dof_type_id"
127 assert node.dofs[0].dof_type == DOF_TYPE
129 node.remove_dof(DOF_TYPE)
130 assert node.dofs == []
132 def test_has_flux_type(self):
133 node = Node(NODE_ID)
134 node.add_dof("dof_type_a_id", DOF_TYPE_A)
135 assert node.has_flux_type(FLUX_TYPE_A) is True
136 assert node.has_flux_type(FLUX_TYPE_B) is False
138 def test_get_dof(self):
139 node = Node(NODE_ID)
140 node.add_dof("dof_id", DOF_TYPE)
142 assert node.dofs[0] == node.get_dof("dof_id")
143 with pytest.raises(KeyError):
144 node.get_dof("unregistered_dof_id")
146 assert node.dofs[0] == node.get_flux_dof(FLUX_TYPE)
147 with pytest.raises(KeyError):
148 node.get_flux_dof(FLUX_TYPE_A)
150 def test_add_remove_boundaries(self):
151 node = Node(NODE_ID)
152 condition_id = "condition_id"
153 with pytest.raises(ValueError):
154 node.add_boundary_condition(DOF_TYPE, condition_id)
156 with pytest.raises(ValueError):
157 node.add_boundary_condition(FLUX_TYPE, condition_id)
159 node.add_dof("dof_id", DOF_TYPE)
160 node.add_boundary_condition(FLUX_TYPE, condition_id)
161 assert node.boundary_conditions[0].condition_type == FLUX_TYPE
162 assert node.boundary_conditions[0].condition_id == condition_id
164 with pytest.raises(ValueError):
165 node.add_boundary_condition(DOF_TYPE, condition_id)
167 with pytest.raises(ValueError):
168 node.add_boundary_condition(FLUX_TYPE, condition_id)
170 node.remove_boundary_condition(FLUX_TYPE)
171 assert len(node.boundary_conditions) == 0
173 node.add_boundary_condition(DOF_TYPE, condition_id)
174 assert node.boundary_conditions[0].condition_type == DOF_TYPE
175 assert node.boundary_conditions[0].condition_id == condition_id
176 assert node.dofs[0].dof_id == condition_id
178 def test_is_boundary(self):
179 node = Node(NODE_ID)
180 assert node.is_boundary is False
182 condition_id = "cond_id"
183 node.add_dof("dof_id", DOF_TYPE)
184 node.add_boundary_condition(FLUX_TYPE, condition_id)
185 assert node.is_boundary is True
187 def test_node_local(self):
188 node = Node(NODE_ID)
189 assert node.has_node_local(0, 0) is False
190 assert node.local_nodes == []
192 node.add_node_local(0, 0)
193 assert node.has_node_local(0, 0) is True
194 assert node.local_nodes == [(0, 0)]
196 node.remove_node_local(0, 0)
197 assert node.has_node_local(0, 0) is False
198 assert node.local_nodes == []
201@patch.multiple(
202 "physioblocks.description.nets._flux_type_register",
203 create=True,
204 _fluxes_types={
205 FLUX_TYPE: DOF_TYPE,
206 FLUX_TYPE_A: DOF_TYPE_A,
207 FLUX_TYPE_B: DOF_TYPE_B,
208 },
209 _dof_types={DOF_TYPE: FLUX_TYPE, DOF_TYPE_A: FLUX_TYPE_A, DOF_TYPE_B: FLUX_TYPE_B},
210)
211class TestNet:
212 def test_constructor(self):
213 net = Net()
214 assert net.blocks == {}
215 assert net.nodes == {}
217 def test_set(self):
218 net = Net()
219 block_id = "b"
220 node_id = "n"
221 with pytest.raises(AttributeError):
222 net.blocks = {}
223 net.blocks[block_id] = BlockDescription(block_id, Block, {})
224 assert net.blocks == {}
226 with pytest.raises(AttributeError):
227 net.nodes = {}
228 net.nodes[node_id] = Node(node_id)
229 assert net.nodes == {}
231 with pytest.raises(ValueError):
232 net.local_to_global_node_id(block_id, 0)
234 def test_add_remove_node(self):
235 net = Net()
236 node_a_id = "node_a"
237 node_b_id = "node_b"
238 node_a = net.add_node(node_a_id)
239 node_b = net.add_node(node_b_id)
241 assert node_a == net.nodes[node_a_id]
242 assert node_b == net.nodes[node_b_id]
244 net.remove_node(node_b_id)
246 with pytest.raises(KeyError):
247 net.nodes[node_b_id]
249 with pytest.raises(ValueError):
250 net.add_node(node_a_id)
252 def test_add_remove_block(self, flux_definition_type_a, flux_definition_type_b):
253 with (
254 patch.object(
255 BlockA,
256 attribute="_fluxes",
257 new={
258 0: flux_definition_type_a,
259 1: flux_definition_type_a,
260 },
261 ),
262 patch.object(
263 BlockB,
264 attribute="_fluxes",
265 new={0: flux_definition_type_b},
266 ),
267 ):
268 net = Net()
269 node_a_id = "node_a"
270 node_b_id = "node_b"
271 net.add_node(node_a_id)
272 net.add_node(node_b_id)
274 block_a_id = "block_a"
275 block_b_id = "block_b"
276 block_c_id = "block_c"
278 block_a_desc = net.add_block(
279 block_a_id,
280 BlockDescription(block_a_id, BlockA, FLUX_TYPE_A),
281 {0: node_a_id, 1: node_b_id},
282 )
284 block_b_desc = net.add_block(
285 block_b_id,
286 BlockDescription(block_b_id, BlockB, FLUX_TYPE_B),
287 {0: node_b_id},
288 )
290 assert net.local_to_global_node_id(block_a_id, 0) == node_a_id
291 assert net.local_to_global_node_id(block_a_id, 1) == node_b_id
292 assert net.local_to_global_node_id(block_b_id, 0) == node_b_id
294 assert net.blocks[block_a_id] == block_a_desc
295 assert net.blocks[block_b_id] == block_b_desc
296 assert DOF_TYPE_B in [dof.dof_type for dof in net.nodes[node_b_id].dofs]
297 net.add_block(
298 block_c_id,
299 BlockDescription(block_c_id, BlockA, FLUX_TYPE_A),
300 {0: node_a_id, 1: node_b_id},
301 )
302 net.remove_block(block_c_id)
303 assert net.nodes[node_a_id].has_flux_type(FLUX_TYPE_A) is True
305 net.remove_block(block_b_id)
306 assert DOF_TYPE_B not in [dof.dof_type for dof in net.nodes[node_b_id].dofs]
308 with pytest.raises(KeyError):
309 net.blocks[block_b_id]
311 # id already exists
312 err_msg = str.format(
313 "Block with id {0} is already defined in the net.", block_a_id
314 )
315 with pytest.raises(ValueError, match=err_msg):
316 net.add_block(
317 block_a_id,
318 BlockDescription(block_a_id, BlockA, FLUX_TYPE_B),
319 {0: node_a_id, 1: node_b_id},
320 )
322 # too many nodes for block
323 err_msg = str.format(
324 "Linked node ids list and {0} local nodes list size mismatch.",
325 block_b_id,
326 )
327 with pytest.raises(ValueError, match=err_msg):
328 net.add_block(
329 block_b_id,
330 BlockDescription(block_b_id, BlockB, FLUX_TYPE_B),
331 {0: node_a_id, 1: node_b_id},
332 )
334 net.remove_node(node_a_id)
335 with pytest.raises(KeyError):
336 net.blocks[block_a_id]
338 def test_set_remove_boundary(self, flux_definition_type_a):
339 net = Net()
340 node_a_id = "node_a"
341 node_b_id = "node_b"
342 flux_a_id = "flux_a"
343 node_a = net.add_node(node_a_id)
344 net.add_node(node_b_id)
346 # No potential or flux matching the condition
347 with pytest.raises(ValueError):
348 net.set_boundary(node_a_id, FLUX_TYPE_A, flux_a_id)
350 with pytest.raises(ValueError):
351 net.set_boundary(node_a_id, DOF_TYPE_A, flux_a_id)
353 block_id = "block_a"
354 with patch.object(
355 BlockA,
356 attribute="_fluxes",
357 new={0: flux_definition_type_a, 1: flux_definition_type_a},
358 ):
359 net.add_block(
360 block_id,
361 BlockDescription(block_id, BlockA, FLUX_TYPE_A),
362 {0: node_a_id, 1: node_b_id},
363 )
365 net.set_boundary(node_a_id, FLUX_TYPE_A, flux_a_id)
366 assert node_a.boundary_conditions[0].condition_type == FLUX_TYPE_A
367 assert node_a.boundary_conditions[0].condition_id == flux_a_id
369 assert net.boundary_conditions[node_a_id] == node_a.boundary_conditions
371 # Boundary condition with a matching type already registered
372 with pytest.raises(ValueError):
373 net.set_boundary(node_a_id, FLUX_TYPE_A, flux_a_id)
375 with pytest.raises(ValueError):
376 net.set_boundary(node_a_id, DOF_TYPE_A, flux_a_id)
378 net.remove_boundary(node_a_id, FLUX_TYPE_A)
379 assert len(node_a.boundary_conditions) == 0