Eigenvalue analyses (pyfe3d.solver.eigen)#
Linear buckling, with the constitutive and geometric stiffness matrices, and
natural frequency, with the constitutive stiffness and mass matrices, the
eigenvalue problems that the element matrices of this package are usually
assembled for. Neither problem is safe to hand to
scipy.sparse.linalg.eigsh() without preparation:
diagonal_preconditioner()equilibrates the diagonal of the matrices, which does not change the eigenvalues;estimate_cayley_sigma()estimates, from the matrices themselves, a safe shift for the Cayley mode used for linear buckling;check_eigenpairs()verifies the result: it checks the residual of every eigenpair and, when the constitutive stiffness matrix is positive definite, that no load multiplier was missed below the highest one found, by counting the negative pivots ofK + s*KGjust below it, seeis_positive_definite().
linear_buckling() and natural_frequency() put them together and
are what most users should call. They also solve unsymmetric problems, e.g.
with the load stiffness of a follower load or with the aerodynamic matrix of
piston theory, and they follow the same solver policy: the sparse solvers
run first, their eigenpairs are verified, and a dense solver is used only as
a last resort, with a DenseFallbackWarning. complex_frequency()
follows the same policy for the free vibrations of damped systems, with the
stiffness, damping and mass matrices, a quadratic eigenvalue problem.
Note
ARPACK, used by scipy.sparse.linalg.eigsh(), has been
reported to return wrong eigenpairs at random, without raising, when
linked against Intel MKL 2024.2.0 to 2025.0.0, which includes some
Anaconda builds of SciPy, whenever its parameter ncv exceeds 32.
MKL 2025.0.1 or newer is not affected. The faulty versions are detected
and ncv is then capped at 32, see pyfe3d.solver.arpackutils.
The verification of check_eigenpairs() catches such a failure,
which is another reason to keep it on.
Notes#
The eigenvalue problems are linear buckling
and natural frequency
The reason this module exists is that neither problem is safe to hand to
scipy.sparse.linalg.eigsh() without preparation, and the two traps are
easy to fall into and silent when they bite:
The matrices are badly scaled. A shell element mixes translations and rotations, so the diagonal of \(\boldsymbol{K}_{C0}\) spans many orders of magnitude, and the drilling stiffness makes that worse: with the fictitious penalty of
K6ROTthe drilling entries are some six orders of magnitude below the membrane ones, and a condition number of \(10^{14}\) is easy to reach.diagonal_preconditioner()equilibrates the diagonal, which does not change the eigenvalues at all and usually decides whether the iterative solver converges.The shift of the Cayley mode has to be chosen, not guessed. With
eigsh(A=KG, M=K, sigma=sigma, mode='cayley', which='SM')the returned eigenvalues are the ones with the smallest \(|(\mu + \sigma)/(\mu - \sigma)|\), which are the most negative \(\mu\), and therefore the lowest positive load multipliers \(\lambda = -1/\mu\), only while \(\sigma\) is larger than \(|\mu|\) of those critical eigenvalues. Otherwise the eigenvalues closest to \(-\sigma\) come back instead, with no warning.estimate_cayley_sigma()estimates a safe shift from the matrices themselves.
The inertia check of check_eigenpairs(), the number of negative pivots
of \(\boldsymbol{K}_{C0} + s \boldsymbol{K}_G\) just below the highest load multiplier found, is the
only cheap way to know that no mode was skipped, and it is what distinguishes
a converged answer from a plausible-looking one.
Solver policy
With sparse_solver=True the sparse solvers run first for every problem
size, and each result must pass the verification. Only when all of them
failed, e.g. a failed verification, an ARPACK error or a singular
factorization, a dense solver is used, if the dense problem has at most
max_dense_size degrees-of-freedom, with a DenseFallbackWarning
giving the reasons of the failures; its result is verified as well. A
RuntimeError listing every attempted solver is raised otherwise. See
_sparse_first().
Order of the load multipliers
linear_buckling() returns the positive load multipliers first, in
increasing order, followed by the negative ones closest to zero, i.e. the
critical load multipliers of the reversed load, then the complex ones by
increasing modulus. For symmetric matrices, the negative ones are computed
with \(-\boldsymbol{K}_G\) when fewer than num_eigvalues positive load multipliers
exist, see _eigsh_both_signs().
The shift estimate, the verification, the dense fallback, the solvers of
unsymmetric matrices, the order of the load multipliers and the cap of
ncv depend only on NumPy and SciPy. The dsteqr routine of the Intel
MKL versions in the note above returns wrong eigenvectors for matrices
larger than 32 by 32, see pyfe3d.solver.arpackutils.
- exception pyfe3d.solver.eigen.DenseFallbackWarning#
Issued by
linear_buckling(),natural_frequency()andcomplex_frequency()when the sparse eigenvalue solvers failed and a dense solver was used as a last resortA subclass of
RuntimeWarning, issued withwarnings.warn(), whose message gives the reasons of the failures of the sparse solvers. It can be turned into an error withwarnings.simplefilter('error', DenseFallbackWarning).
- pyfe3d.solver.eigen.check_eigenpairs(K, KG, eigvals, eigvecs, rtol=0.001, min_rel_gap=0.0001, check_inertia=True, K_positive_definite=None)#
Verify the eigenpairs of a linear buckling problem
The eigenpairs, load multipliers and buckling modes, of the linear buckling problem of
KandKG, seelinear_buckling(), are verified with two checks:the relative residual of every eigenpair with a finite load multiplier
lamand eigenvectoru,norm(K @ u + lam*(KG @ u))/(norm(K @ u) + abs(lam)*norm(KG @ u)), see_relative_residuals(), must not exceedrtol;with
check_inertia=Trueand whenKis positive definite, no positive load multiplier is missing below the highest positive one returned,lam_n: the number of negative pivots ofK + s*KG, withs = lam_n*(1 - min_rel_gap), must be the number of returned load multipliers in(0, s). When this count is unknown, see_negative_pivots(), the weaker check thatK + s*KGwiths = lam_1*(1 - min_rel_gap)is positive definite,lam_1being the lowest positive load multiplier, is done.
The eigenpairs may be real or complex; the inertia check ignores the complex load multipliers and requires symmetric matrices. The eigenpairs with a non-finite load multiplier are skipped. When no load multiplier is finite, the check fails at once. The inertia check is skipped when no load multiplier is positive or when
Kis not positive definite, seeis_positive_definite(); missing negative load multipliers are not detected, see_check_both_signs(). Nothing is raised or printed; the result tells which check failed.- Parameters:
- K, KGsparse_matrix
Constitutive and geometric stiffness matrices. Shape
(n, n).- eigvalsarray_like
Load multipliers, dimensionless, real or complex. 1-D array of size
k.- eigvecsarray_like
Eigenvectors, one per column. Shape
(n, k).- rtolfloat, optional
Largest acceptable relative residual, dimensionless. Default
1.e-3.- min_rel_gapfloat, optional
Relative margin below the load multipliers at which the inertia is tested, dimensionless. Default
1.e-4.- check_inertiabool, optional
Whether the inertia check is done, only valid for symmetric matrices. Default
True.- K_positive_definitebool, callable or None, optional
Whether
Kis positive definite, or a function ofKreturning it, e.g. to verify several solutions with one factorization ofK.Noneusesis_positive_definite(). DefaultNone.
- Returns:
- errorstr or None
Description of the check that failed, or
Nonewhen all passed.
See also
Notes
The eigenpairs are those of \((\boldsymbol{K}_{C0} + \lambda \boldsymbol{K}_G) \boldsymbol{u} = \boldsymbol{0}\). The inertia check is the one that catches a solver that converged to an interior part of the spectrum, which is silent otherwise. For symmetric \(\boldsymbol{K}_{C0}\) and \(\boldsymbol{K}_G\) with a positive definite \(\boldsymbol{K}_{C0}\), \(\boldsymbol{K}_{C0} + s \boldsymbol{K}_G\) is congruent to \(\boldsymbol{I} + s \boldsymbol{K}_{C0}^{-1/2} \boldsymbol{K}_G \boldsymbol{K}_{C0}^{-1/2}\), whose eigenvalues are \(1 + s \mu_i = 1 - s/\lambda_i\), with \(\mu_i = -1/\lambda_i\) the eigenvalues of \(\boldsymbol{K}_G \boldsymbol{u} = \mu \boldsymbol{K}_{C0} \boldsymbol{u}\). For \(s > 0\), \(1 - s/\lambda_i < 0\) exactly when \(0 < \lambda_i < s\), hence the number of negative eigenvalues of \(\boldsymbol{K}_{C0} + s \boldsymbol{K}_G\), equal to its number of negative pivots by Sylvester’s law of inertia, is the number of load multipliers in \((0, s)\). This is the Sturm sequence check of the eigenvalue solvers of structural analysis ([1], Section 10.2.2, p. 849, Section 11.4.3, p. 943, and Section 11.6, p. 954), where the number of negative elements of \(\boldsymbol{D}\) in the factorization \(\boldsymbol{L} \boldsymbol{D} \boldsymbol{L}^\top\) of \(\boldsymbol{K} - \mu \boldsymbol{M}\) is the number of eigenvalues of the pencil \((\boldsymbol{K}, \boldsymbol{M})\) smaller than \(\mu\). When only one positive load multiplier was returned, none is expected in \((0, s)\) and the weaker check is done. The gap
min_rel_gapkeeps \(s\) away from \(\lambda_n\), where \(\boldsymbol{K}_{C0} + s \boldsymbol{K}_G\) is singular.References
[1]Bathe, K. J., Finite Element Procedures, 2nd ed., K. J. Bathe, Watertown, MA, 2014, ISBN 978-0-9790049-5-7.
- pyfe3d.solver.eigen.complex_frequency(K, C, M, num_eigvalues=6, tol=0, sigma=None, precondition=True, check=True, check_rtol=1e-06, sparse_solver=True, max_dense_size=2000, silent=True)#
Complex eigenvalues of a damped system from the stiffness, damping and mass matrices
Solves the quadratic eigenvalue problem of the free vibrations of a damped system,
(lam**2*M + lam*C + K) @ u = 0, whose eigenvalueslamgive the damped circular frequenciesomegad = abs(lam.imag)and the damping ratioszeta = -lam.real/abs(lam).K,CandMmust be real and may be unsymmetric, e.g.Kwith the aerodynamic matrices of the piston theory andCwith its damping matrix, orCwith gyroscopic terms.Kmust have no null row or column, seecheck_null_rows_cols(), whereasCandMmay have null rows and columns. The matrices are optionally preconditioned withdiagonal_preconditioner().The problem is linearized into a generalized eigenvalue problem of size
2*n, the first companion form, solved withscipy.sparse.linalg.eigs()in shift-invert mode, which finds the eigenvalues nearest the shiftsigmaand factorizes thenbynmatrixsigma**2*M + sigma*C + K, see_eigs_qep(). The eigenvalues of real matrices are real or come in complex conjugate pairs, and one eigenvalue per pair, the one with a positive imaginary part, is returned, with its eigenvector. Thenum_eigvalueseigenvalues nearest the shift are returned, sorted by increasing imaginary part, then by increasing modulus, see_qep_modes(); the real ones, e.g. of overdamped modes, come first.The relative residual of each eigenpair must not be larger than
check_rtol, unless its backward error is at the round-off, see_check_qep(). When the verification fails or the sparse solver raises an error, the dense solver of_dense_qep(),scipy.linalg.eig()on the linearization, is used as a last resort, with aDenseFallbackWarning, see the solver policy ofpyfe3d.solver.eigen. Whether an eigenvalue nearer the shift than the returned ones was missed is not verified.With
Cnull, the eigenvalues are1j*omegan, with the natural frequenciesomeganofnatural_frequency().- Parameters:
- K, C, Msparse_matrix
Stiffness, damping and mass matrices of the free degrees-of-freedom, i.e. with the constrained ones partitioned out, e.g.
KC0uu = KC0[bu, :][:, bu].Kshould include the constant terms that apply, e.g. the geometric stiffness matrix of a preload. Real, shape(n, n).- num_eigvaluesint, optional
Number of eigenvalues to compute, one per complex conjugate pair. Default
6.- tolfloat, optional
Tolerance passed to
scipy.sparse.linalg.eigs(),0meaning machine precision. Default0.- sigmafloat, complex or None, optional
Shift, in rad/s, near which the eigenvalues are sought; it must not be an eigenvalue.
Noneuses1., which gives the eigenvalues of lowest modulus, see the Notes. DefaultNone.- preconditionbool, optional
Whether to equilibrate the diagonal of
Kbefore solving, the same transformation being applied toCandM. DefaultTrue.- checkbool, optional
Whether to verify the eigenpairs of the sparse solver. With
Falsethey are returned without verification and without dense fallback. DefaultTrue.- check_rtolfloat, optional
Largest acceptable relative residual of the eigenpairs, dimensionless. An eigenpair with a larger one is also accepted when its normwise backward error is not larger than
BACKWARD_ERROR_RTOL, the machine precision, see_quadratic_backward_errors(). Default1.e-6.- sparse_solverbool, optional
With
False, the full problem is solved with the dense solver, returning all the finite eigenvalues, one per complex conjugate pair, without verification. DefaultTrue.- max_dense_sizeint, optional
Maximum size of the dense problem solved as a last resort, i.e. of the linearization,
2*n;0disables the dense fallback. Default2000.- silentbool, optional
With
False, the solver used and the eigenvalues, damped circular frequencies and damping ratios are printed, seepyfe3d.solver.logger. DefaultTrue.
- Returns:
- eigvalsnp.ndarray
Complex eigenvalues
lam, in rad/s, withlam.imag >= 0. 1-D array of sizek, at mostnum_eigvalues.- eigvecsnp.ndarray
Complex eigenvectors, one per column, in the same order, of unit Euclidean norm with the component of largest modulus real and positive. Shape
(n, k).
- Raises:
- numpy.linalg.LinAlgError
When
Khas a null row or column, with the indices of these degrees-of-freedom, seecheck_null_rows_cols().- ValueError
When
K,CorMis complex.- RuntimeError
When
check=Trueand no eigenvalue solver passed the verification.
- Warns:
- DenseFallbackWarning
When the sparse solver failed and the dense solver was used.
Notes
The problem solved is
\[(\lambda^2 \boldsymbol{M} + \lambda \boldsymbol{C} + \boldsymbol{K}) \boldsymbol{u} = \boldsymbol{0}\]the free vibrations \(\boldsymbol{q} = \boldsymbol{u} e^{\lambda t}\) of \(\boldsymbol{M} \ddot{\boldsymbol{q}} + \boldsymbol{C} \dot{\boldsymbol{q}} + \boldsymbol{K} \boldsymbol{q} = \boldsymbol{0}\). With \(\lambda = -\zeta \omega_n \pm i \omega_d\), \(\omega_d = |Im(\lambda)|\) is the damped circular frequency and \(\zeta = -Re(\lambda)/|\lambda|\) the damping ratio: a single degree-of-freedom, \(m \lambda^2 + c \lambda + k = 0\), has the roots \(\lambda = -\zeta \omega_n \pm i \omega_n \sqrt{1 - \zeta^2}\) for \(\zeta < 1\), with \(\omega_n = \sqrt{k/m} = |\lambda|\) and \(\zeta = c/(2 \sqrt{k m})\). A real \(\lambda\), e.g. of an overdamped mode, gives \(\omega_d = 0\), and a positive \(Re(\lambda)\) a growing motion, i.e. an unstable system, by flutter when \(\lambda\) is complex. \(\zeta\) is not defined for \(\lambda = 0\). With \(\boldsymbol{C} = \boldsymbol{0}\), \(\lambda^2 = -\omega_n^2\) of
natural_frequency().Linearization
With \(\boldsymbol{z} = \{\boldsymbol{u}^\top, \lambda \boldsymbol{u}^\top\}^\top\), the problem is the generalized eigenvalue problem of size \(2n\)
\[\begin{split}\begin{bmatrix} \boldsymbol{0} & \boldsymbol{I} \\ -\boldsymbol{K} & -\boldsymbol{C} \end{bmatrix} \boldsymbol{z} = \lambda \begin{bmatrix} \boldsymbol{I} & \boldsymbol{0} \\ \boldsymbol{0} & \boldsymbol{M} \end{bmatrix} \boldsymbol{z}\end{split}\]the first companion form \(\boldsymbol{A} - \lambda \boldsymbol{B}\) of [1] (Section 3.4, Eq. (3.6) with \(\boldsymbol{N} = \boldsymbol{I}\), p. 253): the first row is \(\lambda \boldsymbol{u} = \lambda \boldsymbol{u}\), and the second gives the quadratic problem. It is valid for any \(\boldsymbol{C}\), symmetric or not, singular or not, and it requires neither a non-singular \(\boldsymbol{K}\) nor a non-singular \(\boldsymbol{M}\): a singular \(\boldsymbol{M}\), e.g. a lumped mass matrix without rotary inertia, only gives infinite eigenvalues, \(\boldsymbol{B}\) being singular, which are discarded. Its shift-invert operator needs the factorization of the \(n \times n\) matrix \(\boldsymbol{K}_{dyn}(\sigma) = \sigma^2 \boldsymbol{M} + \sigma \boldsymbol{C} + \boldsymbol{K}\) only, see
_eigs_qep(); MSC/Nastran uses this linearization with a shift-invert Lanczos algorithm ([1], Section 7.1, p. 278). The symmetric linearizations of symmetric \(\boldsymbol{K}\), \(\boldsymbol{C}\) and \(\boldsymbol{M}\) give pencils that are indefinite, unless the system is overdamped ([1], Section 3.8.2, p. 258), for which the symmetric solver of ARPACK cannot be used, and they exclude the unsymmetric matrices of the piston theory. The eigenvectors \(\boldsymbol{u}\) are taken from the first or the last \(n\) rows of \(\boldsymbol{z}\), see_companion_vectors().Shift
The default shift \(\sigma = 1\) rad/s is the counterpart of the shift \(\omega_n^2 = -1\) of
natural_frequency(), \(\lambda^2 = -\omega_n^2 = 1\): with \(\boldsymbol{C} = \boldsymbol{0}\) the factorized matrix is the same, \(\boldsymbol{K} + \boldsymbol{M}\), and \(|\lambda - 1|^2 = 1 + \omega_n^2\) for \(\lambda = \pm i \omega_n\), such that the lowest natural frequencies are found first. With \(\lambda = \omega_n(-\zeta \pm i \sqrt{1 - \zeta^2})\), \(|\lambda - 1|^2 = 1 + 2 \zeta \omega_n + \omega_n^2\), which increases with \(\omega_n\) for \(\omega_n > -\zeta\). When \(\boldsymbol{M}\) is non-singular, \(\det \boldsymbol{K}_{dyn}(\sigma) = \det \boldsymbol{M} \prod_i (\sigma - \lambda_i)\) over the \(2n\) eigenvalues, such that \(\boldsymbol{K}_{dyn}(1)\) is singular only when \(\lambda = 1\) is an eigenvalue, i.e. never for the rigid-body modes, \(\lambda = 0\), nor for a stable system, \(Re(\lambda) \le 0\). A complex shift gives a complex operator, and the conjugate of each eigenvalue found with \(Im(\lambda) < 0\) is returned ([1], Section 6.1, p. 273).Verification
The relative residual \(\|(\lambda^2 \boldsymbol{M} + \lambda \boldsymbol{C} + \boldsymbol{K}) \boldsymbol{u}\| / (|\lambda|^2 \|\boldsymbol{M} \boldsymbol{u}\| + |\lambda| \, \|\boldsymbol{C} \boldsymbol{u}\| + \|\boldsymbol{K} \boldsymbol{u}\|)\) of each eigenpair is checked, the rigid-body modes being relative to the matrix-norm scale, see
_quadratic_residuals(). Unlikenatural_frequency(), no inertia check tells that no eigenvalue nearer the shift was missed: by Sylvester’s law of inertia, the negative pivots of \(\boldsymbol{K} - s \boldsymbol{M}\) count the real eigenvalues below \(s\) of a symmetric pencil with a positive definite \(\boldsymbol{M}\), whereas the eigenvalues of a damped system are complex, and the symmetric linearizations are definite only for overdamped systems, whose eigenvalues are real ([1], Sections 3.8.2 and 3.9, p. 258). No count of the eigenvalues of a quadratic eigenvalue problem inside a region of the complex plane is implemented, and that ARPACK found the eigenvalues of largest \(|1/(\lambda - \sigma)|\) is therefore not verified; the dense solution,sparse_solver=False, is the reference. With \(\boldsymbol{C} = \boldsymbol{0}\) and symmetric matrices,natural_frequency()verifies it with the inertia check.A rigid-body mode, \(\boldsymbol{K} \boldsymbol{u} = \boldsymbol{0}\), gives \(\lambda = 0\), a double eigenvalue when \(\boldsymbol{C} \boldsymbol{u} = \boldsymbol{0}\), which the solvers return as two eigenvalues null up to the round-off.
References
- pyfe3d.solver.eigen.diagonal_preconditioner(K, floor=1e-30)#
Jacobi preconditioner of a stiffness matrix
Returns the diagonal matrix
Pwith the entries1/sqrt(max(abs(K[i, i]), floor)), such thatP @ K @ Phas a diagonal of ones, or minus ones for negative entries, when the moduli of the diagonal entries ofKare larger thanfloor. For a generalized eigenvalue problem both matrices are transformed,P @ A @ PandP @ B @ P, which leaves the eigenvalues unchanged, and the eigenvectors of the original problem are recovered withu = P @ u_scaled.- Parameters:
- Ksparse_matrix
Matrix whose diagonal sets the scaling, normally the constitutive stiffness matrix with the boundary conditions already applied. Shape
(n, n).- floorfloat, optional
Lower bound for the diagonal entries, which guards against the null diagonal of an unconstrained degree-of-freedom. Same dimension as the diagonal entries of
K. Default1.e-30.
- Returns:
- Psparse_matrix
Diagonal preconditioner, created with
scipy.sparse.diags(), shape(n, n).
Notes
The diagonal matrix \(\boldsymbol{P}\) has the entries
\[P_{ii} = \frac{1}{\sqrt{max(|K_{{C0}_{ii}}|, floor)}}\]such that \(\boldsymbol{P} \boldsymbol{K}_{C0} \boldsymbol{P}\) has a unit diagonal, up to the sign of the negative entries, which appear e.g. in a stiffness matrix with the load stiffness of a follower load beyond the critical load. For a generalized eigenvalue problem both matrices are transformed, \(\boldsymbol{P} \boldsymbol{A} \boldsymbol{P}\) and \(\boldsymbol{P} \boldsymbol{B} \boldsymbol{P}\), which leaves the eigenvalues unchanged and maps the eigenvectors to \(\boldsymbol{P}^{-1} \boldsymbol{u}\): \(\boldsymbol{P} \boldsymbol{A} \boldsymbol{P} \boldsymbol{y} = w \boldsymbol{P} \boldsymbol{B} \boldsymbol{P} \boldsymbol{y}\) is \(\boldsymbol{A} \boldsymbol{u} = w \boldsymbol{B} \boldsymbol{u}\) with \(\boldsymbol{u} = \boldsymbol{P} \boldsymbol{y}\), \(\boldsymbol{P}\) being non-singular. The equilibration removes the spread of the diagonal entries, which usually reduces the condition number, see the notes of
pyfe3d.solver.eigen.Examples
>>> P = diagonal_preconditioner(Kuu) >>> eigvals, eigvecs = eigsh(A=P@KGuu@P, M=P@Kuu@P) >>> eigvecs = P @ eigvecs
- pyfe3d.solver.eigen.estimate_cayley_sigma(K, KG, safety=10.0, max_iter=50, rel_tol=0.001)#
Safe shift for the Cayley mode of
scipy.sparse.linalg.eigsh()The largest modulus of the eigenvalues
muof the generalized eigenvalue problemKG @ u = mu * K @ uis estimated with power iterations, and the result is multiplied bysafety.The fallback value
1.is returned whenKis singular or not positive definite, or when the linear solution is inaccurate.Kis factorized withscipy.sparse.linalg.splu()and the power iteration starts from a vector drawn fromnumpy.random.RandomStatewith the seed42, such that the result is reproducible. The fallback value1.is also returned when the ratio of norms is not finite or zero, and when any exception is raised during the estimate.- Parameters:
- K, KGsparse_matrix
Constitutive and geometric stiffness matrices, with the boundary conditions already applied. Shape
(n, n).- safetyfloat, optional
Multiplier applied to the estimated largest
abs(mu). Dimensionless. Default10..- max_iterint, optional
Maximum number of power iterations. Default
50.- rel_tolfloat, optional
Relative increment below which the power iteration is stopped. Dimensionless. Default
1.e-3.
- Returns:
- sigmafloat
Shift to be passed to
eigsh(..., sigma=sigma, mode='cayley'), of the dimension of the eigenvaluesmu.
Notes
The shift has to exceed \(|\mu|\) of the critical eigenvalues of \(\boldsymbol{K}_G \boldsymbol{u} = \mu \boldsymbol{K}_{C0} \boldsymbol{u}\), see the module documentation. The largest \(|\mu|\) is estimated with power iterations on \(\boldsymbol{K}_{C0}^{-1} \boldsymbol{K}_G\), whose ratio of norms in the \(\boldsymbol{K}_{C0}\)-norm grows towards it, and the result is multiplied by
safety.At each iteration \(\boldsymbol{x}\) is normalized such that \(\boldsymbol{x}^\top \boldsymbol{K}_{C0} \boldsymbol{x} = 1\), \(\boldsymbol{y} = \boldsymbol{K}_{C0}^{-1} \boldsymbol{K}_G \boldsymbol{x}\) is computed and the ratio of norms is \(\sqrt{|\boldsymbol{y}^\top \boldsymbol{K}_{C0} \boldsymbol{y}|}\); \(\boldsymbol{y}\) becomes the next \(\boldsymbol{x}\). The estimate is the largest ratio found, and the iterations stop when a ratio exceeds the previous estimate by at most
rel_toltimes the ratio, or aftermax_iteriterations. For a symmetric positive definite \(\boldsymbol{K}_{C0}\) and a symmetric \(\boldsymbol{K}_G\), \(\boldsymbol{K}_{C0}^{-1} \boldsymbol{K}_G\) is self-adjoint in the inner product of \(\boldsymbol{K}_{C0}\), such that the ratio tends to its spectral radius \(\max |\mu|\). At the first iteration the relative residual of the linear solution must not exceed1e-6.The fallback value
1.is returned when \(\boldsymbol{K}_{C0}\) is singular or not positive definite, or when the linear solution is inaccurate, since in those cases the power iteration says nothing.
- pyfe3d.solver.eigen.is_positive_definite(A)#
Tells whether the symmetric sparse matrix
Ais positive definiteAis factorized with a symmetric permutation and without row interchanges, and it is considered positive definite when all the pivots of the factorization are positive.The factorization uses
scipy.sparse.linalg.splu()with the ordering'MMD_AT_PLUS_A',diag_pivot_thresh=0.and the symmetric mode. The result isFalsewhen the factorization fails becauseAis exactly singular, when a row interchange was nevertheless made, or when a pivot is not positive. The symmetry ofAis not checked.- Parameters:
- Asparse_matrix
Symmetric matrix, shape
(n, n).
- Returns:
- positive_definitebool
Truewhen all the pivots are positive.
See also
Notes
The Gaussian elimination of a positive definite matrix never meets a zero or a negative pivot, and by Sylvester’s law of inertia a negative pivot means a negative eigenvalue: without row interchanges, the factorization of the symmetric matrix is \(\boldsymbol{L} \boldsymbol{D}_U \boldsymbol{L}^\top\), with the pivots on the diagonal \(\boldsymbol{D}_U\) of \(\boldsymbol{U}\), and \(\boldsymbol{A}\) has as many negative eigenvalues as \(\boldsymbol{D}_U\) has negative entries, see
_negative_pivots().
- pyfe3d.solver.eigen.linear_buckling(K, KG, num_eigvalues=6, tol=0, sigma=None, precondition=True, check=True, check_rtol=0.001, symmetric=None, sparse_solver=True, max_dense_size=2000)#
Linear buckling analysis with a verified spectrum
Computes the load multipliers and the buckling modes of the linear buckling problem of
KandKG, the lowest positive load multipliers being sought.Kmust have no null row or column, seecheck_null_rows_cols(), whereasKGmay have null rows and columns.KandKGare symmetric whensymmetric=Nonefinds them so, seeis_symmetric().Symmetric matrices. The sparse solvers are
scipy.sparse.linalg.eigsh()in Cayley mode, using the shift ofestimate_cayley_sigma(), orsigmawhen given, and then the same with a 10 times larger shift. Each one computes the lowest positive load multipliers and, when fewer thannum_eigvaluesexist, the negative ones closest to zero, i.e. the critical load multipliers of the reversed load, with-KG, see_eigsh_both_signs(). The eigenpairs are verified with the residual and inertia checks ofcheck_eigenpairs(), for both signs, see_check_both_signs(). The dense fallback condenses out the degrees-of-freedom whereKGhas null rows and solves the condensed problem withscipy.linalg.eigh(), see_eigh_condensed().Unsymmetric matrices, e.g.
KGwith the load stiffness of a follower load, may have complex load multipliers. The sparse solver is a search of shifts along the positive real axis withscipy.sparse.linalg.eigs(), see_eigs_shift_search(), which finds the lowest positive real load multiplier; the returned eigenpairs are thenum_eigvaluesload multipliers nearest the last shift of the search. Only the residuals are verified. The dense fallback,_eig_condensed(), keeps the degrees-of-freedom where either the row or the column ofKGis not null; when none of its load multipliers is positive and real, they are returned with aRuntimeWarning. Eigenvalues whose imaginary part is below1e-8of their modulus are returned as real, with real eigenvectors.The sparse solvers run first and the dense solver is a last resort, see the solver policy of
pyfe3d.solver.eigen.- Parameters:
- K, KGsparse_matrix
Constitutive and geometric stiffness matrices of the free degrees-of-freedom, i.e. with the constrained ones partitioned out, e.g.
KC0uu = KC0[bu, :][:, bu].Kshould include the constant terms that apply, e.g. the geometric stiffness matrix of a preload, andKGis the matrix multiplied by the load multiplier. Shape(n, n).- num_eigvaluesint, optional
Number of load multipliers to compute, at most
n - 2with the sparse solvers. Default6.- tolfloat, optional
Tolerance passed to
scipy.sparse.linalg.eigsh()andscipy.sparse.linalg.eigs(),0meaning machine precision. Default0.- sigmafloat or None, optional
Shift of the Cayley mode of the symmetric sparse solver. With
Noneit is estimated from the matrices, which is the recommended use. DefaultNone.- preconditionbool, optional
Whether to equilibrate the diagonal before solving, see
diagonal_preconditioner(). This does not change the eigenvalues. DefaultTrue.- checkbool, optional
Whether to verify the eigenpairs of the sparse solvers. With
False, the first sparse solver is returned without verification and without dense fallback. DefaultTrue.- check_rtolfloat, optional
Largest acceptable relative residual of the eigenpairs, dimensionless. Default
1.e-3.- symmetricbool or None, optional
Whether
KandKGare symmetric.Nonechecks it withis_symmetric(),Trueuses the solvers of symmetric matrices,Falsethe solvers of unsymmetric matrices. DefaultNone.- sparse_solverbool, optional
With
Truethe sparse solvers run first, the dense one being a last resort. WithFalse, the full problem is explicitly solved with a dense solver,scipy.linalg.eigh(), which requires a positive definiteK, orscipy.linalg.eig()for unsymmetric matrices, returning all the load multipliers, without verification; it is a reference for validations. DefaultTrue.- max_dense_sizeint, optional
Maximum size of the dense problem solved as a last resort, the number of non-null rows of
KG, or of non-null rows or columns for unsymmetric matrices;0disables the dense fallback. Default2000.
- Returns:
- eigvalsnp.ndarray
Load multipliers, dimensionless: the positive ones first in increasing order, followed by the negative ones in order of increasing modulus, then the complex ones by increasing modulus, see
_lb_order(). Fewer thannum_eigvaluesare returned by the symmetric sparse solvers when fewer finite load multipliers exist, the infinite ones, e.g. of the null rows ofKG, being discarded. The dense solvers return as-inf, last, the load multipliers of the eigenvalues null up to the round-off, see_null_mu_pencil(). Complex only when complex load multipliers are returned.- eigvecsnp.ndarray
Eigenvectors, one per column, in the same order. Shape
(n, k).
- Raises:
- numpy.linalg.LinAlgError
When
Khas a null row or column, with the indices of these degrees-of-freedom, seecheck_null_rows_cols().- RuntimeError
When
check=Trueand no eigenvalue solver passed the verification, with the list of the attempted solvers and their failures.
- Warns:
- DenseFallbackWarning
When the sparse solvers failed and the dense solver was used.
- RuntimeWarning
For unsymmetric matrices, when the dense fallback found no positive real load multiplier.
Notes
The problem solved is \((\boldsymbol{K}_{C0} + \lambda \boldsymbol{K}_G) \boldsymbol{u} = \boldsymbol{0}\), written as \(\boldsymbol{K}_G \boldsymbol{u} = \mu \boldsymbol{K}_{C0} \boldsymbol{u}\) with \(\lambda = -1/\mu\), whose lowest positive load multipliers are the most negative \(\mu\), returned by the Cayley mode while the shift is large enough, see the notes of
pyfe3d.solver.eigen. Withprecondition=Truethe problem solved is the one of \(\boldsymbol{P} \boldsymbol{K}_{C0} \boldsymbol{P}\) and \(\boldsymbol{P} \boldsymbol{K}_G \boldsymbol{P}\), with the same eigenvalues, seediagonal_preconditioner(); the verification always uses the original matrices.A null row or column of \(\boldsymbol{K}_{C0}\) is a degree-of-freedom without stiffness, a modelling error: with the same null row or column of \(\boldsymbol{K}_G\), \(\det(\boldsymbol{K}_{C0} + \lambda \boldsymbol{K}_G) = 0\) for every \(\lambda\), a singular pencil whose computed eigenvalues are arbitrary, and otherwise \(\lambda = 0\) is a spurious load multiplier. It is therefore reported instead of being removed, unlike in structsolve, see
check_null_rows_cols(). A null row of \(\boldsymbol{K}_G\), at a degree-of-freedom that the load does not destabilize, only gives an infinite load multiplier.A failure of the verification usually means that the mesh, and not the solver, needs attention: a spectrum with many nearly coincident eigenvalues, as in the buckling of a cylindrical shell, is the typical case.
Non-conservative systems, e.g. under follower loads, may lose stability by flutter, which this static criterion does not detect, see
natural_frequency(). When no returned load multiplier is positive and real, no divergence load was found.
- pyfe3d.solver.eigen.natural_frequency(K, M, num_eigvalues=6, tol=0, precondition=True, symmetric=None, check=True, check_rtol=1e-06, sparse_solver=True, max_dense_size=2000)#
Natural frequencies from the stiffness and mass matrices
Kmust have no null row or column, seecheck_null_rows_cols(), whereasMmay have null rows and columns, e.g. a lumped mass matrix without rotary inertia. The matrices are optionally preconditioned withdiagonal_preconditioner(), and the eigenvalues, the squares of the circular frequencies, are computed in shift-invert mode withsigma=-1.andwhich='LM', withscipy.sparse.linalg.eigsh()for symmetric matrices andscipy.sparse.linalg.eigs()otherwise, e.g. with the aerodynamic matrix of piston theory or the load stiffness of a follower load inK.KandMare symmetric whensymmetric=Nonefinds them so, seeis_symmetric(). The number of computed frequencies isk = min(num_eigvalues, n - 2).The eigenpairs are verified, see
_check_freq(): the relative residual of each eigenpair must not be larger thancheck_rtol, unless its backward error is at the round-off, and, for symmetric matrices, no eigenvalue lower than the highest one returned may be missing. When the verification fails or the sparse solver raises an error, the dense solver of_dense_freq()is used as a last resort, with aDenseFallbackWarning, see the solver policy ofpyfe3d.solver.eigen.The eigenpairs are sorted together by increasing frequency, the eigenvectors are transformed back to the original degrees-of-freedom, and negative eigenvalues of symmetric matrices give a zero frequency.
- Parameters:
- K, Msparse_matrix
Constitutive stiffness and mass matrices of the free degrees-of-freedom, i.e. with the constrained ones partitioned out, e.g.
KC0uu = KC0[bu, :][:, bu].Kshould include the constant terms that apply, e.g. the geometric stiffness matrix of a preload. Shape(n, n).- num_eigvaluesint, optional
Number of frequencies to compute. Default
6.- tolfloat, optional
Tolerance passed to
scipy.sparse.linalg.eigsh()orscipy.sparse.linalg.eigs(),0meaning machine precision. Default0.- preconditionbool, optional
Whether to equilibrate the diagonal of
Kbefore solving. DefaultTrue.- symmetricbool or None, optional
Whether
KandMare symmetric.Nonechecks it withis_symmetric(). DefaultNone.- checkbool, optional
Whether to verify the eigenpairs of the sparse solver. With
Falsethey are returned without verification and without dense fallback. DefaultTrue.- check_rtolfloat, optional
Largest acceptable relative residual of the eigenpairs, dimensionless. An eigenpair with a larger one is also accepted when its normwise backward error is not larger than
BACKWARD_ERROR_RTOL, the machine precision, e.g. an eigenvalue close to zero of a model pre-stressed close to buckling, see_backward_errors(). Default1.e-6.- sparse_solverbool, optional
With
False, the full problem is solved with a dense solver, returning all the frequencies, without verification. DefaultTrue.- max_dense_sizeint, optional
Maximum size of the dense problem solved as a last resort;
0disables the dense fallback. Default2000.
- Returns:
- omegannp.ndarray
Circular frequencies in rad/s, in increasing order. 1-D array of size
k. For unsymmetric matrices whose eigenvalues \(\omega_n^2\) are complex, the complex \(\omega_n = \sqrt{\omega_n^2}\) (principal root), sorted by increasing real part of \(\omega_n^2\), then by increasing imaginary part.- eigvecsnp.ndarray
Eigenvectors, one per column, in the same order. Shape
(n, k).
- Raises:
- numpy.linalg.LinAlgError
When
Khas a null row or column, with the indices of these degrees-of-freedom, seecheck_null_rows_cols().- RuntimeError
When
check=Trueand no eigenvalue solver passed the verification.
- Warns:
- DenseFallbackWarning
When the sparse solver failed and the dense solver was used.
Notes
The problem solved is \((\boldsymbol{K}_{C0} - \omega_n^2 \boldsymbol{M}) \boldsymbol{u} = \boldsymbol{0}\), and the frequencies of symmetric matrices are \(\omega_n = \sqrt{\max( \omega_n^2, 0)}\). In the shift-invert mode, the sparse solvers find the eigenvalues closest to the shift
sigma=-1., i.e. the lowest ones when \(\boldsymbol{K}_{C0}\) is positive semi-definite and \(\boldsymbol{M}\) positive definite, factorizing \(\boldsymbol{K}_{C0} + \boldsymbol{M}\), which is not singular when \(\boldsymbol{K}_{C0}\) is singular only because of unconstrained rigid-body modes that have mass.A null row or column of \(\boldsymbol{K}_{C0}\) is a degree-of-freedom without stiffness, a modelling error: with the same null row or column of \(\boldsymbol{M}\), \(\det(\boldsymbol{K}_{C0} - \omega_n^2 \boldsymbol{M}) = 0\) for every \(\omega_n^2\), a singular pencil whose computed eigenvalues are arbitrary, and otherwise \(\omega_n^2 = 0\) is a spurious frequency. It is therefore reported instead of being removed, unlike in structsolve, see
check_null_rows_cols().A mass matrix without rotary inertia, and one without inertia associated with the drilling rotation, is singular, so the eigenvalues associated with those degrees-of-freedom are infinite. The shift-invert mode used here looks for the eigenvalues closest to zero and is not troubled by them, but a lumped mass matrix will place the massless degrees-of-freedom wherever the factorization puts them, so prefer a consistent mass matrix when the lowest frequencies matter.
Non-conservative systems, e.g. under follower loads, may lose stability by flutter, which the static criterion of
linear_buckling()does not detect: withKthe tangent stiffness matrix at a load level, including the load stiffness, the eigenvalues \(\omega_n^2\) are real and positive below the flutter load and become complex conjugate pairs above it, when two natural frequencies coalesce, a double root of the frequency equation. This is the dynamic method of [1] (Introduction, Section 6, pp. 12-15, and Section 7, pp. 15-17), where \(\omega_n\) is the frequency \(\Omega\) of the characteristic exponent \(s = i\Omega\). A real negative \(\omega_n^2\), i.e. a real positive \(s\), indicates divergence ([1], p. 17).References