Coverage for tests / tests_computing / test_assembling.py: 100%
92 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
29import numpy as np
30import pytest
31from numpy.typing import NDArray
33from physioblocks.computing.assembling import EqSystem
36@dataclass
37class ParamsF1:
38 a: float
39 x0: float
42def f1(params: ParamsF1) -> float:
43 return params.a * params.x0
46def df1_dx0(params: ParamsF1) -> float:
47 return params.a
50@dataclass
51class ParamsF2:
52 a: float
53 b: float
54 x0: float
55 x1: float
58def f2(params: ParamsF2) -> NDArray[np.float64]:
59 return np.array(
60 [params.a * params.x0 + params.x1, params.b * params.x1 + params.x0],
61 )
64def df2_dx0(params: ParamsF2) -> NDArray[np.float64]:
65 return np.array(
66 [params.a, 1.0],
67 )
70def df2_dx1(params: ParamsF2) -> NDArray[np.float64]:
71 return np.array(
72 [1.0, params.b],
73 )
76@dataclass
77class ParamsF3:
78 a: float
79 b: float
80 c: float
81 x1: float
82 x2: float
83 x3: float
86def f3(params: ParamsF3):
87 return np.array(
88 [
89 params.a * params.x1,
90 params.b * params.x2 + params.x3,
91 params.c * params.x3 + params.x2,
92 ]
93 )
96def df3_dx1(params: ParamsF3):
97 return np.array(
98 [
99 params.a,
100 0.0,
101 0.0,
102 ]
103 )
106def df3_dx2(params: ParamsF3):
107 return np.array([0.0, params.b, 1.0])
110def df3_dx3(params: ParamsF3):
111 return np.array([0.0, 1.0, params.c])
114@pytest.fixture
115def params_f1():
116 return ParamsF1(1.0, 0.1)
119@pytest.fixture
120def params_f2():
121 return ParamsF2(a=1.0, b=2.0, x0=0.1, x1=0.2)
124@pytest.fixture
125def res_ref():
126 return np.array([0.4, 0.5, 0.7, 0.7])
129@pytest.fixture
130def grad_ref():
131 return np.array(
132 [
133 [2.0, 1.0, 0.0, 0.0],
134 [1.0, 2.0, 0.0, 0.0],
135 [0.0, 0.0, 1.0, 1.0],
136 [0.0, 0.0, 1.0, 1.0],
137 ]
138 )
141@pytest.fixture
142def eq_system_ref():
143 eq_system = EqSystem(4)
144 x0 = 0.1
145 x1 = 0.2
146 x2 = 0.3
147 x3 = 0.4
148 a = 1.0
150 eq_system.add_system_part(0, 1, f1, {0: df1_dx0}, ParamsF1(a, x0))
152 eq_system.add_system_part(
153 0, 2, f2, {0: df2_dx0, 1: df2_dx1}, ParamsF2(a, a, x0, x1)
154 )
156 eq_system.add_system_part(
157 1,
158 3,
159 f3,
160 {1: df3_dx1, 2: df3_dx2, 3: df3_dx3},
161 ParamsF3(a, a, a, x1, x2, x3),
162 )
163 return eq_system
166class TestEqSystem:
167 def test_constructor(self):
168 eq_system = EqSystem(0)
169 assert eq_system.system_size == 0
171 def test_set(self):
172 eq_system = EqSystem(0)
173 with pytest.raises(AttributeError):
174 eq_system.system_size = 1
176 def test_add_part(self, params_f1):
177 eq_system = EqSystem(1)
179 grads = {0: df1_dx0}
180 eq_system.add_system_part(0, 1, f1, grads, params_f1)
182 def test_add_part_exceed_residual_size(self, params_f2):
183 eq_system = EqSystem(1)
185 grads = {0: df2_dx0, 1: df2_dx1}
186 with pytest.raises(ValueError):
187 eq_system.add_system_part(0, 2, f2, grads, params_f2)
189 def test_add_part_exceed_gradient_size(self, params_f2):
190 eq_system = EqSystem(2)
191 grads = {0: df2_dx0, 2: df2_dx1}
192 with pytest.raises(ValueError):
193 eq_system.add_system_part(0, 2, f2, grads, params_f2)
195 def test_residual_gradient(self, eq_system_ref: EqSystem, res_ref, grad_ref):
196 res = eq_system_ref.compute_residual()
197 grad = eq_system_ref.compute_gradient()
199 assert res == pytest.approx(res_ref)
200 assert grad == pytest.approx(grad_ref)