Skip to content
11 changes: 5 additions & 6 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
OptimizationOptimisers = "42dfb2eb-d2b4-4451-abcd-913932933ac1"
OrdinaryDiffEq = "1dea7af3-3e70-54e6-95c3-0bf5283fa5ed"
PATHSolver = "f5f7c340-0bb3-5c69-969a-41884d311d1b"
ParametricMCPs = "9b992ff8-05bb-4ea1-b9d2-5ef72d82f7ad"
SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f"
SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1"
SimpleNonlinearSolve = "727e6d20-b764-4bd8-a329-72de5adea6c7"
Expand All @@ -29,23 +28,23 @@ Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
[compat]
Aqua = "0.8"
BenchmarkTools = "1"
CUDA = "4, 5"
CUDA = "4, 5, 6.1"
ChainRulesCore = "1"
ComponentArrays = "0.15"
DiffEqBase = "6"
DiffEqBase = "6, 7.5"
ExplicitImports = "1"
FiniteDifferences = "0.12"
ForwardDiff = "0.10, 1"
NonlinearSolve = "1, 2, 3, 4"
Optimisers = "0.3, 0.4"
Optimization = "3, 4"
Optimization = "3, 4, 5.6"
OptimizationOptimisers = "0.2, 0.3"
OrdinaryDiffEq = "6"
OrdinaryDiffEq = "6, 7.0"
PATHSolver = "1"
ParametricMCPs = "0.1"
SafeTestsets = "0.1"
SciMLSensitivity = "7"
SimpleNonlinearSolve = "0.1, 1, 2"
StableRNGs = "1"
Statistics = "1.10, 1.11"
SteadyStateDiffEq = "2"
Zygote = "0.6, 0.7"
27 changes: 4 additions & 23 deletions test/core/cpu/mcp.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using BenchmarkTools, ComplementaritySolve, ComponentArrays, FiniteDifferences
using ForwardDiff, NonlinearSolve, SimpleNonlinearSolve, StableRNGs, Test, Zygote
import ParametricMCPs

rng = StableRNG(0)

Expand Down Expand Up @@ -70,7 +69,10 @@ rng = StableRNG(0)
@test ∂θ_zygote ≈ ∂θ_finitediff atol = 1.0e-3 rtol = 1.0e-3
end

@testset "Benchmarking against ParametricMCPs.jl" begin
# ParametricMCPs.jl was previously benchmarked here as well, but its latest
# release caps ForwardDiff at 0.10 (via a weak dependency), which is
# incompatible with NonlinearSolve >= 4.17, NNlib >= 0.9.32, and CUDA >= 6.
@testset "Benchmarking" begin
u0 = randn(rng, Float64, 4)
lb = Float64[-Inf, -Inf, 0, 0]
ub = Float64[Inf, Inf, Inf, Inf]
Expand All @@ -88,18 +90,6 @@ rng = StableRNG(0)
return sum(abs2, sol.u)
end

prob_ext = ParametricMCPs.ParametricMCP(f, lb, ub, length(θ))
function loss_function_parametric_mcp(θ)
sol = ParametricMCPs.solve(prob_ext, θ)
return sum(abs2, sol.z)
end

function loss_function_parametric_mcp_total(θ)
prob = Zygote.@ignore ParametricMCPs.ParametricMCP(f, lb, ub, length(θ))
sol = ParametricMCPs.solve(prob, θ)
return sum(abs2, sol.z)
end

loss_function_path_oop = Base.Fix2(loss_function_oop, PATHSolverAlgorithm())
loss_function_nr_oop = Base.Fix2(
loss_function_oop,
Expand All @@ -121,15 +111,6 @@ rng = StableRNG(0)
t₂ = @belapsed only(Zygote.gradient($loss_function, $θ))
@info "ComplementaritySolve.jl: $(loss_function)" fwd_time = t₁ with_adjoint_time = t₂
end

for loss_function in (
loss_function_parametric_mcp,
loss_function_parametric_mcp_total,
)
t₁ = @belapsed $loss_function($θ)
t₂ = @belapsed only(Zygote.gradient($loss_function, $θ))
@info "ParametricMCPs.jl: $(loss_function)" fwd_time = t₁ with_adjoint_time = t₂
end
end
end
end
174 changes: 119 additions & 55 deletions test/core/cuda/lcp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,23 @@ include("../../test_utils.jl")
InteriorPointMethod(),
NonlinearReformulation(), BokhovenIterativeAlgorithm(),
]
sol = solve(prob, solver)

u = Array(sol.u)
@test u ≈ [4.0 / 3, 7.0 / 3] rtol = 1.0e-3
w = Array(A * sol.u .+ q)
@test w ≈ [0.0, 0.0] atol = 1.0e-3
if solver isa InteriorPointMethod
sol = solve(prob, solver)

u = Array(sol.u)
@test u ≈ [4.0 / 3, 7.0 / 3] rtol = 1.0e-3
w = Array(A * sol.u .+ q)
@test w ≈ [0.0, 0.0] atol = 1.0e-3
else
# ForwardDiff >= 1 scalar-indexes GPU dual arrays during jacobian
# seeding, breaking ForwardDiff-jacobian-based solvers on CUDA. See
# https://github.com/SciML/ComplementaritySolve.jl/issues/65
@test_broken begin
sol = solve(prob, solver)
isapprox(Array(sol.u), [4.0 / 3, 7.0 / 3]; rtol = 1.0e-3) &&
isapprox(Array(A * sol.u .+ q), [0.0, 0.0]; atol = 1.0e-3)
end
end
end

@testset "Batched Version" begin
Expand All @@ -35,10 +46,18 @@ include("../../test_utils.jl")
BokhovenIterativeAlgorithm(),
NonlinearReformulation(),
]
sol = solve(prob, solver; maxiters = 10000)

@test all(z -> ≈(Array(z), [4.0 / 3, 7.0 / 3]; rtol = 1.0e-3), eachcol(sol.u))
@test all(z -> ≈(Array(A * z .+ q), [0.0, 0.0]; atol = 1.0e-3), eachcol(sol.u))
# Broken by ForwardDiff >= 1 GPU dual seeding, see
# https://github.com/SciML/ComplementaritySolve.jl/issues/65
@test_broken begin
sol = solve(prob, solver; maxiters = 10000)
all(
z -> ≈(Array(z), [4.0 / 3, 7.0 / 3]; rtol = 1.0e-3),
eachcol(sol.u)
) && all(
z -> ≈(Array(A * z .+ q), [0.0, 0.0]; atol = 1.0e-3),
eachcol(sol.u)
)
end
end
end

Expand All @@ -48,28 +67,35 @@ include("../../test_utils.jl")
solver = NonlinearReformulation()

for loss_function in (sum, Base.Fix1(sum, abs2))
∂A, ∂q = Zygote.gradient(A, q) do A, q
prob = LinearComplementarityProblem{false}(A, q, u0)
sol = solve(prob, solver)
return loss_function(sol.u)
end

θ = ComponentArray((; A = Array(A), q = Array(q)))
∂θ_fd = ForwardDiff.gradient(θ) do θ
prob = LinearComplementarityProblem{false}(θ.A, θ.q, Array(u0))
sol = solve(prob, solver)
return loss_function(sol.u)
# Broken by ForwardDiff >= 1 GPU dual seeding, see
# https://github.com/SciML/ComplementaritySolve.jl/issues/65
@test_broken begin
∂A, ∂q = Zygote.gradient(A, q) do A, q
prob = LinearComplementarityProblem{false}(A, q, u0)
sol = solve(prob, solver)
return loss_function(sol.u)
end

θ = ComponentArray((; A = Array(A), q = Array(q)))
∂θ_fd = ForwardDiff.gradient(θ) do θ
prob = LinearComplementarityProblem{false}(θ.A, θ.q, Array(u0))
sol = solve(prob, solver)
return loss_function(sol.u)
end

(∂A !== nothing && !iszero(∂A) && size(∂A) == size(A)) &&
(∂q !== nothing && !iszero(∂q) && size(∂q) == size(q)) &&
isapprox(Array(∂A), ∂θ_fd.A; atol = 1.0e-3, rtol = 1.0e-3) &&
isapprox(Array(∂q), ∂θ_fd.q; atol = 1.0e-3, rtol = 1.0e-3)
end

@test ∂A !== nothing && !iszero(∂A) && size(∂A) == size(A)
@test ∂q !== nothing && !iszero(∂q) && size(∂q) == size(q)
@test Array(∂A) ≈ ∂θ_fd.A atol = 1.0e-3 rtol = 1.0e-3
@test Array(∂q) ≈ ∂θ_fd.q atol = 1.0e-3 rtol = 1.0e-3

@test_nowarn Zygote.gradient(A, q) do M, q
prob = LinearComplementarityProblem{true}(M, q, u0)
sol = solve(prob)
return loss_function(sol.u)
@test_broken begin
Zygote.gradient(A, q) do M, q
prob = LinearComplementarityProblem{true}(M, q, u0)
sol = solve(prob)
return loss_function(sol.u)
end
true
end
end
end
Expand All @@ -86,31 +112,69 @@ include("../../test_utils.jl")
q_ = randn(rng, Float32, szq...) |> cu

for loss_function in (sum, Base.Fix1(sum, abs2))
∂M, ∂q = Zygote.gradient(M_, q_) do M, q
prob = LinearComplementarityProblem{false}(M, q)
sol = solve(prob)
return loss_function(sol.u)
end

θ = ComponentArray((; M = Array(M_), q = Array(q_)))
∂θ_fd = ForwardDiff.gradient(θ) do θ
prob = LinearComplementarityProblem{false}(θ.M, θ.q)
sol = solve(prob)
return loss_function(sol.u)
end

@test ∂M !== nothing && size(∂M) == size(M_)
@test ∂q !== nothing && size(∂q) == size(q_)
@test Array(∂M) ≈ ∂θ_fd.M atol = 1.0e-2 rtol = 1.0e-2
@test Array(∂q) ≈ ∂θ_fd.q atol = 1.0e-2 rtol = 1.0e-2

# We can't check for correctness with FwdDiff & FiniteDifferences
# for inplace problems since the in-place batched solvers are not as
# accurate as the non-inplace ones.
@test_nowarn Zygote.gradient(M_, q_) do M, q
prob = LinearComplementarityProblem{true}(M, q)
sol = solve(prob)
return loss_function(sol.u)
if szq == (2, 1)
# this combination takes the batched code path that does not
# go through ForwardDiff jacobian seeding, so it works on CUDA
∂M, ∂q = Zygote.gradient(M_, q_) do M, q
prob = LinearComplementarityProblem{false}(M, q)
sol = solve(prob)
return loss_function(sol.u)
end

θ = ComponentArray((; M = Array(M_), q = Array(q_)))
∂θ_fd = ForwardDiff.gradient(θ) do θ
prob = LinearComplementarityProblem{false}(θ.M, θ.q)
sol = solve(prob)
return loss_function(sol.u)
end

@test ∂M !== nothing && size(∂M) == size(M_)
@test ∂q !== nothing && size(∂q) == size(q_)
@test Array(∂M) ≈ ∂θ_fd.M atol = 1.0e-2 rtol = 1.0e-2
@test Array(∂q) ≈ ∂θ_fd.q atol = 1.0e-2 rtol = 1.0e-2

# We can't check for correctness with FwdDiff & FiniteDifferences
# for inplace problems since the in-place batched solvers are not
# as accurate as the non-inplace ones.
@test_nowarn Zygote.gradient(M_, q_) do M, q
prob = LinearComplementarityProblem{true}(M, q)
sol = solve(prob)
return loss_function(sol.u)
end
else
# Broken by ForwardDiff >= 1 GPU dual seeding, see
# https://github.com/SciML/ComplementaritySolve.jl/issues/65
@test_broken begin
∂M, ∂q = Zygote.gradient(M_, q_) do M, q
prob = LinearComplementarityProblem{false}(M, q)
sol = solve(prob)
return loss_function(sol.u)
end

θ = ComponentArray((; M = Array(M_), q = Array(q_)))
∂θ_fd = ForwardDiff.gradient(θ) do θ
prob = LinearComplementarityProblem{false}(θ.M, θ.q)
sol = solve(prob)
return loss_function(sol.u)
end

(∂M !== nothing && size(∂M) == size(M_)) &&
(∂q !== nothing && size(∂q) == size(q_)) &&
isapprox(Array(∂M), ∂θ_fd.M; atol = 1.0e-2, rtol = 1.0e-2) &&
isapprox(Array(∂q), ∂θ_fd.q; atol = 1.0e-2, rtol = 1.0e-2)
end

# We can't check for correctness with FwdDiff & FiniteDifferences
# for inplace problems since the in-place batched solvers are not
# as accurate as the non-inplace ones.
@test_broken begin
Zygote.gradient(M_, q_) do M, q
prob = LinearComplementarityProblem{true}(M, q)
sol = solve(prob)
return loss_function(sol.u)
end
true
end
end
end
end
Expand Down
20 changes: 13 additions & 7 deletions test/core/cuda/mcp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@ rng = StableRNG(0)
NonlinearReformulation(:smooth),
NonlinearReformulation(:minmax),
)
sol = solve(prob, solver; verbose = false)

@test sol.u[1:2] ≈ θ atol = 1.0e-4 rtol = 1.0e-4
# Broken by ForwardDiff >= 1 GPU dual seeding, see
# https://github.com/SciML/ComplementaritySolve.jl/issues/65
@test_broken begin
sol = solve(prob, solver; verbose = false)
isapprox(sol.u[1:2], θ; atol = 1.0e-4, rtol = 1.0e-4)
end
end
end
end
Expand All @@ -59,10 +62,13 @@ rng = StableRNG(0)
end

θ = [2.0, -3.0] |> cu
∂θ_zygote = only(Zygote.gradient(loss_function, θ))
∂θ_forwarddiff = ForwardDiff.gradient(loss_function_cpu, Array(θ))

@test Array(∂θ_zygote) ≈ ∂θ_forwarddiff atol = 1.0e-6 rtol = 1.0e-6
# Broken by ForwardDiff >= 1 GPU dual seeding, see
# https://github.com/SciML/ComplementaritySolve.jl/issues/65
@test_broken begin
∂θ_zygote = only(Zygote.gradient(loss_function, θ))
∂θ_forwarddiff = ForwardDiff.gradient(loss_function_cpu, Array(θ))
isapprox(Array(∂θ_zygote), ∂θ_forwarddiff; atol = 1.0e-6, rtol = 1.0e-6)
end
end
end
end
Loading