Skip to content

Fix Transformation trial displacements when retained DOFs are free - #115

Merged
claudioperez merged 1 commit into
peer-open-source:stablefrom
gaaraujo:fix/transformation-enforceSPs
Aug 11, 2026
Merged

Fix Transformation trial displacements when retained DOFs are free#115
claudioperez merged 1 commit into
peer-open-source:stablefrom
gaaraujo:fix/transformation-enforceSPs

Conversation

@gaaraujo

@gaaraujo gaaraujo commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

This fixes an issue in the Transformation constraint handler when a retained node is only partially fixed by SP constraints.

In enforceSPs(doMP==0), when needRetainedData == 0, the retained data are zeroed and only SP-fixed DOFs were previously restored from the retained node’s trial displacement. Free retained DOFs therefore remained incorrectly set to zero, causing the constrained node to stop tracking the retained node correctly after the first analysis step.

The fix is copying all retained DOFs from the retained node’s current trial displacement.

This PR also updates addSP_Constraint to return early when the SP acts on a DOF already eliminated by an MP constraint, preventing unrelated modID entries from being marked.

MWE

Assuming you run the following file as OpenSees test_retained_sp.tcl <partial|full>

  • partial reproduces the issue and verifies the fix.
  • full provides a sanity check with the retained node fully fixed.
# Retained-node SP + equalDOF under Transformation (multi-step).
#
# Usage:
#   OpenSees test_retained_sp.tcl partial   ;# default: Uy fixed, Ux free on retained
#   OpenSees test_retained_sp.tcl full      ;# both DOFs fixed on retained
#
# Inclined trusses 1--2 and 3--4 meeting at a
# coincident node (equalDOF 2->3), plus a horizontal truss 2--5.
# Horizontal load is applied at the free tip (node 5). Five static
# LoadControl steps under constraints Transformation.

wipe

set mode partial
if {$argc >= 1} {
    set mode [lindex $argv 0]
}
if {$mode ni {partial full}} {
    puts stderr "ERROR: mode must be partial or full (got '$mode')"
    exit 1
}

model basic -ndm 2 -ndf 2

node 1 0.0 0.0
node 2 1.0 1.0
node 3 1.0 1.0
node 4 2.0 0.0
node 5 3.0 1.0

fix 1 1 1
fix 4 1 1
fix 5 0 1
if {$mode eq "full"} {
    fix 2 1 1
} else {
    # partial fix on the RETAINED node: Uy fixed, Ux free
    fix 2 0 1
}

equalDOF 2 3   1 2

uniaxialMaterial Elastic 1 1000.0
element truss 1 1 2 10.0 1
element truss 2 3 4 10.0 1
element truss 3 2 5 10.0 1

timeSeries Linear 1
pattern Plain 1 1 {
    load 5 100.0 0.0
}

constraints Transformation
numberer Plain
system UmfPack
test NormUnbalance 1.0e-8 2 0
algorithm Newton
integrator LoadControl 0.2
analysis Static

set tol 1.0e-10
set fail 0
set nSteps 5
for {set step 1} {$step <= $nSteps} {incr step} {
    if {[analyze 1] != 0} {
        puts "FAIL: analysis did not converge at step $step (mode=$mode)"
        exit 1
    }
    set ux2 [nodeDisp 2 1]
    set uy2 [nodeDisp 2 2]
    set ux3 [nodeDisp 3 1]
    set uy3 [nodeDisp 3 2]
    set ux5 [nodeDisp 5 1]
    set diffX [expr {abs($ux2 - $ux3)}]
    set diffY [expr {abs($uy2 - $uy3)}]
    puts [format "mode=%s step %d: retained=(% .8e,% .8e) constrained=(% .8e,% .8e) tip_ux=% .8e |dX|=%.3e |dY|=%.3e" \
        $mode $step $ux2 $uy2 $ux3 $uy3 $ux5 $diffX $diffY]
    if {$diffX > $tol || $diffY > $tol} { set fail 1 }
    if {[expr {abs($uy2)}] > $tol || [expr {abs($uy3)}] > $tol} { set fail 1 }
    if {$mode eq "full"} {
        if {[expr {abs($ux2)}] > $tol || [expr {abs($ux3)}] > $tol} { set fail 1 }
    }
}

if {$fail} {
    puts "FAIL: constrained node does not track retained node (mode=$mode)"
    exit 1
}

set ux2 [nodeDisp 2 1]
set ux5 [nodeDisp 5 1]
set ux_tip_rel [expr {100.0 * 2.0 / 10000.0}]
if {$mode eq "partial"} {
    # Inclined pair: K_xx = EA/L = 10000/sqrt(2); tip spring carries P with ux5-ux2 = P*L/EA
    set ux_apex [expr {100.0 * sqrt(2.0) / 10000.0}]
    set ux_tip [expr {$ux_apex + $ux_tip_rel}]
    if {[expr {abs($ux2 - $ux_apex)}] > 1.0e-9} {
        puts [format "FAIL: wrong apex ux2=%.6e expected %.6e" $ux2 $ux_apex]
        exit 1
    }
    if {[expr {abs($ux5 - $ux_tip)}] > 1.0e-9} {
        puts [format "FAIL: wrong tip ux5=%.6e expected %.6e" $ux5 $ux_tip]
        exit 1
    }
} else {
    if {[expr {abs($ux5 - $ux_tip_rel)}] > 1.0e-9} {
        puts [format "FAIL: wrong tip ux5=%.6e expected %.6e (apex should be fixed)" $ux5 $ux_tip_rel]
        exit 1
    }
    if {[expr {abs($ux5)}] < 1.0e-12} {
        puts "FAIL: expected nonzero tip motion in full mode"
        exit 1
    }
}

puts "PASS mode=$mode"
exit 0

When needRetainedData==0, copy all retained DOFs from the retained
node's trial displacement, not only SP-fixed ones. Also ignore SPs that
act on DOFs already eliminated by an MP constraint.
@gaaraujo
gaaraujo force-pushed the fix/transformation-enforceSPs branch from b6f1f55 to a4097c9 Compare August 9, 2026 23:46
@claudioperez
claudioperez merged commit cc3cce5 into peer-open-source:stable Aug 11, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants