OpenSees Cloud
OpenSees AMI
A Model of Inconsistency
Original Post - 10 Aug 2025 - Michael H. Scott
Visit Structural Analysis Is Simple on Substack.
Except for matrix storage schemes, I can explain every high level analysis piece of OpenSees with a simple two DOF spring model. The model has three springs, each with bilinear force-deformation response.
This model first appeared in Scott and Fenves (2010) and then in a post to demonstrate what happens when you use an inconsistent tangent with Newton-Raphson and its variants.
As described in the article, imposing an artificial stiffness coupling between springs 1 and 2 gives the following inconsistent tangent stiffness matrix for the model
\[{\bf K}_T = \left[ \begin{array}{cc} k_1+k_2-1 & -k_2+0.5 \\ -k_2+0.5 & k_2+k_3 \end{array} \right]\]where k1, k2, and k3 are the tangent stiffness of springs 1, 2, and 3, respectively.
In preparing the article, I used a standalone MATLAB script to analyze the spring model–adding inconsistent stiffness to the global system of equations was not so easy in OpenSees at the time.
Now, with the
BYOM approach,
we can wrap a dummy Elastic
material of
zero stiffness with the Penalty
material wrapper using the -noStress
option. That was a mouthful, but we’re adding stiffness without adding
resisting force and then using zero length elements to manipulate matrix
entries.
import openseespy.opensees as ops
ops.wipe()
ops.model('basic','-ndm',1,'-ndf',1)
ops.node(0,0); ops.fix(0,1)
ops.node(1,0)
ops.node(2,0)
ops.uniaxialMaterial('Steel01',1,10,10,0.1)
ops.uniaxialMaterial('Steel01',2,4,2,0.5)
ops.uniaxialMaterial('Steel01',3,7,7,0)
# Spring elements
ops.element('zeroLength',1,0,1,'-mat',1,'-dir',1)
ops.element('zeroLength',2,1,2,'-mat',2,'-dir',1)
ops.element('zeroLength',3,0,2,'-mat',3,'-dir',1)
# Dummy elastic material
ops.uniaxialMaterial('Elastic',0,0)
# Diagonal '-1' stiffness
ops.uniaxialMaterial('Penalty',4,0,-1,'-noStress')
ops.element('zeroLength',4,0,1,'-mat',4,'-dir',1)
# Off-diagonal '+0.5' stiffness
ops.uniaxialMaterial('Penalty',5,0,-0.5,'-noStress')
ops.element('zeroLength',5,1,2,'-mat',5,'-dir',1)
ops.uniaxialMaterial('Penalty',6,0,0.5,'-noStress')
ops.element('zeroLength',6,0,1,'-mat',6,'-dir',1)
ops.element('zeroLength',7,0,2,'-mat',6,'-dir',1)
ops.timeSeries('Constant',1)
ops.pattern('Plain',1,1)
ops.load(1,6)
ops.load(2,12)
ops.test('RelativeNormUnbalance',1e-4,150,1)
ops.algorithm('Newton') # Or whatever
ops.analysis('Static','-noWarnings')
ops.analyze(1)
We could combine elements between the same nodes, but let’s keep everything separate for clarity.
Anyway, use the Newton
algorithm for this analysis and watch the
iterations flip-flop around the correct solution of U1=2
and U2=5. Grind on the analysis with ModifiedNewton
and
dip below tolerance, sufficiently close to the correct solution, after
143 iterations. The KrylovNewton
and SecantNewton
algorithms should
get you to the correct solution after a few iterations. Use
ExpressNewton
and you’ll get somewhere–just not anywhere near the
correct solution.