BELFEM 0.9.3
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
169 const char * mRunLabel = "eigenvalue" ;
170
171 // which ARPACK family drives this field. TOLD, not detected:
172 // the thermal Jacobian is symmetric by construction and the
173 // magnetic h-phi one is not. Detecting
174 // it would mean comparing A against A^T across a distribution
175 // that may be transposed column blocks already ( see the CSC
176 // note in run_parpack ), which is a bigger job than the caller
177 // simply knowing. Defaults to the SAFE answer: the nonsymmetric
178 // driver is correct for a symmetric matrix, merely slower
179 bool mSymmetric = false ;
180
181 // ---- shift-invert ( mode 3 ) state -------------------------
182 // ARPACK's reverse-communication arrays. Members, not locals:
183 // the loop hands them to Fortran across many calls and dseupd
184 // requires them untouched between the last step and the
185 // extraction, so nothing transient may own them
186 Vector< real > mSiResid ; // [ n ]
187 Vector< real > mSiBasis ; // [ n * ncv ], the Lanczos basis
188 Vector< real > mSiWorkD ; // [ 3 * n ]
189 Vector< real > mSiWorkL ; // [ ncv * ( ncv + 8 ) ]
190 Vector< int_t > mSiIparam ; // [ 11 ]
191 Vector< int_t > mSiIpntr ; // [ 11 ]
192 Vector< real > mSiLambda ; // [ nev ], eigenvalues of A
193
194 // right-hand side and solution of one inverse application.
195 // Sized once ; the solve writes into them every iteration
196 Vector< real > mSiRhs ;
197 Vector< real > mSiLhs ;
198
199 // dedicated solver for the inverse operator. NOT mSolver,
200 // which belongs to compute_lambda_max() and is built from the
201 // parent's solver type -- this one is MUMPS specifically,
202 // because it is the wrapper that can hold a factorization
203 // still and solve against it repeatedly
204 Solver * mShiftInvertSolver = nullptr ;
205
206 // the tolerance the last run was actually given. configure()
207 // picks between mEpsilon and mFoldEpsilon and an explicit
208 // set_tolerance() overrides both, so recomputing that choice at
209 // the point of use duplicates the rule and lets the two drift.
210 // Recorded once, read where it is reported
211 real mEffectiveTolerance = BELFEM_QUIET_NAN ;
212
213 // set by a driver when configure() found no legal ncv. It is
214 // the difference between "this iterate did not converge", which
215 // gets a few strikes, and "no run of this size can ever be
216 // attempted", which latches at once -- and a bare NaN cannot
217 // tell the two apart. Cleared at the top of every run
218 bool mSubspaceInfeasible = false ;
219
220 // consecutive failures. Every outcome but Infeasible gets a few
221 // strikes ( a non-positive iterate may be transient: the Jacobian
222 // is rebuilt every step ); Infeasible latches at once because it
223 // depends on size and budget, not on the numbers. Every rank
224 // counts the same broadcast outcome
225 int_t mFailureCount = 0 ;
226 int_t mMaxFailures = 3 ;
227
228 // which driver produced the last result. It depends on the
229 // rank count AND on the build ( multi-rank without PARPACK
230 // still runs ARPACK on the master ), so a caller cannot
231 // derive it and has to be told
232 string mBackendLabel = "ARPACK" ;
233
234 // work data
235 string mWhich;
236 real mLambdaMax = BELFEM_QUIET_NAN ;
237
238 Vector< real > mLambdaReal ;
239 Vector< real > mLambdaImag ;
240
241 Vector< real > mXreal ;
242 Vector< real > mYreal ;
243 Vector< real > mZreal ;
244
245 Vector< real > mXimag ;
246 Vector< real > mYimag ;
247 Vector< real > mZimag ;
248
249 Vector< cplx > mX ;
250 Vector< cplx > mY ;
251 Vector< cplx > mZ ;
252
253 bool mMatrixFlag = false ;
254 bool mFirstRun = true ;
255
256 Solver * mSolver = nullptr ;
257
258 int_t mOriginalBase = 0 ;
259
260#ifdef BELFEM_PARPACK
261 // row-distributed view of the master's Jacobian. The master
262 // owns the full matrix ; every other rank holds only its
263 // assembly submatrix, so the row blocks PARPACK needs have to
264 // be scattered from rank 0
265 sparse::DistMatrixCSR< int_t > * mDistMatrix = nullptr ;
266
267 // one-based copies of the distributed pattern. DistMatrixCSR
268 // is zero-based by construction ( distribute_values() forces
269 // SpMatrixIndexingBase::Cpp on the source ), and its arrays
270 // are handed out const, so the shift lives here
271 Vector< int_t > mParpackPointers ;
272 Vector< int_t > mParpackIndices ;
273
274 // structural fingerprint of the matrix the pattern was built
275 // from. NOT reset() -- that runs after every Jacobian
276 // computation, so rebuilding there would redistribute the
277 // pattern once per Newton iteration
278 const SpMatrix * mDistSource = nullptr ;
279 int_t mDistNumRows = 0 ;
280 int_t mDistNumNonzeros = 0 ;
281
282#endif
283
284//------------------------------------------------------------------------------
285 public:
286//------------------------------------------------------------------------------
287
288 EigenValues( DofManager * aParent ) ;
289
290 ~EigenValues();
291
292 // NON-COPYABLE, NON-MOVABLE. This class owns four raw pointers
293 // and deletes all four in its destructor -- mSolver,
294 // mShiftInvertSolver, mM and ( under PARPACK ) mDistMatrix --
295 // so the implicit copy would be a shallow pointer copy and the
296 // second destructor a double free. Nothing copies it today
297 // ( DofManager holds it by pointer and news it once ), which is
298 // exactly why the hazard was latent rather than loud.
299 //
300 // Same shape and same remedy as SpMatrix ( cl_SpMatrix.hpp )
301 // and Solver ( cl_Solver.hpp ). Deleting them makes any future attempt
302 // a compile error instead of a run-time double free
303 EigenValues( const EigenValues & ) = delete ;
304 EigenValues( EigenValues && ) = delete ;
305 EigenValues & operator=( const EigenValues & ) = delete ;
307
308//------------------------------------------------------------------------------
309
310 void
311 set_num_minvals( const index_t aNumMinVals );
312
313//------------------------------------------------------------------------------
314
315 void
316 set_num_maxvals( const index_t aNumMaxVals );
317
318//------------------------------------------------------------------------------
319
328 void
329 set_tolerance( const real aTolerance );
330
335 void
336 set_subspace_size( const index_t aSubspaceSize );
337
346 void
347 set_max_iterations( const index_t aNumMaxIter );
348
361 void
362 set_symmetric( const bool aSymmetric );
363
369 bool
370 is_symmetric() const ;
371
372//------------------------------------------------------------------------------
373
374 real
376
377//------------------------------------------------------------------------------
378
379 real
381
382//------------------------------------------------------------------------------
383
390 real
392
393 real
395
396//------------------------------------------------------------------------------
397
410 const Vector< real > &
411 lambda_real() const ;
412
413 const Vector< real > &
414 lambda_imag() const ;
415
419 int_t
421
425 const string &
426 backend_label() const ;
427
433 outcome() const ;
434
435//------------------------------------------------------------------------------
436
437 void
438 reset();
439
440//------------------------------------------------------------------------------
441 private:
442//------------------------------------------------------------------------------
443
444 void
445 compute_matrices() ;
446
456 bool
457 configure( const int_t aJob,
458 const bool aFolded,
459 const int_t aNumEigenValues,
460 int_t & aSubspaceSize,
461 int_t & aNumMaxIter,
462 real & aTolerance ) ;
463
468 real
469 run( const int_t aJob, const real aSigma );
470
471 void
472 link_matrix();
473
474 real
475 run_arpack( const int_t aJob, const real aSigma );
476
477 real
478 run_parpack( const int_t aJob, const real aSigma );
479
485 real
486 reduce_extremum( const int_t aJob, const int_t aNumConverged );
487
493 real
494 report_unavailable( const int_t aOutcome );
495
513 real
514 run_shift_invert( int_t & aOutcome );
515
516 void
517 restore_indexing_base();
518//------------------------------------------------------------------------------
519
520 };
521
522//------------------------------------------------------------------------------
523
524 inline const Vector< real > &
526 {
527 return mLambdaReal ;
528 }
529
530//------------------------------------------------------------------------------
531
532 inline const Vector< real > &
534 {
535 return mLambdaImag ;
536 }
537
538//------------------------------------------------------------------------------
539
540 inline int_t
545
546//------------------------------------------------------------------------------
547
548 inline const string &
550 {
551 return mBackendLabel ;
552 }
553
554//------------------------------------------------------------------------------
555
556 inline bool
558 {
559 return mSymmetric ;
560 }
561
562//------------------------------------------------------------------------------
563
564 inline EigenOutcome
566 {
567 return mOutcome ;
568 }
569
570//------------------------------------------------------------------------------
571 }
572 }
573}
574#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
Main workhorse: DOF management, assembly coordination and the solver interface.
Definition cl_FEM_DofManager.hpp:52
int_t number_of_converged_values() const
how many of the requested values actually converged
Definition cl_FEM_DofMgr_EigenValues.hpp:541
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:912
EigenOutcome outcome() const
how the last compute_conditioning() ended.
Definition cl_FEM_DofMgr_EigenValues.hpp:565
const string & backend_label() const
"ARPACK" or "PARPACK", whichever drove the last call
Definition cl_FEM_DofMgr_EigenValues.hpp:549
const Vector< real > & lambda_imag() const
Definition cl_FEM_DofMgr_EigenValues.hpp:533
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:525
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:1629
real compute_conditioning()
Definition cl_FEM_DofMgr_EigenValues.cpp:927
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:920
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:1785
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:557
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:86