Skip to content

Commit 82f4b6e

Browse files
LaPy test cases (Remaining) (#44)
* test_visualization_meshes * test_TriaMesh_Geodesics
1 parent 1d91511 commit 82f4b6e

File tree

8 files changed

+347
-26
lines changed

8 files changed

+347
-26
lines changed

data/cubeTetra.ev

Lines changed: 4 additions & 4 deletions
Large diffs are not rendered by default.

data/cubeTria.ev

Lines changed: 4 additions & 4 deletions
Large diffs are not rendered by default.

lapy/utils/tests/expected_outcomes.json

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,8 @@
115115
],
116116
"tolerance": 1e-4
117117
},
118-
"test_compute_distance_tet": {"exp_compute_distance": 0.0},
119-
"test_triangle_mesh_visualization_using_checksum": {
120-
"real_img_checksum": "abaaa757a1e5c1f42a8302fbfe068f49"
121-
},
122-
"test_TriaMesh_Geodesics": {
123-
"real_img_checksum": "8166f546e6630f017d3ec1f655c0ac85"
124-
},
125-
"test_Laplace_Geodesics": {
126-
"real_img_checksum": "25f9b10a5d417de3854cf8b8faf77001"
118+
"test_compute_distance_tet": {
119+
"exp_compute_distance": 0.0
127120
},
128121
"test_Geodesics_format": {
129122
"expected_matrix_format": "csc",
@@ -150,6 +143,16 @@
150143
6.000013,
151144
6.000008
152145
]
146+
},
147+
"test_visualization_triangle_mesh": {
148+
"expected_elements": 4800,
149+
"expected_dof": 2402,
150+
"expected_ev": [-4.1549094e-05, 4.169634, 4.170457]
151+
},
152+
"test_visualization_tetrahedral_mesh": {
153+
"expected_elements": 48000,
154+
"expected_dof": 9261,
155+
"expected_ev": [8.4652565e-05, 9.889787, 9.889887]
153156
}
154157
}
155158
}

lapy/utils/tests/test_TetMesh_Geodesics.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ def test_evals_evec_dimension(load_tet_mesh, loaded_data):
7272

7373

7474
def test_gradients_normalization_and_divergence(load_tet_mesh, loaded_data):
75+
"""
76+
Test the computation of gradients, normalization, and divergence for a TetMesh.
77+
78+
Parameters:
79+
load_tet_mesh (fixture): Fixture to load a TetMesh for testing.
80+
loaded_data (dict): Dictionary containing loaded test data.
81+
82+
Raises:
83+
AssertionError: If any test condition is not met.
84+
"""
7585
T = load_tet_mesh
7686
tria = T.boundary_tria()
7787
bvert = np.unique(tria.t)
@@ -157,7 +167,6 @@ def test_tetMesh_Geodesics_format(load_tet_mesh, loaded_data):
157167
b0 = -divx
158168

159169
# solve H x = b0
160-
# print("Matrix Format now: "+H.getformat())
161170
if useCholmod:
162171
print("Solver: cholesky decomp - performance optimal ...")
163172
chol = cholesky(H)
@@ -174,7 +183,6 @@ def test_tetMesh_Geodesics_format(load_tet_mesh, loaded_data):
174183
v1func = T.v[:, 0] * T.v[:, 0] + T.v[:, 1] * T.v[:, 1] + T.v[:, 2] * T.v[:, 2]
175184
grad = compute_gradient(T, v1func)
176185
glength = np.sqrt(np.sum(grad * grad, axis=1))
177-
# fcols=glength
178186
A, B = fem.stiffness, fem.mass
179187
Bi = B.copy()
180188
Bi.data **= -1
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import json
2+
3+
import numpy as np
4+
import pytest
5+
from scipy.sparse.linalg import splu
6+
7+
from ...diffgeo import compute_divergence, compute_geodesic_f, compute_gradient
8+
from ...heat import diffusion
9+
from ...solver import Solver
10+
from ...tria_mesh import TriaMesh
11+
12+
13+
@pytest.fixture
14+
def loaded_data():
15+
"""
16+
Load and provide the expected outcomes data from a JSON file.
17+
18+
Returns:
19+
dict: Dictionary containing the expected outcomes data.
20+
"""
21+
with open("lapy/utils/tests/expected_outcomes.json", "r") as f:
22+
expected_outcomes = json.load(f)
23+
return expected_outcomes
24+
25+
26+
# Fixture to load the TetMesh
27+
@pytest.fixture
28+
def load_square_mesh():
29+
T = TriaMesh.read_off("data/square-mesh.off")
30+
return T
31+
32+
33+
def test_tria_qualities(load_square_mesh):
34+
"""
35+
Test triangle mesh quality computation.
36+
"""
37+
T = load_square_mesh
38+
computed_q = T.tria_qualities()
39+
expected_q_length = 768
40+
assert len(computed_q) == expected_q_length
41+
42+
43+
# Laplace
44+
def test_Laplace_Geodesics(load_square_mesh):
45+
"""
46+
Test Laplace solver for geodesics on a mesh.
47+
"""
48+
49+
T = load_square_mesh
50+
51+
# compute first eigenfunction
52+
fem = Solver(T, lump=True)
53+
eval, evec = fem.eigs()
54+
# vfunc = evec[:, 1]
55+
56+
# Get A,B (lumped), and inverse of B (as it is diagonal due to lumping)
57+
A, B = fem.stiffness, fem.mass
58+
Bi = B.copy()
59+
Bi.data **= -1
60+
61+
assert B.sum() == 1.0
62+
assert Bi is not B
63+
# Convert A to a dense NumPy array
64+
A_dense = A.toarray()
65+
66+
# Assert that A is symmetric
67+
assert (A_dense == A_dense.T).all()
68+
69+
expected_eval_length = 10
70+
assert len(eval) == expected_eval_length
71+
72+
73+
# Geodesics
74+
def test_Laplace_Geodesics_with_Gradient_Divergence(load_square_mesh):
75+
"""
76+
Test Laplace geodesics using gradient and divergence.
77+
"""
78+
T = load_square_mesh
79+
80+
# Load eigenfunction
81+
fem = Solver(T, lump=True)
82+
eval, evec = fem.eigs()
83+
vfunc = evec[:, 1]
84+
85+
# Compute Laplacian using -div(grad(f))
86+
grad = compute_gradient(T, vfunc)
87+
divx = -compute_divergence(T, grad)
88+
89+
# Get the lumped mass matrix B
90+
fem = Solver(T, lump=True)
91+
B = fem.mass
92+
Bi = B.copy()
93+
Bi.data **= -1
94+
95+
# Apply Laplacian operator and then the inverse of B
96+
Laplacian_result = -divx # The Laplacian result
97+
98+
# Apply the inverse of B to recover vfunc
99+
recovered_vfunc = Bi.dot(Laplacian_result)
100+
101+
# Check if the original vfunc and the recovered vfunc length are equal
102+
assert len(recovered_vfunc) == len(vfunc)
103+
104+
expected_eval_length = 10
105+
assert len(eval) == expected_eval_length
106+
107+
108+
def test_heat_diffusion_shape(load_square_mesh):
109+
"""
110+
Test the shape of the heat diffusion result on a square mesh.
111+
112+
Parameters:
113+
load_square_mesh: Fixture providing a loaded square mesh.
114+
115+
This test function computes the heat diffusion and verifies that the shape
116+
of the result matches the expected shape.
117+
118+
Returns:
119+
None
120+
"""
121+
T = load_square_mesh
122+
bvert = T.boundary_loops()
123+
u = diffusion(T, bvert, m=1)
124+
expected_shape = (len(T.v),)
125+
assert u.shape == expected_shape
126+
127+
128+
def test_Geodesics_format(loaded_data, load_square_mesh):
129+
"""
130+
Test geodesics format and accuracy.
131+
"""
132+
T = load_square_mesh
133+
bvert = T.boundary_loops()
134+
u = diffusion(T, bvert, m=1)
135+
# compute gradient of heat diffusion
136+
tfunc = compute_gradient(T, u)
137+
138+
# normalize gradient
139+
X = -tfunc / np.sqrt((tfunc**2).sum(1))[:, np.newaxis]
140+
X = np.nan_to_num(X)
141+
divx = compute_divergence(T, X)
142+
# compute distance
143+
144+
useCholmod = True
145+
try:
146+
from sksparse.cholmod import cholesky
147+
except ImportError:
148+
useCholmod = False
149+
150+
fem = Solver(T, lump=True)
151+
A, B = fem.stiffness, fem.mass
152+
H = -A
153+
b0 = divx
154+
155+
# solve H x = b0
156+
# we don't need the B matrix here, as divx is the integrated divergence
157+
print("Matrix Format now: " + H.getformat())
158+
if useCholmod:
159+
print("Solver: cholesky decomp - performance optimal ...")
160+
chol = cholesky(H)
161+
x = chol(b0)
162+
else:
163+
print("Solver: spsolve (LU decomp) - performance not optimal ...")
164+
lu = splu(H)
165+
x = lu.solve(b0)
166+
167+
# remove shift
168+
x = x - min(x)
169+
170+
Bi = B.copy()
171+
vf = fem.poisson(-Bi * divx)
172+
vf = vf - min(vf)
173+
gf = compute_geodesic_f(T, u)
174+
expected_matrix_format = loaded_data["expected_outcomes"]["test_Geodesics_format"][
175+
"expected_matrix_format"
176+
]
177+
assert H.getformat() == expected_matrix_format
178+
assert not useCholmod, "Solver: cholesky decomp - performance optimal ..."
179+
expected_max_x = loaded_data["expected_outcomes"]["test_Geodesics_format"][
180+
"max_distance"
181+
]
182+
expected_sqrt_2_div_2 = loaded_data["expected_outcomes"]["test_Geodesics_format"][
183+
"expected_sqrt_2_div_2"
184+
]
185+
assert np.isclose(max(x), expected_max_x)
186+
computed_sqrt_2_div_2 = np.sqrt(2) / 2
187+
assert np.isclose(computed_sqrt_2_div_2, expected_sqrt_2_div_2)
188+
expected_max_abs_diff = loaded_data["expected_outcomes"]["test_Geodesics_format"][
189+
"expected_max_abs_diff"
190+
]
191+
computed_max_abs_diff = max(abs(gf - x))
192+
assert np.allclose(computed_max_abs_diff, expected_max_abs_diff)

lapy/utils/tests/test_tet_mesh.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,21 +89,19 @@ def test_is_oriented(tet_mesh_fixture):
8989
), f"Expected is_oriented result {expected_result}, but got {result}"
9090

9191

92-
def test_boundary_trai(tet_mesh_fixture):
92+
def test_boundary_tria(tet_mesh_fixture):
9393
"""
9494
Test computation of boundary triangles from tet mesh.
9595
96-
- `BT.t` represents the array of boundary triangles.
97-
- `.shape[0]` counts the number of boundary triangles.
96+
`BT.t` represents the array of boundary triangles.
97+
`.shape[0]` counts the number of boundary triangles.
9898
"""
9999
mesh = tet_mesh_fixture
100100
boundary_tria_mesh = mesh.boundary_tria()
101101

102102
expected_num_traingles = 12
103103
assert boundary_tria_mesh.t.shape[0] == expected_num_traingles
104104

105-
# print(f"Found {boundary_tria_mesh.t.shape[0]} triangles on boundary.")
106-
107105
# Check if the boundary triangle mesh is not oriented (this should fail)
108106
result = boundary_tria_mesh.is_oriented()
109107
expected_result = False

lapy/utils/tests/test_tria_mesh.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ def test_volume_(tria_mesh_fixture):
297297

298298
def test_vertex_normals(tria_mesh_fixture, loaded_data):
299299
"""
300-
Testing the computation of vertex normals for oriented mesh
300+
Testing the computation of vertex normals for oriented mesh.
301301
"""
302302

303303
# Calling tria_mesh_fixture.orient_() will modify the tria_mesh_fixture in-place and
@@ -348,7 +348,7 @@ def test_boundary_mesh(tria_mesh_fixture):
348348

349349
def test_refine_and_boundary_loops(tria_mesh_fixture, loaded_data):
350350
"""
351-
Testing boundary loops after refining the mesh
351+
Testing boundary loops after refining the mesh.
352352
"""
353353
# Create a boundary mesh by dropping triangles
354354
tria_mesh_fixture.orient_()

0 commit comments

Comments
 (0)