BELFEM 0.9.0
Berkeley Lab Finite Element Framework
Loading...
Searching...
No Matches
cl_FEM_DofMgr_EigenValues.hpp
Go to the documentation of this file.
1/*
2 * BELFEM -- The Berkeley Lab Finite Element Framework
3 * Copyright (c) 2026, The Regents of the University of California,
4 * through Lawrence Berkeley National Laboratory (subject to receipt of any required
5 * approvals from the U.S. Dept. of Energy). All rights reserved.
6 *
7 * Developers: Christian Messe, Gregory Giard
8 *
9 * See the top-level LICENSE file for the complete license and disclaimer.
10 */
11#ifndef CL_FEM_DOFMGR_EIGENVALUES_HPP
12#define CL_FEM_DOFMGR_EIGENVALUES_HPP
13
14#include "typedefs.hpp"
15#include "cl_Vector.hpp"
16#include "cl_Bitset.hpp"
17#include "cl_Solver.hpp"
18#include "arpacktools.hpp"
19
20namespace belfem
21{
22 class SpMatrix ;
23
24#ifdef BELFEM_PARPACK
25 namespace sparse
26 {
27 template< typename T > class DistMatrixCSR ;
28 }
29#endif
30
50
60
61 namespace fem
62 {
63 class DofManager ;
64
65 namespace dofmgr
66 {
68 {
69 const proc_t mCommRank ;
70 const proc_t mCommSize ;
71 DofManager * mParent ;
72
73 // matrix data
74 SpMatrix * mK = nullptr ;
75 SpMatrix * mM = nullptr ;
76 int_t mN = 0 ;
77 int_t mNNZ = 0 ;
78
79
80 // options
81 int_t mNumMinVals = 1 ;
82 int_t mNumMaxVals = 1 ;
83 int_t mNumMaxIter = 300 ;
84
85 // the three tuning knobs used to be hardwired here and were
86 // reachable from nowhere -- no caller in the tree ever invoked
87 // the setters. They are sized automatically now ( configure() ),
88 // and these flags are what keeps an explicit caller in charge:
89 // once a setter has been used, the heuristic leaves that knob
90 // alone for the rest of the run
91 bool mSubspaceSizeExplicit = false ;
92 bool mToleranceExplicit = false ;
93 bool mNumMaxIterExplicit = false ;
94
95 // ARPACK accepts a Ritz value when its error bound falls below
96 // mEpsilon * |lambda|, and that bound cannot go below the
97 // backward error of the matvec, roughly eps_mach * |lambda_max|.
98 // The small end is therefore only reachable while
99 //
100 // mEpsilon > eps_mach * kappa ( ~ 1e-16 * kappa )
101 //
102 // so 1e-7 caps the usable condition number at about 1e9 -- below
103 // what an h-phi Jacobian reaches on a refined mesh, where SM then
104 // grinds to mNumMaxIter and converges nothing. 1e-4 lifts that
105 // ceiling to ~1e12 and still pins lambda to four digits, which is
106 // far more than a conditioning DIAGNOSTIC needs.
107 // Tighten only with that inequality in hand
108 real mEpsilon = 1e-4 ;
109
110 // floor for the Krylov subspace handed to ( p )dnaupd. Remark 4
111 // of dnaupd: raising ncv at fixed nev usually REDUCES the total
112 // OP*x count, at the price of an n x ncv basis
113 int_t mSubspaceSize = 20 ;
114
115 // Tolerance for the FOLDED run, which is a different job from
116 // the plain one. lambda_min falls out of a cancellation,
117 // lambda_min = sigma - mu, and mu is of order sigma, so an
118 // error of tol*sigma in mu lands undivided on lambda_min:
119 //
120 // rel_err( lambda_min ) ~ max( tol, eps_mach ) * kappa
121 //
122 // 1e-4 would therefore be worthless at the small end ( it caps
123 // kappa at ~1e4 before the answer is all noise ), while the
124 // FOLDED problem is an exterior one and can afford a tight
125 // tolerance -- it converges in a handful of restarts either way.
126 // The floor is eps_mach*kappa and cannot be bought off
127 real mFoldEpsilon = 1e-10 ;
128
129 // NOTE: the spectral FOLD that this class used to run for the
130 // small end has been REPLACED by shift-invert
131 // ( run_shift_invert ), because an affine shift cannot improve
132 // eigenvalue SEPARATION and separation is what sets the
133 // convergence rate -- measured, see compute_conditioning().
134 // The sigma argument on the Fortran drivers is deliberately
135 // KEPT: it costs nothing, the folded operator is still correct
136 // where it converges, and the shift-invert shims share those
137 // drivers' argument shape
138
139 // ceiling on the Arnoldi basis, which is n x ncv doubles. When
140 // not even the smallest legal ncv fits, the diagnostic reports
141 // Infeasible rather than allocating past it
142 size_t mBasisBudget = 512UL * 1024UL * 1024UL ;
143
144 // output
145 Vector< int_t > mInfo ;
146
147 // the SIGNED real and imaginary part of the Ritz value that won
148 // the last reduction. The function value is a magnitude, which
149 // is all the caller used to need ; the fold needs the sign to
150 // tell a positive definite spectrum from one straddling zero,
151 // and the imaginary part to notice a complex pair. Copied from
152 // the winning SLOT, so the two always describe the same value
153 real mExtremalReal = BELFEM_QUIET_NAN ;
154 real mExtremalImag = BELFEM_QUIET_NAN ;
155
156 // set once an end of the spectrum is proven out of reach, and
157 // never cleared: retrying it every timestep cost 4.7 s per step
158 // on tapestack3d and produced nothing. Assigned ONLY from a
159 // broadcast outcome -- see EigenOutcome
160 bool mDiagnosticUnavailable = false ;
161
162 // what ended the last compute_conditioning(), for the caller
163 // and for the one-time message
164 EigenOutcome mOutcome = EigenOutcome::Ok ;
165
166 // which of the two runs is in flight, for the messages. A
167 // failure that does not say whether the plain end or the fold
168 // died cannot be acted on -- measured 2026-08-28, when exactly
169 // that ambiguity cost a diagnostic cycle
170 const char * mRunLabel = "eigenvalue" ;
171
172 // which ARPACK family drives this field. TOLD, not detected:
173 // the thermal Jacobian is symmetric by construction and the
174 // magnetic h-phi one is not ( Christian, 2026-08-28 ). Detecting
175 // it would mean comparing A against A^T across a distribution
176 // that may be transposed column blocks already ( see the CSC
177 // note in run_parpack ), which is a bigger job than the caller
178 // simply knowing. Defaults to the SAFE answer: the nonsymmetric
179 // driver is correct for a symmetric matrix, merely slower
180 bool mSymmetric = false ;
181
182 // ---- shift-invert ( mode 3 ) state -------------------------
183 // ARPACK's reverse-communication arrays. Members, not locals:
184 // the loop hands them to Fortran across many calls and dseupd
185 // requires them untouched between the last step and the
186 // extraction, so nothing transient may own them
187 Vector< real > mSiResid ; // [ n ]
188 Vector< real > mSiBasis ; // [ n * ncv ], the Lanczos basis
189 Vector< real > mSiWorkD ; // [ 3 * n ]
190 Vector< real > mSiWorkL ; // [ ncv * ( ncv + 8 ) ]
191 Vector< int_t > mSiIparam ; // [ 11 ]
192 Vector< int_t > mSiIpntr ; // [ 11 ]
193 Vector< real > mSiLambda ; // [ nev ], eigenvalues of A
194
195 // right-hand side and solution of one inverse application.
196 // Sized once ; the solve writes into them every iteration
197 Vector< real > mSiRhs ;
198 Vector< real > mSiLhs ;
199
200 // dedicated solver for the inverse operator. NOT mSolver,
201 // which belongs to compute_lambda_max() and is built from the
202 // parent's solver type -- this one is MUMPS specifically,
203 // because it is the wrapper that can hold a factorization
204 // still and solve against it repeatedly
205 Solver * mShiftInvertSolver = nullptr ;
206
207 // the tolerance the last run was actually given. configure()
208 // picks between mEpsilon and mFoldEpsilon and an explicit
209 // set_tolerance() overrides both, so recomputing that choice at
210 // the point of use duplicates the rule and lets the two drift.
211 // Recorded once, read where it is reported
212 real mEffectiveTolerance = BELFEM_QUIET_NAN ;
213
214 // set by a driver when configure() found no legal ncv. It is
215 // the difference between "this iterate did not converge", which
216 // gets a few strikes, and "no run of this size can ever be
217 // attempted", which latches at once -- and a bare NaN cannot
218 // tell the two apart. Cleared at the top of every run
219 bool mSubspaceInfeasible = false ;
220
221 // consecutive failures. Every outcome but Infeasible gets a few
222 // strikes ( a non-positive iterate may be transient: the Jacobian
223 // is rebuilt every step ); Infeasible latches at once because it
224 // depends on size and budget, not on the numbers. Every rank
225 // counts the same broadcast outcome
226 int_t mFailureCount = 0 ;
227 int_t mMaxFailures = 3 ;
228
229 // which driver produced the last result. It depends on the
230 // rank count AND on the build ( multi-rank without PARPACK
231 // still runs ARPACK on the master ), so a caller cannot
232 // derive it and has to be told
233 string mBackendLabel = "ARPACK" ;
234
235 // work data
236 string mWhich;
237 real mLambdaMax = BELFEM_QUIET_NAN ;
238
239 Vector< real > mLambdaReal ;
240 Vector< real > mLambdaImag ;
241
242 Vector< real > mXreal ;
243 Vector< real > mYreal ;
244 Vector< real > mZreal ;
245
246 Vector< real > mXimag ;
247 Vector< real > mYimag ;
248 Vector< real > mZimag ;
249
250 Vector< cplx > mX ;
251 Vector< cplx > mY ;
252 Vector< cplx > mZ ;
253
254 bool mMatrixFlag = false ;
255 bool mFirstRun = true ;
256
257 Solver * mSolver = nullptr ;
258
259 int_t mOriginalBase = 0 ;
260
261#ifdef BELFEM_PARPACK
262 // row-distributed view of the master's Jacobian. The master
263 // owns the full matrix ; every other rank holds only its
264 // assembly submatrix, so the row blocks PARPACK needs have to
265 // be scattered from rank 0
266 sparse::DistMatrixCSR< int_t > * mDistMatrix = nullptr ;
267
268 // one-based copies of the distributed pattern. DistMatrixCSR
269 // is zero-based by construction ( distribute_values() forces
270 // SpMatrixIndexingBase::Cpp on the source ), and its arrays
271 // are handed out const, so the shift lives here
272 Vector< int_t > mParpackPointers ;
273 Vector< int_t > mParpackIndices ;
274
275 // structural fingerprint of the matrix the pattern was built
276 // from. NOT reset() -- that runs after every Jacobian
277 // computation, so rebuilding there would redistribute the
278 // pattern once per Newton iteration
279 const SpMatrix * mDistSource = nullptr ;
280 int_t mDistNumRows = 0 ;
281 int_t mDistNumNonzeros = 0 ;
282
283#endif
284
285//------------------------------------------------------------------------------
286 public:
287//------------------------------------------------------------------------------
288
289 EigenValues( DofManager * aParent ) ;
290
291 ~EigenValues();
292
293 // NON-COPYABLE, NON-MOVABLE. This class owns four raw pointers
294 // and deletes all four in its destructor -- mSolver,
295 // mShiftInvertSolver, mM and ( under PARPACK ) mDistMatrix --
296 // so the implicit copy would be a shallow pointer copy and the
297 // second destructor a double free. Nothing copies it today
298 // ( DofManager holds it by pointer and news it once ), which is
299 // exactly why the hazard was latent rather than loud.
300 //
301 // Same shape and same remedy as SpMatrix ( cl_SpMatrix.hpp )
302 // and Solver ( cl_Solver.hpp ). Deleting them makes any future attempt
303 // a compile error instead of a run-time double free
304 EigenValues( const EigenValues & ) = delete ;
305 EigenValues( EigenValues && ) = delete ;
306 EigenValues & operator=( const EigenValues & ) = delete ;
308
309//------------------------------------------------------------------------------
310
311 void
312 set_num_minvals( const index_t aNumMinVals );
313
314//------------------------------------------------------------------------------
315
316 void
317 set_num_maxvals( const index_t aNumMaxVals );
318
319//------------------------------------------------------------------------------
320
329 void
330 set_tolerance( const real aTolerance );
331
336 void
337 set_subspace_size( const index_t aSubspaceSize );
338
347 void
348 set_max_iterations( const index_t aNumMaxIter );
349
362 void
363 set_symmetric( const bool aSymmetric );
364
370 bool
371 is_symmetric() const ;
372
373//------------------------------------------------------------------------------
374
375 real
377
378//------------------------------------------------------------------------------
379
380 real
382
383//------------------------------------------------------------------------------
384
391 real
393
394 real
396
397//------------------------------------------------------------------------------
398
411 const Vector< real > &
412 lambda_real() const ;
413
414 const Vector< real > &
415 lambda_imag() const ;
416
420 int_t
422
426 const string &
427 backend_label() const ;
428
434 outcome() const ;
435
436//------------------------------------------------------------------------------
437
438 void
439 reset();
440
441//------------------------------------------------------------------------------
442 private:
443//------------------------------------------------------------------------------
444
445 void
446 compute_matrices() ;
447
457 bool
458 configure( const int_t aJob,
459 const bool aFolded,
460 const int_t aNumEigenValues,
461 int_t & aSubspaceSize,
462 int_t & aNumMaxIter,
463 real & aTolerance ) ;
464
469 real
470 run( const int_t aJob, const real aSigma );
471
472 void
473 link_matrix();
474
475 real
476 run_arpack( const int_t aJob, const real aSigma );
477
478 real
479 run_parpack( const int_t aJob, const real aSigma );
480
486 real
487 reduce_extremum( const int_t aJob, const int_t aNumConverged );
488
494 real
495 report_unavailable( const int_t aOutcome );
496
514 real
515 run_shift_invert( int_t & aOutcome );
516
517 void
518 restore_indexing_base();
519//------------------------------------------------------------------------------
520
521 };
522
523//------------------------------------------------------------------------------
524
525 inline const Vector< real > &
527 {
528 return mLambdaReal ;
529 }
530
531//------------------------------------------------------------------------------
532
533 inline const Vector< real > &
535 {
536 return mLambdaImag ;
537 }
538
539//------------------------------------------------------------------------------
540
541 inline int_t
546
547//------------------------------------------------------------------------------
548
549 inline const string &
551 {
552 return mBackendLabel ;
553 }
554
555//------------------------------------------------------------------------------
556
557 inline bool
559 {
560 return mSymmetric ;
561 }
562
563//------------------------------------------------------------------------------
564
565 inline EigenOutcome
567 {
568 return mOutcome ;
569 }
570
571//------------------------------------------------------------------------------
572 }
573 }
574}
575#endif //CL_FEM_DOFMGR_EIGENVALUES_HPP
Unified interface to the sparse direct solvers.
Definition cl_Solver.hpp:34
Sparse matrix in CSR or CSC format.
Definition cl_SpMatrix.hpp:52
this class creates the DOFs based on the passed equation object.
Definition cl_FEM_DofManager.hpp:55
int_t number_of_converged_values() const
how many of the requested values actually converged
Definition cl_FEM_DofMgr_EigenValues.hpp:542
void set_num_minvals(const index_t aNumMinVals)
Definition cl_FEM_DofMgr_EigenValues.cpp:131
real compute_smallest_eigenvalues()
compute the eigenvalues at one end of the spectrum and return the extremal magnitude found there.
Definition cl_FEM_DofMgr_EigenValues.cpp:914
EigenOutcome outcome() const
how the last compute_conditioning() ended.
Definition cl_FEM_DofMgr_EigenValues.hpp:566
const string & backend_label() const
"ARPACK" or "PARPACK", whichever drove the last call
Definition cl_FEM_DofMgr_EigenValues.hpp:550
const Vector< real > & lambda_imag() const
Definition cl_FEM_DofMgr_EigenValues.hpp:534
const Vector< real > & lambda_real() const
real and imaginary parts of the values found by the last call above.
Definition cl_FEM_DofMgr_EigenValues.hpp:526
void set_max_iterations(const index_t aNumMaxIter)
restart budget handed to ( p )dnaupd.
Definition cl_FEM_DofMgr_EigenValues.cpp:202
EigenValues & operator=(const EigenValues &)=delete
void set_tolerance(const real aTolerance)
relative accuracy demanded of each Ritz value.
Definition cl_FEM_DofMgr_EigenValues.cpp:162
EigenValues(const EigenValues &)=delete
real compute_lambda_max()
Definition cl_FEM_DofMgr_EigenValues.cpp:1647
real compute_conditioning()
Definition cl_FEM_DofMgr_EigenValues.cpp:929
EigenValues(DofManager *aParent)
Definition cl_FEM_DofMgr_EigenValues.cpp:41
void set_num_maxvals(const index_t aNumMaxVals)
Definition cl_FEM_DofMgr_EigenValues.cpp:147
EigenValues(EigenValues &&)=delete
void set_symmetric(const bool aSymmetric)
declare the matrix symmetric, selecting dsaupd / dseupd over dnaupd / dneupd.
Definition cl_FEM_DofMgr_EigenValues.cpp:194
real compute_largest_eigenvalues()
Definition cl_FEM_DofMgr_EigenValues.cpp:922
void set_subspace_size(const index_t aSubspaceSize)
floor for the Krylov subspace.
Definition cl_FEM_DofMgr_EigenValues.cpp:178
void reset()
Definition cl_FEM_DofMgr_EigenValues.cpp:1803
EigenValues & operator=(EigenValues &&)=delete
bool is_symmetric() const
whether the spectral ratio this object returns may be called kappa_2.
Definition cl_FEM_DofMgr_EigenValues.hpp:558
Definition cl_SolverDistMatrix.hpp:147
constexpr index_t gInfoNumConverged
index of the number of converged Ritz values
Definition arpacktools.hpp:33
Definition cl_SolverDistMatrix.cpp:21
USER GUIDES:
Definition cl_Capacitor.cpp:16
int proc_t
Definition commtypes.hpp:29
ArpackWhat
Definition cl_FEM_DofMgr_EigenValues.hpp:52
@ LambaMinMagn
Definition cl_FEM_DofMgr_EigenValues.hpp:53
@ LambaMaxImag
Definition cl_FEM_DofMgr_EigenValues.hpp:58
@ LambaMaxMagn
Definition cl_FEM_DofMgr_EigenValues.hpp:54
@ LambaMinImag
Definition cl_FEM_DofMgr_EigenValues.hpp:57
@ LambaMinReal
Definition cl_FEM_DofMgr_EigenValues.hpp:55
@ LambaMaxReal
Definition cl_FEM_DofMgr_EigenValues.hpp:56
EigenOutcome
Definition cl_FEM_DofMgr_EigenValues.hpp:38
@ Complex
the winning Ritz value is not real
Definition cl_FEM_DofMgr_EigenValues.hpp:42
@ Infeasible
no legal ncv fits the basis budget, or no usable solver
Definition cl_FEM_DofMgr_EigenValues.hpp:44
@ NotPositiveDefinite
shift-invert returned lambda_min <= 0: the ratio is not a condition number
Definition cl_FEM_DofMgr_EigenValues.hpp:41
@ Ok
a usable value came back
Definition cl_FEM_DofMgr_EigenValues.hpp:39
@ Inconsistent
shift-invert returned lambda_min > rho, which cannot be
Definition cl_FEM_DofMgr_EigenValues.hpp:43
@ NotConverged
ARPACK ran out of restarts.
Definition cl_FEM_DofMgr_EigenValues.hpp:40
@ SolverFailed
Definition cl_FEM_DofMgr_EigenValues.hpp:45
uint32_t index_t
Definition typedefs.hpp:52
double real
Definition typedefs.hpp:36
int32_t int_t
Definition typedefs.hpp:51
#define BELFEM_QUIET_NAN
Definition typedefs.hpp:87