diff --git a/src/minimal_covers.jl b/src/minimal_covers.jl index 61008ff..b056414 100644 --- a/src/minimal_covers.jl +++ b/src/minimal_covers.jl @@ -1806,9 +1806,9 @@ function _abslog2_auglag(sys::SupportSystem{T}, x0; # Absolute thresholds preserve covariance under rescaling because the # residuals are logarithmic. vtol = tol === nothing ? 1000 * eps(T) : T(tol) - # Precision bounds the usable penalty: the cap is `1e5` (LSQR) or `1e8` + # Precision bounds the usable penalty: the cap is `1e6` (LSQR) or `1e8` # (exact solves) in `Float64` and grows as `1/sqrt(eps(T))`. - κcap = T(use_lsqr ? 1e5 : 1e8) * sqrt(T(eps(Float64)) / eps(T)) + κcap = T(use_lsqr ? 1e6 : 1e8) * sqrt(T(eps(Float64)) / eps(T)) # Slack below the inner solver's resolution is indistinguishable from an # active entry. zdrain = sqrt(eps(T)) diff --git a/test/minimal_covers.jl b/test/minimal_covers.jl index 596e7ce..85834c2 100644 --- a/test/minimal_covers.jl +++ b/test/minimal_covers.jl @@ -582,7 +582,9 @@ end # cover. With either preconditioner it certifies the minimizer, which is also # the minimizer of the asymmetric problem. (Whether the full multiplier # iteration converges on its own here depends on rounding, so the test - # does not rely on it.) + # does not rely on it. When the asymmetric solve does stop at the multiplier + # iteration's KKT tolerance, its scaled entries are within about 1e-10 of the + # certified minimizer rather than at roundoff.) an, sn = @test_logs (:warn, r"Increase `maxouter` or `κ`") MatrixCovers._symcover_min_abslog2(A; flopbudget=Inf, maxouter=3, polish=false) @test !sn.converged @@ -592,10 +594,10 @@ end ap, sp = MatrixCovers._symcover_min_abslog2(A; flopbudget=budget, maxouter=3) @test sp.converged && sp.polish.certified @test cover_objective(AbsLog{2}(), ap, ap, A) <= cover_objective(AbsLog{2}(), an, an, A) - @test ap[Ai] .* ap[Aj] ≈ ga[Ai] .* gb[Aj] rtol=1e-12 + @test ap[Ai] .* ap[Aj] ≈ ga[Ai] .* gb[Aj] rtol=1e-9 @test cover_objective(AbsLog{2}(), ap, ap, A) ≈ cover_objective(AbsLog{2}(), ga, gb, A) rtol=1e-12 end - @test af[Ai] .* af[Aj] ≈ ga[Ai] .* gb[Aj] rtol=1e-12 + @test af[Ai] .* af[Aj] ≈ ga[Ai] .* gb[Aj] rtol=1e-9 # The forest factor needs no budget: any budget below the full factor's # gives the forest regime for every solve, and the cover is unchanged. @@ -884,7 +886,7 @@ end # The LSQR path caps escalation where its accuracy floor makes larger # weights useless. _, sl = MatrixCovers._symcover_min_abslog2(A; linsolve=:lsqr) - @test sl.κs[end] <= 1e5 + @test sl.κs[end] <= 1e6 @test sl.kkt[end] < 1e-8 # Stopping far short of convergence without the active-set finish emits a warning. @@ -930,14 +932,14 @@ end end @testset "MMC :lsqr drains slack multipliers at the penalty cap" begin - # Draining these multipliers needs a penalty above the `:lsqr` cap of 1e5. + # Draining these multipliers needs a penalty above the `:lsqr` cap of 1e6. # With the penalty held at the cap they are zeroed directly; left to drain by - # `2(κ-1)z` per update they hold the KKT residual at 2.6e-6. + # `2(κ-1)z` per update they hold the KKT residual at 6.2e-8. # Without the active-set finish (`polish=false`), slack multipliers are zeroed. # Whether the residual then reaches the tolerance depends on rounding (with the # forest preconditioner it can stall just above it); the solve warns exactly # when it does not. The default solve's finish certifies the result. - A = banded_sparse_sym(StableRNG(5), 10_000, 3, 1.0) + A = banded_sparse_sym(StableRNG(1), 10_000, 3, 1.0) for fillbudget in (MatrixCovers.LSQR_FILL_BUDGET, 0) (a, s), logs = capture_logs() do MatrixCovers._symcover_min_abslog2(A; linsolve=:lsqr, fillbudget, polish=false) @@ -946,11 +948,11 @@ end @test all(l -> l.level == Base.CoreLogging.Warn && occursin("try a larger `κ`", l.message), logs) @test s.precond === (fillbudget == 0 ? :forest : :factor) @test s.nzeroed > 0 - @test maximum(s.κs) == 1e5 + @test maximum(s.κs) == 1e6 @test s.kkt[end] < 1e-9 _, sp = @test_logs symcover_min(AbsLog{2}(), A; fillbudget) @test MatrixCovers.converged(sp) - @test cover_objective(AbsLog{2}(), a, A) ≈ 161350.449205 rtol=1e-9 + @test cover_objective(AbsLog{2}(), a, A) ≈ 163679.149704 rtol=1e-9 @test iscover(a, A) end