OpenSees Cloud
OpenSees AMI
Static Analysis with Uniform Excitation
Original Post - 28 Jul 2024 - Michael H. Scott
Show your support at Buy Me a Coffee.
The UniformExcitation defines reference nodal loads in proportion to the 
mass (nodal plus element contributions), multiplied by negative 
acceleration, which is specified in a time series. There’s nothing 
inherent in its implementation that ties the UniformExcitation to only 
dynamic analysis and earthquake excitations.
So, if I had known sooner that the UniformExcitation load pattern works 
in a static analysis, a few posts would have been much easier to write.
Rather than use simple 1D models to show what I mean, in this post I 
will use an OpenSees model of a water tower developed by 
Björnsson and Krishnan (2014), 
plotted below using 
opsvis.

This model has been used as a project for several on-campus and Ecampus deliveries of nonlinear structural analysis (CE 537) in Eastchester.
Gravity Loads
For frame models, it’s usually straightforward to define mass and weight separately at each node. There’s nothing wrong with that approach.
But let’s suppose you have defined the mass in your frame model and you don’t want to define gravity loads manually. The following code will impose gravity loads, without you having to define nodal loads.
#
# Define your model, include mass
#
DIR = 3 # 1 = X, 2 = Y, 3 = Z
ops.timeSeries('Constant',1)
ops.pattern('UniformExcitation',1,DIR,'-accel',1,'-factor',g)
ops.system('UmfPack')
ops.integrator('LoadControl',0.0)
ops.analysis('Static')
ops.analyze(1)
ops.reactions()
Note that the UniformExcitation will flip the sign on acceleration so 
that loads are applied in the negative Z-direction (or whatever 
direction you want). Thus, the positive g as the scale factor.
With the mass of the water tower model prescribed in Björnsson and Krishnan (2014), applying a uniform excitation gives a vertical reaction of 1562 kip at each support. The total weight of the water tower is 6248 kip.

This approach to defining gravity loads will work for any frame or solid model, and will also work for element mass density (as long as the elements in your model implement getResistingForce() correctly, e.g., as shown here).
With the UniformExcitation, all the rigmarole with the Diagonal system, 
the GimmeMCK integrator, the printA function, and loops shown in 
this post
is rendered unnecessary.
The Rayleigh Quotient
We can obtain an approximate mode shape for a model by applying static loads in proportion to the mode’s distribution of mass. This mode shape can then be used to calculate the Rayleigh quotient, which usually gives a very good approximation of the fundamental period.
A previous post 
showed the Rayleigh quotient for a 1D model, and in 
doing so, avoided a lot of complication with applying the 
mass-proportional loads. But now, the UniformExcitation makes the load 
application much easier for any size model.
#
# Define your model, include mass
#
DIR = 1 # 1=X, 2=Y, 3=Z
a = 0.01*g
ops.timeSeries('Constant',1)
ops.pattern('UniformExcitation',1,DIR,'-accel',1,'-factor',-a)
ops.analysis('Static','-noWarnings')
ops.analyze(1)
num = 0; den = 0
for nd in ops.getNodeTags():
    for dof in range(1,DOF+1):
        mj = ops.nodeMass(nd,dof)
        uj = ops.nodeDisp(nd,dof)
        num += mj*uj
        den += mj*uj*uj
    
# Rayleigh quotient
w2 = a*num/den
# Eigenvalue
w2 = ops.eigen(2)[0]
Again, note that the UniformExcitation will flip the sign on the 
acceleration, thus the negative acceleration. Here, a small acceleration 
(0.01*g) is used so that model response is not nonlinear.
The code above assumes all nodal mass. You’re on your own calculating 
the Rayleigh quotient if you use elements with mass density. However, 
after doing a static analysis with UniformExcitation, you can use 
GimmeMCK and printA to get the assembled mass matrix, then do scalar 
triple products with the nodal displacements.
For vibration of the water tower model in the X-direction, the Rayleigh quotient gives 22.66 rad2/sec2, which corresponds to a natural period of 1.32 sec. Eigenvalue analysis of the model gives 22.09 rad2</suo>/sec2 and 1.34 sec.

Load Control Pushover
Because the UniformExcitation defines a reference load pattern, we can 
use this pattern to perform a load-controlled pushover analysis.
In their paper, Björnsson and Krishnan (2014) did a pushover analysis of the water tower based on dynamic response to a “slow, ramped, horizontal ground acceleration that increases at a constant rate of 0.3g/min”. Talked about this approach to pushover analysis in this post, but didn’t really go into much detail.
But now, with the UniformExcitation, we can perform static analysis 
marching through pseudo-time with a linear time series and factor equal 
to the 0.3g/min jerk.
sec = 1
min = 60*sec
#
# Define model, include mass
#
DIR = 1 # 1=X, 2=Y, 3=Z
ops.timeSeries('Linear',1)
ops.pattern('UniformExcitation',1,DIR,'-accel',1,'-factor',-0.3*g/min)
Tfinal = 180*sec
dt = 1*sec
Nsteps = int(Tfinal/dt)
ops.integrator('LoadControl',dt)
ops.analysis('Static','-noWarnings')
ops.analyze(Nsteps)
The base shear-roof displacement response of the water tower model is shown below. The analysis fails to converge at a base shear of about 9000 kN.

The deformed shape of the water tower model (scale=10) at the final analysis step shows the first story braces beginning to buckle.

Because the UniformExcitation defines load in proportion to the mass 
distribution of the model, we essentially just did a first mode modal 
pushover.
Displacement Control Pushover
Widely known and shown in the previous section, load-controlled pushover analyses are not able to track post-peak response, which may or may not be important (probably not important in this case). But if you do want the post-peak response, displacement control is the way to go.
Just like load control, the UniformExcitation defines a reference load 
pattern based on the distribution of mass, then we control the 
displacement at a single DOF. You don’t have to apply a reference load 
at and in the direction of the controlled DOF–node 700, referenced in 
the code below, is on the roof of the water tower model.
#
# Define model, include mass
#
DIR = 1 # 1=X, 2=Y, 3=Z
ops.timeSeries('Linear',3)
ops.pattern('UniformExcitation',2,DIR,'-accel',3,'-factor',-0.1*g)
Umax = 0.6*m
Nsteps = 200
dU = Umax/Nsteps
ops.integrator('DisplacementControl',700,DIR,dU)
ops.analysis('Static','-noWarnings')
ops.analyze(Nsteps)
Also, note that the 0.1*g factor applied in the UniformExcitation leads 
to large enough loads to make the water tower displace, but not so small 
or so large that the displacement controlled analysis has convergence 
issues.
The base shear-roof displacement response of the water tower model is shown below. The displacement-control solution is able to capture the post-peak response.

The final deformed shape (scale=10) shows buckling of the braces in the first story of the water tower model.

That you can do so much with the UniformExcitation in OpenSees is 
another testament to Frank’s good software design.