Non-linear static analysis with the arc-length methods#

The arc-length methods of structsolve trace equilibrium paths that include limit points, where the Newton-Raphson method with load control fails. The methods of the Shell object are passed directly to structsolve.Analysis, selecting the Riks method with NL_method='arc_length_riks', or the Crisfield method with NL_method='arc_length_crisfield'.

The example below is the snap-through benchmark of Sabir and Lock (1972): a shallow cylindrical panel with the straight edges hinged and the curved edges free, loaded by a point force at its center. With a reference load larger than the limit load, the analysis passes the limit point, where the load factor decreases during the snap-through, and continues until the load factor reaches exactly 1.0. The code is extracted from one of the panels unit tests:

def test_riks_hinged_cylindrical_shell():
    s = Shell()
    s.model = 'cylshell_clpt_donnell'
    R = 2540.
    theta = 0.2
    s.a = 508. # length of the free curved edges along x
    s.b = R*theta # length of the hinged straight edges along y
    s.r = R
    s.stack = [0.]
    s.plyt = 12.7
    s.laminaprop = (3102.75, 0.3) # isotropic
    s.m = 6
    s.n = 6
    s.nx = 2*s.m
    s.ny = 2*s.n
    for edge in ('x1', 'x2', 'y1', 'y2'):
        for field in 'uvw':
            setattr(s, edge + field, 1.)
            setattr(s, edge + field + 'r', 1.)
    # hinged straight edges
    for edge in ('y1', 'y2'):
        for field in 'uvw':
            setattr(s, edge + field, 0.)

    # reference load larger than the limit load, such that the analysis must
    # pass the limit point and the snap-through before reaching a load factor
    # of 1
    Pref = 4000.
    s.add_point_load(s.a/2., s.b/2., 0., 0., -Pref, cte=False)

    an = Analysis(s.calc_fext, s.calc_fint, s.calc_kC, s.calc_kG)
    an.NL_method = 'arc_length_riks'
    an.initialInc = 0.05
    an.maxArcLength = 50.
    increments, cs = an.static(NLgeom=True, silent=True)

    lbds = np.asarray(increments)
    wc = np.array([s.uvw(c=c, gridx=3, gridy=3)[1]['w'][1, 1] for c in cs])
    for lbd, w in zip(lbds, wc):
        print('  load %7.1f N, center deflection %8.4f mm' % (lbd*Pref, w))

    assert np.isclose(lbds[-1], 1.)

    # every converged increment is in equilibrium
    fext = s.calc_fext()
    for lbd, c in zip(lbds, cs):
        fint = s.calc_fint(c=c)
        R = lbd*fext - fint
        assert np.linalg.norm(R) <= 1.e-5*max(np.linalg.norm(lbd*fext),
                                              np.linalg.norm(fint))

    # no snap-back for this thickness: the deflection increases monotonically
    assert np.all(np.diff(wc) < 0)

    # limit point: first converged increment followed by a load drop
    ilim = np.argmax(np.diff(lbds) < 0)
    assert 0 < ilim
    assert np.isclose(lbds[ilim]*Pref, 2280., rtol=0.02)
    assert np.isclose(wc[ilim], -8.83, rtol=0.1)

    # snap-through: after the limit point the load drops to a fraction of the
    # limit load and then increases again, up to the reference load
    imin = ilim + np.argmin(lbds[ilim:])
    assert ilim < imin < len(lbds) - 1
    assert lbds[imin] < 0.3*lbds[ilim]
    assert np.all(np.diff(lbds[:ilim + 1]) > 0)
    assert np.all(np.diff(lbds[ilim:imin + 1]) < 0)
    assert np.all(np.diff(lbds[imin:]) > 0)
    assert np.isclose(wc[-1], -29.1, rtol=0.02)