From 45e7c2191b6112c7b66f846d45a4f11cb6f0ffe1 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 1 Apr 2026 15:39:02 +1300 Subject: [PATCH 1/4] Refactor the tests into independent modules --- test/big.jl | 86 --- test/bigfloat_dot.jl | 84 --- test/bigfloat_fma.jl | 92 --- test/bigfloat_neg_abs.jl | 25 - test/copy.jl | 37 -- test/dispatch.jl | 154 ----- test/dummy.jl | 121 +++- test/interface.jl | 254 -------- test/range.jl | 20 - test/rewrite.jl | 270 --------- test/runtests.jl | 49 +- test/test_basics.jl | 551 ++++++++++++++++++ test/test_big.jl | 322 ++++++++++ test/{broadcast.jl => test_broadcast.jl} | 39 +- test/{evalpoly.jl => test_evalpoly.jl} | 17 + test/{hygiene.jl => test_hygiene.jl} | 13 +- test/{int.jl => test_int.jl} | 39 +- test/{matmul.jl => test_matmul.jl} | 30 +- test/test_rewrite.jl | 128 ++++ ...ite_generic.jl => test_rewrite_generic.jl} | 125 +++- ...{SparseArrays.jl => test_sparse_arrays.jl} | 9 +- test/utilities.jl | 14 - 22 files changed, 1346 insertions(+), 1133 deletions(-) delete mode 100644 test/big.jl delete mode 100644 test/bigfloat_dot.jl delete mode 100644 test/bigfloat_fma.jl delete mode 100644 test/bigfloat_neg_abs.jl delete mode 100644 test/copy.jl delete mode 100644 test/dispatch.jl delete mode 100644 test/interface.jl delete mode 100644 test/range.jl delete mode 100644 test/rewrite.jl create mode 100644 test/test_basics.jl create mode 100644 test/test_big.jl rename test/{broadcast.jl => test_broadcast.jl} (70%) rename test/{evalpoly.jl => test_evalpoly.jl} (94%) rename test/{hygiene.jl => test_hygiene.jl} (91%) rename test/{int.jl => test_int.jl} (85%) rename test/{matmul.jl => test_matmul.jl} (96%) create mode 100644 test/test_rewrite.jl rename test/{rewrite_generic.jl => test_rewrite_generic.jl} (82%) rename test/{SparseArrays.jl => test_sparse_arrays.jl} (95%) delete mode 100644 test/utilities.jl diff --git a/test/big.jl b/test/big.jl deleted file mode 100644 index 7205ea28..00000000 --- a/test/big.jl +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright (c) 2019 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. - -function allocation_test( - op, - T, - short, - short_to, - n; - a = T(2), - b = T(3), - c = T(4), -) - @test MA.promote_operation(op, T, T) == T - alloc_test(() -> MA.promote_operation(op, T, T), 0) - if op != div && op != - - @test MA.promote_operation(op, T, T, T) == T - alloc_test(() -> MA.promote_operation(op, T, T, T), 0) - end - g = op(a, b) - @test c === short_to(c, a, b) - @test g == c - A = MA.copy_if_mutable(a) - @test A === short(A, b) - @test g == A - alloc_test(() -> short(A, b), n) - alloc_test(() -> short_to(c, a, b), n) - @test g == MA.buffered_operate!(nothing, op, MA.copy_if_mutable(a), b) - @test g == MA.buffered_operate_to!(nothing, c, op, a, b) - buffer = MA.buffer_for(op, typeof(a), typeof(b)) - @test g == MA.buffered_operate_to!(buffer, c, op, a, b) - alloc_test(() -> MA.buffered_operate_to!(buffer, c, op, a, b), 0) - return -end - -function add_sub_mul_test(op, T; a = T(2), b = T(3), c = T(4)) - g = op(a, b, c) - @test g == MA.buffered_operate!(nothing, op, MA.copy_if_mutable(a), b, c) - buffer = MA.buffer_for(op, typeof(a), typeof(b), typeof(c)) - return alloc_test(() -> MA.buffered_operate!(buffer, op, a, b, c), 0) -end -@testset "$T" for T in [BigInt, BigFloat, Rational{BigInt}] - MA.Test.int_test(T) - @testset "Allocation" begin - MAX_ALLOC = T <: Rational ? 280 : 0 - allocation_test(+, T, MA.add!!, MA.add_to!!, MAX_ALLOC) - allocation_test(-, T, MA.sub!!, MA.sub_to!!, MAX_ALLOC) - allocation_test(*, T, MA.mul!!, MA.mul_to!!, MAX_ALLOC) - add_sub_mul_test(MA.add_mul, T) - add_sub_mul_test(MA.sub_mul, T) - if T <: Rational # https://github.com/jump-dev/MutableArithmetics.jl/issues/167 - allocation_test( - +, - T, - MA.add!!, - MA.add_to!!, - MAX_ALLOC, - a = T(1 // 2), - b = T(3 // 2), - c = T(5 // 2), - ) - allocation_test( - -, - T, - MA.sub!!, - MA.sub_to!!, - MAX_ALLOC, - a = T(1 // 2), - b = T(3 // 2), - c = T(5 // 2), - ) - end - # Requires https://github.com/JuliaLang/julia/commit/3f92832df042198b2daefc1f7ca609db38cb8173 - # for `gcd` to be defined on `Rational`. - if T == BigInt - allocation_test(div, T, MA.div!!, MA.div_to!!, 0) - end - if T == BigInt || T == Rational{BigInt} - allocation_test(gcd, T, MA.gcd!!, MA.gcd_to!!, 0) - allocation_test(lcm, T, MA.lcm!!, MA.lcm_to!!, 0) - end - end -end diff --git a/test/bigfloat_dot.jl b/test/bigfloat_dot.jl deleted file mode 100644 index e7836108..00000000 --- a/test/bigfloat_dot.jl +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (c) 2023 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. - -@testset "prec:$prec size:$size bias:$bias" for (prec, size, bias) in - Iterators.product( - # These precisions (in bits) are most probably smaller than what - # users would set in their code, but they are relevant anyway - # because the compensated summation algorithm used for computing - # the dot product is accurate independently of the machine - # precision (except when vector lengths are really huge with - # respect to the precision). - (32, 64), - # Compensated summation should be accurate even for very large - # input vectors, so test that. - (10000,), - # The zero "bias" signifies that the input will be entirely - # nonnegative (drawn from the interval [0, 1]), while a positive - # bias shifts that interval towards negative infinity. We want to - # test a few different values here, but we do not want to set the - # bias to 0.5, because the expected value is then zero and there's - # no guarantee on the relative error in that case. - (0.0, 2^-2, 2^-2 + 2^-3 + 2^-4), -) - err = setprecision(BigFloat, prec) do - maximum_relative_error = mapreduce(max, 1:10) do _ - # Generate some random vectors for dot(x, y) input. - x = rand(BigFloat, size) .- bias - y = rand(BigFloat, size) .- bias - # Copy x and y so that we can check we haven't mutated them after - # the fact. - old_x, old_y = MA.copy_if_mutable(x), MA.copy_if_mutable(y) - # Compute output = dot(x, y) - buf = MA.buffer_for( - LinearAlgebra.dot, - Vector{BigFloat}, - Vector{BigFloat}, - ) - output = BigFloat() - MA.buffered_operate_to!!(buf, output, LinearAlgebra.dot, x, y) - # Check that we haven't mutated x or y - @test old_x == x - @test old_y == y - # Compute dot(x, y) in larger precision. This will be used to - # compare with our `dot`. - accurate = setprecision(BigFloat, 8 * precision(BigFloat)) do - return LinearAlgebra.dot(x, y) - end - # Compute the relative error - return abs(accurate - output) / abs(accurate) - end - # Return estimate for ULP - return maximum_relative_error / eps(BigFloat) - end - @test 0 <= err < 1 -end - -function alloc_test_helper( - buf::B, - output::BigFloat, - x::Vector{BigFloat}, - y::Vector{BigFloat}, -) where {B<:Any} - let b = buf, o = output, x = x, y = y - () -> MA.buffered_operate_to!!(b, o, LinearAlgebra.dot, x, y) - end -end - -@testset "alloc" begin - x = rand(BigFloat, 1000) - y = rand(BigFloat, 1000) - V = Vector{BigFloat} - alloc_test( - alloc_test_helper( - MA.buffer_for(LinearAlgebra.dot, V, V), - BigFloat(), - x, - y, - ), - 0, - ) -end diff --git a/test/bigfloat_fma.jl b/test/bigfloat_fma.jl deleted file mode 100644 index 95727286..00000000 --- a/test/bigfloat_fma.jl +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (c) 2023 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. - -function test_fma_output_values(x::F, y::F, z::F) where {F<:BigFloat} - two_roundings_reference = x * y + z - one_rounding_reference = fma(x, y, z) - @test one_rounding_reference != two_roundings_reference - - @testset "fma $op output values" for op in (MA.operate!, MA.operate!!) - (a, b, c) = map(MA.copy_if_mutable, (x, y, z)) - @inferred op(fma, a, b, c) - @test one_rounding_reference == a - @test y == b - @test z == c - end - - @testset "fma $op output values" for op in (MA.operate_to!, MA.operate_to!!) - (a, b, c) = map(MA.copy_if_mutable, (x, y, z)) - out = F() - @inferred op(out, fma, a, b, c) - @test one_rounding_reference == out - @test x == a - @test y == b - @test z == c - end - - return nothing -end - -function test_fma_output_values(x::F, y::F, z::F) where {F<:Float64} - return test_fma_output_values(map(BigFloat, (x, y, z))...) -end - -function test_fma_output_values_func(x::F, y::F, z::F) where {F<:Float64} - return let x = x, y = y, z = z - () -> test_fma_output_values(x, y, z) - end -end - -@testset "fma output values: $exp_x $exp_y $sign_x $sign_y" for exp_x in (-3):3, - exp_y in (-3):3, - sign_x in (-1, 1), - sign_y in (-1, 1) - - # Assuming a two-bit mantissa - significand_length = 2 - - sign_z = -sign_x * sign_y - exp_z = exp_x + exp_y - - base = 2.0 - bit_0 = 2.0^0 - bit_1 = 2.0^-1 - - x = sign_x * base^exp_x * (bit_0 + bit_1) - y = sign_y * base^exp_y * (bit_0 + bit_1) - z = sign_z * base^exp_z * (bit_0 + bit_1) - - setprecision( - test_fma_output_values_func(x, y, z), - BigFloat, - significand_length, - ) -end - -@testset "muladd operate_to!! type inferred" begin - m1 = BigFloat(-1.0) - out = BigFloat() - @test iszero(@inferred MA.operate_to!!(out, Base.muladd, m1, m1, m1)) -end - -@testset "muladd operate!! type inferred" begin - x = BigFloat(-1.0) - y = BigFloat(-1.0) - z = BigFloat(-1.0) - @test iszero(@inferred MA.operate!!(Base.muladd, x, y, z)) -end - -@testset "fma $op doesn't allocate" for op in (MA.operate_to!, MA.operate_to!!) - alloc_test(let op = op, o = big"1.3", x = big"1.3" - () -> op(o, Base.fma, x, x, x) - end, 0) -end - -@testset "fma $op doesn't allocate" for op in (MA.operate!, MA.operate!!) - alloc_test(let op = op, x = big"1.3", y = big"1.3", z = big"1.3" - () -> op(Base.fma, x, y, z) - end, 0) -end diff --git a/test/bigfloat_neg_abs.jl b/test/bigfloat_neg_abs.jl deleted file mode 100644 index 81481cc9..00000000 --- a/test/bigfloat_neg_abs.jl +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright (c) 2023 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. - -function neg_abs_test(f::F, x::BigFloat) where {F<:Function} - backup = MA.copy_if_mutable(x) - output = MA.operate!(f, x) - @test f(backup) == output == x - return -end - -@testset "$input" for input in map( - BigFloat, - (-1.3, -1.0, -0.7, -0.0, 0.0, 0.3, 1.0, 1.7), -) - @testset "-" begin - neg_abs_test(-, input) - end - - @testset "Base.abs" begin - neg_abs_test(Base.abs, input) - end -end diff --git a/test/copy.jl b/test/copy.jl deleted file mode 100644 index 531b5c73..00000000 --- a/test/copy.jl +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (c) 2023 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. - -@testset "copy: $T" for T in ( - Float64, - BigFloat, - Int, - BigInt, - Rational{Int}, - Rational{BigInt}, -) - @test MA.operate!!(copy, T(2)) == 2 - @test MA.operate_to!!(T(3), copy, T(2)) == 2 - if MA.mutability(T, copy, T) == MA.IsMutable() - @testset "mutable" begin - @testset "correctness" begin - x = T(2) - y = T(3) - @test MA.operate!(copy, x) === x == 2 - @test MA.operate_to!(y, copy, x) === y == 2 - end - @testset "alloc" begin - f = let x = T(2) - () -> MA.operate!(copy, x) - end - g = let x = T(2), y = T(3) - () -> MA.operate_to!(y, copy, x) - end - alloc_test(f, 0) - alloc_test(g, 0) - end - end - end -end diff --git a/test/dispatch.jl b/test/dispatch.jl deleted file mode 100644 index 272ea8c2..00000000 --- a/test/dispatch.jl +++ /dev/null @@ -1,154 +0,0 @@ -# Copyright (c) 2019 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. - -using LinearAlgebra - -# Tests that the calls are correctly redirected to the mutable calls -# by checking allocations -function dispatch_tests(::Type{T}) where {T} - buffer = zero(T) - a = one(T) - b = one(T) - c = one(T) - x = convert.(T, [1, 2, 3]) - # Need to allocate 1 BigInt for the result and one for the buffer - nalloc = 3 * @allocated(BigInt(1)) - alloc_test(() -> MA.fused_map_reduce(MA.add_mul, x, x), nalloc) - alloc_test(() -> MA.fused_map_reduce(MA.add_dot, x, x), nalloc) - if T <: MA.AbstractMutable - alloc_test(() -> x'x, nalloc) - alloc_test(() -> transpose(x) * x, nalloc) - alloc_test(() -> LinearAlgebra.dot(x, x), nalloc) - end -end - -@testset "Dispatch tests" begin - dispatch_tests(BigInt) - # On `DummyBigInt` allocates more on previous releases of Julia - # as it's dynamically allocated - dispatch_tests(DummyBigInt) - - @testset "dot non-concrete vector" begin - x = [5.0, 6.0] - y = Vector{Union{Float64,String}}(x) - @test MA.operate(LinearAlgebra.dot, x, y) == LinearAlgebra.dot(x, y) - @test MA.operate(*, x', y) == x' * y - end - - @testset "dot vector of vectors" begin - x = [5.0, 6.0] - z = [x, x] - @test MA.operate(LinearAlgebra.dot, z, z) == LinearAlgebra.dot(z, z) - end -end - -@testset "*(::Real, ::Hermitian)" begin - A = DummyBigInt[1 2; 2 3] - B = DummyBigInt[2 4; 4 6] - D = LinearAlgebra.Hermitian(B) - for s in (:L, :U) - Ah = LinearAlgebra.Hermitian(A, s) - @test all(MA.isequal_canonical.(2 * Ah, D)) - @test all(MA.isequal_canonical.(Ah * 2, D)) - end -end - -@testset "*(::AbstractMutable, ::Symmetric)" begin - for A in ([1 2; 5 3], DummyBigInt[1 2; 5 3]) - for x in (2, DummyBigInt(2)) - for s in (:L, :U) - # *(::AbstractMutable, ::Symmetric) - B = LinearAlgebra.Symmetric(A, s) - C = LinearAlgebra.Symmetric(x * A, s) - D = x * B - @test D isa LinearAlgebra.Symmetric - @test MA.isequal_canonical(D, C) - @test MA.isequal_canonical(D + D, 2 * D) - # *(::Symmetric, ::AbstractMutable) - B = LinearAlgebra.Symmetric(A, s) - C = LinearAlgebra.Symmetric(A * x, s) - D = B * x - @test D isa LinearAlgebra.Symmetric - @test MA.isequal_canonical(D, C) - @test MA.isequal_canonical(D + D, 2 * D) - end - end - end -end - -@testset "*(::Complex, ::Hermitian)" begin - A = BigInt[1 2; 2 3] - B = LinearAlgebra.Hermitian(DummyBigInt.(A)) - C = 2im * A - @test 2im * B == C - @test C isa Matrix{Complex{BigInt}} -end - -@testset "operate_to!(::Array, ::typeof(*), ::AbstractMutable, ::Array)" begin - for A in ([1 2; 5 3], DummyBigInt[1 2; 5 3]) - for x in (2, DummyBigInt(2)) - # operate_to!(::Array, *, ::AbstractMutable, ::Array) - B = x * A - C = zero(B) - D = MA.operate_to!(C, *, x, A) - @test C === D - @test typeof(B) == typeof(C) - @test MA.isequal_canonical(B, C) - # operate_to!(::Array, *, ::Array, ::AbstractMutable) - B = A * x - C = zero(B) - D = MA.operate_to!(C, *, A, x) - @test C === D - @test typeof(B) == typeof(C) - @test MA.isequal_canonical(B, C) - end - end -end - -function non_mutable_sum_pr306(x) - y = zero(eltype(x)) - for xi in x - y += xi - end - return y -end - -@testset "sum_with_init" begin - x = convert(Vector{DummyBigInt}, 1:100) - # compilation - @allocated sum(x) - @allocated sum(x; init = DummyBigInt(0)) - @allocated non_mutable_sum_pr306(x) - # now test actual allocations - no_init = @allocated sum(x) - with_init = @allocated sum(x; init = DummyBigInt(0)) - no_ma = @allocated non_mutable_sum_pr306(x) - # There's an additional 16 bytes for kwarg version. Upper bound by 40 to be - # safe between Julia versions - @test with_init <= no_init + 40 - # MA is at least 10-times better than no MA for this example - @test 10 * with_init < no_ma -end - -@testset "sum_with_init_and_dims" begin - x = reshape(convert(Vector{DummyBigInt}, 1:12), 3, 4) - X = reshape(1:12, 3, 4) - for dims in (1, 2, :, 1:2, (1, 2)) - # Without (; init) - @test MA.isequal_canonical(sum(x; dims), DummyBigInt.(sum(X; dims))) - # With (; init) - y = sum(x; init = DummyBigInt(0), dims) - @test MA.isequal_canonical(y, DummyBigInt.(sum(X; dims))) - end -end - -@testset "issue_336" begin - A = DummyBigInt[1 2; 3 4] - B = Any[A[1], A[2]] - @test MA.isequal_canonical(A * B, A * A[1:2]) - @test_throws DimensionMismatch A * Any[A[1]] - @test_throws DimensionMismatch MA.operate(*, A, Any[A[1]]) -end diff --git a/test/dummy.jl b/test/dummy.jl index bf2a4e7a..9f1e6eb5 100644 --- a/test/dummy.jl +++ b/test/dummy.jl @@ -4,54 +4,93 @@ # v.2.0. If a copy of the MPL was not distributed with this file, You can obtain # one at http://mozilla.org/MPL/2.0/. -using LinearAlgebra +import LinearAlgebra import MutableArithmetics as MA +struct DummyMutable end + +function MA.promote_operation( + ::typeof(+), + ::Type{DummyMutable}, + ::Type{DummyMutable}, +) + return DummyMutable +end + +# Test that this does not triggers any ambiguity even if there is a fallback specific to `/`. +function MA.promote_operation( + ::Union{typeof(-),typeof(/)}, + ::Type{DummyMutable}, + ::Type{DummyMutable}, +) + return DummyMutable +end + +MA.mutability(::Type{DummyMutable}) = MA.IsMutable() + # It does not support operation with floats on purpose to test that # MutableArithmetics does not convert to float when it shouldn't. struct DummyBigInt <: MA.AbstractMutable data::BigInt end -DummyBigInt(J::UniformScaling) = DummyBigInt(J.λ) + +DummyBigInt(J::LinearAlgebra.UniformScaling) = DummyBigInt(J.λ) # Broadcast Base.broadcastable(x::DummyBigInt) = Ref(x) + # The version with `DummyBigInt` without `Type` is needed in LinearAlgebra for # Julia v1.6+. Base.ndims(::Union{Type{DummyBigInt},DummyBigInt}) = 0 function Base.promote_rule( ::Type{DummyBigInt}, - ::Type{<:Union{Integer,UniformScaling{<:Integer}}}, + ::Type{<:Union{Integer,LinearAlgebra.UniformScaling{<:Integer}}}, ) return DummyBigInt end + # `copy` on BigInt returns the same instance anyway Base.copy(x::DummyBigInt) = x + MA.mutable_copy(x::DummyBigInt) = DummyBigInt(MA.mutable_copy(x.data)) + LinearAlgebra.symmetric_type(::Type{DummyBigInt}) = DummyBigInt + LinearAlgebra.symmetric(x::DummyBigInt, ::Symbol) = x + LinearAlgebra.issymmetric(::DummyBigInt) = true + LinearAlgebra.hermitian_type(::Type{DummyBigInt}) = DummyBigInt + LinearAlgebra.hermitian(x::DummyBigInt, ::Symbol) = x + LinearAlgebra.dot(x::DummyBigInt, y::DummyBigInt) = x * y + function LinearAlgebra.dot( x::DummyBigInt, - y::Union{Integer,UniformScaling{<:Integer}}, + y::Union{Integer,LinearAlgebra.UniformScaling{<:Integer}}, ) return x * y end + function LinearAlgebra.dot( - x::Union{Integer,UniformScaling{<:Integer}}, + x::Union{Integer,LinearAlgebra.UniformScaling{<:Integer}}, y::DummyBigInt, ) return x * y end + LinearAlgebra.transpose(x::DummyBigInt) = x + LinearAlgebra.adjoint(x::DummyBigInt) = x + MA.mutability(::Type{DummyBigInt}) = MA.IsMutable() + MA.promote_operation(::typeof(zero), ::Type{DummyBigInt}) = DummyBigInt + MA.promote_operation(::typeof(one), ::Type{DummyBigInt}) = DummyBigInt + function MA.promote_operation( ::typeof(+), ::Type{DummyBigInt}, @@ -59,8 +98,11 @@ function MA.promote_operation( ) return DummyBigInt end + _data(x) = x + _data(x::DummyBigInt) = x.data + MA.scaling(x::DummyBigInt) = x function MA.operate_to!( @@ -77,8 +119,11 @@ function MA.buffer_for( ) where {N} return DummyBigInt(BigInt()) end + _undummy(x) = x + _undummy(x::DummyBigInt) = x.data + function MA.buffered_operate_to!( buffer::DummyBigInt, output::DummyBigInt, @@ -94,6 +139,7 @@ function MA.buffered_operate_to!( ) return output end + function MA.buffered_operate!( buffer::DummyBigInt, op::Function, @@ -114,9 +160,11 @@ function MA.operate_to!( ) return MA.operate_to!(output, MA.add_sub_op(op), x, *(y, z, args...)) end + function MA.operate_to!(output::DummyBigInt, op::MA.AddSubMul, x, y, z, args...) return MA.operate_to!(output, MA.add_sub_op(op), x, *(y, z, args...)) end + function MA.operate!( op::Function, x::DummyBigInt, @@ -136,40 +184,85 @@ function MA.promote_operation( ) return DummyBigInt end + Base.convert(::Type{DummyBigInt}, x::Int) = DummyBigInt(x) + MA.isequal_canonical(x::DummyBigInt, y::DummyBigInt) = x.data == y.data + Base.iszero(x::DummyBigInt) = iszero(x.data) + Base.isone(x::DummyBigInt) = isone(x.data) -# We don't define == to tests that implementation of MA can pass the tests without defining ==. + +# We don't define == to tests that implementation of MA can pass the tests +# without defining ==. +# # This is the case for MOI functions for instance. -# For th same reason, we only define `zero` and `one` for `Type{DummyBigInt}`, not for `DummyBigInt`. +# +# For the same reason, we only define `zero` and `one` for `Type{DummyBigInt}`, +# not for `DummyBigInt`. + Base.zero(::Type{DummyBigInt}) = DummyBigInt(zero(BigInt)) + Base.one(::Type{DummyBigInt}) = DummyBigInt(one(BigInt)) + Base.:+(x::DummyBigInt) = DummyBigInt(+x.data) + Base.:+(x::DummyBigInt, y::DummyBigInt) = DummyBigInt(x.data + y.data) -function Base.:+(x::DummyBigInt, y::Union{Integer,UniformScaling{<:Integer}}) + +function Base.:+( + x::DummyBigInt, + y::Union{Integer,LinearAlgebra.UniformScaling{<:Integer}}, +) return DummyBigInt(x.data + y) end -function Base.:+(x::Union{Integer,UniformScaling{<:Integer}}, y::DummyBigInt) + +function Base.:+( + x::Union{Integer,LinearAlgebra.UniformScaling{<:Integer}}, + y::DummyBigInt, +) return DummyBigInt(x + y.data) end + Base.:-(x::DummyBigInt) = DummyBigInt(-x.data) + Base.:-(x::DummyBigInt, y::DummyBigInt) = DummyBigInt(x.data - y.data) -function Base.:-(x::Union{Integer,UniformScaling{<:Integer}}, y::DummyBigInt) + +function Base.:-( + x::Union{Integer,LinearAlgebra.UniformScaling{<:Integer}}, + y::DummyBigInt, +) return DummyBigInt(x - y.data) end -function Base.:-(x::DummyBigInt, y::Union{Integer,UniformScaling{<:Integer}}) + +function Base.:-( + x::DummyBigInt, + y::Union{Integer,LinearAlgebra.UniformScaling{<:Integer}}, +) return DummyBigInt(x.data - y) end + Base.:*(x::DummyBigInt) = x + Base.:*(x::DummyBigInt, y::DummyBigInt) = DummyBigInt(x.data * y.data) -function Base.:*(x::Union{Integer,UniformScaling{<:Integer}}, y::DummyBigInt) + +function Base.:*( + x::Union{Integer,LinearAlgebra.UniformScaling{<:Integer}}, + y::DummyBigInt, +) return DummyBigInt(x * y.data) end -function Base.:*(x::DummyBigInt, y::Union{Integer,UniformScaling{<:Integer}}) + +function Base.:*( + x::DummyBigInt, + y::Union{Integer,LinearAlgebra.UniformScaling{<:Integer}}, +) return DummyBigInt(x.data * y) end -function Base.:^(x::DummyBigInt, y::Union{Integer,UniformScaling{<:Integer}}) + +function Base.:^( + x::DummyBigInt, + y::Union{Integer,LinearAlgebra.UniformScaling{<:Integer}}, +) return DummyBigInt(x.data^y) end diff --git a/test/interface.jl b/test/interface.jl deleted file mode 100644 index 948cb73f..00000000 --- a/test/interface.jl +++ /dev/null @@ -1,254 +0,0 @@ -# Copyright (c) 2019 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. - -using Test -import MutableArithmetics as MA -import LinearAlgebra - -struct DummyMutable end - -function MA.promote_operation( - ::typeof(+), - ::Type{DummyMutable}, - ::Type{DummyMutable}, -) - return DummyMutable -end -# Test that this does not triggers any ambiguity even if there is a fallback specific to `/`. -function MA.promote_operation( - ::Union{typeof(-),typeof(/)}, - ::Type{DummyMutable}, - ::Type{DummyMutable}, -) - return DummyMutable -end -MA.mutability(::Type{DummyMutable}) = MA.IsMutable() - -# Theodorus' constant √3 (deined in global scope due to const) -Base.@irrational theodorus 1.73205080756887729353 sqrt(big(3)) -@testset "promote_operation" begin - @test MA.promote_operation(/, Rational{Int}, Rational{Int}) == Rational{Int} - @test MA.promote_operation(-, DummyMutable, DummyMutable) == DummyMutable - @test MA.promote_operation(/, DummyMutable, DummyMutable) == DummyMutable - @testset "Issue #164" begin - iπ() = MA.promote_operation(+, Int, typeof(π)) - @test iπ() == Float64 - alloc_test(iπ, 0) - ℯbf() = MA.promote_operation(+, typeof(ℯ), BigFloat) - @test ℯbf() == BigFloat - # TODO this allocates as it creates the `BigFloat` - #alloc_test(ℯbf, 0) - bγ() = MA.promote_operation(/, Bool, typeof(Base.MathConstants.γ)) - @test bγ() == Float64 - alloc_test(bγ, 0) - φf32() = MA.promote_operation(*, typeof(Base.MathConstants.φ), Float32) - @test φf32() == Float32 - alloc_test(φf32, 0) - # test user-defined Irrational - i_theodorus() = MA.promote_operation(+, Int, typeof(theodorus)) - @test i_theodorus() == Float64 - alloc_test(i_theodorus, 0) - # test _instantiate(::Type{S}) where {S<:Irrational} return value - @test MA._instantiate(typeof(π)) == π - @test MA._instantiate(typeof(MathConstants.catalan)) == - MathConstants.catalan - @test MA._instantiate(typeof(theodorus)) == theodorus - end - - for op in [real, imag] - @test MA.promote_operation(op, ComplexF64) == Float64 - @test MA.promote_operation(op, Complex{Real}) == Real - end -end - -@testset "Errors" begin - err = ArgumentError( - "Cannot call `operate_to!(::$Int, +, ::$Int, ::$Int)` as objects of type `$Int` cannot be modifed to equal the result of the operation. Use `operate_to!!` instead which returns the value of the result (possibly modifying the first argument) to write generic code that also works when the type cannot be modified.", - ) - @test_throws err MA.operate_to!(0, +, 0, 0) - err = ArgumentError( - "Cannot call `operate!(+, ::$Int, ::$Int)` as objects of type `$Int` cannot be modifed to equal the result of the operation. Use `operate!!` instead which returns the value of the result (possibly modifying the first argument) to write generic code that also works when the type cannot be modified.", - ) - @test_throws err MA.operate!(+, 0, 0) - err = ArgumentError( - "Cannot call `buffered_operate_to!(::$Int, ::$Int, +, ::$Int, ::$Int)` as objects of type `$Int` cannot be modifed to equal the result of the operation. Use `buffered_operate_to!!` instead which returns the value of the result (possibly modifying the first argument) to write generic code that also works when the type cannot be modified.", - ) - @test_throws err MA.buffered_operate_to!(0, 0, +, 0, 0) - err = ArgumentError( - "Cannot call `buffered_operate!(::$Int, +, ::$Int, ::$Int)` as objects of type `$Int` cannot be modifed to equal the result of the operation. Use `buffered_operate!!` instead which returns the value of the result (possibly modifying the first argument) to write generic code that also works when the type cannot be modified.", - ) - @test_throws err MA.buffered_operate!(0, +, 0, 0) - x = DummyMutable() - err = ErrorException( - "`operate_to!(::DummyMutable, +, ::DummyMutable, ::DummyMutable)` is not implemented yet.", - ) - @test_throws err MA.operate_to!(x, +, x, x) - err = ErrorException( - "`operate!(+, ::DummyMutable, ::DummyMutable)` is not implemented yet.", - ) - @test_throws err MA.operate!(+, x, x) - err = ErrorException( - "`buffered_operate_to!(::DummyMutable, ::DummyMutable, +, ::DummyMutable, ::DummyMutable)` is not implemented.", - ) - @test_throws err MA.buffered_operate_to!(x, x, +, x, x) - err = ErrorException( - "`buffered_operate!(::DummyMutable, +, ::DummyMutable, ::DummyMutable)` is not implemented.", - ) - @test_throws err MA.buffered_operate!(x, +, x, x) -end - -@testset "operate" begin - @testset "$T" for T in (Int, BigInt, Rational{Int}) - x = T(7) - @testset "1-ary $op" for op in [+, *, gcd, lcm, copy, abs] - a = op(x) - b = MA.operate(op, x) - @test a == b - if MA.mutability(T, op, T) == MA.IsMutable() - @test a !== b - end - end - ops = [+, *, MA.add_mul, MA.sub_mul, MA.add_dot, gcd, lcm] - @testset "4-ary $op" for op in ops - a = op(x, x, x, x) - b = MA.operate(op, x, x, x, x) - @test a == b - if MA.mutability(T, op, T, T, T, T) == MA.IsMutable() - @test a !== b - end - end - @testset "2-ary $op" for op in [-, /, div] - a = op(x, x) - b = MA.operate(op, x, x) - @test a == b - if MA.mutability(T, op, T, T) == MA.IsMutable() - @test a !== b - end - end - @testset "dot" for U in (Int, BigInt, Rational{Int}, Rational{BigInt}) - y = U(5) - a = LinearAlgebra.dot(x, y) - b = MA.operate(LinearAlgebra.dot, [x], [y]) - @test a == b - if MA.mutability( - promote_type(T, U), - LinearAlgebra.dot, - Vector{T}, - Vector{U}, - ) == MA.IsMutable() - @test a !== b - end - end - end -end - -@testset "add_mul for BitArray" begin - x = BigInt[0, 0] - MA.operate!!(MA.add_mul, x, big(2), trues(2)) - @test x == BigInt[2, 2] - MA.operate!!(MA.add_mul, x, big(3), BitVector([true, false])) - @test x == BigInt[5, 2] - x = BigInt[0 0; 0 0] - MA.operate!!(MA.add_mul, x, big(2), trues(2, 2)) - @test x == BigInt[2 2; 2 2] - MA.operate!!(MA.add_mul, x, big(3), BitArray([true false; true true])) - @test x == BigInt[5 2; 5 5] -end - -@testset "similar_array_type" begin - @test MA.similar_array_type(BitArray{2}, Int) == Array{Int,2} - @test MA.similar_array_type(BitArray{2}, Bool) == BitArray{2} -end - -@testset "similar_array_type_Diagonal" begin - z = zeros(2, 2) - y = MA.operate!!(MA.add_mul, z, big(1), LinearAlgebra.I(2)) - @test y == BigFloat[1 0; 0 1] - y = MA.operate!!(MA.add_mul, z, 2.4, LinearAlgebra.I(2)) - @test y === z - @test y == Float64[2.4 0; 0 2.4] - z = zeros(2, 2) - y = MA.operate!!(MA.add_mul, z, 2.4, LinearAlgebra.Diagonal(1:2)) - @test y == LinearAlgebra.Diagonal(2.4 * (1:2)) -end - -@testset "unary op(::$T)" for T in ( - Float64, - BigFloat, - Int, - BigInt, - Rational{Int}, - Rational{BigInt}, -) - @test MA.operate!!(+, T(7)) == 7 - @test MA.operate!!(*, T(7)) == 7 - @test MA.operate!!(-, T(7)) == -7 - - @test MA.operate_to!!(T(6), +, T(7)) == 7 - @test MA.operate_to!!(T(6), *, T(7)) == 7 - @test MA.operate_to!!(T(6), -, T(7)) == -7 - - @test MA.operate!!(abs, T(7)) == 7 - @test MA.operate!!(abs, T(-7)) == 7 - - @test MA.operate_to!!(T(6), abs, T(7)) == 7 - @test MA.operate_to!!(T(6), abs, T(-7)) == 7 -end - -@testset "Error-free mutability (issue #240)" begin - for op in (+, -, *, /, div) - for T in - (Float64, BigFloat, Int, BigInt, Rational{Int}, Rational{BigInt}) - @test_nowarn MA.mutability(T, op, T, T) # should run without error - end - end -end - -@testset "issue_271_mutability" begin - a = 1 - x = [1; 2;;] - y = [1 2; 3 4] - z = [1 2; 3 4; 5 6] - @test MA.mutability(x, *, x, x') == MA.IsNotMutable() - @test MA.mutability(x, *, x', x) == MA.IsNotMutable() - @test MA.mutability(x, *, x, a, x') == MA.IsNotMutable() - @test MA.mutability(x, *, x', a, x) == MA.IsNotMutable() - @test MA.mutability(y, *, y, y) == MA.IsMutable() - @test MA.mutability(y, *, y, y, y) == MA.IsMutable() - @test MA.mutability(y, *, y, a, y') == MA.IsMutable() - @test MA.mutability(y, *, y', a, y) == MA.IsMutable() - @test MA.mutability(y, *, a, a, y) == MA.IsMutable() - @test MA.mutability(y, *, y, z', z) == MA.IsMutable() - @test MA.mutability(z, *, z, z) == MA.IsNotMutable() - @test MA.mutability(z, *, z, z, y) == MA.IsNotMutable() -end - -@testset "issue_316_SubArray" begin - y = reshape([1.0], 1, 1, 1) - Y = view(y,:,:,1) - ret = reshape([1.0], 1, 1) - ret = MA.operate!!(MA.add_mul, ret, 2.0, Y) - @test ret == reshape([3.0], 1, 1) - @test y == reshape([1.0], 1, 1, 1) -end - -@testset "issue_318_neutral_element" begin - a = rand(3) - A = [rand(2, 2) for _ in 1:3] - @test_throws DimensionMismatch MA.operate(LinearAlgebra.dot, a, A) - y = a' * A - @test isapprox(MA.fused_map_reduce(MA.add_mul, a', A), y) - z = MA.operate(LinearAlgebra.dot, Int[], Int[]) - @test iszero(z) && z isa Int - z = MA.operate(LinearAlgebra.dot, BigInt[], Int[]) - @test iszero(z) && z isa BigInt - z = MA.operate(LinearAlgebra.dot, Int[], Float64[]) - @test iszero(z) && z isa Float64 - z = MA.operate(LinearAlgebra.dot, Matrix{Int}[], Matrix{Float64}[]) - @test iszero(z) && z isa Float64 - @test MA.fused_map_reduce(MA.add_mul, Matrix{Int}[], Float64[]) isa MA.Zero - @test MA.fused_map_reduce(MA.add_mul, Float64[], Matrix{Int}[]) isa MA.Zero -end diff --git a/test/range.jl b/test/range.jl deleted file mode 100644 index 58f46486..00000000 --- a/test/range.jl +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (c) 2019 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. - -using Test -import MutableArithmetics as MA - -function mutating_step_range_test(::Type{T}) where {T} - r = MA.MutatingStepRange(T(2), T(3), T(9)) - expected = MA.mutability(T) isa MA.IsMutable ? 8 * ones(T, 3) : T[2, 5, 8] - @test collect(r) == expected - @test reduce(MA.add!!, r) == T(15) -end - -@testset "MutatingStepRange" begin - mutating_step_range_test(Int) - mutating_step_range_test(BigInt) -end diff --git a/test/rewrite.jl b/test/rewrite.jl deleted file mode 100644 index 5b3707b3..00000000 --- a/test/rewrite.jl +++ /dev/null @@ -1,270 +0,0 @@ -# Copyright (c) 2019 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. - -using LinearAlgebra, SparseArrays, Test -import MutableArithmetics as MA - -@testset "Zero" begin - z = MA.Zero() - @test zero(MA.Zero) isa MA.Zero - @test z + z isa MA.Zero - @test z + 1 == 1 - @test 1 + z == 1 - @test z * z isa MA.Zero - @test z * 1 isa MA.Zero - @test 1 * z isa MA.Zero - @test -z isa MA.Zero - @test +z isa MA.Zero - @test *(z) isa MA.Zero - @test iszero(z) - @test !isone(z) -end - -# Test that the macro call `m` throws an error exception during pre-compilation -macro test_macro_throws(error, m) - # See https://discourse.julialang.org/t/test-throws-with-macros-after-pr-23533/5878 - return quote - @test_throws $(esc(error)) try - @eval $m - catch catched_error - throw(catched_error.error) - end - end -end - -function is_supported_test(T) - @test MA.Test._is_supported(*, T, T) - @test MA.Test._is_supported(+, T, T) -end - -function error_test(x, y, z) - err = ErrorException( - "The curly syntax (sum{},prod{},norm2{}) is no longer supported. Expression: `sum{(i for i = 1:2)}`.", - ) - @test_macro_throws err MA.@rewrite sum{i for i in 1:2} - err = ErrorException("Unexpected comparison in expression `z >= y`.") - @test_macro_throws err MA.@rewrite z >= y - err = ErrorException("Unexpected comparison in expression `y <= z`.") - @test_macro_throws err MA.@rewrite y <= z - err = ErrorException("Unexpected comparison in expression `x <= y <= z`.") - @test_macro_throws err MA.@rewrite x <= y <= z -end - -using LinearAlgebra -using OffsetArrays - -@testset "@rewrite with $T" for (T, supports_float) in [ - (Int, true), - (Float64, true), - (BigInt, true), - (BigFloat, true), - (Rational{Int}, true), - (Rational{BigInt}, true), - (DummyBigInt, false), -] - @testset "is_supported_test" begin - is_supported_test(T) - end - @testset "Error" begin - error_test(T(1), T(2), T(3)) - end - @testset "Scalar" begin - MA.Test.scalar_test(T(2)) - MA.Test.scalar_test(T(3)) - end - @testset "Quadratic" begin - exclude = T == DummyBigInt ? ["quadratic_division"] : String[] - MA.Test.quadratic_test(T(1), T(2), T(3), T(4), exclude = exclude) - end - @testset "Sparse" begin - MA.Test.sparse_test(T(4), T(5), T[8 1 9; 4 3 1; 2 0 8]) - end - exclude = if T == DummyBigInt - ["broadcast_division", "matrix_vector_division"] - elseif T <: Rational - ["broadcast_division"] - else - String[] - end - @testset "Vector" begin - MA.Test.array_test(T[-7, 1, 4], exclude = exclude) - MA.Test.array_test(T[3, 2, 6], exclude = exclude) - end - @testset "Square Matrix" begin - MA.Test.array_test(T[1 2; 2 4], exclude = exclude) - MA.Test.array_test(T[2 4; -1 3], exclude = exclude) - MA.Test.array_test(T[0 -4; 6 -5], exclude = exclude) - MA.Test.array_test(T[5 1 9; -7 2 4; -2 -7 5], exclude = exclude) - end - @testset "Non-square matrix" begin - MA.Test.array_test(T[5 1 9; -7 2 4], exclude = exclude) - end - @testset "Tensor" begin - S = zeros(T, 2, 2, 2) - S[1, :, :] = T[5 -8; 3 -7] - S[2, :, :] = T[-2 8; 8 -1] - MA.Test.array_test(S, exclude = exclude) - end - @testset "Non-array" begin - x = T[2, 4, 3] - MA.Test.non_array_test(x, OffsetArray(x, -length(x))) - end -end - -@testset "generator with not sum" begin - @test MA.@rewrite(minimum(j^2 for j in 2:3)) == 4 - @test MA.@rewrite(maximum(j^2 for j in 2:3)) == 9 -end - -@testset "issue_76_trailing_dimensions" begin - @testset "(4,) + (4,1)" begin - X = [1, 2, 3, 4] - Y = reshape([1, 2, 3, 4], (4, 1)) - @test MA.@rewrite(X + Y) == X + Y - @test MA.@rewrite(Y + X) == Y + X - end - @testset "(4,) + (4,1,1)" begin - X = [1, 2, 3, 4] - Y = reshape([1, 2, 3, 4], (4, 1, 1)) - @test MA.@rewrite(X + Y) == X + Y - @test MA.@rewrite(Y + X) == Y + X - end - @testset "(2,2) + (2,2,1)" begin - X = [1 2; 3 4] - Y = reshape([1, 2, 3, 4], (2, 2, 1)) - @test MA.@rewrite(X + Y) == X + Y - @test MA.@rewrite(Y + X) == Y + X - end - @testset "(4,) - (4,1)" begin - X = [1, 3, 5, 7] - Y = reshape([1, 2, 3, 4], (4, 1)) - @test MA.@rewrite(X - Y) == X - Y - @test MA.@rewrite(Y - X) == Y - X - end - @testset "(4,) - (4,1,1)" begin - X = [1, 3, 5, 7] - Y = reshape([1, 2, 3, 4], (4, 1, 1)) - @test MA.@rewrite(X - Y) == X - Y - @test MA.@rewrite(Y - X) == Y - X - end - @testset "(2,2) - (2,2,1)" begin - X = [1 3; 5 7] - Y = reshape([1, 2, 3, 4], (2, 2, 1)) - @test MA.@rewrite(X - Y) == X - Y - @test MA.@rewrite(Y - X) == Y - X - end -end - -struct _KwargRef{K,V} - data::Dict{K,V} -end - -Base.getindex(x::_KwargRef; i) = x.data[i] - -@testset "test_rewrite_kw_in_ref" begin - x = _KwargRef(Dict(i => i + 1 for i in 2:4)) - @test MA.@rewrite(sum(x[i=j] for j in 2:4)) == 12 -end - -@testset "dispatch_dot" begin - # Symmetric - x = DummyBigInt[1 2; 2 3] - y = LinearAlgebra.Symmetric(x) - @test MA.isequal_canonical( - LinearAlgebra.dot(x, y), - MA.operate(LinearAlgebra.dot, x, y), - ) - a = @allocated LinearAlgebra.dot(x, y) - b = @allocated MA.operate(LinearAlgebra.dot, x, y) - @test a == b - # Symmetric - x = DummyBigInt[1 2; 2 3] - y = LinearAlgebra.Hermitian(x) - @test MA.isequal_canonical( - LinearAlgebra.dot(x, y), - MA.operate(LinearAlgebra.dot, x, y), - ) - a = @allocated LinearAlgebra.dot(x, y) - b = @allocated MA.operate(LinearAlgebra.dot, x, y) - @test a == b - # AbstractArray - x = DummyBigInt[1 2; 2 3] - y = x - @test MA.isequal_canonical( - LinearAlgebra.dot(x, y), - MA.operate(LinearAlgebra.dot, x, y), - ) - a = @allocated LinearAlgebra.dot(x, y) - b = @allocated MA.operate(LinearAlgebra.dot, x, y) - @test a == b -end - -@testset "test_multiply_expr_MA_Zero" begin - x = DummyBigInt(1) - f = DummyBigInt(2) - @test MA.@rewrite( - f * sum(x for i in 1:0), - move_factors_into_sums = false - ) == MA.Zero() - @test MA.@rewrite( - sum(x for i in 1:0) * f, - move_factors_into_sums = false - ) == MA.Zero() - @test MA.@rewrite( - -f * sum(x for i in 1:0), - move_factors_into_sums = false - ) == MA.Zero() - @test MA.@rewrite( - sum(x for i in 1:0) * -f, - move_factors_into_sums = false - ) == MA.Zero() - @test MA.@rewrite( - (f + f) * sum(x for i in 1:0), - move_factors_into_sums = false - ) == MA.Zero() - @test MA.@rewrite( - sum(x for i in 1:0) * (f + f), - move_factors_into_sums = false - ) == MA.Zero() - @test MA.@rewrite( - -[f] * sum(x for i in 1:0), - move_factors_into_sums = false - ) == MA.Zero() - @test MA.@rewrite( - sum(x for i in 1:0) * -[f], - move_factors_into_sums = false - ) == MA.Zero() - @test MA.isequal_canonical( - MA.@rewrite(f + sum(x for i in 1:0), move_factors_into_sums = false), - f, - ) - @test MA.isequal_canonical( - MA.@rewrite(sum(x for i in 1:0) + f, move_factors_into_sums = false), - f, - ) - @test MA.isequal_canonical( - MA.@rewrite(-f + sum(x for i in 1:0), move_factors_into_sums = false), - -f, - ) - @test MA.isequal_canonical( - MA.@rewrite(sum(x for i in 1:0) + -f, move_factors_into_sums = false), - -f, - ) - @test MA.isequal_canonical( - MA.@rewrite( - (f + f) + sum(x for i in 1:0), - move_factors_into_sums = false - ), - f + f, - ) - @test MA.isequal_canonical( - MA.@rewrite( - sum(x for i in 1:0) + (f + f), - move_factors_into_sums = false - ), - f + f, - ) -end diff --git a/test/runtests.jl b/test/runtests.jl index 9a9bb53c..caca14ee 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -5,52 +5,9 @@ # one at http://mozilla.org/MPL/2.0/. using Test -import MutableArithmetics as MA -include("utilities.jl") +is_test(f) = startswith(f, "test_") && endswith(f, ".jl") -include("interface.jl") - -include("range.jl") - -include("copy.jl") - -@testset "Int" begin - include("int.jl") -end -@testset "BigInt" begin - include("big.jl") -end -@testset "BigFloat negation and absolute value" begin - include("bigfloat_neg_abs.jl") -end -@testset "BigFloat fma and muladd" begin - include("bigfloat_fma.jl") -end -@testset "BigFloat dot" begin - include("bigfloat_dot.jl") +@testset "$file" for file in filter(is_test, readdir(@__DIR__)) + include(joinpath(@__DIR__, file)) end -@testset "evalpoly" begin - include("evalpoly.jl") -end -@testset "Broadcast" begin - include("broadcast.jl") -end -include("matmul.jl") -include("dispatch.jl") -include("rewrite.jl") -include("rewrite_generic.jl") - -include("SparseArrays.jl") - -# It is easy to introduce macro scoping issues into MutableArithmetics, -# particularly ones that rely on `MA` or `MutableArithmetics` being present in -# the current scope. To work around that, include the "hygiene" script in a -# clean module with no other scope. - -function _include_sandbox(filename) - mod = @eval module $(gensym()) end - return Base.include(mod, filename) -end - -_include_sandbox("hygiene.jl") diff --git a/test/test_basics.jl b/test/test_basics.jl new file mode 100644 index 00000000..ec18bd67 --- /dev/null +++ b/test/test_basics.jl @@ -0,0 +1,551 @@ +# Copyright (c) 2023 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. + +module TestBasics + +using Test + +import MutableArithmetics as MA + +include("dummy.jl") + +function runtests() + is_test(name::Symbol) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + +function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} + f() # compile + measured_allocations = @allocated f() + @test measured_allocations <= expected_upper_bound + return +end + +function _test_copy(::Type{T}) where {T} + @test MA.operate!!(copy, T(2)) == 2 + @test MA.operate_to!!(T(3), copy, T(2)) == 2 + if MA.mutability(T, copy, T) != MA.IsMutable() + return + end + @testset "correctness" begin + x = T(2) + y = T(3) + @test MA.operate!(copy, x) === x == 2 + @test MA.operate_to!(y, copy, x) === y == 2 + end + @testset "alloc" begin + f = let x = T(2) + () -> MA.operate!(copy, x) + end + g = let x = T(2), y = T(3) + () -> MA.operate_to!(y, copy, x) + end + alloc_test(f, 0) + alloc_test(g, 0) + end + return +end + +test_copy_Float64() = _test_copy(Float64) + +test_copy_BigFloat() = _test_copy(BigFloat) + +test_copy_Int() = _test_copy(Int) + +test_copy_BigInt() = _test_copy(BigInt) + +test_copy_Rational_Int() = _test_copy(Rational{Int}) + +test_copy_Rational_BigInt() = _test_copy(Rational{BigInt}) + +function _test_mutating_step_range(::Type{T}) where {T} + r = MA.MutatingStepRange(T(2), T(3), T(9)) + expected = MA.mutability(T) isa MA.IsMutable ? 8 * ones(T, 3) : T[2, 5, 8] + @test collect(r) == expected + @test reduce(MA.add!!, r) == T(15) + return +end + +test_mutating_step_range_Int() = _test_mutating_step_range(Int) + +test_mutating_step_range_BigInt() = _test_mutating_step_range(BigInt) + +function test_Zero() + z = MA.Zero() + @test zero(MA.Zero) isa MA.Zero + @test z + z isa MA.Zero + @test z + 1 == 1 + @test 1 + z == 1 + @test z * z isa MA.Zero + @test z * 1 isa MA.Zero + @test 1 * z isa MA.Zero + @test -z isa MA.Zero + @test +z isa MA.Zero + @test *(z) isa MA.Zero + @test iszero(z) + @test !isone(z) + return +end + +# *(::Complex, ::Hermitian) +function test_mult_Complex_Hermitian() + A = BigInt[1 2; 2 3] + B = LinearAlgebra.Hermitian(DummyBigInt.(A)) + C = 2im * A + @test 2im * B == C + @test C isa Matrix{Complex{BigInt}} + return +end + +function test_operate_to!_Array_AbstractMutable_Array() + for A in ([1 2; 5 3], DummyBigInt[1 2; 5 3]) + for x in (2, DummyBigInt(2)) + # operate_to!(::Array, *, ::AbstractMutable, ::Array) + B = x * A + C = zero(B) + D = MA.operate_to!(C, *, x, A) + @test C === D + @test typeof(B) == typeof(C) + @test MA.isequal_canonical(B, C) + # operate_to!(::Array, *, ::Array, ::AbstractMutable) + B = A * x + C = zero(B) + D = MA.operate_to!(C, *, A, x) + @test C === D + @test typeof(B) == typeof(C) + @test MA.isequal_canonical(B, C) + end + end + return +end + +function non_mutable_sum_pr306(x) + y = zero(eltype(x)) + for xi in x + y += xi + end + return y +end + +function test_sum_with_init() + x = convert(Vector{DummyBigInt}, 1:100) + # compilation + @allocated sum(x) + @allocated sum(x; init = DummyBigInt(0)) + @allocated non_mutable_sum_pr306(x) + # now test actual allocations + no_init = @allocated sum(x) + with_init = @allocated sum(x; init = DummyBigInt(0)) + no_ma = @allocated non_mutable_sum_pr306(x) + # There's an additional 16 bytes for kwarg version. Upper bound by 40 to be + # safe between Julia versions + @test with_init <= no_init + 40 + # MA is at least 10-times better than no MA for this example + @test 10 * with_init < no_ma +end + +function test_sum_with_init_and_dims() + x = reshape(convert(Vector{DummyBigInt}, 1:12), 3, 4) + X = reshape(1:12, 3, 4) + for dims in (1, 2, :, 1:2, (1, 2)) + # Without (; init) + @test MA.isequal_canonical(sum(x; dims), DummyBigInt.(sum(X; dims))) + # With (; init) + y = sum(x; init = DummyBigInt(0), dims) + @test MA.isequal_canonical(y, DummyBigInt.(sum(X; dims))) + end + return +end + +function test_issue_336() + A = DummyBigInt[1 2; 3 4] + B = Any[A[1], A[2]] + @test MA.isequal_canonical(A * B, A * A[1:2]) + @test_throws DimensionMismatch A * Any[A[1]] + @test_throws DimensionMismatch MA.operate(*, A, Any[A[1]]) + return +end + +function test_dispatch_dot() + # Symmetric + x = DummyBigInt[1 2; 2 3] + y = LinearAlgebra.Symmetric(x) + @test MA.isequal_canonical( + LinearAlgebra.dot(x, y), + MA.operate(LinearAlgebra.dot, x, y), + ) + a = @allocated LinearAlgebra.dot(x, y) + b = @allocated MA.operate(LinearAlgebra.dot, x, y) + @test a == b + # Symmetric + x = DummyBigInt[1 2; 2 3] + y = LinearAlgebra.Hermitian(x) + @test MA.isequal_canonical( + LinearAlgebra.dot(x, y), + MA.operate(LinearAlgebra.dot, x, y), + ) + a = @allocated LinearAlgebra.dot(x, y) + b = @allocated MA.operate(LinearAlgebra.dot, x, y) + @test a == b + # AbstractArray + x = DummyBigInt[1 2; 2 3] + y = x + @test MA.isequal_canonical( + LinearAlgebra.dot(x, y), + MA.operate(LinearAlgebra.dot, x, y), + ) + a = @allocated LinearAlgebra.dot(x, y) + b = @allocated MA.operate(LinearAlgebra.dot, x, y) + @test a == b + return +end + +# Tests that the calls are correctly redirected to the mutable calls +# by checking allocations +function _test_dispatch(::Type{T}) where {T} + buffer = zero(T) + a = one(T) + b = one(T) + c = one(T) + x = convert.(T, [1, 2, 3]) + # Need to allocate 1 BigInt for the result and one for the buffer + nalloc = 3 * @allocated(BigInt(1)) + alloc_test(() -> MA.fused_map_reduce(MA.add_mul, x, x), nalloc) + alloc_test(() -> MA.fused_map_reduce(MA.add_dot, x, x), nalloc) + if T <: MA.AbstractMutable + alloc_test(() -> x'x, nalloc) + alloc_test(() -> transpose(x) * x, nalloc) + alloc_test(() -> LinearAlgebra.dot(x, x), nalloc) + end + return +end + +test_dispatch_BigInt() = _test_dispatch(BigInt) + +test_dispatch_DummyBigInt() = _test_dispatch(DummyBigInt) + +function test_dot_non_concrete_vector() + x = [5.0, 6.0] + y = Vector{Union{Float64,String}}(x) + @test MA.operate(LinearAlgebra.dot, x, y) == LinearAlgebra.dot(x, y) + @test MA.operate(*, x', y) == x' * y + return +end + +function test_dot_vector_of_vectors() + x = [5.0, 6.0] + z = [x, x] + @test MA.operate(LinearAlgebra.dot, z, z) == LinearAlgebra.dot(z, z) + return +end + +# *(::Real, ::Hermitian) +function test_mult_Real_Hermitian() + A = DummyBigInt[1 2; 2 3] + B = DummyBigInt[2 4; 4 6] + D = LinearAlgebra.Hermitian(B) + for s in (:L, :U) + Ah = LinearAlgebra.Hermitian(A, s) + @test all(MA.isequal_canonical.(2 * Ah, D)) + @test all(MA.isequal_canonical.(Ah * 2, D)) + end + return +end + +# *(::AbstractMutable, ::Symmetric) +function test_mult_AbstractMutable_Symmetric() + for A in ([1 2; 5 3], DummyBigInt[1 2; 5 3]) + for x in (2, DummyBigInt(2)) + for s in (:L, :U) + # *(::AbstractMutable, ::Symmetric) + B = LinearAlgebra.Symmetric(A, s) + C = LinearAlgebra.Symmetric(x * A, s) + D = x * B + @test D isa LinearAlgebra.Symmetric + @test MA.isequal_canonical(D, C) + @test MA.isequal_canonical(D + D, 2 * D) + # *(::Symmetric, ::AbstractMutable) + B = LinearAlgebra.Symmetric(A, s) + C = LinearAlgebra.Symmetric(A * x, s) + D = B * x + @test D isa LinearAlgebra.Symmetric + @test MA.isequal_canonical(D, C) + @test MA.isequal_canonical(D + D, 2 * D) + end + end + end +end + +function test_issue_271_mutability() + a = 1 + x = [1; 2;;] + y = [1 2; 3 4] + z = [1 2; 3 4; 5 6] + @test MA.mutability(x, *, x, x') == MA.IsNotMutable() + @test MA.mutability(x, *, x', x) == MA.IsNotMutable() + @test MA.mutability(x, *, x, a, x') == MA.IsNotMutable() + @test MA.mutability(x, *, x', a, x) == MA.IsNotMutable() + @test MA.mutability(y, *, y, y) == MA.IsMutable() + @test MA.mutability(y, *, y, y, y) == MA.IsMutable() + @test MA.mutability(y, *, y, a, y') == MA.IsMutable() + @test MA.mutability(y, *, y', a, y) == MA.IsMutable() + @test MA.mutability(y, *, a, a, y) == MA.IsMutable() + @test MA.mutability(y, *, y, z', z) == MA.IsMutable() + @test MA.mutability(z, *, z, z) == MA.IsNotMutable() + @test MA.mutability(z, *, z, z, y) == MA.IsNotMutable() + return +end + +function test_issue_316_SubArray() + y = reshape([1.0], 1, 1, 1) + Y = view(y,:,:,1) + ret = reshape([1.0], 1, 1) + ret = MA.operate!!(MA.add_mul, ret, 2.0, Y) + @test ret == reshape([3.0], 1, 1) + @test y == reshape([1.0], 1, 1, 1) + return +end + +function test_issue_318_neutral_element() + a = rand(3) + A = [rand(2, 2) for _ in 1:3] + @test_throws DimensionMismatch MA.operate(LinearAlgebra.dot, a, A) + y = a' * A + @test isapprox(MA.fused_map_reduce(MA.add_mul, a', A), y) + z = MA.operate(LinearAlgebra.dot, Int[], Int[]) + @test iszero(z) && z isa Int + z = MA.operate(LinearAlgebra.dot, BigInt[], Int[]) + @test iszero(z) && z isa BigInt + z = MA.operate(LinearAlgebra.dot, Int[], Float64[]) + @test iszero(z) && z isa Float64 + z = MA.operate(LinearAlgebra.dot, Matrix{Int}[], Matrix{Float64}[]) + @test iszero(z) && z isa Float64 + @test MA.fused_map_reduce(MA.add_mul, Matrix{Int}[], Float64[]) isa MA.Zero + @test MA.fused_map_reduce(MA.add_mul, Float64[], Matrix{Int}[]) isa MA.Zero + return +end + +function test_add_mul_for_BitArray() + x = BigInt[0, 0] + MA.operate!!(MA.add_mul, x, big(2), trues(2)) + @test x == BigInt[2, 2] + MA.operate!!(MA.add_mul, x, big(3), BitVector([true, false])) + @test x == BigInt[5, 2] + x = BigInt[0 0; 0 0] + MA.operate!!(MA.add_mul, x, big(2), trues(2, 2)) + @test x == BigInt[2 2; 2 2] + MA.operate!!(MA.add_mul, x, big(3), BitArray([true false; true true])) + @test x == BigInt[5 2; 5 5] + return +end + +function test_similar_array_type() + @test MA.similar_array_type(BitArray{2}, Int) == Array{Int,2} + @test MA.similar_array_type(BitArray{2}, Bool) == BitArray{2} + return +end + +function test_similar_array_type_Diagonal() + z = zeros(2, 2) + y = MA.operate!!(MA.add_mul, z, big(1), LinearAlgebra.I(2)) + @test y == BigFloat[1 0; 0 1] + y = MA.operate!!(MA.add_mul, z, 2.4, LinearAlgebra.I(2)) + @test y === z + @test y == Float64[2.4 0; 0 2.4] + z = zeros(2, 2) + y = MA.operate!!(MA.add_mul, z, 2.4, LinearAlgebra.Diagonal(1:2)) + @test y == LinearAlgebra.Diagonal(2.4 * (1:2)) + return +end + +function test_Errors() + err = ArgumentError( + "Cannot call `operate_to!(::$Int, +, ::$Int, ::$Int)` as objects of type `$Int` cannot be modifed to equal the result of the operation. Use `operate_to!!` instead which returns the value of the result (possibly modifying the first argument) to write generic code that also works when the type cannot be modified.", + ) + @test_throws err MA.operate_to!(0, +, 0, 0) + err = ArgumentError( + "Cannot call `operate!(+, ::$Int, ::$Int)` as objects of type `$Int` cannot be modifed to equal the result of the operation. Use `operate!!` instead which returns the value of the result (possibly modifying the first argument) to write generic code that also works when the type cannot be modified.", + ) + @test_throws err MA.operate!(+, 0, 0) + err = ArgumentError( + "Cannot call `buffered_operate_to!(::$Int, ::$Int, +, ::$Int, ::$Int)` as objects of type `$Int` cannot be modifed to equal the result of the operation. Use `buffered_operate_to!!` instead which returns the value of the result (possibly modifying the first argument) to write generic code that also works when the type cannot be modified.", + ) + @test_throws err MA.buffered_operate_to!(0, 0, +, 0, 0) + err = ArgumentError( + "Cannot call `buffered_operate!(::$Int, +, ::$Int, ::$Int)` as objects of type `$Int` cannot be modifed to equal the result of the operation. Use `buffered_operate!!` instead which returns the value of the result (possibly modifying the first argument) to write generic code that also works when the type cannot be modified.", + ) + @test_throws err MA.buffered_operate!(0, +, 0, 0) + x = DummyMutable() + err = ErrorException( + "`operate_to!(::$DummyMutable, +, ::$DummyMutable, ::$DummyMutable)` is not implemented yet.", + ) + @test_throws err MA.operate_to!(x, +, x, x) + err = ErrorException( + "`operate!(+, ::$DummyMutable, ::$DummyMutable)` is not implemented yet.", + ) + @test_throws err MA.operate!(+, x, x) + err = ErrorException( + "`buffered_operate_to!(::$DummyMutable, ::$DummyMutable, +, ::$DummyMutable, ::$DummyMutable)` is not implemented.", + ) + @test_throws err MA.buffered_operate_to!(x, x, +, x, x) + err = ErrorException( + "`buffered_operate!(::$DummyMutable, +, ::$DummyMutable, ::$DummyMutable)` is not implemented.", + ) + @test_throws err MA.buffered_operate!(x, +, x, x) + return +end + +function test_issue_240_error_free_mutability() + for op in (+, -, *, /, div) + for T in + (Float64, BigFloat, Int, BigInt, Rational{Int}, Rational{BigInt}) + @test_nowarn MA.mutability(T, op, T, T) # should run without error + end + end + return +end + +function test_unary_op() + @testset "unary op(::$T)" for T in ( + Float64, + BigFloat, + Int, + BigInt, + Rational{Int}, + Rational{BigInt}, + ) + @test MA.operate!!(+, T(7)) == 7 + @test MA.operate!!(*, T(7)) == 7 + @test MA.operate!!(-, T(7)) == -7 + @test MA.operate_to!!(T(6), +, T(7)) == 7 + @test MA.operate_to!!(T(6), *, T(7)) == 7 + @test MA.operate_to!!(T(6), -, T(7)) == -7 + @test MA.operate!!(abs, T(7)) == 7 + @test MA.operate!!(abs, T(-7)) == 7 + @test MA.operate_to!!(T(6), abs, T(7)) == 7 + @test MA.operate_to!!(T(6), abs, T(-7)) == 7 + end + return +end + +# Theodorus' constant √3 (deined in global scope due to const) +Base.@irrational theodorus 1.73205080756887729353 sqrt(big(3)) + +function test_issue_164() + iπ() = MA.promote_operation(+, Int, typeof(π)) + @test iπ() == Float64 + alloc_test(iπ, 0) + ℯbf() = MA.promote_operation(+, typeof(ℯ), BigFloat) + @test ℯbf() == BigFloat + # TODO this allocates as it creates the `BigFloat` + #alloc_test(ℯbf, 0) + bγ() = MA.promote_operation(/, Bool, typeof(Base.MathConstants.γ)) + @test bγ() == Float64 + alloc_test(bγ, 0) + φf32() = MA.promote_operation(*, typeof(Base.MathConstants.φ), Float32) + @test φf32() == Float32 + alloc_test(φf32, 0) + # test user-defined Irrational + i_theodorus() = MA.promote_operation(+, Int, typeof(theodorus)) + @test i_theodorus() == Float64 + alloc_test(i_theodorus, 0) + # test _instantiate(::Type{S}) where {S<:Irrational} return value + @test MA._instantiate(typeof(π)) == π + @test MA._instantiate(typeof(MathConstants.catalan)) == + MathConstants.catalan + @test MA._instantiate(typeof(theodorus)) == theodorus + return +end + +function test_promote_operation_complex() + for op in [real, imag] + @test MA.promote_operation(op, ComplexF64) == Float64 + @test MA.promote_operation(op, Complex{Real}) == Real + end + return +end + +function test_promote_operation() + @test MA.promote_operation(/, Rational{Int}, Rational{Int}) == Rational{Int} + @test MA.promote_operation(-, DummyMutable, DummyMutable) == DummyMutable + @test MA.promote_operation(/, DummyMutable, DummyMutable) == DummyMutable + return +end + +function _test_unary_op(::Type{T}, op) where {T} + x = T(7) + a = op(x) + b = MA.operate(op, x) + @test a == b + if MA.mutability(T, op, T) == MA.IsMutable() + @test a !== b + end + return +end + +function _test_binary_op(::Type{T}, op) where {T} + x = T(7) + a = op(x, x) + b = MA.operate(op, x, x) + @test a == b + if MA.mutability(T, op, T, T) == MA.IsMutable() + @test a !== b + end + return +end + +function _test_quaternary_op(::Type{T}, op) where {T} + x = T(7) + a = op(x, x, x, x) + b = MA.operate(op, x, x, x, x) + @test a == b + if MA.mutability(T, op, T, T, T, T) == MA.IsMutable() + @test a !== b + end + return +end + +function _test_operate_dot(::Type{T}, ::Type{U}) where {T,U} + x = T(7) + y = U(5) + a = LinearAlgebra.dot(x, y) + b = MA.operate(LinearAlgebra.dot, [x], [y]) + @test a == b + if MA.mutability( + promote_type(T, U), + LinearAlgebra.dot, + Vector{T}, + Vector{U}, + ) == MA.IsMutable() + @test a !== b + end +end + +function test_operate() + @testset "$T" for T in (Int, BigInt, Rational{Int}) + @testset "1-ary $op" for op in [+, *, gcd, lcm, copy, abs] + _test_unary_op(T, op) + end + ops = [+, *, MA.add_mul, MA.sub_mul, MA.add_dot, gcd, lcm] + @testset "4-ary $op" for op in ops + _test_quaternary_op(T, op) + end + @testset "2-ary $op" for op in [-, /, div] + _test_binary_op(T, op) + end + @testset "dot" for U in (Int, BigInt, Rational{Int}, Rational{BigInt}) + _test_operate_dot(T, U) + end + end + return +end + +end # TestBasics + +TestBasics.runtests() diff --git a/test/test_big.jl b/test/test_big.jl new file mode 100644 index 00000000..96d28a0d --- /dev/null +++ b/test/test_big.jl @@ -0,0 +1,322 @@ +# Copyright (c) 2019 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. + +module TestBig + +using Test + +import LinearAlgebra +import MutableArithmetics as MA + +function runtests() + is_test(name::Symbol) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + +function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} + f() # compile + measured_allocations = @allocated f() + @test measured_allocations <= expected_upper_bound + return +end + +function _test_allocation( + op, + T, + short, + short_to, + n; + a = T(2), + b = T(3), + c = T(4), +) + @test MA.promote_operation(op, T, T) == T + alloc_test(() -> MA.promote_operation(op, T, T), 0) + if op != div && op != - + @test MA.promote_operation(op, T, T, T) == T + alloc_test(() -> MA.promote_operation(op, T, T, T), 0) + end + g = op(a, b) + @test c === short_to(c, a, b) + @test g == c + A = MA.copy_if_mutable(a) + @test A === short(A, b) + @test g == A + alloc_test(() -> short(A, b), n) + alloc_test(() -> short_to(c, a, b), n) + @test g == MA.buffered_operate!(nothing, op, MA.copy_if_mutable(a), b) + @test g == MA.buffered_operate_to!(nothing, c, op, a, b) + buffer = MA.buffer_for(op, typeof(a), typeof(b)) + @test g == MA.buffered_operate_to!(buffer, c, op, a, b) + alloc_test(() -> MA.buffered_operate_to!(buffer, c, op, a, b), 0) + return +end + +function add_sub_mul_test(op, T; a = T(2), b = T(3), c = T(4)) + g = op(a, b, c) + @test g == MA.buffered_operate!(nothing, op, MA.copy_if_mutable(a), b, c) + buffer = MA.buffer_for(op, typeof(a), typeof(b), typeof(c)) + return alloc_test(() -> MA.buffered_operate!(buffer, op, a, b, c), 0) +end + +test_int_test_BigInt() = MA.Test.int_test(BigInt) + +test_int_test_BigFloat() = MA.Test.int_test(BigFloat) + +test_int_test_Rational_BigInt() = MA.Test.int_test(Rational{BigInt}) + +test_allocation_BigInt() = _test_allocation(BigInt) + +test_allocation_BigFloat() = _test_allocation(BigFloat) + +test_allocation_Rational_BigInt() = _test_allocation(Rational{BigInt}) + +function _test_allocation(::Type{T}) where {T} + MAX_ALLOC = T <: Rational ? 280 : 0 + _test_allocation(+, T, MA.add!!, MA.add_to!!, MAX_ALLOC) + _test_allocation(-, T, MA.sub!!, MA.sub_to!!, MAX_ALLOC) + _test_allocation(*, T, MA.mul!!, MA.mul_to!!, MAX_ALLOC) + add_sub_mul_test(MA.add_mul, T) + add_sub_mul_test(MA.sub_mul, T) + if T <: Rational # https://github.com/jump-dev/MutableArithmetics.jl/issues/167 + _test_allocation( + +, + T, + MA.add!!, + MA.add_to!!, + MAX_ALLOC, + a = T(1 // 2), + b = T(3 // 2), + c = T(5 // 2), + ) + _test_allocation( + -, + T, + MA.sub!!, + MA.sub_to!!, + MAX_ALLOC, + a = T(1 // 2), + b = T(3 // 2), + c = T(5 // 2), + ) + end + # Requires https://github.com/JuliaLang/julia/commit/3f92832df042198b2daefc1f7ca609db38cb8173 + # for `gcd` to be defined on `Rational`. + if T == BigInt + _test_allocation(div, T, MA.div!!, MA.div_to!!, 0) + end + if T == BigInt || T == Rational{BigInt} + _test_allocation(gcd, T, MA.gcd!!, MA.gcd_to!!, 0) + _test_allocation(lcm, T, MA.lcm!!, MA.lcm_to!!, 0) + end + return +end + +function test_unary_neg() + for x in BigFloat[-1.3, -1.0, -0.7, -0.0, 0.0, 0.3, 1.0, 1.7] + backup = MA.copy_if_mutable(x) + @test -(backup) == MA.operate!(-, x) == x + end + return +end + +function test_unary_abs() + for x in BigFloat[-1.3, -1.0, -0.7, -0.0, 0.0, 0.3, 1.0, 1.7] + backup = MA.copy_if_mutable(x) + @test abs(backup) == MA.operate!(abs, x) == x + end + return +end + +function _test_fma_output_values(x::F, y::F, z::F) where {F<:BigFloat} + two_roundings_reference = x * y + z + one_rounding_reference = fma(x, y, z) + @test one_rounding_reference != two_roundings_reference + @testset "fma $op output values" for op in (MA.operate!, MA.operate!!) + (a, b, c) = map(MA.copy_if_mutable, (x, y, z)) + @inferred op(fma, a, b, c) + @test one_rounding_reference == a + @test y == b + @test z == c + end + @testset "fma $op output values" for op in (MA.operate_to!, MA.operate_to!!) + (a, b, c) = map(MA.copy_if_mutable, (x, y, z)) + out = F() + @inferred op(out, fma, a, b, c) + @test one_rounding_reference == out + @test x == a + @test y == b + @test z == c + end + return +end + +function _test_fma_output_values(x::F, y::F, z::F) where {F<:Float64} + return _test_fma_output_values(map(BigFloat, (x, y, z))...) +end + +function _test_fma_output_values_func(x::F, y::F, z::F) where {F<:Float64} + return let x = x, y = y, z = z + () -> _test_fma_output_values(x, y, z) + end +end + +function test_fma_output_values() + for exp_x in (-3):3, exp_y in (-3):3, sign_x in (-1, 1), sign_y in (-1, 1) + _test_set_precision(exp_x, exp_y, sign_x, sign_y) + end + return +end + +function _test_set_precision(exp_x, exp_y, sign_x, sign_y) + # Assuming a two-bit mantissa + significand_length = 2 + sign_z = -sign_x * sign_y + exp_z = exp_x + exp_y + base = 2.0 + bit_0 = 2.0^0 + bit_1 = 2.0^-1 + x = sign_x * base^exp_x * (bit_0 + bit_1) + y = sign_y * base^exp_y * (bit_0 + bit_1) + z = sign_z * base^exp_z * (bit_0 + bit_1) + setprecision( + _test_fma_output_values_func(x, y, z), + BigFloat, + significand_length, + ) + return +end + +function test_muladd_operate_to!!_type_inferred() + m1 = BigFloat(-1.0) + out = BigFloat() + @test iszero(@inferred MA.operate_to!!(out, Base.muladd, m1, m1, m1)) + return +end + +function test_muladd_operate!!_type_inferred() + x = BigFloat(-1.0) + y = BigFloat(-1.0) + z = BigFloat(-1.0) + @test iszero(@inferred MA.operate!!(Base.muladd, x, y, z)) + return +end + +function test_fma_operate_to!_doesnt_allocate() + alloc_test(let o = big"1.3", x = big"1.3" + () -> MA.operate_to!(o, Base.fma, x, x, x) + end, 0) + return +end + +function test_fma_operate_to!!_doesnt_allocate() + alloc_test(let o = big"1.3", x = big"1.3" + () -> MA.operate_to!!(o, Base.fma, x, x, x) + end, 0) + return +end + +function test_fma_operate!_doesnt_allocate() + alloc_test(let x = big"1.3", y = big"1.3", z = big"1.3" + () -> MA.operate!(Base.fma, x, y, z) + end, 0) + return +end + +function test_fma_operate!!_doesnt_allocate() + alloc_test(let x = big"1.3", y = big"1.3", z = big"1.3" + () -> MA.operate!!(Base.fma, x, y, z) + end, 0) + return +end + +function test_precision() + iters = Iterators.product( + # These precisions (in bits) are most probably smaller than what + # users would set in their code, but they are relevant anyway + # because the compensated summation algorithm used for computing + # the dot product is accurate independently of the machine + # precision (except when vector lengths are really huge with + # respect to the precision). + (32, 64), + # Compensated summation should be accurate even for very large + # input vectors, so test that. + (10000,), + # The zero "bias" signifies that the input will be entirely + # nonnegative (drawn from the interval [0, 1]), while a positive + # bias shifts that interval towards negative infinity. We want to + # test a few different values here, but we do not want to set the + # bias to 0.5, because the expected value is then zero and there's + # no guarantee on the relative error in that case. + (0.0, 2^-2, 2^-2 + 2^-3 + 2^-4), + ) + @testset "prec:$prec size:$size bias:$bias" for (prec, size, bias) in iters + _test_precision(prec, size, bias) + end + return +end + +function _test_precision(prec, size, bias) + err = setprecision(BigFloat, prec) do + maximum_relative_error = mapreduce(max, 1:10) do _ + # Generate some random vectors for dot(x, y) input. + x = rand(BigFloat, size) .- bias + y = rand(BigFloat, size) .- bias + # Copy x and y so that we can check we haven't mutated them after + # the fact. + old_x, old_y = MA.copy_if_mutable(x), MA.copy_if_mutable(y) + # Compute output = dot(x, y) + buf = MA.buffer_for( + LinearAlgebra.dot, + Vector{BigFloat}, + Vector{BigFloat}, + ) + output = BigFloat() + MA.buffered_operate_to!!(buf, output, LinearAlgebra.dot, x, y) + # Check that we haven't mutated x or y + @test old_x == x + @test old_y == y + # Compute dot(x, y) in larger precision. This will be used to + # compare with our `dot`. + accurate = setprecision(BigFloat, 8 * precision(BigFloat)) do + return LinearAlgebra.dot(x, y) + end + # Compute the relative error + return abs(accurate - output) / abs(accurate) + end + # Return estimate for ULP + return maximum_relative_error / eps(BigFloat) + end + @test 0 <= err < 1 + return +end + +function _alloc_test_helper( + buf::B, + output::BigFloat, + x::Vector{BigFloat}, + y::Vector{BigFloat}, +) where {B<:Any} + let b = buf, o = output, x = x, y = y + () -> MA.buffered_operate_to!!(b, o, LinearAlgebra.dot, x, y) + end +end + +function test_alloc() + x = rand(BigFloat, 1000) + y = rand(BigFloat, 1000) + V = Vector{BigFloat} + buffer = MA.buffer_for(LinearAlgebra.dot, V, V) + alloc_test(_alloc_test_helper(buffer, BigFloat(), x, y), 0) + return +end + +end # TestBig + +TestBig.runtests() diff --git a/test/broadcast.jl b/test/test_broadcast.jl similarity index 70% rename from test/broadcast.jl rename to test/test_broadcast.jl index ba7858a4..7b1e4a68 100644 --- a/test/broadcast.jl +++ b/test/test_broadcast.jl @@ -4,10 +4,28 @@ # v.2.0. If a copy of the MPL was not distributed with this file, You can obtain # one at http://mozilla.org/MPL/2.0/. +module TestBroadcast + using Test + import MutableArithmetics as MA -@testset "Int" begin +function runtests() + is_test(name::Symbol) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + +function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} + f() # compile + measured_allocations = @allocated f() + @test measured_allocations <= expected_upper_bound + return +end + +function test_Int() a = [1, 2] b = 3 c = [3, 4] @@ -17,9 +35,10 @@ import MutableArithmetics as MA # the stack: https://github.com/JuliaLang/julia/pull/33886 alloc_test(() -> MA.broadcast!!(+, a, b), 0) alloc_test(() -> MA.broadcast!!(+, a, c), 0) + return end -@testset "BigInt" begin +function test_BigInt() x = BigInt(1) y = BigInt(2) a = [x, y] @@ -34,9 +53,10 @@ end n = 6 * @allocated(BigInt(1)) alloc_test(() -> MA.broadcast!!(+, a, b), n) alloc_test(() -> MA.broadcast!!(+, a, c), 0) + return end -@testset "broadcast_issue_158" begin +function test_broadcast_issue_158() x, y = BigInt[2 3], BigInt[2 3; 3 4] @test MA.@rewrite(x .+ y) == x .+ y @test MA.@rewrite(x .- y) == x .- y @@ -44,19 +64,28 @@ end @test MA.@rewrite(y .- x) == y .- x @test MA.@rewrite(y .* x) == y .* x @test MA.@rewrite(x .* y) == x .* y + return end struct Struct221 <: AbstractArray{Int,1} end + struct BroadcastStyle221 <: Broadcast.BroadcastStyle end + Base.BroadcastStyle(::Type{Struct221}) = BroadcastStyle221() -@testset "promote_broadcast_for_new_style" begin +function test_promote_broadcast_for_new_style() @test MA.promote_broadcast(MA.add_mul, Vector{Int}, Struct221) === Any + return end -@testset "broadcast_length_1_dimensions" begin +function test_broadcast_length_1_dimensions() A = rand(2, 1, 3) B = rand(2, 3) @test MA.broadcast!!(MA.sub_mul, A, B) ≈ A .- B @test MA.broadcast!!(MA.sub_mul, B, A) ≈ B .- A + return end + +end # module + +TestBroadcast.runtests() diff --git a/test/evalpoly.jl b/test/test_evalpoly.jl similarity index 94% rename from test/evalpoly.jl rename to test/test_evalpoly.jl index 09027969..bdf842e6 100644 --- a/test/evalpoly.jl +++ b/test/test_evalpoly.jl @@ -4,8 +4,23 @@ # v.2.0. If a copy of the MPL was not distributed with this file, You can obtain # one at http://mozilla.org/MPL/2.0/. +module TestEvalPoly + +using Test + +import MutableArithmetics as MA + +function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} + f() # compile + measured_allocations = @allocated f() + @test measured_allocations <= expected_upper_bound + return +end + abstract type OpSignature end + struct RegularSignature <: OpSignature end + struct ToSignature <: OpSignature end function signature_type_of( @@ -159,3 +174,5 @@ const evalpoly_operations = end end end + +end # TestEvalPoly diff --git a/test/hygiene.jl b/test/test_hygiene.jl similarity index 91% rename from test/hygiene.jl rename to test/test_hygiene.jl index 4e0da228..8823b318 100644 --- a/test/hygiene.jl +++ b/test/test_hygiene.jl @@ -4,15 +4,20 @@ # v.2.0. If a copy of the MPL was not distributed with this file, You can obtain # one at http://mozilla.org/MPL/2.0/. -# hygiene.jl -# Make sure that our macros have good hygiene +module TestHygiene module M + using Test + # Import it as a different name so that we test whether MutableArithmetics is # needed in the current scope. import MutableArithmetics as NewSymbolMA +@test !@isdefined(MA) + +@test !@isdefined(MutableArithmetics) + macro _rewrite(expr) variable, code = NewSymbolMA.rewrite(expr) return quote @@ -41,7 +46,7 @@ x = big(1) y = NewSymbolMA.@rewrite(x + (x + 1)^1) @test y == 3 -end +end # M # Test the scoping outside the module. See also the note in runtests.jl. @@ -50,3 +55,5 @@ using Test @testset "test_scoping" begin @test M.@_rewrite(1 + (1 + 1)^1) == 3 end + +end # TestHygiene diff --git a/test/int.jl b/test/test_int.jl similarity index 85% rename from test/int.jl rename to test/test_int.jl index b5693725..9cbafb73 100644 --- a/test/int.jl +++ b/test/test_int.jl @@ -4,10 +4,20 @@ # v.2.0. If a copy of the MPL was not distributed with this file, You can obtain # one at http://mozilla.org/MPL/2.0/. +module TestInt + using Test import MutableArithmetics as MA -@testset "promote_operation" begin +function runtests() + is_test(name::Symbol) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + +function test_promote_operation() @test MA.promote_operation(MA.zero, Int) == Int @test MA.promote_operation(MA.one, Int) == Int @test MA.promote_operation(+, Int, Int) == Int @@ -40,8 +50,10 @@ import MutableArithmetics as MA @test MA.promote_operation(gcd, Integer, Int) == Integer @test MA.promote_operation(&, Integer, Integer, Integer) == Integer @test MA.promote_operation(&, Integer, Integer, Int) == Integer + return end -@testset "add_to!! / add!!" begin + +function test_add_to!!_add!!() @test MA.mutability(Int, MA.add_to!!, Int, Int) isa MA.IsNotMutable @test MA.mutability(Int, MA.add!!, Int) isa MA.IsNotMutable a = 5 @@ -52,9 +64,10 @@ end a = 165 b = 255 @test MA.add!!(a, b) == 420 + return end -@testset "mul_to!! / mul!!" begin +function test_mul_to!!_mul!!() @test MA.mutability(Int, MA.mul_to!!, Int, Int) isa MA.IsNotMutable @test MA.mutability(Int, MA.mul!!, Int) isa MA.IsNotMutable a = 5 @@ -65,9 +78,10 @@ end a = 15 b = 28 @test MA.mul!!(a, b) == 420 + return end -@testset "add_mul_to!! / add_mul!! / add_mul_buf_to!! /add_mul_buf!!" begin +function test_add_mul_to!!_add_mul!!_add_mul_buf_to!!_add_mul_buf!!() @test MA.mutability(Int, MA.add_mul_to!!, Int, Int, Int) isa MA.IsNotMutable @test MA.mutability(Int, MA.add_mul!!, Int, Int) isa MA.IsNotMutable @test MA.mutability(Int, MA.add_mul_buf_to!!, Int, Int, Int, Int) isa @@ -98,25 +112,34 @@ end d = 42 buf = 56 @test MA.add_mul_buf_to!!(buf, d, a, b, c) == 420 + return end -@testset "zero!!" begin +function test_zero!!() @test MA.mutability(Int, MA.zero!!) isa MA.IsNotMutable a = 5 @test MA.zero!!(a) == 0 + return end -@testset "one!!" begin +function test_one!!() @test MA.mutability(Int, MA.one!!) isa MA.IsNotMutable a = 5 @test MA.one!!(a) == 1 + return end -@testset "Zero / Int" begin +function test_Zero_Int() @test MA.Zero() / 1 == MA.Zero() @test_throws DivideError MA.Zero() / 0 + return end -@testset "Division" begin +function test_Division() @test 1 / 2 == MA.operate!!(/, 1, 2) + return end + +end # module + +TestInt.runtests() diff --git a/test/matmul.jl b/test/test_matmul.jl similarity index 96% rename from test/matmul.jl rename to test/test_matmul.jl index 9737dbf7..8ef551aa 100644 --- a/test/matmul.jl +++ b/test/test_matmul.jl @@ -4,13 +4,23 @@ # v.2.0. If a copy of the MPL was not distributed with this file, You can obtain # one at http://mozilla.org/MPL/2.0/. +module TestMatMul + using Test + import MutableArithmetics as MA struct CustomArray{T,N} <: AbstractArray{T,N} end import LinearAlgebra, SparseArrays +function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} + f() # compile + measured_allocations = @allocated f() + @test measured_allocations <= expected_upper_bound + return +end + function dot_test(x, y) @test MA.operate(LinearAlgebra.dot, x, y) == LinearAlgebra.dot(x, y) @test MA.operate(LinearAlgebra.dot, y, x) == LinearAlgebra.dot(y, x) @@ -61,15 +71,8 @@ end @test MA.operate(convert, Int, 1) === 1 end -const EXPECTED_ERROR = string( - "Cannot multiply a `Matrix{NoProdMutable}` with a ", - "`Matrix{NoProdMutable}` because the sum of the product of a ", - "`NoProdMutable` and a `NoProdMutable` could not be inferred so a ", - "`Matrix{Union{}}` allocated to store the output of the ", - "multiplication instead of a `Matrix{$(Int)}`.", -) - struct NoProdMutable <: MA.AbstractMutable end + function MA.promote_operation( ::typeof(*), ::Type{NoProdMutable}, @@ -80,8 +83,15 @@ end function unsupported_product() A = [NoProdMutable() for i in 1:2, j in 1:2] - err = ErrorException(EXPECTED_ERROR) + err = ErrorException( + "Cannot multiply a `Matrix{$NoProdMutable}` with a " * + "`Matrix{$NoProdMutable}` because the sum of the product of a " * + "`$NoProdMutable` and a `$NoProdMutable` could not be inferred so a " * + "`Matrix{Union{}}` allocated to store the output of the " * + "multiplication instead of a `Matrix{$(Int)}`.", + ) @test_throws err A * A + return end @testset "Errors" begin @@ -486,3 +496,5 @@ end test_array_sum(Int) test_sparse_vector_sum(Int) end + +end # TestMatMul diff --git a/test/test_rewrite.jl b/test/test_rewrite.jl new file mode 100644 index 00000000..ecd9d28f --- /dev/null +++ b/test/test_rewrite.jl @@ -0,0 +1,128 @@ +# Copyright (c) 2019 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. + +module TestRewrite + +using Test + +import MutableArithmetics as MA +import OffsetArrays + +include(joinpath(@__DIR__, "dummy.jl")) + +function runtests() + is_test(name::Symbol) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + @testset "$T" for T in ( + Int, + Float64, + BigInt, + BigFloat, + Rational{Int}, + Rational{BigInt}, + DummyBigInt, + ) + getfield(@__MODULE__, name)(T) + end + end + return +end + +# Test that the macro call `m` throws an error exception during pre-compilation +macro test_macro_throws(error, m) + # See https://discourse.julialang.org/t/test-throws-with-macros-after-pr-23533/5878 + return quote + @test_throws $(esc(error)) try + @eval $m + catch catched_error + throw(catched_error.error) + end + end +end + +function test_is_supported_test(::Type{T}) where {T} + @test MA.Test._is_supported(*, T, T) + @test MA.Test._is_supported(+, T, T) + return +end + +function test_Error(::Type{T}) where {T} + x, y, z = T(1), T(2), T(3) + err = ErrorException( + "The curly syntax (sum{},prod{},norm2{}) is no longer supported. Expression: `sum{(i for i = 1:2)}`.", + ) + @test_macro_throws err MA.@rewrite sum{i for i in 1:2} + err = ErrorException("Unexpected comparison in expression `z >= y`.") + @test_macro_throws err MA.@rewrite z >= y + err = ErrorException("Unexpected comparison in expression `y <= z`.") + @test_macro_throws err MA.@rewrite y <= z + err = ErrorException("Unexpected comparison in expression `x <= y <= z`.") + @test_macro_throws err MA.@rewrite x <= y <= z + return +end + +function test_Scalar(::Type{T}) where {T} + MA.Test.scalar_test(T(2)) + MA.Test.scalar_test(T(3)) + return +end + +function test_Quadratic(::Type{T}) where {T} + exclude = T == DummyBigInt ? ["quadratic_division"] : String[] + MA.Test.quadratic_test(T(1), T(2), T(3), T(4); exclude) + return +end + +function test_Sparse(::Type{T}) where {T} + return MA.Test.sparse_test(T(4), T(5), T[8 1 9; 4 3 1; 2 0 8]) +end + +_exclude(::Type{DummyBigInt}) = ["broadcast_division", "matrix_vector_division"] + +_exclude(::Type{<:Rational}) = ["broadcast_division"] + +_exclude(::Type) = String[] + +function test_Vector(::Type{T}) where {T} + exclude = _exclude(T) + MA.Test.array_test(T[-7, 1, 4]; exclude) + MA.Test.array_test(T[3, 2, 6]; exclude) + return +end + +function test_Square_Matrix(::Type{T}) where {T} + exclude = _exclude(T) + MA.Test.array_test(T[1 2; 2 4]; exclude) + MA.Test.array_test(T[2 4; -1 3]; exclude) + MA.Test.array_test(T[0 -4; 6 -5]; exclude) + MA.Test.array_test(T[5 1 9; -7 2 4; -2 -7 5]; exclude) + return +end + +function test_non_square_matrix(::Type{T}) where {T} + exclude = _exclude(T) + MA.Test.array_test(T[5 1 9; -7 2 4]; exclude) + return +end + +function test_tensor(::Type{T}) where {T} + exclude = _exclude(T) + S = zeros(T, 2, 2, 2) + S[1, :, :] = T[5 -8; 3 -7] + S[2, :, :] = T[-2 8; 8 -1] + MA.Test.array_test(S; exclude) + return +end + +function test_offset_array(::Type{T}) where {T} + x = T[2, 4, 3] + MA.Test.non_array_test(x, OffsetArrays.OffsetArray(x, -length(x))) + return +end + +end # TestRewrite + +TestRewrite.runtests() diff --git a/test/rewrite_generic.jl b/test/test_rewrite_generic.jl similarity index 82% rename from test/rewrite_generic.jl rename to test/test_rewrite_generic.jl index 9b86c07f..26869834 100644 --- a/test/rewrite_generic.jl +++ b/test/test_rewrite_generic.jl @@ -10,13 +10,12 @@ using Test import MutableArithmetics as MA +include(joinpath(@__DIR__, "dummy.jl")) + function runtests() - for name in names(@__MODULE__; all = true) - if startswith("$(name)", "test_") - @testset "$(name)" begin - getfield(@__MODULE__, name)() - end - end + is_test(name::Symbol) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() end return end @@ -562,6 +561,120 @@ function test_issue_343() return end +function test_multiply_expr_MA_Zero() + x = DummyBigInt(1) + f = DummyBigInt(2) + @test MA.@rewrite( + f * sum(x for i in 1:0), + move_factors_into_sums = false + ) == MA.Zero() + @test MA.@rewrite( + sum(x for i in 1:0) * f, + move_factors_into_sums = false + ) == MA.Zero() + @test MA.@rewrite( + -f * sum(x for i in 1:0), + move_factors_into_sums = false + ) == MA.Zero() + @test MA.@rewrite( + sum(x for i in 1:0) * -f, + move_factors_into_sums = false + ) == MA.Zero() + @test MA.@rewrite( + (f + f) * sum(x for i in 1:0), + move_factors_into_sums = false + ) == MA.Zero() + @test MA.@rewrite( + sum(x for i in 1:0) * (f + f), + move_factors_into_sums = false + ) == MA.Zero() + @test MA.@rewrite( + -[f] * sum(x for i in 1:0), + move_factors_into_sums = false + ) == MA.Zero() + @test MA.@rewrite( + sum(x for i in 1:0) * -[f], + move_factors_into_sums = false + ) == MA.Zero() + @test MA.isequal_canonical( + MA.@rewrite(f + sum(x for i in 1:0), move_factors_into_sums = false), + f, + ) + @test MA.isequal_canonical( + MA.@rewrite(sum(x for i in 1:0) + f, move_factors_into_sums = false), + f, + ) + @test MA.isequal_canonical( + MA.@rewrite(-f + sum(x for i in 1:0), move_factors_into_sums = false), + -f, + ) + @test MA.isequal_canonical( + MA.@rewrite(sum(x for i in 1:0) + -f, move_factors_into_sums = false), + -f, + ) + @test MA.isequal_canonical( + MA.@rewrite( + (f + f) + sum(x for i in 1:0), + move_factors_into_sums = false + ), + f + f, + ) + @test MA.isequal_canonical( + MA.@rewrite( + sum(x for i in 1:0) + (f + f), + move_factors_into_sums = false + ), + f + f, + ) + return +end + +function test_generator_with_not_sum() + @test MA.@rewrite(minimum(j^2 for j in 2:3)) == 4 + @test MA.@rewrite(maximum(j^2 for j in 2:3)) == 9 + return +end + +function test_issue_76_trailing_dimensions() + @testset "(4,) + (4,1)" begin + X = [1, 2, 3, 4] + Y = reshape([1, 2, 3, 4], (4, 1)) + @test MA.@rewrite(X + Y) == X + Y + @test MA.@rewrite(Y + X) == Y + X + end + @testset "(4,) + (4,1,1)" begin + X = [1, 2, 3, 4] + Y = reshape([1, 2, 3, 4], (4, 1, 1)) + @test MA.@rewrite(X + Y) == X + Y + @test MA.@rewrite(Y + X) == Y + X + end + @testset "(2,2) + (2,2,1)" begin + X = [1 2; 3 4] + Y = reshape([1, 2, 3, 4], (2, 2, 1)) + @test MA.@rewrite(X + Y) == X + Y + @test MA.@rewrite(Y + X) == Y + X + end + @testset "(4,) - (4,1)" begin + X = [1, 3, 5, 7] + Y = reshape([1, 2, 3, 4], (4, 1)) + @test MA.@rewrite(X - Y) == X - Y + @test MA.@rewrite(Y - X) == Y - X + end + @testset "(4,) - (4,1,1)" begin + X = [1, 3, 5, 7] + Y = reshape([1, 2, 3, 4], (4, 1, 1)) + @test MA.@rewrite(X - Y) == X - Y + @test MA.@rewrite(Y - X) == Y - X + end + @testset "(2,2) - (2,2,1)" begin + X = [1 3; 5 7] + Y = reshape([1, 2, 3, 4], (2, 2, 1)) + @test MA.@rewrite(X - Y) == X - Y + @test MA.@rewrite(Y - X) == Y - X + end + return +end + end # module TestRewriteGeneric.runtests() diff --git a/test/SparseArrays.jl b/test/test_sparse_arrays.jl similarity index 95% rename from test/SparseArrays.jl rename to test/test_sparse_arrays.jl index 02dfa17f..c294cab5 100644 --- a/test/SparseArrays.jl +++ b/test/test_sparse_arrays.jl @@ -14,12 +14,9 @@ import Random import SparseArrays function runtests() - for name in names(@__MODULE__; all = true) - if startswith("$(name)", "test_") - @testset "$(name)" begin - getfield(@__MODULE__, name)() - end - end + is_test(name::Symbol) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() end return end diff --git a/test/utilities.jl b/test/utilities.jl deleted file mode 100644 index 93db66ee..00000000 --- a/test/utilities.jl +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright (c) 2019 MutableArithmetics.jl 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 http://mozilla.org/MPL/2.0/. - -include("dummy.jl") - -function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} - f() # compile - measured_allocations = @allocated f() - @test measured_allocations <= expected_upper_bound - return -end From bf77ad1546807821103e3d984e1971f85efe294c Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 1 Apr 2026 15:58:07 +1300 Subject: [PATCH 2/4] Update --- test/test_matmul.jl | 455 ++++++++++++++++++++++---------------------- 1 file changed, 226 insertions(+), 229 deletions(-) diff --git a/test/test_matmul.jl b/test/test_matmul.jl index 8ef551aa..420813be 100644 --- a/test/test_matmul.jl +++ b/test/test_matmul.jl @@ -8,11 +8,19 @@ module TestMatMul using Test +import LinearAlgebra import MutableArithmetics as MA +import SparseArrays -struct CustomArray{T,N} <: AbstractArray{T,N} end +function runtests() + is_test(name::Symbol) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end -import LinearAlgebra, SparseArrays +struct CustomArray{T,N} <: AbstractArray{T,N} end function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} f() # compile @@ -21,7 +29,7 @@ function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} return end -function dot_test(x, y) +function _test_dot(x, y) @test MA.operate(LinearAlgebra.dot, x, y) == LinearAlgebra.dot(x, y) @test MA.operate(LinearAlgebra.dot, y, x) == LinearAlgebra.dot(y, x) @test MA.operate(*, x', y) == x' * y @@ -32,30 +40,32 @@ function dot_test(x, y) LinearAlgebra.transpose(y) * x end -@testset "dot" begin +function test_dot() x = [1im] y = [1] A = reshape(x, 1, 1) B = reshape(y, 1, 1) - dot_test(x, x) - dot_test(y, y) - dot_test(A, A) - dot_test(B, B) - dot_test(x, y) - dot_test(x, A) - dot_test(x, B) - dot_test(y, A) - dot_test(y, B) - dot_test(A, B) + _test_dot(x, x) + _test_dot(y, y) + _test_dot(A, A) + _test_dot(B, B) + _test_dot(x, y) + _test_dot(x, A) + _test_dot(x, B) + _test_dot(y, A) + _test_dot(y, B) + _test_dot(A, B) + return end -@testset "promote_operation" begin +function test_promote_operation() x = [1] @test MA.promote_operation(*, typeof(x'), typeof(x)) == Int @test MA.promote_operation(*, typeof(transpose(x)), typeof(x)) == Int + return end -@testset "convert" begin +function test_convert() @test MA.scaling_convert( LinearAlgebra.UniformScaling{Int}, LinearAlgebra.I, @@ -69,6 +79,7 @@ end ) isa LinearAlgebra.UniformScaling @test MA.operate(convert, Int, LinearAlgebra.I) === 1 @test MA.operate(convert, Int, 1) === 1 + return end struct NoProdMutable <: MA.AbstractMutable end @@ -81,7 +92,7 @@ function MA.promote_operation( return Int # Dummy result just to test error message end -function unsupported_product() +function test_unsupported_product() A = [NoProdMutable() for i in 1:2, j in 1:2] err = ErrorException( "Cannot multiply a `Matrix{$NoProdMutable}` with a " * @@ -94,228 +105,199 @@ function unsupported_product() return end -@testset "Errors" begin - @testset "`promote_op` error" begin - AT = CustomArray{Int,3} - f(x, y) = nothing - err = ErrorException( - "`promote_operation($(f), $(CustomArray{Int,3}), $(CustomArray{Int,3}))` not implemented yet, please report this.", - ) - @test_throws err MA.promote_operation(f, AT, AT) - end +function test_promote_op_error() + AT = CustomArray{Int,3} + f(x, y) = nothing + err = ErrorException( + "`promote_operation($(f), $(CustomArray{Int,3}), $(CustomArray{Int,3}))` not implemented yet, please report this.", + ) + @test_throws err MA.promote_operation(f, AT, AT) + return +end - @testset "Dimension mismatch" begin - A = zeros(1, 1) - B = zeros(2, 2) - @test_throws DimensionMismatch MA.@rewrite A + B - x = ones(1) - y = ones(2) - err = DimensionMismatch( - "one array has length 1 which does not match the length of the next one, 2.", - ) - @test_throws err MA.operate(*, x', y) - @test_throws err MA.operate(*, LinearAlgebra.transpose(x), y) - err = DimensionMismatch( - "matrix A has dimensions (2,2), vector B has length 1", - ) - @test_throws err MA.operate(*, x', B) - a = zeros(0) - @test iszero(@inferred MA.operate(LinearAlgebra.dot, a, a)) - @test iszero(@inferred MA.operate(*, a', a)) - @test iszero(@inferred MA.operate(*, LinearAlgebra.transpose(a), a)) - A = zeros(2) - B = zeros(2, 1) - err = DimensionMismatch( - "Cannot sum or substract a matrix of axes `$(axes(B))` into matrix of axes `$(axes(A))`, expected axes `$(axes(B))`.", - ) - @test_throws err MA.operate!(+, A, B) - A = SparseArrays.spzeros(2) - B = SparseArrays.spzeros(2, 1) - err = DimensionMismatch( - "Cannot sum or substract a matrix of axes `$(axes(B))` into matrix of axes `$(axes(A))`, expected axes `$(axes(B))`.", - ) - @test_throws err MA.operate!(+, A, B) - output = zeros(2) - A = zeros(2, 1) - B = zeros(2, 1) - err = DimensionMismatch( - "Cannot sum or substract matrices of axes `$(axes(A))` and `$(axes(B))` into a matrix of axes `$(axes(output))`, expected axes `$(axes(B))`.", - ) - @test_throws err MA.operate_to!(output, +, A, B) - output = SparseArrays.spzeros(2) - A = SparseArrays.spzeros(2, 1) - B = SparseArrays.spzeros(2, 1) - err = DimensionMismatch( - "Cannot sum or substract matrices of axes `$(axes(A))` and `$(axes(B))` into a matrix of axes `$(axes(output))`, expected axes `$(axes(B))`.", - ) - @test_throws err MA.operate_to!(output, +, A, B) - err = DimensionMismatch( - "Cannot sum or substract a matrix of axes `$(axes(A))` into a matrix of axes `$(axes(output))`, expected axes `$(axes(A))`.", - ) - @test_throws err MA.operate_to!(output, +, A) - @test_throws err MA.operate_to!(output, -, A) - end - @testset "unsupported_product" begin - unsupported_product() - end +function test_DimensionMismatch() + A = zeros(1, 1) + B = zeros(2, 2) + @test_throws DimensionMismatch MA.@rewrite A + B + x = ones(1) + y = ones(2) + err = DimensionMismatch( + "one array has length 1 which does not match the length of the next one, 2.", + ) + @test_throws err MA.operate(*, x', y) + @test_throws err MA.operate(*, LinearAlgebra.transpose(x), y) + err = DimensionMismatch( + "matrix A has dimensions (2,2), vector B has length 1", + ) + @test_throws err MA.operate(*, x', B) + a = zeros(0) + @test iszero(@inferred MA.operate(LinearAlgebra.dot, a, a)) + @test iszero(@inferred MA.operate(*, a', a)) + @test iszero(@inferred MA.operate(*, LinearAlgebra.transpose(a), a)) + A = zeros(2) + B = zeros(2, 1) + err = DimensionMismatch( + "Cannot sum or substract a matrix of axes `$(axes(B))` into matrix of axes `$(axes(A))`, expected axes `$(axes(B))`.", + ) + @test_throws err MA.operate!(+, A, B) + A = SparseArrays.spzeros(2) + B = SparseArrays.spzeros(2, 1) + err = DimensionMismatch( + "Cannot sum or substract a matrix of axes `$(axes(B))` into matrix of axes `$(axes(A))`, expected axes `$(axes(B))`.", + ) + @test_throws err MA.operate!(+, A, B) + output = zeros(2) + A = zeros(2, 1) + B = zeros(2, 1) + err = DimensionMismatch( + "Cannot sum or substract matrices of axes `$(axes(A))` and `$(axes(B))` into a matrix of axes `$(axes(output))`, expected axes `$(axes(B))`.", + ) + @test_throws err MA.operate_to!(output, +, A, B) + output = SparseArrays.spzeros(2) + A = SparseArrays.spzeros(2, 1) + B = SparseArrays.spzeros(2, 1) + err = DimensionMismatch( + "Cannot sum or substract matrices of axes `$(axes(A))` and `$(axes(B))` into a matrix of axes `$(axes(output))`, expected axes `$(axes(B))`.", + ) + @test_throws err MA.operate_to!(output, +, A, B) + err = DimensionMismatch( + "Cannot sum or substract a matrix of axes `$(axes(A))` into a matrix of axes `$(axes(output))`, expected axes `$(axes(A))`.", + ) + @test_throws err MA.operate_to!(output, +, A) + @test_throws err MA.operate_to!(output, -, A) + return end -@testset "Matrix multiplication" begin - @testset "matrix-vector product" begin - A = [1 1 1; 1 1 1; 1 1 1] - x = [1; 1; 1] - y = [0; 0; 0] - - @test MA.mul(A, x) == [3; 3; 3] - @test MA.mul_to!!(y, A, x) == [3; 3; 3] && y == [3; 3; 3] - - A = BigInt[1 1 1; 1 1 1; 1 1 1] - x = BigInt[1; 1; 1] - y = BigInt[0; 0; 0] - - @test MA.mutability(y, *, A, x) isa MA.IsMutable - @test MA.mul(A, x) == BigInt[3; 3; 3] - @test MA.mul_to!!(y, A, x) == BigInt[3; 3; 3] && y == BigInt[3; 3; 3] - @test_throws DimensionMismatch MA.mul(BigInt[1 1; 1 1], BigInt[]) - @test MA.mul_to!!(BigInt[], BigInt[1 1; 1 1], BigInt[1; 1]) == - BigInt[2, 2] - z = BigInt[0, 0] - @test MA.mul_to!!(z, BigInt[1 1; 1 1], BigInt[1; 1]) === z - @test z == BigInt[2, 2] - - @testset "mutability" begin - alloc_test(() -> MA.promote_operation(*, typeof(A), typeof(x)), 0) - alloc_test( - () -> MA.promote_operation( - +, - typeof(y), - MA.promote_operation(*, typeof(A), typeof(x)), - ), - 0, - ) - alloc_test( - () -> MA.promote_operation( - MA.add_mul, - typeof(y), - typeof(A), - typeof(x), - ), - 0, - ) - alloc_test( - () -> MA.mutability( - typeof(y), - MA.add_mul, - typeof(y), - typeof(A), - typeof(x), - ), - 0, - ) - alloc_test(() -> MA.mutability(y, MA.add_mul, y, A, x), 0) - end - BIGINT_ALLOC = 2 * sizeof(Int) + @allocated(BigInt(1)) - alloc_test(() -> MA.add_mul!!(y, A, x), BIGINT_ALLOC) - alloc_test( - () -> MA.operate_fallback!!(MA.IsMutable(), MA.add_mul, y, A, x), - BIGINT_ALLOC, +function test_matrix_vector_product() + A = [1 1 1; 1 1 1; 1 1 1] + x = [1; 1; 1] + y = [0; 0; 0] + @test MA.mul(A, x) == [3; 3; 3] + @test MA.mul_to!!(y, A, x) == [3; 3; 3] && y == [3; 3; 3] + A = BigInt[1 1 1; 1 1 1; 1 1 1] + x = BigInt[1; 1; 1] + y = BigInt[0; 0; 0] + @test MA.mutability(y, *, A, x) isa MA.IsMutable + @test MA.mul(A, x) == BigInt[3; 3; 3] + @test MA.mul_to!!(y, A, x) == BigInt[3; 3; 3] && y == BigInt[3; 3; 3] + @test_throws DimensionMismatch MA.mul(BigInt[1 1; 1 1], BigInt[]) + @test MA.mul_to!!(BigInt[], BigInt[1 1; 1 1], BigInt[1; 1]) == BigInt[2, 2] + z = BigInt[0, 0] + @test MA.mul_to!!(z, BigInt[1 1; 1 1], BigInt[1; 1]) === z + @test z == BigInt[2, 2] + alloc_test(() -> MA.promote_operation(*, typeof(A), typeof(x)), 0) + alloc_test(0) do + return MA.promote_operation( + +, + typeof(y), + MA.promote_operation(*, typeof(A), typeof(x)), ) - alloc_test(() -> MA.operate!!(MA.add_mul, y, A, x), BIGINT_ALLOC) - alloc_test(() -> MA.operate!(MA.add_mul, y, A, x), BIGINT_ALLOC) - # Apparently, all allocations were on creating the buffer since this is allocation free: - buffer = MA.buffer_for(MA.add_mul, typeof(y), typeof(A), typeof(x)) - alloc_test(() -> MA.buffered_operate!(buffer, MA.add_mul, y, A, x), 0) end - @testset "matrix-matrix product" begin - A = [1 2 3; 4 5 6; 6 8 9] - B = [1 -1 2; -2 3 1; 2 -3 1] - C = [one(Int) for i in 1:3, j in 1:3] - - D = [3 -4 7; 6 -7 19; 8 -9 29] - @test MA.mul(A, B) == D - @test MA.mul_to!!(C, A, B) == D - @test C == D - - A = BigInt[1 2 3; 4 5 6; 6 8 9] - B = BigInt[1 -1 2; -2 3 1; 2 -3 1] - C = [one(BigInt) for i in 1:3, j in 1:3] - - D = BigInt[3 -4 7; 6 -7 19; 8 -9 29] - @test MA.mul(A, B) == D - @test MA.mul_to!!(C, A, B) == D - @test C == D - - @test MA.mutability(C, *, A, B) isa MA.IsMutable - @test_throws DimensionMismatch MA.mul( - BigInt[1 1; 1 1], - zeros(BigInt, 1, 1), + alloc_test(0) do + return MA.promote_operation(MA.add_mul, typeof(y), typeof(A), typeof(x)) + end + alloc_test(0) do + return MA.mutability( + typeof(y), + MA.add_mul, + typeof(y), + typeof(A), + typeof(x), ) - @test MA.mul_to!!( - zeros(BigInt, 1, 1), - BigInt[1 1; 1 1], - zeros(BigInt, 2, 1), - ) == zeros(BigInt, 2, 1) - - @testset "mutability" begin - alloc_test(() -> MA.promote_operation(*, typeof(A), typeof(B)), 0) - alloc_test( - () -> MA.promote_operation( - +, - typeof(C), - MA.promote_operation(*, typeof(A), typeof(B)), - ), - 0, - ) - alloc_test( - () -> MA.promote_operation( - MA.add_mul, - typeof(C), - typeof(A), - typeof(B), - ), - 0, - ) - alloc_test( - () -> MA.mutability( - typeof(C), - MA.add_mul, - typeof(C), - typeof(A), - typeof(B), - ), - 0, - ) - alloc_test(() -> MA.mutability(C, MA.add_mul, C, A, B), 0) - end - BIGINT_ALLOC = 2 * sizeof(Int) + @allocated(BigInt(1)) - alloc_test(() -> MA.add_mul!!(C, A, B), BIGINT_ALLOC) - alloc_test(() -> MA.operate!!(MA.add_mul, C, A, B), BIGINT_ALLOC) - alloc_test(() -> MA.operate!(MA.add_mul, C, A, B), BIGINT_ALLOC) end + alloc_test(() -> MA.mutability(y, MA.add_mul, y, A, x), 0) + BIGINT_ALLOC = 2 * sizeof(Int) + @allocated(BigInt(1)) + alloc_test(() -> MA.add_mul!!(y, A, x), BIGINT_ALLOC) + alloc_test( + () -> MA.operate_fallback!!(MA.IsMutable(), MA.add_mul, y, A, x), + BIGINT_ALLOC, + ) + alloc_test(() -> MA.operate!!(MA.add_mul, y, A, x), BIGINT_ALLOC) + alloc_test(() -> MA.operate!(MA.add_mul, y, A, x), BIGINT_ALLOC) + # Apparently, all allocations were on creating the buffer since this is allocation free: + buffer = MA.buffer_for(MA.add_mul, typeof(y), typeof(A), typeof(x)) + alloc_test(() -> MA.buffered_operate!(buffer, MA.add_mul, y, A, x), 0) + return +end + +function test_matrix_matrix_product() + A = [1 2 3; 4 5 6; 6 8 9] + B = [1 -1 2; -2 3 1; 2 -3 1] + C = [one(Int) for i in 1:3, j in 1:3] + D = [3 -4 7; 6 -7 19; 8 -9 29] + @test MA.mul(A, B) == D + @test MA.mul_to!!(C, A, B) == D + @test C == D + A = BigInt[1 2 3; 4 5 6; 6 8 9] + B = BigInt[1 -1 2; -2 3 1; 2 -3 1] + C = [one(BigInt) for i in 1:3, j in 1:3] + D = BigInt[3 -4 7; 6 -7 19; 8 -9 29] + @test MA.mul(A, B) == D + @test MA.mul_to!!(C, A, B) == D + @test C == D + @test MA.mutability(C, *, A, B) isa MA.IsMutable + @test_throws DimensionMismatch MA.mul(BigInt[1 1; 1 1], zeros(BigInt, 1, 1)) + @test MA.mul_to!!( + zeros(BigInt, 1, 1), + BigInt[1 1; 1 1], + zeros(BigInt, 2, 1), + ) == zeros(BigInt, 2, 1) + alloc_test(() -> MA.promote_operation(*, typeof(A), typeof(B)), 0) + alloc_test( + () -> MA.promote_operation( + +, + typeof(C), + MA.promote_operation(*, typeof(A), typeof(B)), + ), + 0, + ) + alloc_test( + () -> MA.promote_operation(MA.add_mul, typeof(C), typeof(A), typeof(B)), + 0, + ) + alloc_test( + () -> MA.mutability( + typeof(C), + MA.add_mul, + typeof(C), + typeof(A), + typeof(B), + ), + 0, + ) + alloc_test(() -> MA.mutability(C, MA.add_mul, C, A, B), 0) + BIGINT_ALLOC = 2 * sizeof(Int) + @allocated(BigInt(1)) + alloc_test(() -> MA.add_mul!!(C, A, B), BIGINT_ALLOC) + alloc_test(() -> MA.operate!!(MA.add_mul, C, A, B), BIGINT_ALLOC) + alloc_test(() -> MA.operate!(MA.add_mul, C, A, B), BIGINT_ALLOC) + return end -@testset "matrix multiplication" begin +function test_matrix_multiplication() X = ones(BigInt, 1, 1) M = ones(1, 1) C = X * M D = MA.operate!!(MA.add_mul, C, X, M) @test D == X * M + X * M + return end -@testset "sub_mul" begin +function test_sub_mul() x = BigFloat[1, 1] A = BigFloat[2 2; 2 2] y = BigFloat[3, 3] MA.operate!!(MA.sub_mul, x, A, y) @test x == [-11, -11] + return end -@testset "add_mul" begin +function test_add_mul() x = BigFloat[1, 1] A = BigFloat[2 2; 2 2] y = BigFloat[3, 3] MA.operate!!(MA.add_mul, x, A, y) @test x == [13, 13] + return end struct Issue65Matrix <: AbstractMatrix{Float64} @@ -327,43 +309,52 @@ struct Issue65OneTo end Base.size(x::Issue65Matrix) = size(x.x) + Base.getindex(x::Issue65Matrix, args...) = getindex(x.x, args...) + Base.axes(x::Issue65Matrix, n) = Issue65OneTo(size(x.x, n)) + Base.convert(::Type{Base.OneTo}, x::Issue65OneTo) = Base.OneTo(x.N) + Base.iterate(x::Issue65OneTo) = iterate(Base.OneTo(x.N)) + Base.iterate(x::Issue65OneTo, arg) = iterate(Base.OneTo(x.N), arg) -@testset "Issue #65" begin +function test_issue_65() x = [1.0 2.0; 3.0 4.0] A = Issue65Matrix(x) @test MA.operate(*, A, x[:, 1]) == x * x[:, 1] @test MA.operate(*, A, x) == x * x + return end -@testset "Issue 154" begin +function test_issue_154() X = big.([1 2; 3 4]) c = big.([5, 6]) MA.operate!!(MA.add_mul, X, c, c') @test X == [26 32; 33 40] + return end -@testset "Issue 153-vector" begin +function test_issue_153_vector() A = big.([1 2; 3 4]) b = big.([5, 6]) ret = big.([0, 0]) LinearAlgebra.mul!(ret, A, b) @test ret == A * b + return end -@testset "Issue 153-matrix" begin +function test_issue_153_matrix() A = big.([1 2; 3 4]) B = big.([5 6; 7 8]) ret = big.([0 0; 0 0]) LinearAlgebra.mul!(ret, A, B) @test ret == A * B + return end -@testset "Abstract eltype in matmul" begin +function test_Abstract_eltype_in_matmul() # Test that we don't initialize the output with zero(T), which might not # exist. for M in (Matrix, LinearAlgebra.Diagonal) @@ -404,9 +395,10 @@ end # @test_broken MA.operate(*, y, x12') ≈ y * x12' # @test_broken MA.operate(*, x12, y) ≈ x12 * y end + return end -@testset "Union{Int,Float64} eltype in matmul" begin +function test_Union_Int_Float64_eltype_in_matmul() # Test that we don't initialize the output with zero(Int), either by taking # the first available type in the union, or by looking at the first element # in the array. @@ -426,24 +418,30 @@ end @test MA.operate(*, x12, y) == x12 * y @test MA.operate(*, x22, y) == x22 * y @test MA.operate(*, y, x22) == y * x22 + return end -@testset "Vector*Transpose{Vector}_issue_256" begin +function test_issue_256_Vector_Transpose_Vector() x = BigInt[1 2; 3 4] A = [1 2; 3 4] y = MA.@rewrite sum(A[i, :] * LinearAlgebra.transpose(x[i, :]) for i in 1:2) @test y == BigInt[10 14; 14 20] + return end struct Monomial end + LinearAlgebra.transpose(m::Monomial) = m + LinearAlgebra.adjoint(m::Monomial) = m + MA.promote_operation(::typeof(*), ::Type{Monomial}, ::Type{Monomial}) = Monomial + Base.:*(m::Monomial, ::Monomial) = m # `Monomial` does not implement `+`, we should check that it does not prevent # to do outer products of vectors -@testset "Vector*Transpose{Vector}_issue_256 with Monomial" begin +function test_issue_256_Vector_Transpose_Vector_Monomial() m = Monomial() a = [m, m] for f in [LinearAlgebra.transpose, LinearAlgebra.adjoint] @@ -452,9 +450,10 @@ Base.:*(m::Monomial, ::Monomial) = m @test T == typeof(a * b) @test T == typeof(MA.operate(*, a, b)) end + return end -@testset "Issue_271" begin +function test_issue_271() A = reshape([1, 2], (2, 1)) B = [1 2] C = MA.operate!!(*, A, B) @@ -465,10 +464,11 @@ end @test A == reshape([1, 2], (2, 1)) @test B == [1 2] @test D == B * A + return end -function test_array_sum(::Type{T}) where {T} - x = zeros(T, 2) +function test_array_sum() + x = zeros(Int, 2) y = copy(x) z = copy(y) alloc_test(() -> MA.operate!(+, y, z), 0) @@ -478,8 +478,8 @@ function test_array_sum(::Type{T}) where {T} return end -function test_sparse_vector_sum(::Type{T}) where {T} - x = SparseArrays.sparsevec([1, 3], T[5, 7]) +function test_sparse_vector_sum() + x = SparseArrays.sparsevec([1, 3], [5, 7]) y = copy(x) z = copy(y) # FIXME not sure what is allocating @@ -492,9 +492,6 @@ function test_sparse_vector_sum(::Type{T}) where {T} return end -@testset "Array sum" begin - test_array_sum(Int) - test_sparse_vector_sum(Int) -end - end # TestMatMul + +TestMatMul.runtests() From d60c0ce183c00aa06b9cd75e39d40a621fe96f4a Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 1 Apr 2026 16:43:15 +1300 Subject: [PATCH 3/4] UPdate --- test/test_evalpoly.jl | 203 ++++++++++++++---------------------------- 1 file changed, 68 insertions(+), 135 deletions(-) diff --git a/test/test_evalpoly.jl b/test/test_evalpoly.jl index bdf842e6..47a201ae 100644 --- a/test/test_evalpoly.jl +++ b/test/test_evalpoly.jl @@ -10,6 +10,14 @@ using Test import MutableArithmetics as MA +function runtests() + is_test(name::Symbol) = startswith("$name", "test_") + @testset "$name" for name in filter(is_test, names(@__MODULE__; all = true)) + getfield(@__MODULE__, name)() + end + return +end + function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} f() # compile measured_allocations = @allocated f() @@ -17,162 +25,87 @@ function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} return end -abstract type OpSignature end +_is_op_to(::Any) = false -struct RegularSignature <: OpSignature end - -struct ToSignature <: OpSignature end - -function signature_type_of( - ::Union{typeof(MA.operate),typeof(MA.operate!),typeof(MA.operate!!)}, -) - return RegularSignature() -end +_is_op_to(::Union{typeof(MA.operate_to!),typeof(MA.operate_to!!)}) = true -function signature_type_of( - ::Union{typeof(MA.operate_to!),typeof(MA.operate_to!!)}, -) - return ToSignature() -end - -function op_may_modify_first_argument(op::Function) - return (op != MA.operate) & (signature_type_of(op) == RegularSignature()) -end - -function op_is_allowed_for_arithmetic( - ::typeof(evalpoly), +function _should_test( ::Union{typeof(MA.operate!),typeof(MA.operate_to!)}, ::Type{F}, -) where {F<:Any} +) where {F} return MA.mutability(F) == MA.IsMutable() end -function op_is_allowed_for_arithmetic( - ::typeof(evalpoly), - ::Union{typeof(MA.operate),typeof(MA.operate!!),typeof(MA.operate_to!!)}, - ::Type{F}, -) where {F<:Any} - return true -end - -function allowed_allocated_byte_count( - ::typeof(evalpoly), - ::Union{typeof(MA.operate),typeof(MA.operate!),typeof(MA.operate!!)}, - ::Type{F}, -) where {F<:Any} - return @allocated zero(F) -end - -function allowed_allocated_byte_count( - ::typeof(evalpoly), - ::Union{typeof(MA.operate_to!),typeof(MA.operate_to!!)}, - ::Type{F}, -) where {F<:Any} - return @allocated nothing +_should_test(op, ::Type{F}) where {F} = true + +function test_evalpoly() + for F in (BigFloat, Rational{Int}, Float64, Float32, Int) + for op in ( + MA.operate, + MA.operate!, + MA.operate_to!, + MA.operate!!, + MA.operate_to!!, + ) + if _should_test(op, F) + _test_evalpoly(op, F, "vector") + _test_evalpoly(op, F, "tuple") + end + end + end + return end -const evalpoly_supported_arithmetics = - (BigFloat, Rational{Int}, Float64, Float32, Int) - -const evalpoly_operations = - (MA.operate, MA.operate!, MA.operate_to!, MA.operate!!, MA.operate_to!!) - -@testset "evalpoly with $op and $F" for F in evalpoly_supported_arithmetics, - op in evalpoly_operations - - op_is_allowed_for_arithmetic(evalpoly, op, F) || continue - - sig = signature_type_of(op) - +function _test_evalpoly(op, ::Type{F}, collection_type) where {F} if MA.mutability(F) == MA.IsMutable() - @testset "empty coefficients $collection_type" for collection_type in - ("vector", "tuple") - out = one(F) - x = one(F) - backup = MA.copy_if_mutable(x) - coefs = F[] - if collection_type == "tuple" - coefs = () - end - - # Check that the result value is OK - if sig == RegularSignature() - @test iszero(@inferred op(evalpoly, x, coefs)) - elseif sig == ToSignature() - @test iszero(@inferred op(out, evalpoly, x, coefs)) - else - error("unexpected") - end - - # Check that the arguments are unmodified - op_may_modify_first_argument(op) || @test backup == x + out = one(F) + x = one(F) + backup = MA.copy_if_mutable(x) + coefs = collection_type == "tuple" ? () : F[] + if _is_op_to(op) + @test iszero(@inferred op(out, evalpoly, x, coefs)) + else + @test iszero(@inferred op(evalpoly, x, coefs)) + end + if op == MA.operate || _is_op_to(op) + @test backup == x end end - - @testset "exact values: $degree, $x_int" for degree in 0:4, x_int in -5:5 + for degree in 0:4, x_int in -5:5 coefs_int = rand(-6:6, degree + 1) - coefs = map(F, coefs_int) x = F(x_int) - + backup = MA.copy_if_mutable(x) reference = evalpoly(x, coefs) - @test reference == evalpoly(x_int, coefs_int) - - @testset "collection type $collection_type" for collection_type in - ("vector", "tuple") - out = zero(F) - if collection_type == "vector" - coefs_arg = coefs - else - coefs_arg = (coefs...,) - end - if sig == RegularSignature() - out = @inferred op(evalpoly, x, coefs_arg) - elseif sig == ToSignature() - out = @inferred op(out, evalpoly, x, coefs_arg) - else - error("unexpected") - end - - # Check that the result value is OK - @test reference == out - - # Check that the arguments are unmodified - if op_may_modify_first_argument(op) - x = F(x_int) - else - @test x == F(x_int) - end - @test coefs == map(F, coefs_int) + out = zero(F) + coefs_arg = collection_type == "vector" ? coefs : (coefs...,) + if _is_op_to(op) + @test reference == @inferred op(out, evalpoly, x, coefs_arg) + else + @test reference == @inferred op(evalpoly, x, coefs_arg) + end + if op == MA.operate || _is_op_to(op) + @test x == backup end + @test coefs == map(F, coefs_int) end - - @testset "allocation" begin - byte_cnt = allowed_allocated_byte_count(evalpoly, op, F) - coefs_tuple = map(F, (0, 1, 0, 1, 1)) - @testset "collection type $collection_type" for collection_type in - ("vector", "tuple") - if collection_type == "vector" - coefs = collect(coefs_tuple) - else - coefs = coefs_tuple - end - local tested_fun - if sig == RegularSignature() - tested_fun = let x = one(F), coefs = coefs - () -> op(evalpoly, x, coefs) - end - elseif sig == ToSignature() - tested_fun = let o = one(F), x = one(F), coefs = coefs - () -> op(o, evalpoly, x, coefs) - end - else - error("unexpected") - end - alloc_test(tested_fun, byte_cnt) + byte_cnt = _is_op_to(op) ? 0 : @allocated(zero(F)) + coefs = if collection_type == "vector" + F[0, 1, 0, 1, 1] + else + F.((0, 1, 0, 1, 1)) + end + let o = one(F), x = one(F), coefs = coefs + if _is_op_to(op) + alloc_test(() -> op(o, evalpoly, x, coefs), byte_cnt) + else + alloc_test(() -> op(evalpoly, x, coefs), byte_cnt) end end + return end end # TestEvalPoly + +TestEvalPoly.runtests() From db90c79a91da95df0ed8819ef6d92946c9fe4669 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 1 Apr 2026 16:50:54 +1300 Subject: [PATCH 4/4] Update --- test/test_broadcast.jl | 15 ++++----------- test/test_evalpoly.jl | 11 ++--------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/test/test_broadcast.jl b/test/test_broadcast.jl index 7b1e4a68..813a3cea 100644 --- a/test/test_broadcast.jl +++ b/test/test_broadcast.jl @@ -18,13 +18,6 @@ function runtests() return end -function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} - f() # compile - measured_allocations = @allocated f() - @test measured_allocations <= expected_upper_bound - return -end - function test_Int() a = [1, 2] b = 3 @@ -33,8 +26,8 @@ function test_Int() @test a == [4, 5] # Need to have immutable structs that contain references to be allocated on # the stack: https://github.com/JuliaLang/julia/pull/33886 - alloc_test(() -> MA.broadcast!!(+, a, b), 0) - alloc_test(() -> MA.broadcast!!(+, a, c), 0) + @test @allocated(MA.broadcast!!(+, a, b)) == 0 + @test @allocated(MA.broadcast!!(+, a, c)) == 0 return end @@ -51,8 +44,8 @@ function test_BigInt() # FIXME This should not allocate but I couldn't figure out where these # allocations come from. n = 6 * @allocated(BigInt(1)) - alloc_test(() -> MA.broadcast!!(+, a, b), n) - alloc_test(() -> MA.broadcast!!(+, a, c), 0) + @test @allocated(MA.broadcast!!(+, a, b)) <= n + @test @allocated(MA.broadcast!!(+, a, c)) == 0 return end diff --git a/test/test_evalpoly.jl b/test/test_evalpoly.jl index 47a201ae..df539048 100644 --- a/test/test_evalpoly.jl +++ b/test/test_evalpoly.jl @@ -18,13 +18,6 @@ function runtests() return end -function alloc_test(f::F, expected_upper_bound::Integer) where {F<:Function} - f() # compile - measured_allocations = @allocated f() - @test measured_allocations <= expected_upper_bound - return -end - _is_op_to(::Any) = false _is_op_to(::Union{typeof(MA.operate_to!),typeof(MA.operate_to!!)}) = true @@ -98,9 +91,9 @@ function _test_evalpoly(op, ::Type{F}, collection_type) where {F} end let o = one(F), x = one(F), coefs = coefs if _is_op_to(op) - alloc_test(() -> op(o, evalpoly, x, coefs), byte_cnt) + @test @allocated(op(o, evalpoly, x, coefs)) <= byte_cnt else - alloc_test(() -> op(evalpoly, x, coefs), byte_cnt) + @test @allocated(op(evalpoly, x, coefs)) <= byte_cnt end end return