diff --git a/TASKS.md b/TASKS.md index 62d161d..7beab1c 100644 --- a/TASKS.md +++ b/TASKS.md @@ -2184,8 +2184,8 @@ repeat it once T21 has landed. The bar is cuDSS with `"matching_alg" = ### Report -- Status: [!] (done on the CPU backend; the "100× on a badly scaled SPD matrix" assertion is `@test_broken`, see - deviations; CUDA from CI) +- Status: [!] (done on the CPU backend; the "100× on a badly scaled SPD matrix" assertion is replaced by what holds, + see deviations; CUDA from CI) - What was built: - `src/solve/refinement.jl` (new): `RefinementWorkspace` (device CSR of the full matrix over the contributions of the user's `nzval`, signed source index = conjugated mirror entry for `"H"`/`"HPD"`, built from the existing @@ -2251,7 +2251,9 @@ repeat it once T21 has landed. The bar is cuDSS with `"matching_alg" = is backward stable and invariant under symmetric diagonal scaling (the only scaling that keeps the matrix SPD). Measured on 3 generators × 3 scalings × 5 right-hand-side choices: the unrefined relres is either at rounding level (`b = A x`) or limited by `cond(A)` (random `b`), and one step gains ≤ 7× (once 42×). The SPD - assertion is `@test_broken` with that comment; the 100× reduction is asserted on a KKT matrix whose + assertion was `@test_broken` with that comment; since the post-T21 test hardening (#79 review: a backend with + other rounding could flip it to an unexpected pass) it asserts what holds instead, `r0 ≤ 100 eps` and + `r1 ≤ r0`; the 100× reduction is asserted on a KKT matrix whose factorization carries static pivot perturbations (the case refinement is meant for, issue #71), together with the `ir_tol`/early-exit/steps-performed checks (also run on the SPD matrix, where they pass). - `ir_tol` is the largest `‖Rₖ‖₂/‖Bₖ‖₂` over the right-hand sides; checking it costs one host synchronization diff --git a/test/test_api.jl b/test/test_api.jl index b7388d1..344da1e 100644 --- a/test/test_api.jl +++ b/test/test_api.jl @@ -63,6 +63,42 @@ end @test maximum(batch_relres([A, A], api_solve(backend, sb, bb), bb)) <= tol(T) end +@testset "SparseMatrixCSC constructor and update! (CPU, $T, $INT)" for backend in filter(b -> b isa CPU, BACKENDS), + T in ELTYPES, INT in INTTYPES + # a SparseMatrixCSC is converted to host CSR arrays (index type kept) once, for the CPU backend + Random.seed!(666) + n = 60 + A = random_spd(T, n, 0.05) + s = spd_structure(T) + for (view, index) in (('L', 'O'), ('U', 'Z'), ('F', 'O')) + solver = DirectSolver(SparseMatrixCSC{T, INT}(triangle_view(A, view)), s, view; index) + @test solver isa DirectSolver{T, INT} + @test solver.backend isa CPU + @test solver.A.index == SDS._index_base(index) + @test size(solver) == (n, n) + b = rand(T, n) + execute!("analysis", solver, nothing, nothing) + execute!("factorization", solver, nothing, nothing) + @test relres(A, api_solve(backend, solver, b), b) <= tol(T) + # new values of the same pattern, then refactorization + A2 = A + 2 * I + update!(solver, SparseMatrixCSC{T, INT}(triangle_view(A2, view))) + execute!("refactorization", solver, nothing, nothing) + @test getparam(solver, "info") == 0 + @test relres(A2, api_solve(backend, solver, b), b) <= tol(T) + # a different size or number of stored entries + @test thrown(() -> update!(solver, SparseMatrixCSC{T, INT}(triangle_view(random_spd(T, n + 1, 0.05), view)))) isa + InvalidValueError + @test thrown(() -> update!(solver, SparseMatrixCSC{T, INT}(triangle_view(A2 + sprand(T, n, n, 0.2), view)))) isa + InvalidValueError + # another index type + @test thrown(() -> update!(solver, SparseMatrixCSC{T, INT == Int32 ? Int64 : Int32}(triangle_view(A2, view)))) isa + InvalidValueError + end + # a rectangular matrix + @test thrown(() -> DirectSolver(SparseMatrixCSC{T, INT}(sprand(T, n, n + 1, 0.05)), s, 'F')) isa InvalidValueError +end + @testset "phases ($(backend_name(backend)), $T, $INT)" for backend in BACKENDS, T in ELTYPES, INT in INTTYPES A = laplacian2d(T, 15, 12) + spdiagm(0 => rand(real(T), 180)) n = size(A, 1) diff --git a/test/test_fgmres.jl b/test/test_fgmres.jl index 569b172..03b79f0 100644 --- a/test/test_fgmres.jl +++ b/test/test_fgmres.jl @@ -37,6 +37,7 @@ end @testset "FGMRES-IR: element types, multiple right-hand sides, solve_mode ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n = 200 for (A, structure) in ((random_spd(T, n, 0.02), spd_structure(T)), (random_symindef(T, n, 0.02), sym_structure(T))) solver = ir_solver(backend, A, structure; params = (("ir_mode", "fgmres"), ("deterministic_mode", 1))) @@ -74,6 +75,7 @@ end @testset "FGMRES-IR: uniform batch ($(backend_name(backend)), $T)" for backend in BACKENDS, T in filter(in((Float64, ComplexF32)), ELTYPES) + Random.seed!(666) n, nb, nrhs = 120, 3, 2 members = batch_members(random_symindef(T, n, 0.03), nb) solver = DirectSolver(api_batch_matrix(backend, members, 'L'), sym_structure(T), 'L') diff --git a/test/test_matching.jl b/test/test_matching.jl index 0a522d9..0dc282d 100644 --- a/test/test_matching.jl +++ b/test/test_matching.jl @@ -13,6 +13,7 @@ host_matching(A::SparseMatrixCSC, structure, alg; view = 'F') = Options(matching_alg = alg); view)) RUN_SHARED && @testset "MC64 jobs against brute force" begin + Random.seed!(666) for trial in 1:12 n = 6 A = sprand(n, n, 0.4) + sparse(randperm(n), 1:n, rand(n) .+ 0.5) # structurally nonsingular @@ -63,6 +64,7 @@ RUN_SHARED && @testset "MC64 jobs against brute force" begin end RUN_SHARED && @testset "symmetric scaling and matching pairs" begin + Random.seed!(666) nh, nj = 40, 15 K = kkt_matrix(Float64, nh, nj, 1.0e-10) for view in ('L', 'U', 'F') @@ -101,6 +103,7 @@ RUN_SHARED && @testset "symmetric scaling and matching pairs" begin end @testset "LU with matching: badly scaled ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n = 80 INT = Int32 A = badly_scaled_general(T, n, 0.05) @@ -209,6 +212,7 @@ end end @testset "inertia with matching ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) INT = Int32 structure = sym_structure(T) for (name, A) in (("random_symindef(100)", random_symindef(T, 100, 0.05)), @@ -260,6 +264,7 @@ end end @testset "uniform batch with matching ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n = 50 A = badly_scaled_general(T, n, 0.08) members = [A, 2 * A, 3 * A] diff --git a/test/test_numeric_cholesky_a.jl b/test/test_numeric_cholesky_a.jl index d7186b7..d91fddf 100644 --- a/test/test_numeric_cholesky_a.jl +++ b/test/test_numeric_cholesky_a.jl @@ -31,6 +31,7 @@ function subtree_move_rounds(S, v) end RUN_SHARED && @testset "plan: regime-A groups" begin + Random.seed!(666) for (name, A, opts) in numeric_a_matrices(Float64) S, _, _, _, Nd, _ = numeric_setup(CPU(), A; opts) sc, plan = S.schedule, Nd.plan @@ -69,6 +70,7 @@ RUN_SHARED && @testset "plan: regime-A groups" begin end @testset "panels, solves, determinism ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) for (name, A, opts) in numeric_a_matrices(T), INT in (name == "laplacian2d(40,40) A+B+C" ? INTTYPES : (Int32,)) @testset "$name $INT" begin S, Nr, info_ref, Sd, Nd, nz = numeric_setup(backend, A, INT; opts) @@ -95,6 +97,7 @@ end end @testset "budget classes and kernel sizes ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) # the same matrix under different budgets: other subtrees, other local sizes, same factor A = laplacian3d(T, 8, 8, 8) for opts in (Options(subtree_budgets = [8192], subtree_parallelism = 0), @@ -114,6 +117,7 @@ end @testset "nlaunches on laplacian2d(100, 100) with AMD ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = laplacian2d(T, 100, 100) b = rand(T, size(A, 1), 2) x = map((Options(reordering_alg = "algo3", subtree_parallelism = 0), @@ -137,6 +141,7 @@ end end @testset "views, index bases, refactorization ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = random_spd(T, 300, 0.02) for opts in (NUMERIC_A_OPTS, NUMERIC_ABC_OPTS) S, Nr, _, Sd, Nd, nz = numeric_setup(backend, A; opts) @@ -172,6 +177,7 @@ end end @testset "info: first non-positive pivot ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n, j = 60, 23 for opts in (NUMERIC_A_OPTS, Options(reordering_alg = "algo5", subtree_parallelism = 0), Options(use_superpanels = 0, subtree_parallelism = 0), NUMERIC_ABC_OPTS) diff --git a/test/test_numeric_cholesky_b.jl b/test/test_numeric_cholesky_b.jl index b9b3004..6f2b53f 100644 --- a/test/test_numeric_cholesky_b.jl +++ b/test/test_numeric_cholesky_b.jl @@ -49,6 +49,7 @@ end # synthetic assembled fronts through the dense part of the kernel, against LAPACK on the host @testset "front_cholesky! on synthetic fronts ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) for W in SDS.REGIME_B_WIDTHS shapes = [(w, m, cb) for w in unique((1, W ÷ 2 + 1, W)) for (m, cb) in ((0, false), (1, true), (37, true), (5, false))] @@ -115,6 +116,7 @@ end end @testset "panels, solves, determinism ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) for (name, A, opts) in numeric_b_matrices(T), INT in (name == "laplacian2d(40,40)" ? INTTYPES : (Int32,)) @testset "$name $INT" begin S, Nr, info_ref, Sd, Nd, nz = numeric_setup(backend, A, INT; opts) @@ -139,6 +141,7 @@ end end @testset "factorization_alg algo1 vs algo2 ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) for A in (laplacian2d(T, 40, 40), random_spd(T, 500, 0.01)) b = rand(T, size(A, 1), 2) x = map(("algo1", "algo2")) do alg @@ -156,6 +159,7 @@ end end @testset "views, index bases, refactorization ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = random_spd(T, 300, 0.02) for opts in (NUMERIC_B_OPTS, NUMERIC_BC_OPTS) S, Nr, _, Sd, Nd, nz = numeric_setup(backend, A; opts) @@ -191,6 +195,7 @@ end end @testset "info: first non-positive pivot ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n, j = 60, 23 for opts in (NUMERIC_B_OPTS, Options(subtree_budgets = Int[], reordering_alg = "algo5"), Options(subtree_budgets = Int[], use_superpanels = 0), NUMERIC_BC_OPTS) diff --git a/test/test_numeric_cholesky_c.jl b/test/test_numeric_cholesky_c.jl index 4e999fe..154723c 100644 --- a/test/test_numeric_cholesky_c.jl +++ b/test/test_numeric_cholesky_c.jl @@ -46,6 +46,7 @@ RUN_SHARED && @testset "storage and plan" begin end @testset "panels, solves, determinism ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) for (name, A) in numeric_c_matrices(T), INT in (name == "laplacian2d(40,40)" ? INTTYPES : (Int32,)) @testset "$name $INT" begin S, Nr, info_ref, Sd, Nd, nz = numeric_c_setup(backend, A, INT) @@ -70,6 +71,7 @@ end end @testset "dense implementations ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = laplacian2d(T, 25, 25) S, Nr, _, Sd, Nd, nz = numeric_c_setup(backend, A) b = rand(T, size(A, 1), 2) @@ -85,6 +87,7 @@ end end @testset "views, index bases, refactorization ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = random_spd(T, 300, 0.02) S, Nr, _, Sd, Nd, nz = numeric_c_setup(backend, A) @test SDS.factorize!(Nd, Sd, nz) == 0 @@ -118,6 +121,7 @@ end end @testset "info: first non-positive pivot ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n, j = 60, 23 for opts in (NUMERIC_C_OPTS, Options(subtree_budgets = Int[], factorization_alg = "algo2", reordering_alg = "algo5"), diff --git a/test/test_numeric_ldlt.jl b/test/test_numeric_ldlt.jl index 40bd1b4..ba94726 100644 --- a/test/test_numeric_ldlt.jl +++ b/test/test_numeric_ldlt.jl @@ -36,15 +36,18 @@ end S, Nr, Sd, Nd, nz = ldlt_setup(backend, A; opts, structure) @test SDS.factorize!(Nd, Sd, nz; opts) == 0 Nh = SDS.host_numeric(Nd) - # the same pivot sequence: local orders and pivot kinds are equal, D and L to rounding + # the same pivot sequence: local orders and pivot kinds are equal, D and L to rounding (scaled by the + # element growth: the device may contract to FMA, #86) @test Nh.piv == Nr.piv @test Nh.pivot_kind == Nr.pivot_kind - @test d_error(Nh, Nr) <= panel_tol(T) - @test panel_error(Nh, Nr) <= panel_tol(T) + @test d_error(Nh, Nr) <= growth_tol(T, Nr) + @test panel_error(Nh, Nr) <= growth_tol(T, Nr) + herm = structure == "H" || T <: Real + # the backward error of the device factor: P A Pᵀ = L D Lᴴ (Lᵀ for complex symmetric) + @test ldlt_error(A, S, Nh; herm) <= tol(T) @test Nh.stats == Nr.stats st = SDS.pivot_totals(Nd) @test st == SDS.pivot_stats(Nr) && Nh.totals == Nr.totals - herm = structure == "H" || T <: Real herm && eig && @test (st.npos, st.nneg) == eigen_npos_nneg(A) herm && @test st.npos + st.nneg == size(A, 1) # device solve: forward (local pivot orders), diagonal, backward sweeps @@ -73,7 +76,8 @@ end SDS.factorize!(Nd, Sd, nz; opts) Nh = SDS.host_numeric(Nd) @test Nh.piv == Nr.piv && Nh.pivot_kind == Nr.pivot_kind - @test d_error(Nh, Nr) <= panel_tol(T) && panel_error(Nh, Nr) <= panel_tol(T) + @test d_error(Nh, Nr) <= growth_tol(T, Nr) && panel_error(Nh, Nr) <= growth_tol(T, Nr) + @test ldlt_error(A, S, Nh) <= tol(T) # views of the matrix give the same factor for view in ('U', 'F') _, _, Sv, Nv, nzv = ldlt_setup(backend, A, Int64; view, opts) @@ -83,6 +87,7 @@ end end @testset "perturbation and pivot_sign ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n, j = 60, 23 R = real(T) # the zero pivot in its own root front, regime C ("algo2"), or as the analysis schedules it @@ -97,7 +102,7 @@ end Nh = SDS.host_numeric(Nd) # the same pivot sequence; D to rounding (the device may contract to FMA), perturbed pivots exactly @test Nh.pivot_kind == Nr.pivot_kind && Nh.piv == Nr.piv - @test d_error(Nh, Nr) <= panel_tol(T) + @test d_error(Nh, Nr) <= growth_tol(T, Nr) pert = Nr.pivot_kind .== SDS.PIVOT_KIND_PERTURBED @test Nh.d[1:n][pert] == Nr.d[1:n][pert] st = SDS.pivot_totals(Nd) @@ -126,7 +131,7 @@ end SDS.factorize!(Nd, Sd, nz; opts) Nh = SDS.host_numeric(Nd) @test Nh.pivot_kind == Nr.pivot_kind && Nh.piv == Nr.piv - @test d_error(Nh, Nr) <= panel_tol(T) + @test d_error(Nh, Nr) <= growth_tol(T, Nr) pert = Nr.pivot_kind .== SDS.PIVOT_KIND_PERTURBED @test Nh.d[1:n][pert] == Nr.d[1:n][pert] @test SDS.pivot_totals(Nd).nperturbed == 1 @@ -169,8 +174,9 @@ end Nh = SDS.host_numeric(Nd) @test Nh.piv == Nr.piv @test Nh.pivot_kind == Nr.pivot_kind - @test d_error(Nh, Nr) <= panel_tol(T) - @test panel_error(Nh, Nr) <= panel_tol(T) + @test d_error(Nh, Nr) <= growth_tol(T, Nr) + @test panel_error(Nh, Nr) <= growth_tol(T, Nr) + @test ldlt_error(A, S, Nh) <= tol(T) @test Nh.stats == Nr.stats @test SDS.pivot_totals(Nd) == SDS.pivot_stats(Nr) ws = SDS.allocate_solve(Sd, T, backend, 1) @@ -185,8 +191,38 @@ end @test thrown(() -> SDS.factorize_ldlt!(Nd, Sd, nz; nb = SDS.LDLT_C_NB + 1)) isa InvalidValueError end +@testset "regime B on the blocked path ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + # regime-B bins of width class ≥ LDLT_BLOCKED_MIN_WCLASS and row class ≥ LDLT_BLOCKED_MIN_FCLASS run the + # blocked steps and GEMMs of ldlt_c.jl after the regular assembly kernels (`ldlt_blocked_path`, #89); the + # regime-C thresholds are raised so that these fronts stay in regime B whatever their row counts + Random.seed!(666) + A = kkt_matrix(T, 200, 100, 0.0; hessian = :indefinite, hessian_scale = 1.0e-3) + opts = Options(user_perm = kkt_interleaved_perm(200, 100), regime_c_width = SDS.REGIME_B_MAX_WIDTH, + regime_c_rows = 1024) + S, Nr, Sd, Nd, nz = ldlt_setup(backend, A; opts) + sc, sp = S.schedule, S.partition + blocked_b = [s for s in 1:SDS.nsupernodes(S) if sc.regime[s] == SDS.REGIME_B && SDS.ldlt_blocked_path(sc, s) && + !SDS.takes_c_path(sc, s)] + @test !isempty(blocked_b) + # 2×2 pivots inside such a front + @test any(s -> any(k -> Nr.pivot_kind[k] == SDS.PIVOT_KIND_2X2_FIRST, SDS.sncols(sp, s)), blocked_b) + @test SDS.factorize!(Nd, Sd, nz; opts) == 0 + Nh = SDS.host_numeric(Nd) + @test Nh.piv == Nr.piv + @test Nh.pivot_kind == Nr.pivot_kind + @test d_error(Nh, Nr) <= growth_tol(T, Nr) + @test panel_error(Nh, Nr) <= growth_tol(T, Nr) + @test ldlt_error(A, S, Nh) <= tol(T) + @test Nh.stats == Nr.stats + @test SDS.pivot_totals(Nd) == SDS.pivot_stats(Nr) + ws = SDS.allocate_solve(Sd, T, backend, 1) + b = rand(T, size(A, 1)) + @test relres(A, device_solve(backend, ws, Sd, Nd, b; deterministic = true), b) <= tol(T) +end + @testset "pivot_type 'D' and 'N' on quasi-definite KKT ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) nh, nj = 300, 100 A = kkt_matrix(T, nh, nj, 1.0e-2) for pt in ('D', 'N') @@ -194,7 +230,7 @@ end S, Nr, Sd, Nd, nz = ldlt_setup(backend, A; opts) SDS.factorize!(Nd, Sd, nz; opts) Nh = SDS.host_numeric(Nd) - @test Nh.piv == Nr.piv && Nh.pivot_kind == Nr.pivot_kind && d_error(Nh, Nr) <= panel_tol(T) + @test Nh.piv == Nr.piv && Nh.pivot_kind == Nr.pivot_kind && d_error(Nh, Nr) <= growth_tol(T, Nr) st = SDS.pivot_totals(Nd) @test st.n2x2 == 0 && st.nperturbed == 0 && (st.npos, st.nneg) == (nh, nj) pt == 'N' && @test Nh.piv == 1:(nh + nj) @@ -202,6 +238,7 @@ end end @testset "determinism and refactorization ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = kkt_matrix(T, 200, 100, 0.0; hessian = :indefinite, hessian_scale = 1.0e-3) opts = Options(user_perm = kkt_interleaved_perm(200, 100), subtree_budgets = [8192, 16384], regime_c_width = 16, regime_c_rows = 128) @@ -231,6 +268,7 @@ end @testset "solve sweeps: diagonal step and dense path ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = kkt_matrix(T, 200, 100, 0.0; hessian = :indefinite, hessian_scale = 1.0e-3) for (rname, rkw) in LDLT_REGIMES opts = Options(; user_perm = kkt_interleaved_perm(200, 100), rkw...) @@ -268,6 +306,7 @@ end @testset "MadNLP-style inertia correction on the device ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) nh, nj = 200, 100 A = kkt_matrix(T, nh, nj, 0.0; hessian = :indefinite) for (label, opts) in MADNLP_ORDERINGS @@ -288,6 +327,7 @@ end end @testset "public API ($(backend_name(backend)), $T, $INT)" for backend in BACKENDS, T in ELTYPES, INT in INTTYPES + Random.seed!(666) nh, nj = 200, 100 n = nh + nj A = kkt_matrix(T, nh, nj, 0.0; hessian = :indefinite, hessian_scale = 1.0e-3) @@ -314,7 +354,7 @@ end @test getparam(solver, "npivots") == 0 && getparam(solver, "npivots") isa INT d = getparam(solver, "diag") @test typeof(KernelAbstractions.get_backend(d)) == typeof(backend) - @test norm(to_host(d) - Nr.d[1:n]) <= panel_tol(T) * norm(Nr.d) + @test norm(to_host(d) - Nr.d[1:n]) <= growth_tol(T, Nr) * norm(Nr.d) hd = zeros(T, n) getparam!(hd, solver, "diag") @test hd == to_host(d) @@ -357,6 +397,7 @@ end @testset "complex symmetric through the API ($(backend_name(backend)), $T)" for backend in BACKENDS, T in COMPLEX_ELTYPES + Random.seed!(666) A = random_symindef(T, 300, 0.02; hermitian = false) solver = DirectSolver(api_matrix(backend, tril(A), Int32), "S", 'L') execute!("analysis", solver, nothing, nothing) diff --git a/test/test_numeric_lu.jl b/test/test_numeric_lu.jl index 73ea9e5..6a50e07 100644 --- a/test/test_numeric_lu.jl +++ b/test/test_numeric_lu.jl @@ -94,6 +94,7 @@ end end @testset "perturbation ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n, j = 60, 23 R = real(T) # a zero row and column j: no row of the front can replace the zero pivot, it is perturbed diff --git a/test/test_reference_cholesky.jl b/test/test_reference_cholesky.jl index f6259f8..b94af2f 100644 --- a/test/test_reference_cholesky.jl +++ b/test/test_reference_cholesky.jl @@ -48,6 +48,7 @@ reference_matrices(::Type{T}) where {T} = end @testset "P A Pᵀ = L Lᴴ and solves: $T" for T in ELTYPES + Random.seed!(666) for (name, A) in reference_matrices(T) @testset "$name" begin S, N, info, _ = reference_cholesky(A) @@ -76,6 +77,7 @@ end end @testset "views, index bases and refactorization: $T" for T in ELTYPES + Random.seed!(666) A = random_spd(T, 300, 0.02) S, N, info, _ = reference_cholesky(A; view = 'L') @test info == 0 @@ -104,6 +106,7 @@ end end @testset "info: first non-positive pivot: $T" for T in ELTYPES + Random.seed!(666) n, j = 60, 23 for opts in (Options(), Options(reordering_alg = "algo5"), Options(use_superpanels = 0)) A = singular_block_matrix(T, n, j; stored_zero = true) @@ -126,6 +129,7 @@ end end @testset "amalgamation on and off: $T" for T in ELTYPES + Random.seed!(666) for A in (laplacian2d(T, 30, 30), random_spd(T, 400, 0.01), laplacian3d(T, 8, 8, 8)) b = rand(T, size(A, 1), 3) S1, N1, i1, _ = reference_cholesky(A) diff --git a/test/test_reference_ldlt.jl b/test/test_reference_ldlt.jl index e8ec8b5..9e89bb2 100644 --- a/test/test_reference_ldlt.jl +++ b/test/test_reference_ldlt.jl @@ -6,6 +6,7 @@ stat_of(N, s, q) = N.stats[(s - 1) * SDS.FRONT_STATS_FIELDS + q] psign_minus(n, j) = (v = zeros(Int8, n); v[j] = -1; v) @testset "inputs and storage" begin + Random.seed!(666) A = random_symindef(Float64, 60, 0.05) S, N, info, C = reference_ldlt(A) @test info == 0 @@ -94,6 +95,7 @@ end end @testset "inertia equals the eigenvalue signs: $T" for T in ELTYPES + Random.seed!(666) nh, nj = 200, 100 cases = [("random_symindef(300,0.02)", random_symindef(T, 300, 0.02), Options()), ("random_symindef(250,0.05) natural", random_symindef(T, 250, 0.05), Options(reordering_alg = "algo5")), @@ -132,6 +134,7 @@ end end @testset "views and refactorization: $T" for T in ELTYPES + Random.seed!(666) A = kkt_matrix(T, 200, 100, 0.0; hessian = :indefinite, hessian_scale = 1.0e-3) opts = Options(user_perm = kkt_interleaved_perm(200, 100)) S, N, _, _ = reference_ldlt(A; view = 'L', opts) @@ -153,6 +156,7 @@ end end @testset "complex symmetric LDLᵀ: $T" for T in COMPLEX_ELTYPES + Random.seed!(666) A = random_symindef(T, 300, 0.02; hermitian = false) @test transpose(A) == A && A' != A S, N, info, _ = reference_ldlt(A; structure = "S") @@ -174,6 +178,7 @@ end end @testset "perturbation and pivot_sign: $T" for T in ELTYPES + Random.seed!(666) n, j = 60, 23 for stored_zero in (false, true), sgn in (1, -1) A = singular_block_matrix(T, n, j; stored_zero) @@ -217,6 +222,7 @@ end end @testset "pivot_type 'D' and 'N' on quasi-definite KKT: $T" for T in ELTYPES + Random.seed!(666) nh, nj = 300, 100 for δ in (1.0e-8, 1.0e-2), pt in ('D', 'N') A = kkt_matrix(T, nh, nj, δ) @@ -239,6 +245,7 @@ end end @testset "MadNLP-style inertia correction ($label): $T" for T in ELTYPES, (label, opts) in MADNLP_ORDERINGS + Random.seed!(666) nh, nj = 200, 100 # primal regularization δw on an indefinite H, no dual regularization (δ = 0); the # 2×2 pivot pairs of the analysis (#64) or the KKT-aware ordering keep every dual row diff --git a/test/test_refinement.jl b/test/test_refinement.jl index 6d05d4d..26eea17 100644 --- a/test/test_refinement.jl +++ b/test/test_refinement.jl @@ -16,6 +16,7 @@ function Base.CoreLogging.handle_message(L::InterruptAtStepLogger, level, messag end RUN_SHARED && @testset "refinement on a badly scaled SPD matrix ($(backend_name(backend)))" for backend in BACKENDS + Random.seed!(666) T = Float64 A = badly_scaled_spd(T, 300, 0.02) # rows scaled by up to 10^(±8) b = A * rand(T, 300) @@ -26,10 +27,12 @@ RUN_SHARED && @testset "refinement on a badly scaled SPD matrix ($(backend_name( @test getparam(solver, "ir_n_steps") == 1 @test r1 <= tol(T) # Cholesky is backward stable under symmetric scaling: the unrefined residual is already at - # rounding level (≈ 3e-16, the gain of one step is 2× to 7×, see the T16 report), so the - # 100× reduction of the task text can't be observed on an SPD matrix; it is checked on the - # perturbed LDLᵀ factorization below. - @test_broken r1 <= r0 / 100 + # rounding level (≈ 1.3 eps; one step gains 3× to 5.4× over 16 seeds on the CPU backend, 2× to 7× + # in the T16 report), so the 100× reduction of the task text can't be observed on an SPD matrix; + # it is checked on the perturbed LDLᵀ factorization below. What holds on every backend: r0 at + # rounding level and a step that does not lose accuracy. + @test r0 <= 100 * eps(T) + @test r1 <= r0 # ir_tol: early exit, the data parameter reports the steps performed x = ir_solve(backend, solver, b; steps = 10, tol = 1.0e-14) @test getparam(solver, "ir_n_steps") < 10 @@ -40,6 +43,7 @@ end RUN_SHARED && @testset "refinement with static pivot perturbation ($(backend_name(backend)), $INT)" for backend in BACKENDS, INT in INTTYPES + Random.seed!(666) # KKT matrix without 2×2 pairs: the zero (2,2) block is perturbed (pivot_epsilon), and refinement # removes the perturbation error (the case of the MadNLP K2 systems, issue #71) T = Float64 @@ -65,6 +69,7 @@ end @testset "refinement: layouts, aliasing, element types ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n = 200 for (A, structure) in ((random_spd(T, n, 0.02), spd_structure(T)), (random_symindef(T, n, 0.02), sym_structure(T))) # bitwise comparisons between solves: the atomic forward sweep (default on GPUs for real T) @@ -128,6 +133,7 @@ end end @testset "solve sub-phases compose to \"solve\" ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n = 150 for (A, structure) in ((random_spd(T, n, 0.03), spd_structure(T)), (random_symindef(T, n, 0.03), sym_structure(T))) # bitwise comparison: deterministic forward sweep (the atomic one is run-dependent on GPUs) @@ -151,6 +157,7 @@ end end @testset "solve_mode ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n = 120 b = rand(T, n) cases = Any[(random_spd(T, n, 0.03), spd_structure(T)), (random_symindef(T, n, 0.03), sym_structure(T))] @@ -187,6 +194,7 @@ end end @testset "user_host_interrupt ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = random_symindef(T, 300, 0.02) b = rand(T, 300) for structure in (sym_structure(T), spd_structure(T)) @@ -242,6 +250,7 @@ end end @testset "LinearAlgebra layer refines ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = random_spd(T, 100, 0.03) b = rand(T, 100) for F in (cholesky(api_matrix(backend, tril(A), Int32); view = 'L'), ldlt(api_matrix(backend, tril(A), Int32); view = 'L')) @@ -257,6 +266,7 @@ end end RUN_SHARED && @testset "logging" begin + Random.seed!(666) backend = first(BACKENDS) A = random_spd(Float64, 80, 0.05) b = rand(80) diff --git a/test/test_schur.jl b/test/test_schur.jl index d0f9623..d6e4ed6 100644 --- a/test/test_schur.jl +++ b/test/test_schur.jl @@ -28,6 +28,7 @@ schur_ldlt(structure) = structure in ("S", "H") T in ELTYPES, (structure, gen) in schur_cases(T), (rname, opts) in schur_regimes() + Random.seed!(666) A = gen() n = size(A, 1) flags = schur_test_flags(n) @@ -133,6 +134,7 @@ end @testset "Schur complement: solve_mode and transposed input ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = random_general(T, 90, 0.05) n = size(A, 1) flags = schur_test_flags(n) @@ -175,6 +177,7 @@ end end RUN_SHARED && @testset "Schur complement: orderings, edge cases, errors ($(backend_name(backend)))" for backend in BACKENDS + Random.seed!(666) T = Float64 # two disconnected blocks, Schur indices in the second one only: the first block is a separate tree A = blockdiag(laplacian2d(T, 6, 6), laplacian2d(T, 7, 5)) diff --git a/test/test_solve.jl b/test/test_solve.jl index c2fc3c8..3db38e7 100644 --- a/test/test_solve.jl +++ b/test/test_solve.jl @@ -16,6 +16,7 @@ nregime(S, r) = count(==(r), S.schedule.regime) solve_allocated(x, ws, S, N, b; kwargs...) = @allocated SDS.sweep_solve!(x, ws, S, N, b; kwargs...) RUN_SHARED && @testset "solve plan and workspace" begin + Random.seed!(666) for (name, A) in solve_matrices(Float64) C = SDS.CSR(tril(A)) S = SDS.symbolic_analysis(C, "SPD", 'L'; opts = SOLVE_OPTS) @@ -43,6 +44,7 @@ RUN_SHARED && @testset "solve plan and workspace" begin end @testset "permutation kernels ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) n, nrhs = 37, 4 perm = randperm(n) pd = to_device(backend, perm) @@ -74,6 +76,7 @@ end end @testset "residuals, all regimes ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) for (name, A) in solve_matrices(T), INT in (name == "laplacian2d(40,40)" ? INTTYPES : (Int32,)) @testset "$name $INT" begin S, Sd, Nd, ws = solve_setup(backend, A, INT; opts = SOLVE_OPTS) @@ -93,6 +96,7 @@ end @testset "forward and backward sweeps against L ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = laplacian3d(T, 10, 10, 10) S, Sd, Nd, ws = solve_setup(backend, A; opts = SOLVE_OPTS, nrhs = 2) L = SDS.extract_L(S, SDS.host_numeric(Nd)) @@ -120,6 +124,7 @@ end end @testset "deterministic and atomic variants ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) for (name, A) in solve_matrices(T) S, Sd, Nd, ws = solve_setup(backend, A; opts = SOLVE_OPTS) @test ws.atomic == SDS.capabilities(backend, T).atomic_add @@ -136,6 +141,7 @@ end end @testset "right-hand-side layouts and columns ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = random_spd(T, 500, 0.01) S, Sd, Nd, ws = solve_setup(backend, A; opts = SOLVE_OPTS) n = size(A, 1) @@ -166,6 +172,7 @@ end end @testset "schedules and dense implementations ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) A = laplacian3d(T, 8, 8, 8) b = rand(T, size(A, 1), 2) for opts in (Options(), Options(subtree_budgets = Int[]), Options(factorization_alg = "algo1", regime_c_width = 16), @@ -186,6 +193,7 @@ end end @testset "no allocations (CPU, $T)" for T in ELTYPES + Random.seed!(666) A = laplacian2d(T, 40, 40) S, Sd, Nd, ws = solve_setup(CPU(), A; opts = SOLVE_OPTS) b = rand(T, size(A, 1), 5) diff --git a/test/test_ubatch.jl b/test/test_ubatch.jl index dec7b8a..1c3dfab 100644 --- a/test/test_ubatch.jl +++ b/test/test_ubatch.jl @@ -36,6 +36,7 @@ end @testset "batches of SPD and symmetric systems ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) for (structure, A) in ((spd_structure(T), ubatch_spd(T)), ("S", ubatch_sym(T))), nb in (1, 2, 3, 16, 64) @testset "$structure nb = $nb" begin members = batch_members(A, nb) @@ -52,6 +53,7 @@ end end @testset "regimes A, B, C and refinement ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) nb, nrhs = 5, 3 for (structure, A) in ((spd_structure(T), ubatch_spd(T)), ("S", ubatch_sym(T))), (rname, opts) in ubatch_regimes() @testset "$structure $rname" begin @@ -75,6 +77,7 @@ end @testset "per-member inertia and pivot statistics ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) nb = 3 A = random_symindef(T, 60, 0.05) # "S" (real) / "H" (complex) members = batch_members(A, nb) @@ -102,6 +105,7 @@ end end @testset "ubatch_index and ubatch_mask ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) nb, nrhs = 4, 2 for (structure, A) in ((spd_structure(T), ubatch_spd(T)), ("S", ubatch_sym(T))), (rname, opts) in ubatch_regimes() @testset "$structure $rname" begin @@ -163,6 +167,7 @@ end end @testset "right-hand side layouts ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) nb = 3 for (structure, A) in ((spd_structure(T), ubatch_spd(T)), ("S", ubatch_sym(T))), nrhs in (1, 2) n = size(A, 1) @@ -200,6 +205,7 @@ end end @testset "a failed member ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) nb, j = 4, 37 A = ubatch_spd(T) n = size(A, 1) @@ -240,6 +246,7 @@ end end @testset "LinearAlgebra layer and errors ($(backend_name(backend)), $T)" for backend in BACKENDS, T in ELTYPES + Random.seed!(666) nb = 3 A = ubatch_spd(T) n = size(A, 1) diff --git a/test/utils.jl b/test/utils.jl index 9cfd799..83ba26e 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -169,14 +169,35 @@ panel_tol(::Type{T}) where {T} = 100 * eps(real(T)) """ factor_growth(Nr) -Element growth `max(max |L|, max |Uᵀ|) / min |d[1:n]|` of the reference factor +Element growth `max(max |L|, max |Uᵀ|) / min σ(pivot)` of the reference factor `Nr` (`ufactor` is empty for the symmetric structures, then only `L` counts). +The minimum runs over the pivots of `D`: `|d[k]|` of a 1×1 pivot and a lower +bound `|det| / ‖·‖_F` of the smallest singular value of a 2×2 block (whose +diagonal may be zero; the smaller determinant of the symmetric and the +Hermitian block). Perturbed pivots (`±ε`, compared exactly by the tests) are +left out. Also used by `test_numeric_lu.jl`, where `Nr.pivot_kind` can hold +`PIVOT_KIND_PERTURBED`: leaving those out of the minimum only tightens the LU +tolerance. """ function factor_growth(Nr) n = length(Nr.piv) big = max(maximum(abs, Nr.factor; init = zero(real(eltype(Nr.factor)))), maximum(abs, Nr.ufactor; init = zero(real(eltype(Nr.ufactor))))) - return big / minimum(abs, @view Nr.d[1:n]) + d, kind = Nr.d, Nr.pivot_kind + small = typemax(real(eltype(d))) + k = 1 + while k <= n + if kind[k] == SparseDirectSolver.PIVOT_KIND_2X2_FIRST + a, b, c = d[k], d[n + k], d[k + 1] + det = min(abs(a * c - b * b), abs(a * c - abs2(b))) + small = min(small, det / sqrt(abs2(a) + 2 * abs2(b) + abs2(c))) + k += 2 + else + kind[k] == SparseDirectSolver.PIVOT_KIND_PERTURBED || (small = min(small, abs(d[k]))) + k += 1 + end + end + return big / small end """ @@ -427,7 +448,8 @@ end `max |D_device - D_ref| / max |D_ref|` (both entries `d[k]` and the 2×2 subdiagonals `d[n + k]`) of a host copy `Nh` of a device LDLᵀ factor and the -reference factor `Nr`; compare with [`panel_tol`](@ref) (T15). +reference factor `Nr`; compare with [`growth_tol`](@ref) (T15; `panel_tol` +only on generators without element growth). """ d_error(Nh, Nr) = maximum(abs, Nh.d - Nr.d) / maximum(abs, Nr.d)