Convergence of a solution algorithm typically means your model satisfies equilibrium and compatibility for the current applied loads and any nonlinear response.
But there are pathological cases where the algorithm converges while the model remains in a state that violates equilibrium or compatibility.
A simple case (brought to my attention by Chen Fei) involves a single force-based element with elastic sections and a uniform distributed load. The model is from a previous post.
import openseespy.opensees as opsfrom math import iscloseft = 1kip = 1inch = ft/12ksi = kip/inch**2L = 20*ftE = 29000*ksiI = 800*inch**4A = 15*inch**2w = 1.0*kip/ftops.wipe()ops.model('basic','-ndm',2,'-ndf',3)ops.node(1,0,0); ops.fix(1,1,1,1)ops.node(2,L,0); ops.fix(2,0,1,0)ops.geomTransf('Linear',1)Np = 3ops.section('Elastic',1,E,A,I)ops.beamIntegration('Lobatto',1,1,Np)ops.element('forceBeamColumn',1,1,2,1,1)ops.timeSeries('Constant',1)ops.pattern('Plain',1,1)ops.eleLoad('-ele',1,'-type','beamUniform',-w)ops.test('NormDispIncr',1e-8,10,1) # No equilibrium, assertion fails# ops.test('NormUnbalance',1e-8,10,1) # Equilibrium, assertion passesops.analysis('Static','-noWarnings')ops.analyze(1)ops.reactions()assert isclose (ops.nodeDisp(2,3),(w*L**3)/(48*E*I))
If the analysis uses NormDispIncr as the convergence test, a solution is found right away with the norm equal to zero.
CTestNormDispIncr::test() - iteration: 1 current Norm: 0 (max: 1e-08, Norm R: 33.3333)
However, the norm of the residual is 33.3333 at the final converged state–clearly not in equilibrium–and the assertion will fail.
This pathological convergence is due to how the force-based element handles member loads (not as equivalent nodal loads) and the boundary conditions are such that the model has no unconstrained translational DOFs. As a result, the nodes do not displace on the first iteration and the convergence test reports exactly zero–which is suspicious in and of itself.
If we switch the convergence test to NormUnbalance the analysis takes two iterations and we get to an acceptably small residual norm.
CTestNormUnbalance::test() - iteration: 1 current Norm: 33.3333 (max: 1e-08, Norm deltaX: 0)CTestNormUnbalance::test() - iteration: 2 current Norm: 1.40925e-14 (max: 1e-08, Norm deltaX: 0.00103448)
The assertion will pass in this case.
If we subdivide the member into two elements of length 0.4L and 0.6L and return to the NormDispIncr convergence test, the analysis will converge in three iterations, which is odd for a linear problem, but the assertion passes.
CTestNormDispIncr::test() - iteration: 1 current Norm: 0.00349945 (max: 1e-08, Norm R: 13.7275)CTestNormDispIncr::test() - iteration: 2 current Norm: 0.00105341 (max: 1e-08, Norm R: 4.59318e-15)CTestNormDispIncr::test() - iteration: 3 current Norm: 3.25479e-19 (max: 1e-08, Norm R: 7.32283e-15)
At any rate, if you’re using member loads on force-based elements, use the NormUnbalance convergence test to be safe. If for whatever reason you need to use NormDispIncr, you’ll usually be fine–just expect an extra iteration for the force-based elements to wake up to their having member loads.
