Linear buckling with a prestress#
A linear buckling analysis with lb() has three steps, of which only
the last one is done by structsolve:
the linear static solution \(\boldsymbol{c}_0\) of a reference load \(\boldsymbol{F}_{ref}\), \(\boldsymbol{K}_{C0} \boldsymbol{c}_0 = \boldsymbol{F}_{ref}\), e.g. with
solve(),static()orAnalysis.static(NLgeom=False);the geometric stiffness matrix \(\boldsymbol{K}_G(\boldsymbol{c}_0)\) of the prestress of \(\boldsymbol{c}_0\), computed by the model, e.g. from the pre-buckling stress resultants at each integration point;
the eigenvalue problem
\[(\boldsymbol{K}_{C0} + \lambda \boldsymbol{K}_G(\boldsymbol{c}_0)) \boldsymbol{u} = \boldsymbol{0}\]solved by
lb(K0, KG), which returns the load multipliers \(\lambda\) and the buckling modes \(\boldsymbol{u}\).
Since the static solution is linear, \(\boldsymbol{K}_G\) is proportional to the load, \(\boldsymbol{K}_G(\lambda \boldsymbol{c}_0) = \lambda \boldsymbol{K}_G(\boldsymbol{c}_0)\), and the eigenvalue problem gives the load levels \(\lambda \boldsymbol{F}_{ref}\) at which \(\boldsymbol{K}_{C0} + \lambda \boldsymbol{K}_G\) is singular.
Meaning of the load multipliers#
\(\lambda\) multiplies the reference load: the critical load is \(\lambda_1
\boldsymbol{F}_{ref}\), and a reference load \(\alpha\) times larger gives load
multipliers \(\alpha\) times smaller. A load that is kept constant, e.g. a
preload \(\boldsymbol{F}_0\), is not multiplied by \(\lambda\): its geometric stiffness
matrix belongs to the first matrix, lb(K0 + KG0, KG), and the critical
load is \(\boldsymbol{F}_0 + \lambda_1 \boldsymbol{F}_{ref}\).
Negative and complex load multipliers#
A negative \(\lambda\) is a buckling load of the reversed reference load,
e.g. a plate under tension buckles only under the opposite, compressive,
load. lb() returns the positive load multipliers first, in
increasing order, followed, when fewer than num_eigvalues positive ones
exist, by the negative load multipliers closest to zero, i.e. the critical
load multipliers of the reversed load, see Eigenvalue solvers. A reference
load without any positive load multiplier, e.g. a tensile load, thus gives
the critical load multipliers of the reversed load directly. The null rows
of KG, e.g. of the in-plane dofs, give infinite load multipliers, which
the sparse solvers do not return.
Complex load multipliers only exist for unsymmetric matrices, e.g. with the
load stiffness of follower loads, for which lb() returns the lowest
positive real load multiplier, see Follower loads and unsymmetric matrices.
Example#
The plate of panels under a
uniaxial compression, whose matrices K and KG are saved in
tests/data: a reference load twice as large, a constant preload, and the
reversed, tensile, reference load, which has only negative load
multipliers:
def test_lb_reference_load():
"""The load multipliers multiply the reference load of KG: a reference
load twice as large halves them, a constant preload belongs to K, and
the reversed, tensile, reference load gives the negative load
multipliers closest to zero, without the dense fallback. Plate of
panels under a uniaxial compression, saved in tests/data"""
import warnings
from structsolve.linear_buckling import DenseFallbackWarning
K, KG = load_saved_matrices('plate_ssss_Nxx')
ref = np.array([157.5386, 226.3470, 232.5623])
eigvals, _ = lb(K, KG, silent=True, num_eigvalues=5)
np.testing.assert_allclose(eigvals[:3], ref, rtol=1e-5)
lambda_1 = eigvals[0]
# a reference load twice as large
eigvals2, _ = lb(K, 2*KG, silent=True, num_eigvalues=5)
np.testing.assert_allclose(eigvals2, eigvals/2, rtol=1e-8)
# a constant preload of 0.4 lambda_1 times the reference load
eigvals_pre, _ = lb(K + 0.4*lambda_1*KG, KG, silent=True,
num_eigvalues=5)
np.testing.assert_allclose(eigvals_pre[0], 0.6*lambda_1, rtol=1e-8)
# the reversed reference load: no positive load multiplier
with warnings.catch_warnings():
warnings.simplefilter('error', DenseFallbackWarning)
eigvals_rev, eigvecs_rev = lb(K, -KG, silent=True, num_eigvalues=5)
np.testing.assert_allclose(eigvals_rev, -eigvals, rtol=1e-8)
assert np.all(relative_residuals(K, -KG, eigvals_rev, eigvecs_rev)
< 1e-3)
The negative load multipliers closest to zero after the positive ones, for the sparse and the dense solvers:
@pytest.mark.parametrize('max_dense_size', [0, 2000])
def test_lb_negative_multipliers_closest_to_zero(max_dense_size):
"""After the positive load multipliers, the negative ones closest to
zero, i.e. the critical load multipliers of the reversed load, for the
sparse and the dense solvers"""
positive = np.linspace(1., 20., 5)
negative = -np.linspace(2., 40., 35)
K, KG = mixed_sign_problem(np.concatenate([positive, negative]),
num_null=10)
eigvals, eigvecs = lb(K, KG, silent=True, num_eigvalues=10,
max_dense_size=max_dense_size)
np.testing.assert_allclose(eigvals[:5], positive, rtol=1e-8)
np.testing.assert_allclose(eigvals[5:], negative[:5], rtol=1e-8)
assert np.all(relative_residuals(K, KG, eigvals, eigvecs) < 1e-6)
eig_dense, _ = lb(K, KG, silent=True, sparse_solver=False)
np.testing.assert_allclose(eig_dense[:10], eigvals, rtol=1e-8)
# the infinite load multipliers of the null rows of KG come last
assert np.all(np.abs(eig_dense[-10:]) > 1e8)
# the same order with the solvers of unsymmetric matrices
eig_gen, _ = lb(K, KG, silent=True, num_eigvalues=10, symmetric=False,
max_dense_size=max_dense_size)
pos, neg = eig_gen[eig_gen > 0], eig_gen[eig_gen < 0]
np.testing.assert_allclose(pos, positive[:pos.size], rtol=1e-8)
np.testing.assert_allclose(neg, negative[:neg.size], rtol=1e-8)
np.testing.assert_array_equal(eig_gen, np.concatenate((pos, neg)))
A complete Ritz model of a plate, in which the geometric stiffness matrix is integrated from the stress resultants of the reference load, is in Linear buckling analysis.