Elastic Response Spectra

Elastic Response Spectra

April 2020, By Amir Hossein Namadchi

This one deals with an elastic SDOF system subjected to the El Centro ground acceleration, $\ddot{d_g}$. The aim is to obtain response spectra for the system with various damping values.

This notebook is adapted from https://github.com/AmirHosseinNamadchi/OpenSeesPy-Examples/blob/master/Elastic%20Response%20Spectra.ipynb

EPSDOF

import numpy as np
import opensees.openseespy as ops
import matplotlib.pyplot as plt

# Loading El Centro EQ data (North-south component)
el_centro_raw = np.loadtxt('elCentro.txt')
plt.figure(figsize=(15,3))
plt.plot(el_centro_raw[:,0], el_centro_raw[:,1], color='k')

plt.ylabel('$\ddot{d_g} (g)$', {'size':14})
plt.xlabel('Time (sec)', {'size':13})

plt.grid()
plt.yticks(fontsize = 14)
plt.xticks(fontsize = 14)
plt.xlim([0.0, el_centro_raw[-1,0]]);

Output:

<Figure size 1500x300 with 1 Axes>

Analysis

# Define a period range below
T_min = 0.00001
T_max = 5
dT = 0.05
# a list of damping ratios to be included
zeta_list = np.array([0.02, 0.03, 0.05])


# Base units
cm = 1.0
sec = 1.0
# Gravitational constant
g = 981*cm/sec**2

The analyze_SDOF function performs numerical time integration of a damped elastic SDOF system subjected to the previously defined base excitation. Then, it returns the absolute maximum of response parameters as a dict. It should be reminded that, for a given ground motion, the response parameters of the SDOF system depends only on the natural period and its damping ratio, i.e. $T$ and $\zeta$.

def analyze_SDOF(period, damping_ratio):
    
    # natural frequency
    omega = (2*np.pi)/period
    # stiffness
    k = omega**2
    # Damping
    c = 2*damping_ratio*omega
    
    # Model Definition
    ops.wipe()
    ops.model('basic', '-ndm', 1, '-ndf', 1)
    ops.node(1, 0.0)
    ops.node(2, 0.0)
    ops.uniaxialMaterial('Elastic', 1, k)
    ops.element('zeroLength', 1, *[1, 2], '-mat', 1, '-dir', 1)
    # unit mass is assumed
    ops.mass(2, 1.0)
    ops.rayleigh(c, 0, 0, 0)
    ops.fix(1, 1)

    ## Loading
    dt = 0.02
    ops.timeSeries('Path', 1,
               dt=dt,
               values=el_centro_raw[:,1]*g,
               time=el_centro_raw[:,0])

    ops.pattern('UniformExcitation', 1, 1, '-accel', 1)

    ## Analysis
    ops.constraints('Transformation')
    ops.numberer('Plain')
    ops.system('ProfileSPD')
    ops.algorithm('Linear', False, False, True)
    ops.integrator('Newmark', 0.5, 0.25)
    ops.analysis('Transient')
        
    results = {'D':[],'V':[], 'A':[]}
    for i in range(len(el_centro_raw)):
        ops.analyze(1, dt)
        results['D'].append(ops.nodeDisp(2, 1))
        results['V'].append(ops.nodeVel(2, 1))
        results['A'].append(ops.nodeAccel(2, 1))    
        
    return {'SD': np.max(np.abs(results['D'])),
            'SV': np.max(np.abs(results['V'])),
            'SA': np.max(np.abs(results['A']))}
    

Here, I use nested loops to analyse the system for various damping ratios and periods.

data_frame = dict()

for z in zeta_list:    
    # re-initialization
    resp = {'T':[0],'SD':[0], 'SV':[0], 'SA':[0]}
    
    for T in np.arange(T_min, T_max, dT):
        SR = analyze_SDOF(T, z)
        resp['SD'].append(SR['SD'])
        resp['SV'].append(SR['SV'])
        resp['SA'].append(SR['SA'])
        resp['T'].append(T)
    
    # Appending keys and values dynamically
    data_frame[z] = resp
    print('Done with zeta=', z,'!')
        

Output:

Done with zeta= 0.02 !
Done with zeta= 0.03 !
Done with zeta= 0.05 !

Visualization

# Displacment -----------
plt.figure(figsize=(14,5))

for z in zeta_list:
    plt.plot(data_frame[z]['T'], data_frame[z]['SD'],
             label=('$\zeta$ = '+str(z)))

plt.ylabel('Displacement (cm)', {'size':14})
plt.xlabel('Period (sec)', {'size':14})
plt.legend()
plt.grid()
plt.yticks(fontsize = 14)
plt.xticks(fontsize = 14)
plt.title('Displacement Response Spectrum',
           {'size':18});

# Velocity ------------
plt.figure(figsize=(14,5))

for z in zeta_list:
    plt.plot(data_frame[z]['T'], data_frame[z]['SV'],
             label=('$\zeta$ = '+str(z)))

plt.ylabel('Velocity (cm/sec)', {'size':14})
plt.xlabel('Period (sec)', {'size':14})
plt.legend()
plt.grid()
plt.yticks(fontsize = 14)
plt.xticks(fontsize = 14)
plt.title('Veloctiy Response Spectrum', {'size':18});

# Acceleration ------------
plt.figure(figsize=(14,5))

for z in zeta_list:
    plt.plot(data_frame[z]['T'], np.array(data_frame[z]['SA'])/g,
             label=('$\zeta$ = '+str(z))) 

plt.ylabel('Acceleration (g)', {'size':14})
plt.xlabel('Period (sec)', {'size':14})
plt.legend()
plt.grid()
plt.yticks(fontsize = 14)
plt.xticks(fontsize = 14)
plt.title('Acceleration Response Spectrum',
          {'size':18});

Output:

<Figure size 1400x500 with 1 Axes>
<Figure size 1400x500 with 1 Axes>
<Figure size 1400x500 with 1 Axes>

References

  • Chopra, A.K., 2017. Dynamics of structures. theory and applications to. Earthquake Engineering.