GCC Code Coverage Report


Directory: ./
File: mesh/mesh_manifold_harmonics.cpp
Date: 2026-09-27 03:24:14
Exec Total Coverage
Lines: 0 177 0.0%
Functions: 0 4 0.0%
Branches: 0 318 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2000-2022 Inria
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * * Neither the name of the ALICE Project-Team nor the names of its
14 * contributors may be used to endorse or promote products derived from this
15 * software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 *
29 * Contact: Bruno Levy
30 *
31 * https://www.inria.fr/fr/bruno-levy
32 *
33 * Inria,
34 * Domaine de Voluceau,
35 * 78150 Le Chesnay - Rocquencourt
36 * FRANCE
37 *
38 */
39
40 #include <geogram/mesh/mesh_manifold_harmonics.h>
41 #include <geogram/mesh/mesh.h>
42 #include <geogram/mesh/mesh_geometry.h>
43 #include <geogram/basic/file_system.h>
44 #include <geogram/basic/geometry_nd.h>
45 #include <geogram/bibliography/bibliography.h>
46 #include <geogram/NL/nl.h>
47
48 namespace {
49 using namespace GEO;
50
51 /**
52 * \brief Computes a coefficient of the P1 Laplacian.
53 * \param[in] M a reference to a surface mesh
54 * \param[in] f facet index
55 * \param[in] v1 , v2 global indices of two vertices of \p f
56 * \return the cotangent of the angle at the corner of \p f opposite to
57 * \p v1 and \p v2
58 */
59 ✗ inline double P1_FEM_coefficient(
60 const Mesh& M, index_t f, index_t v1, index_t v2
61 ) {
62 ✗ index_t v3 = NO_VERTEX;
63 ✗ for(index_t lv=0; lv<M.facets.nb_vertices(f); ++lv) {
64 ✗ index_t v = M.facets.vertex(f,lv);
65 ✗ if(v != v1 && v != v2) {
66 ✗ v3 = v;
67 ✗ break;
68 }
69 }
70 ✗ geo_assert(v3 != NO_VERTEX);
71
72 // cotan weights, in arbitrary dimension
73 ✗ const double* p1 = M.vertices.point_ptr(v1);
74 ✗ const double* p2 = M.vertices.point_ptr(v2);
75 ✗ const double* p3 = M.vertices.point_ptr(v3);
76
77 ✗ double Lu = 0.0;
78 ✗ double Lv = 0.0;
79 ✗ double cosangle = 0.0;
80 ✗ for(index_t d=0; d<M.vertices.dimension(); ++d) {
81 ✗ double u = p2[d] - p1[d];
82 ✗ double v = p3[d] - p1[d];
83 ✗ Lu += u*u;
84 ✗ Lv += v*v;
85 ✗ cosangle += u*v;
86 }
87 ✗ double Luv = ::sqrt(Lu*Lv);
88 ✗ if(Luv < 1e-50) {
89 ✗ cosangle = 1.0;
90 } else {
91 ✗ cosangle /= Luv;
92 }
93 ✗ geo_clamp(cosangle, -1.0, 1.0);
94 ✗ return 1.0 / ::tan(::acos(cosangle));
95 }
96
97
98 /**
99 * \brief Assemble the stiffness and mass matrices
100 * of the Laplacian in OpenNL.
101 * \details This function is supposed to be called
102 * between nlBegin(NL_SYSTEM) and nlEnd().
103 * \param[in] M a const reference to a surface mesh.
104 * \param[in] discretization the discretization of the Laplace-Beltrami
105 * operator, one of:
106 * - COMBINATORIAL: 1.0 everywhere
107 * - UNIFORM: combinatorial divided by node degree
108 * - FEM_P1: linear finite elements
109 * - FEM_P1_LUMPED: linear finite elements with lumped mass matrix
110 */
111 ✗ void assemble_Laplacian_matrices(
112 const Mesh& M,
113 LaplaceBeltramiDiscretization discretization
114 ) {
115
116 // Step 1: compute vertices degrees (used by
117 // uniform weights).
118 // **************************************************
119
120 ✗ vector<index_t> v_degree;
121 ✗ if(discretization == UNIFORM) {
122 ✗ v_degree.assign(M.vertices.nb(), 0);
123 ✗ for(index_t c: M.facet_corners) {
124 ✗ index_t v = M.facet_corners.vertex(c);
125 ✗ ++v_degree[v];
126 }
127 }
128
129 // Sum of row coefficient associated with each vertex
130 ✗ vector<double> v_row_sum(M.vertices.nb(), 0.0);
131
132 // Step 2: compute stiffness matrix
133 // **************************************************
134
135 ✗ nlMatrixMode(NL_STIFFNESS_MATRIX);
136 ✗ nlBegin(NL_MATRIX);
137
138 ✗ for(index_t f: M.facets) {
139 ✗ index_t fnv = M.facets.nb_vertices(f);
140 ✗ for(index_t lv=0; lv<fnv; ++lv) {
141 ✗ index_t v1 = M.facets.vertex(f,lv);
142 ✗ index_t v2 = M.facets.vertex(f,(lv+1)%fnv);
143 ✗ switch(discretization) {
144 ✗ case COMBINATORIAL: {
145 ✗ double w = 1.0;
146 ✗ nlAddIJCoefficient(v1,v2,w);
147 ✗ v_row_sum[v1] += w;
148 ✗ } break;
149 ✗ case UNIFORM: {
150 ✗ double w = 1.0 / double(v_degree[v1]);
151 ✗ nlAddIJCoefficient(v1,v2,w);
152 ✗ v_row_sum[v1] += w;
153 ✗ } break;
154 ✗ case FEM_P1:
155 case FEM_P1_LUMPED: {
156 ✗ double w = 0.5 * P1_FEM_coefficient(M,f,v1,v2);
157 ✗ nlAddIJCoefficient(v1,v2,w);
158 ✗ nlAddIJCoefficient(v2,v1,w);
159 ✗ v_row_sum[v1] += w;
160 ✗ v_row_sum[v2] += w;
161 ✗ } break;
162 }
163 }
164 }
165 ✗ for(index_t v: M.vertices) {
166 // Diagonal term is minus row sum
167 // plus small number to make M non-singular
168 ✗ nlAddIJCoefficient(v,v,-v_row_sum[v] + 1e-6);
169 }
170 ✗ nlEnd(NL_MATRIX);
171
172 // Step 3: compute mass matrix
173 // **************************************************
174
175 ✗ if(discretization == FEM_P1 || discretization == FEM_P1_LUMPED) {
176 ✗ nlMatrixMode(NL_MASS_MATRIX);
177 ✗ nlBegin(NL_MATRIX);
178 ✗ for(index_t f: M.facets) {
179 ✗ index_t v1 = M.facets.vertex(f,0);
180 ✗ index_t v2 = M.facets.vertex(f,1);
181 ✗ index_t v3 = M.facets.vertex(f,2);
182 ✗ const double* p1 = M.vertices.point_ptr(v1);
183 ✗ const double* p2 = M.vertices.point_ptr(v2);
184 ✗ const double* p3 = M.vertices.point_ptr(v3);
185 ✗ double A = Geom::triangle_area(
186 ✗ p1,p2,p3, coord_index_t(M.vertices.dimension())
187 );
188
189 ✗ if(discretization == FEM_P1_LUMPED) {
190
191 ✗ nlAddIJCoefficient(v1,v1,A/3.0);
192 ✗ nlAddIJCoefficient(v2,v2,A/3.0);
193 ✗ nlAddIJCoefficient(v3,v3,A/3.0);
194
195 ✗ } else if(discretization == FEM_P1) {
196
197 ✗ nlAddIJCoefficient(v1,v2,A/12.0);
198 ✗ nlAddIJCoefficient(v1,v3,A/12.0);
199 ✗ nlAddIJCoefficient(v2,v3,A/12.0);
200 ✗ nlAddIJCoefficient(v2,v1,A/12.0);
201 ✗ nlAddIJCoefficient(v3,v1,A/12.0);
202 ✗ nlAddIJCoefficient(v3,v2,A/12.0);
203
204 ✗ nlAddIJCoefficient(v1,v1,A/6.0);
205 ✗ nlAddIJCoefficient(v2,v2,A/6.0);
206 ✗ nlAddIJCoefficient(v3,v3,A/6.0);
207 }
208 }
209 ✗ nlEnd(NL_MATRIX);
210 }
211 ✗ }
212 }
213
214
215 namespace GEO {
216
217
218
219 ✗ void mesh_compute_manifold_harmonics(
220 Mesh& M, index_t nb_eigens,
221 LaplaceBeltramiDiscretization discretization,
222 const std::string& attribute_name,
223 double shift,
224 bool print_spectrum
225 ) {
226
227 ✗ geo_cite("DBLP:conf/smi/Levy06");
228 ✗ geo_cite("DBLP:journals/cgf/ValletL08");
229
230 ✗ if(M.vertices.attributes().is_defined(attribute_name)) {
231 ✗ M.vertices.attributes().delete_attribute_store(attribute_name);
232 }
233
234 // Step 1: configure eigen solver
235 // **************************************************
236
237
238 ✗ if(!nlInitExtension("ARPACK")) {
239 ✗ Logger::err("MH")
240 ✗ << "Could not initialize OpenNL ARPACK extension"
241 ✗ << std::endl;
242 ✗ return;
243 }
244
245 ✗ nlNewContext();
246
247 ✗ nlEigenSolverParameteri(NL_EIGEN_SOLVER, NL_ARPACK_EXT);
248 ✗ nlEigenSolverParameteri(NL_NB_VARIABLES, NLint(M.vertices.nb()));
249 ✗ nlEigenSolverParameteri(NL_NB_EIGENS, (NLint)nb_eigens);
250 ✗ nlEigenSolverParameterd(NL_EIGEN_SHIFT, shift);
251
252 ✗ if(discretization == COMBINATORIAL) {
253 ✗ nlEigenSolverParameteri(NL_SYMMETRIC, NL_TRUE);
254 }
255
256 ✗ nlEnable(NL_VARIABLES_BUFFER);
257
258 ✗ nlBegin(NL_SYSTEM);
259
260 ✗ Attribute<double> eigen_vector;
261 ✗ eigen_vector.create_vector_attribute(
262 M.vertices.attributes(), attribute_name, nb_eigens
263 );
264
265 ✗ for(index_t eigen=0; eigen<nb_eigens; ++eigen) {
266 // Bind directly the variables buffer to the attribute in
267 // the mesh, to avoid copying data.
268 ✗ nlBindBuffer(
269 NL_VARIABLES_BUFFER,
270 NLuint(eigen),
271 ✗ &eigen_vector[0] + eigen, // base address for eigenvector
272 NLuint(sizeof(double)*nb_eigens) // number of bytes between two
273 // consecutive components in current eigenvector
274 );
275 }
276
277 // Step 2: assemble matrices
278 // *************************
279
280 ✗ assemble_Laplacian_matrices(M, discretization);
281
282 ✗ nlEnd(NL_SYSTEM);
283
284 // Step 3: solve and cleanup
285 // *************************
286
287 ✗ nlEigenSolve();
288
289 ✗ if(print_spectrum) {
290 ✗ for(index_t i=0; i<nb_eigens; ++i) {
291 ✗ Logger::out("MH") << i << ":" << nlGetEigenValue(i)
292 ✗ << std::endl;
293 }
294 }
295
296 ✗ nlDeleteContext(nlGetCurrent());
297 ✗ }
298
299
300 ✗ void mesh_compute_manifold_harmonics_by_bands(
301 Mesh& M, index_t nb_eigens,
302 LaplaceBeltramiDiscretization discretization,
303 ManifoldHarmonicsCallback callback,
304 index_t nb_eigens_per_band,
305 double initial_shift,
306 void* client_data
307 ) {
308
309 // Step 1: configure eigen solver and assemble matrices
310 // ****************************************************
311
312 ✗ if(!nlInitExtension("ARPACK")) {
313 ✗ Logger::err("MH")
314 ✗ << "Could not initialize OpenNL ARPACK extension"
315 ✗ << std::endl;
316 ✗ return;
317 }
318
319 ✗ nlNewContext();
320
321 ✗ nlEigenSolverParameteri(NL_EIGEN_SOLVER, NL_ARPACK_EXT);
322 ✗ nlEigenSolverParameteri(NL_NB_VARIABLES, NLint(M.vertices.nb()));
323 ✗ nlEigenSolverParameteri(NL_NB_EIGENS, (NLint)nb_eigens_per_band);
324
325 ✗ if(discretization == COMBINATORIAL) {
326 ✗ nlEigenSolverParameteri(NL_SYMMETRIC, NL_TRUE);
327 }
328
329 ✗ nlBegin(NL_SYSTEM);
330 ✗ assemble_Laplacian_matrices(M, discretization);
331 ✗ nlEnd(NL_SYSTEM);
332
333
334 // Step 2: main loop
335 // *****************
336
337 ✗ double shift = initial_shift;
338 ✗ index_t current_eigen = 0;
339 ✗ index_t current_band = 0;
340 ✗ double latest_eigen = 0.0;
341
342 ✗ vector<double> eigen_vector(M.vertices.nb());
343
344 for(;;) {
345
346 ✗ bool compute_band = true;
347 ✗ while(compute_band) {
348 ✗ Logger::out("MH")
349 ✗ << "Compute band, shift=" << shift << std::endl;
350 ✗ nlEigenSolverParameterd(NL_EIGEN_SHIFT, shift);
351 ✗ nlEigenSolve();
352 ✗ compute_band = false;
353
354 // Test whether the current band overlaps the previous one.
355 // If this is not the case, go back (move shift towards zero)
356 // a little bit.
357
358 ✗ if(current_band != 0) {
359 ✗ if(::fabs(nlGetEigenValue(0)) > ::fabs(latest_eigen)) {
360 ✗ Logger::out("MH")
361 ✗ << "Bands do no overlap (going back a little bit)"
362 ✗ << std::endl;
363 ✗ shift -= 0.2*(
364 ✗ nlGetEigenValue(nb_eigens_per_band - 1) -
365 ✗ nlGetEigenValue(0)
366 );
367 ✗ compute_band = true;
368 }
369 }
370
371 }
372
373 ✗ for(index_t i=0; i<nb_eigens_per_band; ++i) {
374 // Output all the eigenpairs with an eigenvalue that was
375 // not previously seen (ignore the part of the current band
376 // that overlaps the previous band).
377 ✗ if(
378 ✗ current_eigen == 0 ||
379 ✗ ::fabs(nlGetEigenValue(i)) > ::fabs(latest_eigen)
380 ) {
381 ✗ latest_eigen = nlGetEigenValue(i);
382 ✗ for(index_t j: M.vertices) {
383 ✗ eigen_vector[j] = nlMultiGetVariable(j,i);
384 }
385 ✗ callback(
386 current_eigen,
387 ✗ nlGetEigenValue(i), eigen_vector.data(), client_data
388 );
389 ✗ ++current_eigen;
390 ✗ if(current_eigen >= nb_eigens) {
391 ✗ nlDeleteContext(nlGetCurrent());
392 ✗ return;
393 }
394 }
395 }
396
397 // Move to next band / next eigen shift.
398 ✗ ++current_band;
399 ✗ shift += 0.8 * (
400 ✗ nlGetEigenValue(nb_eigens_per_band - 1) - nlGetEigenValue(0)
401 );
402 ✗ }
403 ✗ }
404
405
406 }
407