OpenSees Cloud

OpenSees AMI

Two Node Link's Awakening

Original Post - 23 Feb 2025 - Michael H. Scott

Visit Structural Analysis Is Simple on Substack.


The twoNodeLink, implemented by Andreas Schellenberg, is one of the lesser utilized general purpose elements in OpenSees. In simple terms, the twoNodeLink element is a zeroLength element with length. And the element is not dis-similar to the link elements you will find in SAP.

Like the zeroLength element, the twoNodeLink element uses uncoupled uniaxial materials to define force-deformation response between two nodes where the deformation is the relative displacement between the nodes.

As far as I can tell, the primary use for twoNodeLink elements in OpenSees has been to define damper elements; however, the twoNodeLink can do so much more than dampers.

But the point of this post is to demonstrate the fundamental similarities and differences between zeroLength and twoNodeLink elements. Consider the simple case of axial loading shown below.

Zero length and two node link elements

The two elements take the same input: two nodes with an arbitrary number of uncoupled uniaxial materials acting along or about local axis directions; however, the twoNodeLink determines its local x-axis from the position vector between nodal locations whereas the zeroLength element requires user input for its local x-axis. In the code below, the local x-axis for the zeroLength element coincides with the global vertical axis.

import openseespy.opensees as ops
from math import isclose

k = 10 # Spring stiffness
P = 5  # Load
L = 10 # Link length

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

ops.node(0,0,0); ops.fix(0,1,1,1)

ops.uniaxialMaterial('Elastic',1,k)

ops.node(1,0,0); ops.fix(1,1,0,1)
ops.element('zeroLength',1,0,1,'-mat',1,'-dir',1,'-orient',0,1,0)

ops.node(2,0,L); ops.fix(2,1,0,1)
ops.element('twoNodeLink',2,0,2,'-mat',1,'-dir',1)

ops.timeSeries('Constant',1)
ops.pattern('Plain',1,1)
ops.load(1,0,P,0)
ops.load(2,0,P,0)

ops.analysis('Static','-noWarnings')
ops.analyze(1)

u1 = ops.nodeDisp(1,2)
u2 = ops.nodeDisp(2,2)

assert isclose(u1,P/k)
assert isclose(u2,P/k)

Even though they have different lengths, the twoNodeLink and zeroLength elements give the same nodal displacement in this case.

The twoNodeLink also handles flexural response, and this is where the element diverges from the zeroLength formulation. With a moment-rotation spring in the twoNodeLink, a shear distance, or center of rotation, input is required. By default, the center of rotation, c, is half way between the nodes, i.e., c=0.5, but can range anywhere from 0 to 1. In the model below, there are uncoupled translational and rotational springs in the elements and a lateral load.

Zero length and two node link elements

The lateral displacement for the zeroLength element will depend only on the translational spring while the lateral displacement with the twoNodeLink element also has a rotational component that depends on the rotational spring and the location of the center of rotation.

import openseespy.opensees as ops
from math import isclose

kt = 10 # Translational stiffness
kr = 20 # Rotational stiffness
P = 5  # Load
L = 10 # Link length
c = 0.5 # Shear distance

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

ops.node(0,0,0); ops.fix(0,1,1,1)

ops.uniaxialMaterial('Elastic',1,kt)
ops.uniaxialMaterial('Elastic',2,kr)

ops.node(1,0,0); ops.fix(1,0,1,0)
ops.element('zeroLength',1,0,1,'-mat',1,2,'-dir',2,3,'-orient',0,1,0)

ops.node(2,0,L); ops.fix(2,0,1,0)
ops.element('twoNodeLink',2,0,2,'-mat',1,2,'-dir',2,3,'-shearDist',c)

ops.timeSeries('Constant',1)
ops.pattern('Plain',1,1)
ops.load(1,P,0,0)
ops.load(2,P,0,0)

ops.analysis('Static','-noWarnings')
ops.analyze(1)

# X-displacement
u1 = ops.nodeDisp(1,1)
u2 = ops.nodeDisp(2,1)

assert isclose(u1,P/kt)
assert isclose(u2,P/kt + P*L*(1-c)/kr*L*(1-c))

# Rotation
u1 = ops.nodeDisp(1,3)
u2 = ops.nodeDisp(2,3)

assert isclose(u1,0,abs_tol=1e-10)
assert isclose(u2,-P*L*(1-c)/kr)

All four assertions should check out.



The Legend of Zelda reference aside, why does the title of this post imply an awakening of the twoNodeLink element? Well, if instead of uncoupled uniaxial materials we put a section that couples axial, shear, and moment between the two nodes, we have a more efficient and robust implementation of the MVLEM family of elements. All of the constitutive response is handled in the section, e.g., FiberSection and NDFiberSection, not repackaged as a somehow different element formulation in SFI-MVLEM and E-SFI-MVLEM. And we can use the patch, layer, and fiber commands to build wall sections, not confined to areas and reinforcing ratios.

I also suspect we can deal with bond slip models better than the non-sense using zero length section elements and concrete stress blocks that’s been floating around OpenSees for some time now. But I haven’t worked out those details yet.