OpenSees Cloud
OpenSees AMI
Minimal Plotting Example
Original Post - 31 Jul 2023 - Michael H. Scott
Visit Structural Analysis Is Simple on Substack.
OpenSees is not a GUI. OpenSees is an API.
While “no GUI and difficult to use” is a frequent knock, “no GUI” is actually a strength of OpenSees, not a weakness. “No GUI” means the developers remain focused on the core computational framework instead of getting mired in pop-up windows and click bindings.
But even if you’re comfortable with no graphical user input, you still need some graphical user output from OpenSees. What is the point of response history analyses if you can’t see the lovely IDA curves that result?
Because OpenSees is an API, you need something extra to plot results.
If you use OpenSees.exe (Tcl), you will need to incorporate third party
software into your plotting workflow. MATLAB is the most common choice
because you can generate production quality figures pretty easily. And
don’t tell me gnuplot
is a viable approach to keeping the plotting
workflow in Tcl.
Strangely, I’ve seen some people use Python as the third party plotting software for their OpenSees.exe analysis results. No, I’m not talking about OpenSeesPy. I’m talking about using OpenSees.exe, the Tcl version, then using Python to plot.
Usually this awkward workflow is borne out of inheriting a set of Tcl scripts from other team members. And instead of spending two hours now to convert scripts to save 200 hours later, the new user will keep trucking with the Tcl-Python workflow. In some cases, the team will convince themselves this stilted workflow is a publishable “framework”, especially if a dash of Excel can be added to the mix.
But anyway, if you use OpenSeesPy, there’s no need for third party
software in your plotting workflow. First, make a minimal set of imports
to openseespy
,
matplotlib
,
and numpy
.
import openseespy.opensees as ops
import matplotlib.pyplot as plt
import numpy as np
Then define your model, e.g., a two-DOF elastic mass-spring system, but the same concepts apply to RC moment frames.
m = 1.0
k = 600
c = 4
g = 386.4
ops.wipe()
ops.model('basic','-ndm',1,'-ndf',1)
ops.node(0,0); ops.fix(0,1)
ops.node(1,0); ops.mass(1,m)
ops.node(2,0); ops.mass(2,m)
ops.uniaxialMaterial('Elastic',1,k,c)
ops.element('zeroLength',1,0,1,'-mat',1,'-dir',1)
ops.element('zeroLength',2,1,2,'-mat',1,'-dir',1)
ops.timeSeries('Path',1,'-dt',0.02,'-filePath','tabasFN.txt')
ops.pattern('UniformExcitation',1,1,'-accel',1,'-factor',g)
From here, there are three basic approaches to plotting the response history results, e.g., the roof displacement and the base shear.
Recorder + np.loadtxt
The most straightforward approach is to save the results to files using
recorders, load the files using
np.loadtxt
,
then finally plot using matplotlib
.
ops.recorder('Node','-file','dispTHA.out','-time','-node',2,'-dof',1,'disp')
ops.recorder('Node','-file','baseTHA.out','-time','-node',0,'-dof',1,'reaction')
ops.analysis('Transient')
ops.analyze(2000,0.02)
disp = np.loadtxt('dispTHA.out')
base = np.loadtxt('baseTHA.out')
plt.figure()
plt.subplot(2,1,1)
plt.plot(disp[:,0],disp[:,1])
plt.subplot(2,1,2)
plt.plot(base[:,0],base[:,1])
plt.show()
The advantage here is the results are saved to files so you can work with the data elsewhere. The disadvantages are you can end up with a lot of files and you may need to join different files in order to make certain plots.
DIY Filestream + np.loadtxt
If you want to record disparate response quantities into custom formatted files, you can open a file stream in Python and use utility functions to write the results at every time step.
ops.analysis('Transient')
output = open('results.out','w')
for i in range(2000):
ops.analyze(1,0.02)
ops.reactions()
output.write(f'{ops.getTime()} {ops.nodeDisp(2,1)} {ops.nodeReaction(0,1)}\n')
output.close()
data = np.loadtxt('results.out')
plt.figure()
plt.subplot(2,1,1)
plt.plot(data[:,0],data[:,1])
plt.subplot(2,1,2)
plt.plot(data[:,0],data[:,2])
plt.show()
The advantage of this approach is you can minimize the number of files
required to store your analysis results–you could jam everything into
one file if you were so inclined. However, the disadvantage is the
analysis will run a little slower due to a) all the calls to utility
functions and b) calling ops.analyze()
one step at a time. And don’t
forget to call ops.reactions()
inside the analysis loop.
Plotting In Memory
The third approach is to use utility functions to obtain analysis results and store the data in lists within your script.
ops.analysis('Transient')
tplot = []
uplot = []
fplot = []
for i in range(2000):
ops.analyze(1,0.02)
ops.reactions()
tplot.append(ops.getTime())
uplot.append(ops.nodeDisp(2,1))
fplot.append(ops.nodeReaction(0,1))
plt.figure()
plt.subplot(2,1,1)
plt.plot(tplot,uplot)
plt.subplot(2,1,2)
plt.plot(tplot,fplot)
plt.show()
The advantage of plotting in-memory is you don’t produce any files,
which may be an important consideration for web applications. If you do
want to save the results to a file, use
np.savetxt
.
The disadvantage is
you slow the analysis down a little bit more by appending the results to
dynamically sized lists at every time step.
Final Result
Regardless of which method you use, you will get the following plot for the roof displacement and base shear of the simple two-DOF elastic system.
Because the example is minimal, I did zero titling, labeling, and formatting. I’ll leave that to you.