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

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 unittest.mock import patch 

28 

29import pytest 

30 

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 

40 

41NODE_ID = "node" 

42 

43FLUX_TYPE = "flux_type" 

44FLUX_TYPE_A = "flux_type_a" 

45FLUX_TYPE_B = "flux_type_b" 

46 

47DOF_TYPE = "dof_type" 

48DOF_TYPE_A = "dof_type_a" 

49DOF_TYPE_B = "dof_type_b" 

50 

51POTENTIAL_A = "potential_a" 

52POTENTIAL_B = "potential_b" 

53 

54 

55@pytest.fixture 

56def expression(): 

57 return Expression(1, None, {}) 

58 

59 

60@pytest.fixture 

61def flux_definition_type_a(expression): 

62 return ExpressionDefinition(expression, [TermDefinition(POTENTIAL_A, 1)]) 

63 

64 

65@pytest.fixture 

66def flux_definition_type_b(expression): 

67 return ExpressionDefinition(expression, [TermDefinition(POTENTIAL_B, 1)]) 

68 

69 

70class BlockA(Block): 

71 pass 

72 

73 

74class BlockB(Block): 

75 pass 

76 

77 

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 == [] 

96 

97 def test_set(self): 

98 node = Node(NODE_ID) 

99 

100 with pytest.raises(AttributeError): 

101 node.name = "" 

102 

103 with pytest.raises(AttributeError): 

104 node.dofs = [] 

105 dof = Dof("id", DOF_TYPE) 

106 node.dofs.append(dof) 

107 assert node.dofs == [] 

108 

109 with pytest.raises(AttributeError): 

110 node.local_nodes = [] 

111 node.local_nodes.append((0, 0)) 

112 assert node.local_nodes == [] 

113 

114 with pytest.raises(AttributeError): 

115 node.is_boundary = True 

116 

117 with pytest.raises(AttributeError): 

118 node.boundary_conditions = [] 

119 

120 node.boundary_conditions.append(DOF_TYPE) 

121 assert node.boundary_conditions == [] 

122 

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 

128 

129 node.remove_dof(DOF_TYPE) 

130 assert node.dofs == [] 

131 

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 

137 

138 def test_get_dof(self): 

139 node = Node(NODE_ID) 

140 node.add_dof("dof_id", DOF_TYPE) 

141 

142 assert node.dofs[0] == node.get_dof("dof_id") 

143 with pytest.raises(KeyError): 

144 node.get_dof("unregistered_dof_id") 

145 

146 assert node.dofs[0] == node.get_flux_dof(FLUX_TYPE) 

147 with pytest.raises(KeyError): 

148 node.get_flux_dof(FLUX_TYPE_A) 

149 

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) 

155 

156 with pytest.raises(ValueError): 

157 node.add_boundary_condition(FLUX_TYPE, condition_id) 

158 

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 

163 

164 with pytest.raises(ValueError): 

165 node.add_boundary_condition(DOF_TYPE, condition_id) 

166 

167 with pytest.raises(ValueError): 

168 node.add_boundary_condition(FLUX_TYPE, condition_id) 

169 

170 node.remove_boundary_condition(FLUX_TYPE) 

171 assert len(node.boundary_conditions) == 0 

172 

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 

177 

178 def test_is_boundary(self): 

179 node = Node(NODE_ID) 

180 assert node.is_boundary is False 

181 

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 

186 

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 == [] 

191 

192 node.add_node_local(0, 0) 

193 assert node.has_node_local(0, 0) is True 

194 assert node.local_nodes == [(0, 0)] 

195 

196 node.remove_node_local(0, 0) 

197 assert node.has_node_local(0, 0) is False 

198 assert node.local_nodes == [] 

199 

200 

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 == {} 

216 

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 == {} 

225 

226 with pytest.raises(AttributeError): 

227 net.nodes = {} 

228 net.nodes[node_id] = Node(node_id) 

229 assert net.nodes == {} 

230 

231 with pytest.raises(ValueError): 

232 net.local_to_global_node_id(block_id, 0) 

233 

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) 

240 

241 assert node_a == net.nodes[node_a_id] 

242 assert node_b == net.nodes[node_b_id] 

243 

244 net.remove_node(node_b_id) 

245 

246 with pytest.raises(KeyError): 

247 net.nodes[node_b_id] 

248 

249 with pytest.raises(ValueError): 

250 net.add_node(node_a_id) 

251 

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) 

273 

274 block_a_id = "block_a" 

275 block_b_id = "block_b" 

276 block_c_id = "block_c" 

277 

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 ) 

283 

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 ) 

289 

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 

293 

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 

304 

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] 

307 

308 with pytest.raises(KeyError): 

309 net.blocks[block_b_id] 

310 

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 ) 

321 

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 ) 

333 

334 net.remove_node(node_a_id) 

335 with pytest.raises(KeyError): 

336 net.blocks[block_a_id] 

337 

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) 

345 

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) 

349 

350 with pytest.raises(ValueError): 

351 net.set_boundary(node_a_id, DOF_TYPE_A, flux_a_id) 

352 

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 ) 

364 

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 

368 

369 assert net.boundary_conditions[node_a_id] == node_a.boundary_conditions 

370 

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) 

374 

375 with pytest.raises(ValueError): 

376 net.set_boundary(node_a_id, DOF_TYPE_A, flux_a_id) 

377 

378 net.remove_boundary(node_a_id, FLUX_TYPE_A) 

379 assert len(node_a.boundary_conditions) == 0