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.