Eccentrically Loaded Bolt Groups

Tables 7-6 through 7-13 of the AISC Steel Manual contain values for C, the effective number of bolts that resist shear in eccentrically loaded bolt groups.

For example, in a bolt group with three vertical rows of 4 bolts spaced s=3 inch with srow=3 inch row spacing and a load at \theta=30 degrees from vertical at a horizontal eccentricity of eX=12 inch from the bolt group centroid, the coefficient is C=3.82, i.e., the available strength of this bolt group is 3.82 times (not 12 times) the available strength of one bolt.

The tabulated values of C are based on the Instantaneous Center of Rotation (IC) Method. The Steel Manual also describes the Elastic Method to compute C, but that approach can be too conservative. The IC Method assumes all bolts in the group rotate about an instantaneous center of rotation under the action of the eccentric load.

OpenSees can handle the IC method pretty easily using zero length elements and a rigid diaphragm constraint; however, we have to define the model in 3D to accommodate the instantaneous center of rotation.

import openseespy.opensees as ops
import math

ops.wipe()
ops.model('basic','-ndm',3,'-ndf',6)

In addition to the kinematic assumption, the IC Method assumes a nonlinear force-deformation relationship for each bolt, R=R_{ult}(1-e^{-10\Delta})^{0.55}, where R_{ult} is the bolt ultimate strength, which we can assume is 1 kip in order to compute the C coefficients. The maximum bolt deformation is assumed to be 0.34 inch.

OpenSees does not have a uniaxialMaterial or hystereticBackbone model that fits this function, but it’s easy enough the create the force-deformation relationship as a MultiLinear material.

Rult = 1.0 # Bolt strength
dmax = 0.34 # Maximum bolt deformation

N = 10 # Number of points
uf = [] # Array of deformation and force values
for i in range(1,N+1):
    d = i*dmax/N
    uf.append(d)
    R = Rult*(1-math.exp(-10*d))**0.55
    uf.append(R)

ops.uniaxialMaterial('MultiLinear',1,*uf)

Next, the force-deformation response needs to be applied in arbitrary directions for each bolt. We cannot use a regular zeroLength element because its materials act in orthogonal directions, uncoupled. Instead, we can use a CoupledZeroLength element which applies a uniaxial force-deformation relationship based on the strain magnitude and direction, i.e., “along the hypotenuse”.

For each bolt, we create two collocated nodes–one completely fixed (tag < 0) and the other (tag > 0) with out of plane DOFs constrained–then we define a CoupledZeroLength element. Finally, we define an additional node (tag = 0) at the point of loading (with out of plane DOFs constrained) and enforce a rigid diaphragm constraint with the loaded node as primary and the bolt nodes (tag > 0) as secondary.

s = 3 # bolt spacing
ex = 12 # load eccentricity
n = 4 # bolts per row
Nrows = 3 # number of rows
srow = 3 # row spacing

theta = 30*math.pi/180 # load direction

ndTags = []
for r in range(Nrows):
    for i in range(n):
        ndTag = r*n + i + 1
        ndTags.append(ndTag)
        ops.node(-ndTag,-r*srow,i*s,0)
        ops.fix(-ndTag,1,1,1,1,1,1)
        ops.node(ndTag,-r*srow,i*s,0)
        ops.fix(ndTag,0,0,1,1,1,0)
    
        ops.element('CoupledZeroLength',ndTag,-ndTag,ndTag,1,2,1)

ops.node(0,ex-srow*(Nrows-1)/2,s*(n-1)/2,0)
ops.fix(0,0,0,1,1,1,0)
ops.rigidDiaphragm(3,0,*ndTags)

This gives the 3D model shown below, plotted with opsvis.

We then apply a load at the primary node and perform nonlinear static analysis until the deformation of any bolt reaches the 0.34 inch limit prescribed in the IC method.

ops.timeSeries('Linear',1)
ops.pattern('Plain',1,1)
ops.load(0,-math.sin(theta),-math.cos(theta),0,0,0,0)

ops.test('NormDispIncr',1e-6,10,0)
ops.algorithm('KrylovNewton')
ops.system('UmfPack')
ops.constraints('Transformation')

Pmax = Rult*Nrows*n # Maximum possible load
dP = 0.01
Nsteps = int(Pmax/dP)
ops.integrator('LoadControl',dP)

ops.analysis('Static')

for i in range(Nsteps):
    ok = ops.analyze(1)
    if ok < 0:
        break
    stopAnalysis = False
    for j in ops.getEleTags():
        eps = ops.eleResponse(j,'material','strain')
        if abs(eps[0]) >= dmax:
            stopAnalysis = True
            break
    if stopAnalysis:
        break
            
C = ops.getLoadFactor(1)/Rult

I resorted to the KrylovNewton algorithm because it seems the CoupledZeroLength element does not return a consistent tangent, which led to convergence problems when I ran the analysis with the default Newton-Raphson algorithm.

Anyway, the coefficient computed by the OpenSees analysis is C=3.82, right on with the value tabulated in Table 7-10 of the Steel Manual!

The displaced shape of the model (magnification=5) at the end of the analysis is shown below, where we can see the bolt group has moved about an instantaneous center of rotation located somewhere around (X,Y)=(-4,6).

It would be a snap to put this code in nested loops to generate all of Tables 7-6 through 7-13 and to explore specific cases not shown in the tables.

And add this to the list of things you didn’t know could be done with OpenSees.


This post was suggested by a collaborator and seconded by current and former colleagues in Eastchester.

One thought on “Eccentrically Loaded Bolt Groups

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.