Bumped into this when looking a bit more at AlgorithmsInterface.jl:
The "are all criteria active" hasconverged uses a length (non-recursive) and compares it to get_active_stopping_criteria (recursive), so this can mismatch when combining & and |.
Current tests only have s1 & s2 or s1 | s2, not s1 & s2 | s3.
MWE:
s1 = StopWhenGradientNormLess(1.0e-4) # indicates_convergence == true
s2 = StopAfterIteration(10) # indicates_convergence == false
cap = StopAfterIteration(20)
sc = (s1 | s2) & cap # StopWhenAll(StopWhenAny(s1, s2), cap)
# every leaf active: the group has genuinely stopped, and s1 is a convergence certificate
s1.at_iteration = 1
s2.at_iteration = 11
cap.at_iteration = 21
has_converged(sc) # false -- expected true
# active leaves = [s1, s2, cap] -> 3, length(sc.criteria) -> 2, gate fails
# now deactivate s2, which is the criterion that cannot certify anything
s2.at_iteration = -1
has_converged(sc) # true
# active leaves = [s1, cap] -> 2 == 2, gate passes
Disclaimer: found with assistance of AI
Bumped into this when looking a bit more at
AlgorithmsInterface.jl:The "are all criteria active"
hasconvergeduses alength(non-recursive) and compares it toget_active_stopping_criteria(recursive), so this can mismatch when combining&and|.Current tests only have
s1 & s2ors1 | s2, nots1 & s2 | s3.MWE:
Disclaimer: found with assistance of AI