Coding single degree of freedom (SDF) response in order to generate earthquake response spectra is a rite of passage in earthquake engineering research and education. I wrote my first response spectrum in MATLAB. Nowadays, people are likely to use Python.

To generate response spectra in OpenSees, you can create a simple one-dimensional model of SDF response. I like to use a zero length element, but if you prefer unnecessarily normalizing with respect to lollipop geometry, you can use a truss or beam element instead.

```
ops.model('basic','-ndm',1,'-ndf',1)
ops.node(1,0); ops.fix(1,1)
ops.node(2,0); ops.mass(2,1.0) # mass = 1.0
ops.uniaxialMaterial('Elastic',1,k) # k is mass*omega**2
ops.element('zeroLength',1,1,2,'-mat',1,'-dir',1)
```

The brute force approach to construct a response spectrum for a given ground motion would be to define a new model for each natural period. To get the maximum displacement, you can use an envelope node recorder then read the maximum displacement from the file.

```
while Tn <= Tnf:
k = (2*pi/Tn)**2 # mass = 1.0
# Define model with k
#
ops.timeSeries('Path', 1, '-dt', dtf, '-filePath', filename)
ops.pattern('UniformExcitation',1,1,'-accel',1)
# Define analysis options
#
ops.analysis('Transient')
ops.recorder('EnvelopeNode','-file','disp.out','-node', 2,'-dof',1,'disp')
ops.analyze(N,dt)
ops.wipe()
# Read umax from 'disp.out'
Tn += dTn
```

But, you’re redefining the model over and over, which is a waste of time because the stiffness is the only property that’s changing. A more efficient approach would be to define the model once, then use the `parameter`

and `updateParameter`

commands along with `reset`

.

```
# Define model
#
# Define time series and load pattern
#
# Define transient analysis
#
ops.parameter(1,'element',1,'E')
while Tn <= Tnf:
ops.reset()
k = (2*pi/Tn)**2 # mass = 1.0
ops.updateParameter(1,k)
tag = ops.recorder('EnvelopeNode','-file','disp.out','-node', 2,'-dof',1,'disp')
ops.analyze(N,dt)
ops.remove('recorder',tag)
# Read umax from 'disp.out'
Tn += dTn
```

Note that the envelope node recorder will get bogged down by the repeated analyses on one model. I’m not sure why that’s the case, but I used the `remove`

command to solve the issue.

Both the brute force and the update parameter approach take more time than necessary. OpenSees sets up the same analysis objects and uses the same runtime indirection whether it’s solving one equation or 100,000 equations. That computational infrastructure carries a lot of overhead.

So, while writing a paper on constant ductility spectra a few years ago, a colleague in Eastchester and I decided to add a command to OpenSees, `sdfResponse`

, that computes bilinear elasto-plastic response of an SDF system for any given mass, damping, stiffness, yield force, and input ground motion. The command implements in C++ a local Newmark time loop with inner Newton loop to solve for nonlinear SDF response history.

Here’s the relevant snippet of response spectrum code.

```
while Tn <= Tnf:
k = (2*pi/Tn)**2 # mass = 1.0
# m z k Fy alpha
u = ops.sdfResponse(1.0, 0, k, 1e16, 0.1, dtf, filename, dt)
umax = u[0]
Tn += dTn
```

Setting a high yield force makes the SDF system elastic. This command is also available with OpenSees Tcl.

Here is a time comparison between the three approaches in computing a linear-elastic response spectrum.

The single element OpenSees SDF modeling approaches take 5-8 times longer than `sdfResponse`

. The extra time will quickly add up when you generate spectra for many ground motions. We were able to generate constant ductility spectra for 46 ground motions in 30 minutes instead of 4 hours.