Linear buckling analysis#

Geometric stiffness matrix of a static solution#

A square isotropic plate, with \(w\) constrained at the nodes of the four edges and in-plane point supports at the middle of the edges, under a uniaxial compression applied as a consistent edge load with the rows of BFSCPlate2D.update_Su() at \(\xi = \pm 1\). The geometric stiffness matrix of update_KG() is computed with the linear static solution, and the critical force per unit length is compared, within 5 %, with \(k_c \pi^2 D/b^2\), with the buckling coefficient \(k_c = 4\):

def test_linear_buckling_iso_SSSS(plot_static=False, plot_lb=False):
    """See Bruhn Fig. C5.2, case C with clamped loaded edges
    """
    # number of nodes
    nx = 5 # along x
    ny = 5 # along y

    # getting integration points
    nint = 4
    points, weights = get_points_weights(nint=nint)

    # geometry
    a = 3 # along x
    b = 3 # along y

    # material properties
    E = 200e9
    nu = 0.3
    h = 0.001
    lam = isotropic_plate(thickness=h, E=E, nu=nu)

    # creating mesh
    x = np.linspace(0, a, nx)
    y = np.linspace(0, b, ny)
    xmesh, ymesh = np.meshgrid(x, y)

    # node coordinates and position in the global matrix
    ncoords = np.vstack((xmesh.T.flatten(), ymesh.T.flatten())).T
    nids = 1 + np.arange(ncoords.shape[0])
    nid_pos = dict(zip(nids, np.arange(len(nids))))

    # identifying nodal connectivity for plate elements
    # similar than Nastran's CQUAD4
    #
    #   ^ y
    #   |
    #
    #  4 ________ 3
    #   |       |
    #   |       |   --> x
    #   |       |
    #   |_______|
    #  1         2


    nids_mesh = nids.reshape(nx, ny)
    n1s = nids_mesh[:-1, :-1].flatten()
    n2s = nids_mesh[1:, :-1].flatten()
    n3s = nids_mesh[1:, 1:].flatten()
    n4s = nids_mesh[:-1, 1:].flatten()

    num_elements = len(n1s)
    print('num_elements', num_elements)

    N = DOF*nx*ny
    Kr = np.zeros(KC0_SPARSE_SIZE*num_elements, dtype=INT)
    Kc = np.zeros(KC0_SPARSE_SIZE*num_elements, dtype=INT)
    Kv = np.zeros(KC0_SPARSE_SIZE*num_elements, dtype=DOUBLE)
    KGr = np.zeros(KG_SPARSE_SIZE*num_elements, dtype=INT)
    KGc = np.zeros(KG_SPARSE_SIZE*num_elements, dtype=INT)
    KGv = np.zeros(KG_SPARSE_SIZE*num_elements, dtype=DOUBLE)
    init_k_KC0 = 0
    init_k_KG = 0

    plates = []
    for n1, n2, n3, n4 in zip(n1s, n2s, n3s, n4s):
        plate = BFSCPlate2D()
        plate.n1 = n1
        plate.n2 = n2
        plate.n3 = n3
        plate.n4 = n4
        plate.c1 = DOF*nid_pos[n1]
        plate.c2 = DOF*nid_pos[n2]
        plate.c3 = DOF*nid_pos[n3]
        plate.c4 = DOF*nid_pos[n4]
        plate.ABD = lam.ABD
        plate.lex = a/(nx - 1)
        plate.ley = b/(ny - 1)
        plate.init_k_KC0 = init_k_KC0
        plate.init_k_KG = init_k_KG
        update_KC0(plate, points, weights, Kr, Kc, Kv)
        init_k_KC0 += KC0_SPARSE_SIZE
        init_k_KG += KG_SPARSE_SIZE
        plates.append(plate)

    KC0 = coo_matrix((Kv, (Kr, Kc)), shape=(N, N)).tocsc()

    # applying boundary conditions

    # locating nodes
    bk = np.zeros(KC0.shape[0], dtype=bool) # constrained DOFs, can be used to prescribe displacements

    x = ncoords[:, 0]
    y = ncoords[:, 1]

    # applying boundary conditions
    # simply supported
    check = isclose(x, 0) | isclose(x, a) | isclose(y, 0) | isclose(y, b)
    bk[6::DOF] = check
    # point supports
    check = isclose(x, a/2) & (isclose(y, 0) | isclose(y, b))
    bk[0::DOF] = check
    check = isclose(y, b/2) & (isclose(x, 0) | isclose(x, a))
    bk[3::DOF] = check

    # unconstrained nodes
    bu = ~bk # logical_not

    # defining external force vector
    fext = np.zeros(KC0.shape[0], dtype=float)

    # applying unitary load along u at x=a
    # nodes at vertices get 1/2 the force
    for plate in plates:
        pos1 = nid_pos[plate.n1]
        pos2 = nid_pos[plate.n2]
        pos3 = nid_pos[plate.n3]
        pos4 = nid_pos[plate.n4]
        if isclose(x[pos3], a):
            Nxx = -1
            xi = +1
        elif isclose(x[pos1], 0):
            Nxx = +1
            xi = -1
        else:
            continue
        lex = plate.lex
        ley = plate.ley
        indices = []
        c1 = DOF*pos1
        c2 = DOF*pos2
        c3 = DOF*pos3
        c4 = DOF*pos4
        cs = [c1, c2, c3, c4]
        for ci in cs:
            for i in range(DOF):
                indices.append(ci + i)
        fe = np.zeros(4*DOF, dtype=float)
        for j in range(nint):
            eta = points[j]
            plate.update_Su(xi, eta)
            Su = np.asarray(plate.Su)
            fe += ley/2*weights[j]*Su*Nxx
        fext[indices] += fe

    Kuu = KC0[bu, :][:, bu]
    fextu = fext[bu]

    # static solver
    uu = spsolve(Kuu, fextu)
    u = np.zeros(KC0.shape[0], dtype=float)
    u[bu] = uu

    if plot_static:
        import matplotlib
        matplotlib.use('TkAgg')
        import matplotlib.pyplot as plt
        plt.gca().set_aspect('equal')
        uplot = u[0::DOF].reshape(nx, ny).T
        vplot = u[3::DOF].reshape(nx, ny).T
        print('u extremes', uplot.min(), uplot.max())
        print('v extremes', vplot.min(), vplot.max())
        levels = np.linspace(uplot.min(), uplot.max(), 300)
        plt.contourf(xmesh, ymesh, uplot, levels=levels)
        plt.colorbar()
        plt.show()

    # eigenvalue solver

    # getting integration points
    for plate in plates:
        update_KG(u, plate, points, weights, KGr, KGc, KGv)
    KG = coo_matrix((KGv, (KGr, KGc)), shape=(N, N)).tocsc()
    KGuu = KG[bu, :][:, bu]

    # solving modified generalized eigenvalue problem
    # Original: (KC0 + lambda*KG)*v = 0
    # Modified: (-1/lambda)*KC0*v = KG*v  #NOTE here we find (-1/lambda)
    num_eigenvalues = 5
    eigvals, eigvecsu = eigsh(A=KGuu, k=num_eigenvalues, which='SM', M=Kuu,
            tol=1e-6, sigma=1., mode='cayley')
    eigvals = -1./eigvals
    eigvecs = np.zeros((KC0.shape[0], num_eigenvalues), dtype=float)
    eigvecs[bu, :] = eigvecsu

    if plot_lb:
        import matplotlib
        matplotlib.use('TkAgg')
        import matplotlib.pyplot as plt
        plt.gca().set_aspect('equal')
        mode = 0
        wplot = eigvecs[6::DOF, mode].reshape(nx, ny).T
        levels = np.linspace(wplot.min(), wplot.max(), 300)
        plt.contourf(xmesh, ymesh, wplot, levels=levels)
        plt.colorbar()
        plt.show()

    kc = 4
    Nxxcr_theoretical = E*np.pi**2*(h/b)**2*kc/(12*(1 - nu**2))*h
    print(Nxxcr_theoretical, eigvals[0])
    print(eigvals[0]/(E*np.pi**2*(h/b)**2/(12*(1 - nu**2))*h))
    assert isclose(eigvals[0], Nxxcr_theoretical, rtol=0.05)

    # the same critical loads with the linear buckling solver of structsolve
    eigvals_lb, _ = lb(Kuu, KGuu, num_eigvalues=num_eigenvalues,
                       symmetric=True, silent=True)
    assert np.allclose(eigvals_lb, eigvals, rtol=1e-6)

Constant membrane forces#

A laminated plate, with \(w\) constrained at the nodes of the four edges, under a uniform compressive force per unit length, Nxx = -1, given directly to update_KG_cte_N() without a static analysis, such that the load multipliers are the critical forces per unit length:

def test_linear_buckling(plot_static=False, plot_lb=False):
    # number of nodes
    nx = 7 # along x
    ny = 5 # along y

    # getting integration points
    points, weights = get_points_weights(nint=4)

    # geometry
    a = 7 # along x
    b = 3 # along y

    # material properties
    E11 = 142.5e9
    E22 = 8.7e9
    nu12 = 0.28
    G12 = 5.1e9
    G13 = 5.1e9
    G23 = 5.1e9
    laminaprop = (E11, E22, nu12, G12, G13, G23)
    stack = [0, 45, -45, 90, 90, -45, 45, 0]
    ply_thickness = 0.001
    lam = laminated_plate(stack=stack, plyt=ply_thickness, laminaprop=laminaprop)

    # creating mesh
    x = np.linspace(0, a, nx)
    y = np.linspace(0, b, ny)
    xmesh, ymesh = np.meshgrid(x, y)

    # node coordinates and position in the global matrix
    ncoords = np.vstack((xmesh.T.flatten(), ymesh.T.flatten())).T
    nids = 1 + np.arange(ncoords.shape[0])
    nid_pos = dict(zip(nids, np.arange(len(nids))))

    # identifying nodal connectivity for plate elements
    # similar than Nastran's CQUAD4
    #
    #   ^ y
    #   |
    #
    #  4 ________ 3
    #   |       |
    #   |       |   --> x
    #   |       |
    #   |_______|
    #  1         2


    nids_mesh = nids.reshape(nx, ny)
    n1s = nids_mesh[:-1, :-1].flatten()
    n2s = nids_mesh[1:, :-1].flatten()
    n3s = nids_mesh[1:, 1:].flatten()
    n4s = nids_mesh[:-1, 1:].flatten()

    num_elements = len(n1s)
    print('num_elements', num_elements)

    N = DOF*nx*ny
    Kr = np.zeros(KC0_SPARSE_SIZE*num_elements, dtype=INT)
    Kc = np.zeros(KC0_SPARSE_SIZE*num_elements, dtype=INT)
    Kv = np.zeros(KC0_SPARSE_SIZE*num_elements, dtype=DOUBLE)
    KGr = np.zeros(KG_SPARSE_SIZE*num_elements, dtype=INT)
    KGc = np.zeros(KG_SPARSE_SIZE*num_elements, dtype=INT)
    KGv = np.zeros(KG_SPARSE_SIZE*num_elements, dtype=DOUBLE)
    init_k_KC0 = 0
    init_k_KG = 0

    plates = []
    for n1, n2, n3, n4 in zip(n1s, n2s, n3s, n4s):
        plate = BFSCPlate2D()
        plate.c1 = DOF*nid_pos[n1]
        plate.c2 = DOF*nid_pos[n2]
        plate.c3 = DOF*nid_pos[n3]
        plate.c4 = DOF*nid_pos[n4]
        plate.ABD = lam.ABD
        plate.lex = a/(nx - 1)
        plate.ley = b/(ny - 1)
        plate.init_k_KC0 = init_k_KC0
        plate.init_k_KG = init_k_KG
        update_KC0(plate, points, weights, Kr, Kc, Kv)
        init_k_KC0 += KC0_SPARSE_SIZE
        init_k_KG += KG_SPARSE_SIZE
        plates.append(plate)

    KC0 = coo_matrix((Kv, (Kr, Kc)), shape=(N, N)).tocsc()

    # applying boundary conditions
    # simply supported

    # locating nodes
    bk = np.zeros(KC0.shape[0], dtype=bool) # constrained DOFs, can be used to prescribe displacements

    x = ncoords[:, 0]
    y = ncoords[:, 1]

    # constraining w at all edges
    check = (np.isclose(x, 0.) | np.isclose(x, a) | np.isclose(y, 0.) | np.isclose(y, b))
    bk[6::DOF] = check
    # constraining u at x = 0
    check = np.isclose(x, 0.)
    bk[0::DOF] = check
    # constraining v at y = 0
    check = np.isclose(y, 0.)
    bk[3::DOF] = check

    # unconstrained nodes
    bu = ~bk # logical_not

    Kuu = KC0[bu, :][:, bu]

    # eigenvalue solver
    Nxx = -1
    Nyy = 0
    Nxy = 0
    for plate in plates:
        update_KG_cte_N(Nxx, Nyy, Nxy, plate, points, weights, KGr, KGc, KGv)
    KG = coo_matrix((KGv, (KGr, KGc)), shape=(N, N)).tocsc()
    KGuu = KG[bu, :][:, bu]

    # solving modified generalized eigenvalue problem
    # Original: (KC0 + lambda*KG)*v = 0
    # Modified: (-1/lambda)*KC0*v = KG*v  #NOTE here we find (-1/lambda)
    num_eigenvalues = 5
    eigvals, eigvecsu = eigsh(A=KGuu, k=num_eigenvalues, which='SM', M=Kuu,
            tol=1e-6, sigma=1., mode='cayley')
    eigvals = -1./eigvals
    eigvecs = np.zeros((KC0.shape[0], num_eigenvalues), dtype=float)
    eigvecs[bu, :] = eigvecsu

    if plot_lb:
        import matplotlib
        matplotlib.use('TkAgg')
        import matplotlib.pyplot as plt
        plt.gca().set_aspect('equal')
        mode = 0
        wplot = eigvecs[6::DOF, mode].reshape(nx, ny).T
        levels = np.linspace(wplot.min(), wplot.max(), 300)
        plt.contourf(xmesh, ymesh, wplot, levels=levels)
        plt.colorbar()
        plt.show()

    print('eigvals', eigvals)
    assert np.allclose(eigvals, [ 9469.11822736, 11706.06566909, 12835.65352178, 18537.34972827, 26327.59284352], rtol=1e-5)