Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/minimal_covers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
20 changes: 11 additions & 9 deletions test/minimal_covers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand All @@ -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

Expand Down
Loading