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.
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.
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.