Arch Dynamic Snapping

Curved arch

April 2020, Amir Hossein Namadchi

This is an OpenSeesPy simulation of one of the numerical examples in A robust composite time integration scheme for snap-through problems by Yenny Chandra et. al. It’s a special problem involving dynamic snap-through with large deformations in elastic range. In their study, a new three sub-step composite time integration algorithm was presented to handle such problems. Here, I will use the Bathe scheme (TRBDF2) to perform transient analysis.

Curved Arch

import numpy as np
from sees import render
import opensees.openseespy as ops
import matplotlib.pyplot as plt

from time import process_time

Below, the base units are defined as python variables:

# Units
mm = 1.0   # milimeters
N = 1.0    # Newtons
sec = 1.0  # Seconds

# Create the model

def arch_model(
    Rise   = 500,
    Offset = 200,
    L      = None, #5000,
    Angle  = None,
    Radius = None,
    ne  = 10):
    

    # Define material parameters
    E = 200
    A = 1e4
    I = 1e8

    # Compute radius
    if Radius is None:
        Radius = Rise/2 + (2*L)**2/(8*Rise)
        th = 2*(np.pi/2 - Angle)
    else:
        L = 0.5*np.sqrt(8*Rise*(Radius - Rise/2))
        th = 2*np.arcsin(L/Radius)

    #
    # Build the model
    #
    model = ops.Model(ndm=2, ndf=3)

    # Create nodes
    nen = 2
    nn  = ne*(nen-1)+1
    mid = (nn+1)//2      # midpoint node

    for i, angle in enumerate(np.linspace(-th/2, th/2, nn)):
        tag = i + 1

        # Compute x and add offset if midpoint
        x = Radius*np.sin(angle)
        if tag == mid:
            x -= Offset

        # Compute y
        y = Radius*np.cos(angle) - Radius*np.cos(-th/2)

        # create the node
        model.node(tag, x, y)


    # Create elements
    transfTag = 1
    model.geomTransf("Corotational", transfTag)
    for i in range(ne):
        tag   = i+1
        nodes = (i+1, i+2)
        model.element("ElasticBeamColumn", tag, *nodes, A, E, I, transfTag)


    model.fix( 1, 1, 1, 0)
    model.fix(nn, 1, 1, 0)
    
    # Create a load pattern that scales linearly
    model.pattern("Plain", 1, "Linear")

    # Add a nodal load to the pattern
    model.load(mid, 0.0, -1.0, 0.0, pattern=1)

    
    model.system("ProfileSPD")
    # model.system("FullGeneral")
    # model.system("BandGeneral")
    # model.system("Umfpack", det=True)

    model.test("NormUnbalance", 1e-6, 25, 0)
    model.algorithm("Newton")
    model.analysis("Static")


    return model, mid

arch_model(15.39553, 0, Angle=0.2014, Radius=762, ne=20)

Output:

(<opensees.openseespy.Model at 0x7f5666f598e0>, 11)

Model Defintion

# Node Coordinates Matrix (size : nn x 3)
node_coords = np.array([[-152.4, 0], [-137.337, 2.91714],
                        [-122.218, 5.53039], [-107.049, 7.8387],
                        [-91.8371, 9.84114], [-76.5878, 11.5369],
                        [-61.3076, 12.9252], [-46.0024, 14.0057],
                        [-30.6787, 14.7777], [-15.3424, 15.2411],
                        [0, 15.3955], [15.3424, 15.2411], 
                        [30.6787, 14.7777], [46.0024, 14.0057],
                        [61.3076, 12.9252], [76.5878, 11.5369], 
                        [91.8371, 9.84114], [107.049, 7.8387], 
                        [122.218, 5.53039], [137.337, 2.91714],
                        [152.4, 0]], dtype = np.float64)*mm

# Element Connectivity Matrix (size: nel x 2)
connectivity = [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], 
                [6, 7], [7, 8], [8, 9], [9, 10], [10, 11],
                [11, 12], [12, 13], [13, 14], [14, 15], 
                [15, 16], [16, 17], [17, 18], [18, 19],
                [19, 20], [20, 21]]

# Get Number of total Nodes
nn = len(node_coords)
# Get Number of total Elements
nel = len(connectivity)

#Boundary Conditions (size: fixed_nodes x 4)
B_C = [[1,1,1,1],
       [nn,1,1,1]]


# Modulus of Elasticity
E = 206843*(N/mm**2)
# Mass Density
rho = (7.83e-9)*(N*(sec**2)/(mm**4))
# Cross-sectional area, 2nd Moment of Inertia
A   = 12.7*0.6096*mm*mm
I   = (1/12)*(12.7*(0.6096**3))*mm**4
model = ops.Model( ndm=2, ndf=3)

# Adding nodes to the model object using list comprehensions
for n in range(nn):
    model.node(n+1,*node_coords[n])

# Applying BC
for n in range(len(B_C)):
    model.fix(B_C[n][0],*B_C[n][1:])


# Define Transformation
model.geomTransf('Corotational', 1)

# Adding Elements
for e in range(nel):
     model.element('elasticBeamColumn', e+1, *connectivity[e], 
                   A, E, I, 1, '-mass', rho*A,'-cMass',1)

Loading

# load function (Applied @ top node)
F = lambda t: (t if t<=8 else 8)*N

# Dynamic Analysis Parameters
dt = 0.0001
time = 15
time_domain = np.arange(0,time,dt)

# Loading Definition
model.timeSeries('Path', 1 , dt=dt,
                 values=np.vectorize(F)(time_domain),
                 time=time_domain)
model.pattern('Plain', 1, 1)
model.load(11, *[0.0, -1.0, 0.0], pattern=1)

# Analysis
model.constraints('Transformation')
model.numberer('RCM')
model.system('ProfileSPD')
model.test('NormUnbalance', 1e-6, 100)
model.algorithm('Newton')
model.integrator('TRBDF2')
model.analysis('Transient')


# let's do this
time = []     # list to hold time stations for plotting
d_list = []      # list to hold vertical displacments of the top node

# start the timer
tic = process_time()

for i in range(len(time_domain)):
    model.analyze(1, dt)
    time.append(model.getTime())
    d_list.append(model.nodeDisp(11,2))

# stop the timer
toc = process_time()   

print('Time elapsed:',toc-tic, 'sec')

Output:

Time elapsed: 44.4480693 sec

Visualization

plt.figure(figsize=(14,5))
ax1 = plt.axes()  # standard axes
plt.grid()
plt.yticks(fontsize = 14)
plt.xticks(fontsize = 14)
plt.title('Time history of vertical displacement of the top node',
          {'fontstyle':'italic','size':18});

ax2 = plt.axes([0.65, 0.60, 0.2, 0.25])


ax1.plot(time, d_list,'k')
ax2.plot(time, d_list,'k')
ax2.set_xlim(left=13.5, right=14)
ax2.set_ylim(bottom=-15, top=-30)

ax1.set_xlabel('Time (sec)', {'fontstyle':'italic','size':14})
ax1.set_ylabel('Vertical Displacement (mm)', {'fontstyle':'italic','size':14});

Output:

<Figure size 1400x500 with 2 Axes>

Closure

The figure deomnstrates a dynamic jump from the quasi static configuration. It then begins to oscillate around the remote equilibrium configuration. It is to be noted that conventional time integration algorithm like the Newmark method might not be able to present a stable and bounded solution like this. It is therefore necessary to employ an energy-conserving algorithms with proper numerical dissipation in order to tackle these kind of problems (like TRBDF2 and TRBDF3 in OpenSees).

References

  • Chandra, Y., Zhou, Y., Stanciulescu, I., Eason, T. and Spottswood, S., 2015. A robust composite time integration scheme for snap-through problems. Computational Mechanics, 55(5), pp.1041-1056.