Two Story Steel MRF
December 2021, By Amir Hossein Namadchi
In this notebook, a 2 story steel moment resisting frame is modeled. This is an OpenSeesPy simulation of TCL version of the model, presented by Fernando Gutiérrez Urzúa in his YouTube channel. Some minor modifications are made by me in the python version. According to his, modeling assumptions are:
- Columns are modeled as distributed plasticity elements
- Beams are modeled as concentrated plasticity elements
- Plastic hinges are modeled as two nodes at the same coordinates, connected via a rotational spring.
The functions defined in this notebook are as follows:
W_section
: Creates W-Section based on nominal dimension and generates fibers over itbuild_model
: Builds the 2D Steel Moment resisting Frame Modelrun_gravity
: Runs gravity analysisrun_modal
: Runs Modal analysisrun_pushover
: Runs Pushover analysisrun_time_history
: Runs Time history analysisreset_analysis
: Resets the analysis by setting time to 0,removing the recorders and wiping the analysis.
Please note that some functions use data obtained by running other functions. For example, in order to run Pushover analysis, some components of eigenvectors are needed that is obtained via run_modal
function.
It’s highly recommended to watch Dr. Fernando Gutiérrez Urzúa videos for better understanding.
Dependencies
import opensees.openseespy as ops
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import time
Functions
Below functions can be encapsulated in a single .py file.
def W_section(section, sec_tag, mat_tag, nf_dw, nf_tw, nf_bf, nf_tf):
"""
Creates W-Section based on nominal dimension and generates
fibers over it
TCL version by: Remo M. de Souza, 08/99
Python version by: Amir Hossein Namadchi, 2022
Keyword arguments:
section -- a dict type containing section info
sec_tag -- Section tag
mat_tag -- Material Tag
nf_dw -- Number of fibers along web depth
nf_tw -- Number of fibers along web thickness
nf_bf -- Number of fibers along flange width
nf_tf -- Number of fibers along flange thickness
"""
# unpack variables for readability
d, bf = section['d'], section['bf']
tf, tw = section['tf'], section['tw']
dw = d - 2*tf
y1, y2, y3, y4 = -d/2, -dw/2, dw/2, d/2
z1, z2, z3, z4 = -bf/2, -tw/2, tw/2, bf/2
ops.section('Fiber', sec_tag)
ops.patch('quad', mat_tag, nf_bf, nf_tf,
*[y1,z4], *[y1,z1], *[y2,z1], *[y2,z4])
ops.patch('quad', mat_tag, nf_tw, nf_dw,
*[y2,z3], *[y2,z2], *[y3,z2], *[y3,z3])
ops.patch('quad', mat_tag, nf_bf, nf_tf,
*[y3,z4], *[y3,z1], *[y4,z1], *[y4,z4])
def build_model():
"""
Builds the 2D Steel Moment resisting Frame Model
"""
## Units
m = 1.0 # Meters
KN = 1.0 # KiloNewtons
sec = 1.0 # Seconds
mm = 0.001*m # Milimeters
cm = 0.01*m # Centimeters
ton = KN*(sec**2)/m # mass unit (derived)
g = 9.81*(m/sec**2) # gravitational constant
ops.wipe()
ops.model('basic','-ndm',2,'-ndf',3)
L_x = 6.0*m # Span
L_y = 3.5*m # Story Height
# Node Coordinates Matrix (size : nn x 2)
node_coords = np.array([[0, 0], [L_x, 0],
[0, L_y], [L_x, L_y],
[0, 2*L_y], [L_x, 2*L_y],
[0, L_y], [L_x, L_y],
[0, 2*L_y], [L_x, 2*L_y]])
# Element Connectivity Matrix (size: nel x 2)
connectivity = [[1,3], [2,4],
[3,5], [4,6],
[7,8], [9,10],
[7,3], [8,4],
[9,5], [10,6]
]
# Get Number of elements
nel = len(connectivity)
# Distinguish beams, columns & hinges by their element tag ID
all_the_beams = [5, 6]
all_the_cols = [1, 2, 3, 4]
all_the_hinges = [7, 8, 9, 10]
# Columns are modeled using `Steel01` material, beams are
# assumed elastic and plastic hinges are modeled via `Bilin`
# material, proposed by *D. G. Lignos & H. Krawinkler*
# Uniaxial bilinear steel material with kinematic hardening
mat_S355 = {'ID':'Steel01',
'matTag':1,
'Fy':(3.55e5)*(KN/m**2),
'E0':(2.1e8)*(KN/m**2),
'b':0.01}
## Material used for plastic hinges
# Modified Ibarra-Medina-Krawinkler Deterioration Model with Bilinear Hysteretic Response (Bilin Material)
# https://ascelibrary.org/doi/full/10.1061/%28ASCE%29ST.1943-541X.0000376
mat_lignos = {'ID':'Bilin',
'matTag':2,
'K0':(64033.2)*(KN/m),
'as_Plus':0.00203545752454409,
'as_Neg':0.00203545752454409,
'My_Plus':101.175*(KN*m),
'My_Neg':-101.175*(KN*m),
'Lamda_S':1.50476106091578,
'Lamda_C':1.50476106091578,
'Lamda_A':1.50476106091578,
'Lamda_K':1.50476106091578,
'c_S':1, 'c_C':1,
'c_A':1, 'c_K':1,
'theta_p_Plus':0.0853883552651735,
'theta_p_Neg': 0.0853883552651735,
'theta_pc_Plus':0.234610805942179,
'theta_pc_Neg':0.234610805942179,
'Res_Pos': 0.4,
'Res_Neg': 0.4,
'theta_u_Plus': 0.4,
'theta_u_Neg': 0.4,
'D_Plus':1,
'D_Neg':1 }
# Main Beams and Columns
sections = {'IPE220':{'d':220.0*mm, 'tw':5.9*mm,
'bf':110.0*mm, 'tf':9.2*mm,
'A':33.4*(cm**2),
'I1':2772.0*(cm**4), 'I2':205.0*(cm**4)},
'HE180B':{'d':180.0*mm, 'tw':8.5*mm,
'bf':180.0*mm, 'tf':14.0*mm,
'A':65.3*(cm**2),
'I1':3830.0*(cm**4), 'I2':1360.0*(cm**4)}
}
# For columns, `nonlinearBeamColumn` and for beams,
# `elasticBeamColumn` is employed. Furthermore, `zeroLength`
# element is used to model plastic hinges.
# Main Nodes
[ops.node(n+1,*node_coords[n])
for n in range(len(node_coords))];
# Boundary Conditions
## Fixing the Base Nodes
[ops.fix(n, 1, 1, 1)
for n in [1, 2]];
## Tie the displacements (not rotations) in plastic hinges:
ops.equalDOF(3, 7, *[1,2])
ops.equalDOF(4, 8, *[1,2])
ops.equalDOF(5, 9, *[1,2])
ops.equalDOF(6, 10, *[1,2])
# Materials
## For Columns
ops.uniaxialMaterial(*mat_S355.values())
## For Plastic Hinges
ops.uniaxialMaterial(*mat_lignos.values())
# Adding Sections
## For Columns
W_section(sections['HE180B'],
1, mat_S355['matTag'], *[4, 2, 4, 2])
# Transformations
ops.geomTransf('PDelta', 1)
# Adding Elements
## Beams
[ops.element('elasticBeamColumn', e, *connectivity[e-1],
sections['IPE220']['A'], mat_S355['E0'],
sections['IPE220']['I1'], 1)
for e in all_the_beams];
## Columns
[ops.element('nonlinearBeamColumn', e, *connectivity[e-1],
4, 1, 1)
for e in all_the_cols];
## Plastic Hinges
[ops.element('zeroLength', e, *connectivity[e-1],
'-mat', mat_lignos['matTag'], '-dir', 6)
for e in all_the_hinges];
global m_1
D_L = 20.0*(KN/m) # Distributed load
C_L = 50.0*(KN) # Concentrated load
m_1 = 75.0*ton # lumped mass 1
# Now, loads & lumped masses will be added to the domain.
loaded_nodes = [3,4,5,6]
loaded_elems = [5,6]
ops.timeSeries('Linear',1,'-factor',1.0)
ops.pattern('Plain', 1, 1)
[ops.load(n, *[0,-C_L,0]) for n in loaded_nodes];
ops.eleLoad('-ele', *loaded_elems,'-type', '-beamUniform',-D_L)
[ops.mass(n, *[m_1*0.5,0,0]) for n in loaded_nodes];
print('Model built successfully!')
def run_gravity(steps = 10):
"""
Runs gravity analysis.
Note that the model should be built before
calling this function.
Keyword arguments:
steps -- total number of analysis steps
"""
ops.initialize()
# Records the response of a number of nodes at every converged step
ops.recorder('Node', '-file', 'assets/Gravity_Reactions.out',
'-time','-node', *[1,2], '-dof', *[1,2,3], 'reaction')
# plain constraint handler enforces homogeneous single point constraints
ops.constraints('Plain')
# RCM numberer uses the reverse Cuthill-McKee scheme to order the matrix equations
ops.numberer('RCM')
# Constructs a profileSPDSOE (Symmetric Positive Definite) system of equation object
ops.system('ProfileSPD')
# Uses the norm of the left hand side solution vector of the matrix equation to
# determine if convergence has been reached
ops.test('NormDispIncr', 1.0e-6, 100, 0, 2)
# Uses the Newton-Raphson algorithm to solve the nonlinear residual equation
ops.algorithm('Newton')
# Uses LoadControl integrator object
ops.integrator('LoadControl', 0.1)
# Constructs the Static Analysis object
ops.analysis('Static')
# Records the current state of the model
ops.record()
# Performs the analysis
ops.analyze(steps)
print("Gravity analysis Done!")
def run_modal(n_evs = 2):
"""
Runs Modal analysis.
Note that the model should be built before
calling this function.
Keyword arguments:
n_evs -- number of eigenvalues
"""
ops.initialize()
# Records Eigenvector entries for Node 1,3 & 5 @ dof 1
ops.recorder('Node', '-file',
'assets/ModalAnalysis_Node_EigenVectors_EigenVec1.out',
'-node', *[1,3,5], '-dof', 1, 'eigen 1')
ops.recorder('Node', '-file',
'assets/ModalAnalysis_Node_EigenVectors_EigenVec2.out',
'-node', *[1,3,5], '-dof', 1, 'eigen 2')
# Constructs a transformation constraint handler,
# which enforces the constraints using the transformation method.
ops.constraints('Transformation')
# Constructs a Plain degree-of-freedom numbering object
# to provide the mapping between the degrees-of-freedom at
# the nodes and the equation numbers.
ops.numberer('Plain')
# Construct a BandGeneralSOE linear system of equation object
ops.system('BandGen')
# Uses the norm of the left hand side solution vector of the matrix equation to
# determine if convergence has been reached
ops.test('NormDispIncr', 1.0e-12, 25, 0, 2)
# Uses the Newton-Raphson algorithm to solve the nonlinear residual equation
ops.algorithm('Newton')
# Create a Newmark integrator.
ops.integrator('Newmark', 0.5, 0.25)
# Constructs the Transient Analysis object
ops.analysis('Transient')
# Eigenvalue analysis
lamda = np.array(ops.eigen(n_evs))
# Writing Eigenvalue information to file
with open("assets/ModalAnalysis_Node_EigenVectors_EigenVal.out", "w") as eig_file:
# Writing data to a file
eig_file.write("lambda omega period frequency\n")
for l in lamda:
line_to_write = [l, l**0.5, 2*np.pi/(l**0.5), (l**0.5)/(2*np.pi)]
eig_file.write('{:2.6e} {:2.6e} {:2.6e} {:2.6e}'.format(*line_to_write))
eig_file.write('\n')
# Record eigenvectors
ops.record()
print("Modal analysis Done!")
def run_pushover(steps = 5000):
"""
Runs Pushover analysis.
Note that the model should be built before
calling this function. Also, Gravity analysis
should be called afterwards. Morover, the function
requires some components of eigenvectors obtained
by calling the `run_modal` function.
Keyword arguments:
steps -- total number of analysis steps
"""
## Records the response of a number of nodes at every converged step
# Global behaviour
# records horizontal reactions of node 1 & 2
ops.recorder('Node', '-file',
'assets/Pushover_Horizontal_Reactions.out',
'-time','-node', *[1,2], '-dof', 1, 'reaction')
# records horizontal displacements of node 3 & 5
ops.recorder('Node','-file',
'assets/Pushover_Story_Displacement.out',
'-time','-node', *[3,5], '-dof',1, 'disp')
# Local behaviour
# records Mz_1 & Mz_2 for each hinge. other forces are zero
ops.recorder('Element','-file',
'assets/Pushover_BeamHinge_GlbForc.out',
'-time','-ele', *[7,8,9,10],'force')
# records the rotation of each hinges, ranging from 7 to 10
ops.recorder('Element','-file',
'assets/Pushover_BeamHinge_Deformation.out',
'-time','-eleRange',*[7, 10], 'deformation')
# records Px_1,Py_1,Mz_1,Px_2,Py_2,Mz_2 for elements 1 to 4
ops.recorder('Element','-file',
'assets/Pushover_Column_GlbForc.out',
'-time','-eleRange',*[1,4], 'globalForce')
# eps, theta_1, theta_2 for elements 1 to 4
ops.recorder('Element','-file',
'assets/Pushover_Column_ChordRot.out',
'-time','-ele', *[1,2,3,4], 'chordRotation')
# Measure analysis duration
tic = time.time()
# load eigenvectors for mode 1
phi = np.abs(np.loadtxt('assets/ModalAnalysis_Node_EigenVectors_EigenVec1.out'))
# Apply lateral load based on first mode shape in x direction (EC8-1)
ops.pattern('Plain', 2, 1)
[ops.load(n, *[m_1*phi[1],0,0]) for n in [3,4]];
[ops.load(n, *[m_1*phi[2],0,0]) for n in [5,6]];
# Define step parameters
step = +1.0e-04
number_of_steps = steps
# Constructs a transformation constraint handler,
# which enforces the constraints using the transformation method.
ops.constraints('Transformation')
# RCM numberer uses the reverse Cuthill-McKee scheme to order the matrix equations
ops.numberer('RCM')
# Construct a BandGeneralSOE linear system of equation object
ops.system('BandGen')
# Uses the norm of the left hand side solution vector of the matrix equation to
# determine if convergence has been reached
ops.test('NormDispIncr', 0.000001, 100)
# Line search increases the effectiveness of the Newton method
# when convergence is slow due to roughness of the residual.
ops.algorithm('NewtonLineSearch',True, 0.8,
1000, 0.1, 10.0)
# Displacement Control tries to determine the time step that will
# result in a displacement increment for a particular degree-of-freedom
# at a node to be a prescribed value.
# Target node is 5 and dof is 1
ops.integrator('DisplacementControl',5,1, step)
# Constructs the Static Analysis object
ops.analysis('Static')
# Records the current state of the model
ops.record()
# Performs the analysis
ops.analyze(number_of_steps)
toc = time.time()
print('Pushover Analysis Done in {:1.2f} seconds'.format(toc-tic))
def run_time_history(g_motion_id = 1, scaling_id = 1,
lamda = 1, acc_dir = 'assets/acc_1.txt'):
"""
Runs Time history analysis.
Note that the model should be built before
calling this function. Also, Gravity analysis
should be called afterwards.
Keyword arguments:
g_motion_id -- Groundmotion id (in case you run many GMs, like in an IDA)
scaling_id -- Scaling id (in case you run many GMs, like in an IDA)
lamda -- Scaling of the GM
acc_dir -- file directory of GM to read from
"""
## Records the response of a number of nodes at every converged step
# Global behaviour
# records horizontal reactions of node 1 & 2
ops.recorder('Node','-file',
('assets/TimeHistory_Horizontal_Reactions.'
+str(g_motion_id)+'.'+str(scaling_id)+'.out'),
'-time','-node',*[1,2],'-dof',1,'reaction')
# records horizontal displacements of node 3 & 5
ops.recorder('Node','-file',
('assets/TimeHistory_Story_Displacement.'
+str(g_motion_id)+'.'+str(scaling_id)+'.out'),
'-time','-node',*[3,5],'-dof',1,'disp')
# Local behaviour
# records Mz_1 & Mz_2 for each hinge. other forces are zero
ops.recorder('Element','-file',
('assets/TimeHistory_BeamHinge_GlbForc.'
+str(g_motion_id)+'.'+str(scaling_id)+'.out'),
'-time','-ele',*[7,8,9,10],'force')
# records the rotation of each hinges, ranging from 7 to 10
ops.recorder('Element','-file',
('assets/TimeHistory_BeamHinge_Deformation.'
+str(g_motion_id)+'.'+str(scaling_id)+'.out'),
'-time','-eleRange',*[7,10],'deformation')
# records Px_1,Py_1,Mz_1,Px_2,Py_2,Mz_2 for elements 1 to 4
ops.recorder('Element','-file',
('assets/TimeHistory_Column_GlbForc.'
+str(g_motion_id)+'.'+str(scaling_id)+'.out'),
'-time','-eleRange',*[1,4],'globalForce')
# eps, theta_1, theta_2 for elements 1 to 4
ops.recorder('Element','-file',
('assets/TimeHistory_Column_ChordRot.'
+str(g_motion_id)+'.'+str(scaling_id)+'.out'),
'-time','-ele',*[1,2,3,4],'chordRotation')
# Reading omega for obraining Rayleigh damping model
omega = np.loadtxt('assets/ModalAnalysis_Node_EigenVectors_EigenVal.out',skiprows=1)[:,1]
xis = np.array([0.03, 0.03])
a_R, b_R = 2*((omega[0]*omega[1])/(omega[1]**2-omega[0]**2))*(
np.array([[omega[1],-omega[0]],
[-1/omega[1],1/omega[0]]])@xis)
## Analysis Parameters
accelerogram = np.loadtxt(acc_dir) # Loads accelerogram file
dt = 0.02 # Time-Step
n_steps = len(accelerogram) # Number of steps
tol = 1.0e-6 # prescribed tolerance
max_iter = 5000 # Maximum number of iterations per step
# Uses the norm of the left hand side solution vector of the matrix equation to
# determine if convergence has been reached
ops.test('NormDispIncr', tol, max_iter,0,0)
# RCM numberer uses the reverse Cuthill-McKee scheme to order the matrix equations
ops.numberer('RCM')
# Construct a BandGeneralSOE linear system of equation object
ops.system('BandGen')
# The relationship between load factor and time is input by the user as a
# series of discrete points
ops.timeSeries('Path', 2, '-dt', dt, '-values', *accelerogram, '-factor', lamda)
# allows the user to apply a uniform excitation to a model acting in a certain direction
ops.pattern('UniformExcitation', 3, 1,'-accel', 2)
# Constructs a transformation constraint handler,
# which enforces the constraints using the transformation method.
ops.constraints('Transformation')
# Create a Newmark integrator.
ops.integrator('Newmark', 0.5, 0.25)
# assign damping to all previously-defined elements and nodes
ops.rayleigh(a_R, b_R, 0.0, 0.0)
# Introduces line search to the Newton algorithm to solve the nonlinear residual equation
ops.algorithm('NewtonLineSearch',True,False,False,False,0.8,100,0.1,10.0)
# Constructs the Transient Analysis object
ops.analysis('Transient')
# Measure analysis duration
t = 0
ok = 0
print('Running Time-Histroy analysis with lambda=',lamda)
start_time = time.time()
final_time = ops.getTime() + n_steps*dt
dt_analysis = 0.1*dt
while (ok == 0 and t <= final_time):
ok = ops.analyze(1, dt_analysis)
t = ops.getTime()
finish_time = time.time()
if ok == 0:
print('Time-History Analysis Done in {:1.2f} seconds'.format(finish_time-start_time))
else:
print('Time-History Analysis Failed in {:1.2f} seconds'.format(finish_time-start_time))
ops.wipe()
def reset_analysis():
"""
Resets the analysis by setting time to 0,
removing the recorders and wiping the analysis.
"""
# Reset for next analysis case
## Set the time in the Domain to zero
ops.setTime(0.0)
## Set the loads constant in the domain
ops.loadConst()
## Remove all recorder objects.
ops.remove('recorders')
## destroy all components of the Analysis object
ops.wipeAnalysis()
Analysis
Gravity Analysis
build_model();
run_gravity();
reset_analysis();
ops.wipe();
Output:
Modified Ibarra-Medina-Krawinkler Model with Bilinear Hysteretic Response
Model built successfully!
Gravity analysis Done!
df_reactions = pd.read_table('assets/Gravity_Reactions.out', header=None,
sep=" ", names=["Pseudo-Time","R1_1","R1_2","R1_3","R2_1","R2_2","R2_3"])
df_reactions
Output:
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>Pseudo-Time</th>
<th>R1_1</th>
<th>R1_2</th>
<th>R1_3</th>
<th>R2_1</th>
<th>R2_2</th>
<th>R2_3</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>0.0</td>
<td>0.000000</td>
<td>0</td>
<td>0.000000</td>
<td>0.000000</td>
<td>0</td>
<td>0.000000</td>
</tr>
<tr>
<th>1</th>
<td>0.1</td>
<td>0.749647</td>
<td>22</td>
<td>-0.884039</td>
<td>-0.749647</td>
<td>22</td>
<td>0.884039</td>
</tr>
<tr>
<th>2</th>
<td>0.2</td>
<td>1.499200</td>
<td>44</td>
<td>-1.768080</td>
<td>-1.499200</td>
<td>44</td>
<td>1.768080</td>
</tr>
<tr>
<th>3</th>
<td>0.3</td>
<td>2.248670</td>
<td>66</td>
<td>-2.652130</td>
<td>-2.248670</td>
<td>66</td>
<td>2.652130</td>
</tr>
<tr>
<th>4</th>
<td>0.4</td>
<td>2.998040</td>
<td>88</td>
<td>-3.536180</td>
<td>-2.998040</td>
<td>88</td>
<td>3.536180</td>
</tr>
<tr>
<th>5</th>
<td>0.5</td>
<td>3.747320</td>
<td>110</td>
<td>-4.420230</td>
<td>-3.747320</td>
<td>110</td>
<td>4.420230</td>
</tr>
<tr>
<th>6</th>
<td>0.6</td>
<td>4.496520</td>
<td>132</td>
<td>-5.304280</td>
<td>-4.496520</td>
<td>132</td>
<td>5.304280</td>
</tr>
<tr>
<th>7</th>
<td>0.7</td>
<td>5.245620</td>
<td>154</td>
<td>-6.188340</td>
<td>-5.245620</td>
<td>154</td>
<td>6.188340</td>
</tr>
<tr>
<th>8</th>
<td>0.8</td>
<td>5.994630</td>
<td>176</td>
<td>-7.072400</td>
<td>-5.994630</td>
<td>176</td>
<td>7.072400</td>
</tr>
<tr>
<th>9</th>
<td>0.9</td>
<td>6.743550</td>
<td>198</td>
<td>-7.956470</td>
<td>-6.743550</td>
<td>198</td>
<td>7.956470</td>
</tr>
<tr>
<th>10</th>
<td>1.0</td>
<td>7.492370</td>
<td>220</td>
<td>-8.840540</td>
<td>-7.492370</td>
<td>220</td>
<td>8.840540</td>
</tr>
</tbody>
</table>
</div>
Pseudo-Time R1_1 R1_2 R1_3 R2_1 R2_2 R2_3
0 0.0 0.000000 0 0.000000 0.000000 0 0.000000
1 0.1 0.749647 22 -0.884039 -0.749647 22 0.884039
2 0.2 1.499200 44 -1.768080 -1.499200 44 1.768080
3 0.3 2.248670 66 -2.652130 -2.248670 66 2.652130
4 0.4 2.998040 88 -3.536180 -2.998040 88 3.536180
5 0.5 3.747320 110 -4.420230 -3.747320 110 4.420230
6 0.6 4.496520 132 -5.304280 -4.496520 132 5.304280
7 0.7 5.245620 154 -6.188340 -5.245620 154 6.188340
8 0.8 5.994630 176 -7.072400 -5.994630 176 7.072400
9 0.9 6.743550 198 -7.956470 -6.743550 198 7.956470
10 1.0 7.492370 220 -8.840540 -7.492370 220 8.840540
Modal Analysis
build_model();
run_modal();
reset_analysis();
ops.wipe();
Output:
Model built successfully!
Modal analysis Done!
df_eigs = pd.read_table('assets/ModalAnalysis_Node_EigenVectors_EigenVal.out', sep=" ")
df_eigs
Output:
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>lambda</th>
<th>omega</th>
<th>period</th>
<th>frequency</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>7.352407</td>
<td>2.711532</td>
<td>2.317208</td>
<td>0.431554</td>
</tr>
<tr>
<th>1</th>
<td>102.341000</td>
<td>10.116370</td>
<td>0.621091</td>
<td>1.610071</td>
</tr>
</tbody>
</table>
</div>
lambda omega period frequency
0 7.352407 2.711532 2.317208 0.431554
1 102.341000 10.116370 0.621091 1.610071
Pushover Analysis
build_model();
run_gravity();
reset_analysis();
run_pushover();
ops.wipe();
Output:
Model built successfully!
Gravity analysis Done!
Pushover Analysis Done in 1.68 seconds
Time-History Analysis
build_model();
run_gravity();
reset_analysis();
run_time_history();
ops.wipe();
Output:
Model built successfully!
Gravity analysis Done!
Running Time-Histroy analysis with lambda= 1
Time-History Analysis Done in 17.42 seconds
Visualization
Pushover Analysis
pover_x_react = np.loadtxt('assets/Pushover_Horizontal_Reactions.out')
pover_story_disp = np.loadtxt('assets/Pushover_Story_Displacement.out')
plt.figure(figsize=(10,5))
plt.plot(pover_story_disp[:,2],
-(pover_x_react[:,1]+pover_x_react[:,2]), color = '#2ab7ca', linewidth=1.75)
plt.ylabel('Base Shear (KN)', {'fontname':'Cambria', 'fontstyle':'italic','size':14})
plt.xlabel('Roof Displacement (m)', {'fontname':'Cambria', 'fontstyle':'italic','size':14})
plt.grid(which='both')
plt.title('Pushover Curve',{'fontname':'Cambria', 'fontstyle':'normal','size':16})
plt.yticks(fontname = 'Cambria', fontsize = 14)
plt.xticks(fontname = 'Cambria', fontsize = 14);
Output:
<Figure size 720x360 with 1 Axes>
Time History Analysis
Ground Motion histroy
G_M =np.loadtxt('assets/acc_1.txt')
times = np.arange(0,0.02*len(G_M),0.02)
plt.figure(figsize=(12,4))
plt.plot(times,G_M, color = '#6495ED', linewidth=1.2)
plt.ylabel('Acceleration (m/s2)', {'fontname':'Cambria', 'fontstyle':'italic','size':14})
plt.xlabel('Time (sec)', {'fontname':'Cambria', 'fontstyle':'italic','size':14})
plt.grid(which='both')
plt.title('Time history of Ground Motion record',{'fontname':'Cambria', 'fontstyle':'normal','size':16})
plt.yticks(fontname = 'Cambria', fontsize = 14);
plt.xticks(fontname = 'Cambria', fontsize = 14);
Output:
<Figure size 864x288 with 1 Axes>
Time history of story displacement
story_disp_th = np.loadtxt('assets/TimeHistory_Story_Displacement.1.1.out')
plt.figure(figsize=(12,5))
plt.plot(story_disp_th[:,0], story_disp_th[:,1], color = '#DE3163', linewidth=1.2)
plt.plot(story_disp_th[:,0], story_disp_th[:,2], color = '#FFBF00', linewidth=1.2)
plt.ylabel('Horizontal Displacement (m)', {'fontname':'Cambria', 'fontstyle':'italic','size':14})
plt.xlabel('Time (sec)', {'fontname':'Cambria', 'fontstyle':'italic','size':14})
plt.grid(which='both')
plt.title('Time history of horizontal dispacement',{'fontname':'Cambria', 'fontstyle':'normal','size':16})
plt.yticks(fontname = 'Cambria', fontsize = 14);
plt.xticks(fontname = 'Cambria', fontsize = 14);
plt.legend(['Story 1', 'Story 2'],prop={'family':'Cambria','size':14});
Output:
<Figure size 864x360 with 1 Axes>
References
-
Fernando Gutiérrez Urzúa's YouTube channel (https://www.youtube.com/user/lfgurzua)
-
OpenseesPy Documentation (https://openseespydoc.readthedocs.io/en/latest/)