Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for LinearAlgebra wrapper operations #3132

Merged
merged 4 commits into from
Nov 21, 2022
Merged
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
121 changes: 121 additions & 0 deletions test/linear_algebra.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

"""
TestLinearAlgebra

The purpose of this file is to test various fallbacks that MutableArithmetics
implements in order to fix compatibility between JuMP types and the various
wrappers in LinearAlgebra.

Historically, these have been a big source of issues for users, so let's track
what works, what doesnt, and make sure that we don't regress over time.
"""
module TestLinearAlgebra

using Test

using JuMP
import LinearAlgebra

function runtests()
for name in names(@__MODULE__; all = true)
if startswith("$(name)", "test_")
@testset "$(name)" begin
getfield(@__MODULE__, name)()
end
end
end
return
end

function _test_matvec(model, A, b)
expr_vec = @expression(model, A * b)
expr_adj = @expression(model, b' * A)
@test size(expr_vec) == (2,)
@test size(expr_adj) == (1, 2)
if expr_vec isa AbstractArray{Float64}
@test A * b ≈ expr_vec
@test b' * A ≈ expr_adj
else
for i in 1:2
@test isequal_canonical(
expr_vec[i],
sum(A[i, j] * b[j] for j in 1:2),
)
@test isequal_canonical(
expr_adj[i],
sum(b[j] * A[j, i] for j in 1:2),
)
end
end
return
end

for T in (
:Adjoint,
:Diagonal,
:Hermitian,
:Symmetric,
:Transpose,
:LowerTriangular,
:UpperTriangular,
# TODO(odow): wrapper type that needs oneunit(VariableRef).
# :UnitLowerTriangular,
# :UnitUpperTriangular,
)
f = getfield(LinearAlgebra, T)
macro_matvec = Symbol("test_$(T)_macro_matvec")
matvec = Symbol("test_$(T)_matvec")
matvec_alloc = Symbol("test_$(T)_matvec_alloc")
test_broken = T in (:LowerTriangular, :UpperTriangular)
@eval begin
function $macro_matvec()
model = Model()
@variable(model, x[1:2, 1:2])
for X in (x, [1.0 2.0; 3.0 4.0])
if X isa Matrix{VariableRef} && $f == LinearAlgebra.Hermitian
continue # Cannot call f(X) for this wrapper type.
end
_test_matvec(model, $f(X), [1.0, 2.0])
_test_matvec(model, $f(X), x[:, 1])
_test_matvec(model, $f(X), x[:, 1] .+ [1.0, 2.0])
end
return
end
function $matvec()
if $f == LinearAlgebra.Hermitian
return # Cannot call f(X) for this wrapper type.
end
model = Model()
@variable(model, x[1:2, 1:2])
if $test_broken
@test_broken $f(x) * [1.0, 2.0] isa Vector{AffExpr}
else
@test $f(x) * [1.0, 2.0] isa Vector{AffExpr}
end
return
end
function $matvec_alloc()
if $test_broken || $f == LinearAlgebra.Hermitian
return
end
model = Model()
@variable(model, x[1:2, 1:2])
A, b = $f(x), [1.0, 2.0]
alloc_macro = @allocated(@expression(model, A * b))
# This is a bit of a weird one: we want to test that A * b in
# the REPL is efficient. We can do that by checking that there
# are the same number (or fewer) of allocations as the macro, in
# the hope that the macro is efficient.
@test @allocated(A * b) <= alloc_macro
return
end
end
end

end # module

TestLinearAlgebra.runtests()