From 565dcd30bb9ce34f76ec27091236fe276498c53c Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Mon, 2 Jun 2025 21:08:00 +0530 Subject: [PATCH] Move testhelpers over from julia repo --- test/adjtrans.jl | 7 +- test/bidiag.jl | 15 +- test/cholesky.jl | 5 +- test/dense.jl | 8 +- test/diagonal.jl | 13 +- test/generic.jl | 13 +- test/hessenberg.jl | 8 +- test/matmul.jl | 5 +- test/special.jl | 6 +- test/structuredbroadcast.jl | 6 +- test/symmetric.jl | 9 +- test/testhelpers/DualNumbers.jl | 46 ++ test/testhelpers/FillArrays.jl | 66 +++ test/testhelpers/Furlongs.jl | 110 ++++ test/testhelpers/ImmutableArrays.jl | 31 + test/testhelpers/InfiniteArrays.jl | 53 ++ test/testhelpers/OffsetArrays.jl | 852 ++++++++++++++++++++++++++++ test/testhelpers/Quaternions.jl | 66 +++ test/testhelpers/SizedArrays.jl | 106 ++++ test/testhelpers/StructArrays.jl | 39 ++ test/triangular.jl | 9 +- test/tridiag.jl | 15 +- test/uniformscaling.jl | 8 +- test/unitful.jl | 6 +- 24 files changed, 1446 insertions(+), 56 deletions(-) create mode 100644 test/testhelpers/DualNumbers.jl create mode 100644 test/testhelpers/FillArrays.jl create mode 100644 test/testhelpers/Furlongs.jl create mode 100644 test/testhelpers/ImmutableArrays.jl create mode 100644 test/testhelpers/InfiniteArrays.jl create mode 100644 test/testhelpers/OffsetArrays.jl create mode 100644 test/testhelpers/Quaternions.jl create mode 100644 test/testhelpers/SizedArrays.jl create mode 100644 test/testhelpers/StructArrays.jl diff --git a/test/adjtrans.jl b/test/adjtrans.jl index adf6d9a7..3a28d0b1 100644 --- a/test/adjtrans.jl +++ b/test/adjtrans.jl @@ -6,12 +6,13 @@ isdefined(Main, :pruned_old_LA) || @eval Main include("prune_old_LA.jl") using Test, LinearAlgebra -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") -isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) +isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($TESTHELPERS, "OffsetArrays.jl")) using .Main.OffsetArrays -isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "ImmutableArrays.jl")) +isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($TESTHELPERS, "ImmutableArrays.jl")) using .Main.ImmutableArrays @testset "Adjoint and Transpose inner constructor basics" begin diff --git a/test/bidiag.jl b/test/bidiag.jl index bccaedf5..f4a89cf4 100644 --- a/test/bidiag.jl +++ b/test/bidiag.jl @@ -7,24 +7,25 @@ isdefined(Main, :pruned_old_LA) || @eval Main include("prune_old_LA.jl") using Test, LinearAlgebra, Random using LinearAlgebra: BlasReal, BlasFloat -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") -isdefined(Main, :Quaternions) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Quaternions.jl")) +isdefined(Main, :Quaternions) || @eval Main include(joinpath($TESTHELPERS, "Quaternions.jl")) using .Main.Quaternions -isdefined(Main, :InfiniteArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "InfiniteArrays.jl")) +isdefined(Main, :InfiniteArrays) || @eval Main include(joinpath($TESTHELPERS, "InfiniteArrays.jl")) using .Main.InfiniteArrays -isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) +isdefined(Main, :FillArrays) || @eval Main include(joinpath($TESTHELPERS, "FillArrays.jl")) using .Main.FillArrays -isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) +isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($TESTHELPERS, "OffsetArrays.jl")) using .Main.OffsetArrays -isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($TESTHELPERS, "SizedArrays.jl")) using .Main.SizedArrays -isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "ImmutableArrays.jl")) +isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($TESTHELPERS, "ImmutableArrays.jl")) using .Main.ImmutableArrays include("testutils.jl") # test_approx_eq_modphase diff --git a/test/cholesky.jl b/test/cholesky.jl index c03ec3f0..82b6e056 100644 --- a/test/cholesky.jl +++ b/test/cholesky.jl @@ -8,9 +8,10 @@ using Test, LinearAlgebra, Random using LinearAlgebra: BlasComplex, BlasFloat, BlasReal, QRPivoted, PosDefException, RankDeficientException, chkfullrank -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") -isdefined(Main, :Quaternions) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Quaternions.jl")) +isdefined(Main, :Quaternions) || @eval Main include(joinpath($TESTHELPERS, "Quaternions.jl")) using .Main.Quaternions function unary_ops_tests(a, ca, tol; n=size(a, 1)) diff --git a/test/dense.jl b/test/dense.jl index 89bf340b..bd61fba7 100644 --- a/test/dense.jl +++ b/test/dense.jl @@ -8,11 +8,13 @@ using Test, LinearAlgebra, Random using LinearAlgebra: BlasComplex, BlasFloat, BlasReal using Test: GenericArray -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") -isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") + +isdefined(Main, :FillArrays) || @eval Main include(joinpath($TESTHELPERS, "FillArrays.jl")) import Main.FillArrays -isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($TESTHELPERS, "SizedArrays.jl")) using Main.SizedArrays @testset "Check that non-floats are correctly promoted" begin diff --git a/test/diagonal.jl b/test/diagonal.jl index 1c85153f..7e92e81f 100644 --- a/test/diagonal.jl +++ b/test/diagonal.jl @@ -7,21 +7,22 @@ isdefined(Main, :pruned_old_LA) || @eval Main include("prune_old_LA.jl") using Test, LinearAlgebra, Random using LinearAlgebra: BlasFloat, BlasComplex -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") -isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) +isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($TESTHELPERS, "OffsetArrays.jl")) using .Main.OffsetArrays -isdefined(Main, :InfiniteArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "InfiniteArrays.jl")) +isdefined(Main, :InfiniteArrays) || @eval Main include(joinpath($TESTHELPERS, "InfiniteArrays.jl")) using .Main.InfiniteArrays -isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) +isdefined(Main, :FillArrays) || @eval Main include(joinpath($TESTHELPERS, "FillArrays.jl")) using .Main.FillArrays -isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($TESTHELPERS, "SizedArrays.jl")) using .Main.SizedArrays -isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "ImmutableArrays.jl")) +isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($TESTHELPERS, "ImmutableArrays.jl")) using .Main.ImmutableArrays const n=12 # Size of matrix problem to test diff --git a/test/generic.jl b/test/generic.jl index 521beaf4..4fd48a6f 100644 --- a/test/generic.jl +++ b/test/generic.jl @@ -8,21 +8,22 @@ using Test, LinearAlgebra, Random using Test: GenericArray using LinearAlgebra: isbanded -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") -isdefined(Main, :Quaternions) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Quaternions.jl")) +isdefined(Main, :Quaternions) || @eval Main include(joinpath($TESTHELPERS, "Quaternions.jl")) using .Main.Quaternions -isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) +isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($TESTHELPERS, "OffsetArrays.jl")) using .Main.OffsetArrays -isdefined(Main, :DualNumbers) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "DualNumbers.jl")) +isdefined(Main, :DualNumbers) || @eval Main include(joinpath($TESTHELPERS, "DualNumbers.jl")) using .Main.DualNumbers -isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) +isdefined(Main, :FillArrays) || @eval Main include(joinpath($TESTHELPERS, "FillArrays.jl")) using .Main.FillArrays -isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($TESTHELPERS, "SizedArrays.jl")) using .Main.SizedArrays Random.seed!(123) diff --git a/test/hessenberg.jl b/test/hessenberg.jl index bf2a725f..13159d98 100644 --- a/test/hessenberg.jl +++ b/test/hessenberg.jl @@ -6,11 +6,13 @@ isdefined(Main, :pruned_old_LA) || @eval Main include("prune_old_LA.jl") using Test, LinearAlgebra, Random -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") -isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") + +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($TESTHELPERS, "SizedArrays.jl")) using .Main.SizedArrays -isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "ImmutableArrays.jl")) +isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($TESTHELPERS, "ImmutableArrays.jl")) using .Main.ImmutableArrays # for tuple tests below diff --git a/test/matmul.jl b/test/matmul.jl index d9635123..6f4d9da3 100644 --- a/test/matmul.jl +++ b/test/matmul.jl @@ -8,9 +8,10 @@ using Base: rtoldefault using Test, LinearAlgebra, Random using LinearAlgebra: mul!, Symmetric, Hermitian -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") -isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($TESTHELPERS, "SizedArrays.jl")) using .Main.SizedArrays ## Test Julia fallbacks to BLAS routines diff --git a/test/special.jl b/test/special.jl index 64d5d706..8cfb6cdf 100644 --- a/test/special.jl +++ b/test/special.jl @@ -7,8 +7,10 @@ isdefined(Main, :pruned_old_LA) || @eval Main include("prune_old_LA.jl") using Test, LinearAlgebra, Random using LinearAlgebra: rmul!, BandIndex -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") -isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") + +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($TESTHELPERS, "SizedArrays.jl")) using .Main.SizedArrays n= 10 #Size of matrix to test diff --git a/test/structuredbroadcast.jl b/test/structuredbroadcast.jl index 5e6a68d7..b523a494 100644 --- a/test/structuredbroadcast.jl +++ b/test/structuredbroadcast.jl @@ -6,8 +6,10 @@ isdefined(Main, :pruned_old_LA) || @eval Main include("prune_old_LA.jl") using Test, LinearAlgebra -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") -isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") + +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($TESTHELPERS, "SizedArrays.jl")) using .Main.SizedArrays @testset "broadcast[!] over combinations of scalars, structured matrices, and dense vectors/matrices" begin diff --git a/test/symmetric.jl b/test/symmetric.jl index 3f1397c0..716f6571 100644 --- a/test/symmetric.jl +++ b/test/symmetric.jl @@ -6,15 +6,16 @@ isdefined(Main, :pruned_old_LA) || @eval Main include("prune_old_LA.jl") using Test, LinearAlgebra, Random -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") -isdefined(Main, :Quaternions) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Quaternions.jl")) +isdefined(Main, :Quaternions) || @eval Main include(joinpath($TESTHELPERS, "Quaternions.jl")) using .Main.Quaternions -isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($TESTHELPERS, "SizedArrays.jl")) using .Main.SizedArrays -isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "ImmutableArrays.jl")) +isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($TESTHELPERS, "ImmutableArrays.jl")) using .Main.ImmutableArrays Random.seed!(1010) diff --git a/test/testhelpers/DualNumbers.jl b/test/testhelpers/DualNumbers.jl new file mode 100644 index 00000000..5c481aef --- /dev/null +++ b/test/testhelpers/DualNumbers.jl @@ -0,0 +1,46 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module DualNumbers + +export Dual + +# Dual numbers type with minimal interface +# example of a (real) number type that subtypes Number, but not Real. +# Can be used to test generic linear algebra functions. + +struct Dual{T<:Real} <: Number + val::T + eps::T +end +Base.:+(x::Dual, y::Dual) = Dual(x.val + y.val, x.eps + y.eps) +Base.:-(x::Dual, y::Dual) = Dual(x.val - y.val, x.eps - y.eps) +Base.:*(x::Dual, y::Dual) = Dual(x.val * y.val, x.eps * y.val + y.eps * x.val) +Base.:*(x::Number, y::Dual) = Dual(x*y.val, x*y.eps) +Base.:*(x::Dual, y::Number) = Dual(x.val*y, x.eps*y) +Base.:/(x::Dual, y::Dual) = Dual(x.val / y.val, (x.eps*y.val - x.val*y.eps)/(y.val*y.val)) + +Base.:(==)(x::Dual, y::Dual) = x.val == y.val && x.eps == y.eps + +Base.promote_rule(::Type{Dual{T}}, ::Type{T}) where {T} = Dual{T} +Base.promote_rule(::Type{Dual{T}}, ::Type{S}) where {T,S<:Real} = Dual{promote_type(T, S)} +Base.promote_rule(::Type{Dual{T}}, ::Type{Dual{S}}) where {T,S} = Dual{promote_type(T, S)} + +Base.convert(::Type{Dual{T}}, x::Dual{T}) where {T} = x +Base.convert(::Type{Dual{T}}, x::Dual) where {T} = Dual(convert(T, x.val), convert(T, x.eps)) +Base.convert(::Type{Dual{T}}, x::Real) where {T} = Dual(convert(T, x), zero(T)) + +Base.float(x::Dual) = Dual(float(x.val), float(x.eps)) +# the following two methods are needed for normalize (to check for potential overflow) +Base.typemax(x::Dual) = Dual(typemax(x.val), zero(x.eps)) +Base.prevfloat(x::Dual{<:AbstractFloat}) = prevfloat(x.val) + +Base.abs2(x::Dual) = x*x +Base.abs(x::Dual) = sqrt(abs2(x)) +Base.sqrt(x::Dual) = Dual(sqrt(x.val), x.eps/(2sqrt(x.val))) + +Base.isless(x::Dual, y::Dual) = x.val < y.val +Base.isless(x::Real, y::Dual) = x < y.val +Base.isinf(x::Dual) = isinf(x.val) & isfinite(x.eps) +Base.real(x::Dual) = x # since we currently only consider Dual{<:Real} + +end # module diff --git a/test/testhelpers/FillArrays.jl b/test/testhelpers/FillArrays.jl new file mode 100644 index 00000000..d3b8d74d --- /dev/null +++ b/test/testhelpers/FillArrays.jl @@ -0,0 +1,66 @@ +module FillArrays + +struct Fill{T, N, S<:NTuple{N,Integer}} <: AbstractArray{T,N} + value::T + size::S +end + +Fill(v, size::Vararg{Integer}) = Fill(v, size) + +Base.size(F::Fill) = F.size + +Base.copy(F::Fill) = F + +Base.AbstractArray{T,N}(F::Fill{<:Any,N}) where {T,N} = Fill(T(F.value), F.size) + +@inline getindex_value(F::Fill) = F.value + +@inline function Base.getindex(F::Fill{<:Any,N}, i::Vararg{Int,N}) where {N} + @boundscheck checkbounds(F, i...) + getindex_value(F) +end + +@inline function Base.setindex!(F::Fill, v, k::Integer) + @boundscheck checkbounds(F, k) + v == getindex_value(F) || throw(ArgumentError("Cannot setindex! to $v for a Fill with value $(getindex_value(F)).")) + F +end + +@inline function Base.fill!(F::Fill, v) + v == getindex_value(F) || throw(ArgumentError("Cannot fill! with $v a Fill with value $(getindex_value(F)).")) + F +end + +Base.zero(F::Fill) = Fill(zero(F.value), size(F)) + +Base.show(io::IO, F::Fill) = print(io, "Fill($(F.value), $(F.size))") +Base.show(io::IO, ::MIME"text/plain", F::Fill) = show(io, F) + +_first_or_one(t::Tuple) = t[1] +_first_or_one(t::Tuple{}) = 1 + +_match_size(sz::Tuple{}, inner::Tuple{}, outer::Tuple{}) = () +function _match_size(sz::Tuple, inner::Tuple, outer::Tuple) + t1 = (_first_or_one(sz), _first_or_one(inner), _first_or_one(outer)) + t2 = _match_size(sz[2:end], inner[2:end], outer[2:end]) + (t1, t2...) +end + +function _repeat_size(sz::Tuple, inner::Tuple, outer::Tuple) + t = _match_size(sz, inner, outer) + map(*, getindex.(t, 1), getindex.(t, 2), getindex.(t, 3)) +end + +function Base.repeat(A::Fill; inner=ntuple(x->1, ndims(A)), outer=ntuple(x->1, ndims(A))) + Base.require_one_based_indexing(A) + length(inner) >= ndims(A) || + throw(ArgumentError("number of inner repetitions $(length(inner)) cannot be "* + "less than number of dimensions of input array $(ndims(A))")) + length(outer) >= ndims(A) || + throw(ArgumentError("number of outer repetitions $(length(outer)) cannot be "* + "less than number of dimensions of input array $(ndims(A))")) + sz = _repeat_size(size(A), Tuple(inner), Tuple(outer)) + Fill(getindex_value(A), sz) +end + +end diff --git a/test/testhelpers/Furlongs.jl b/test/testhelpers/Furlongs.jl new file mode 100644 index 00000000..3ddf42bf --- /dev/null +++ b/test/testhelpers/Furlongs.jl @@ -0,0 +1,110 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module Furlongs + +export Furlong + +# Here we implement a minimal dimensionful type Furlong, which is used +# to test dimensional correctness of various functions in Base. + +# represents a quantity in furlongs^p +struct Furlong{p,T<:Number} <: Number + val::T + Furlong{p,T}(v::Number) where {p,T} = new(v) +end +Furlong(x::T) where {T<:Number} = Furlong{1,T}(x) +Furlong(x::Furlong) = x +(::Type{T})(x::Furlong{0}) where {T<:Number} = T(x.val)::T +(::Type{T})(x::Furlong{0}) where {T<:Furlong{0}} = T(x.val)::T +(::Type{T})(x::Furlong{0}) where {T<:Furlong} = typeassert(x, T) +Furlong{p}(v::Number) where {p} = Furlong{p,typeof(v)}(v) +Furlong{p}(x::Furlong{q}) where {p,q} = (typeassert(x, Furlong{p}); Furlong{p,typeof(x.val)}(x.val)) +Furlong{p,T}(x::Furlong{q}) where {T,p,q} = (typeassert(x, Furlong{p}); Furlong{p,T}(T(x.val))) + +Base.promote_rule(::Type{Furlong{p,T}}, ::Type{Furlong{p,S}}) where {p,T,S} = + Furlong{p,promote_type(T,S)} +Base.promote_rule(::Type{Furlong{0,T}}, ::Type{S}) where {T,S<:Union{Real,Complex}} = + Furlong{0,promote_type(T,S)} +# only Furlong{0} forms a ring and isa Number +Base.convert(::Type{T}, y::Number) where {T<:Furlong{0}} = T(y)::T +Base.convert(::Type{Furlong}, y::Number) = Furlong{0}(y) +Base.convert(::Type{Furlong{<:Any,T}}, y::Number) where {T<:Number} = Furlong{0,T}(y) +Base.convert(::Type{T}, y::Number) where {T<:Furlong} = typeassert(y, T) # throws, since cannot convert a Furlong{0} to a Furlong{p} +# other Furlong{p} form a group +Base.convert(::Type{T}, y::Furlong) where {T<:Furlong{0}} = T(y)::T +Base.convert(::Type{Furlong}, y::Furlong) = y +Base.convert(::Type{Furlong{<:Any,T}}, y::Furlong{p}) where {p,T<:Number} = Furlong{p,T}(y) +Base.convert(::Type{T}, y::Furlong) where {T<:Furlong} = T(y)::T + +Base.one(::Furlong{p,T}) where {p,T} = one(T) +Base.one(::Type{Furlong{p,T}}) where {p,T} = one(T) +Base.oneunit(::Furlong{p,T}) where {p,T} = Furlong{p,T}(one(T)) +Base.oneunit(::Type{Furlong{p,T}}) where {p,T} = Furlong{p,T}(one(T)) +Base.zero(::Furlong{p,T}) where {p,T} = Furlong{p,T}(zero(T)) +Base.zero(::Type{Furlong{p,T}}) where {p,T} = Furlong{p,T}(zero(T)) +Base.iszero(x::Furlong) = iszero(x.val) +Base.float(x::Furlong{p}) where {p} = Furlong{p}(float(x.val)) +Base.eps(::Type{Furlong{p,T}}) where {p,T<:AbstractFloat} = Furlong{p}(eps(T)) +Base.eps(::Furlong{p,T}) where {p,T<:AbstractFloat} = eps(Furlong{p,T}) +Base.floatmin(::Type{Furlong{p,T}}) where {p,T<:AbstractFloat} = Furlong{p}(floatmin(T)) +Base.floatmin(::Furlong{p,T}) where {p,T<:AbstractFloat} = floatmin(Furlong{p,T}) +Base.floatmax(::Type{Furlong{p,T}}) where {p,T<:AbstractFloat} = Furlong{p}(floatmax(T)) +Base.floatmax(::Furlong{p,T}) where {p,T<:AbstractFloat} = floatmax(Furlong{p,T}) +Base.conj(x::Furlong{p,T}) where {p,T} = Furlong{p,T}(conj(x.val)) + +# convert Furlong exponent p to a canonical form +canonical_p(p) = isinteger(p) ? Int(p) : Rational{Int}(p) + +Base.abs(x::Furlong{p}) where {p} = Furlong{p}(abs(x.val)) +Base.abs2(x::Furlong{p}) where {p} = Furlong{canonical_p(2p)}(abs2(x.val)) +Base.inv(x::Furlong{p}) where {p} = Furlong{canonical_p(-p)}(inv(x.val)) + +for f in (:isfinite, :isnan, :isreal, :isinf) + @eval Base.$f(x::Furlong) = $f(x.val) +end +for f in (:real,:imag,:complex,:+,:-) + @eval Base.$f(x::Furlong{p}) where {p} = Furlong{p}($f(x.val)) +end + +import Base: +, -, ==, !=, <, <=, isless, isequal, *, /, //, div, rem, mod, ^ +for op in (:+, :-) + @eval function $op(x::Furlong{p}, y::Furlong{p}) where {p} + v = $op(x.val, y.val) + Furlong{p}(v) + end +end +for op in (:(==), :(!=), :<, :<=, :isless, :isequal) + @eval $op(x::Furlong{p}, y::Furlong{p}) where {p} = $op(x.val, y.val)::Bool +end +for (f,op) in ((:_plus,:+),(:_minus,:-),(:_times,:*),(:_div,://)) + @eval function $f(v::T, ::Furlong{p}, ::Union{Furlong{q},Val{q}}) where {T,p,q} + s = $op(p, q) + Furlong{canonical_p(s),T}(v) + end +end +for (op,eop) in ((:*, :_plus), (:/, :_minus), (://, :_minus), (:div, :_minus)) + @eval begin + $op(x::Furlong{p}, y::Furlong{q}) where {p,q} = + $eop($op(x.val, y.val),x,y) + $op(x::Furlong{p}, y::S) where {p,S<:Number} = $op(x,Furlong{0,S}(y)) + $op(x::S, y::Furlong{p}) where {p,S<:Number} = $op(Furlong{0,S}(x),y) + end +end +# to fix an ambiguity +//(x::Furlong, y::Complex) = x // Furlong{0,typeof(y)}(y) +for op in (:rem, :mod) + @eval begin + $op(x::Furlong{p}, y::Furlong) where {p} = Furlong{p}($op(x.val, y.val)) + $op(x::Furlong{p}, y::Number) where {p} = Furlong{p}($op(x.val, y)) + end +end +Base.sqrt(x::Furlong) = _div(sqrt(x.val), x, Val(2)) +Base.muladd(x::Furlong, y::Furlong, z::Furlong) = x*y + z +Base.muladd(x::Furlong, y::Number, z::Number) = x*y + z +Base.muladd(x::Furlong, y::Furlong, z::Number) = x*y + z +Base.muladd(x::Number, y::Furlong, z::Number) = x*y + z +Base.muladd(x::Number, y::Number, z::Furlong) = x*y + z +Base.muladd(x::Number, y::Furlong, z::Furlong) = x*y + z +Base.muladd(x::Furlong, y::Number, z::Furlong) = x*y + z + +end diff --git a/test/testhelpers/ImmutableArrays.jl b/test/testhelpers/ImmutableArrays.jl new file mode 100644 index 00000000..8f2d23be --- /dev/null +++ b/test/testhelpers/ImmutableArrays.jl @@ -0,0 +1,31 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# ImmutableArrays (arrays that implement getindex but not setindex!) + +# This test file defines an array wrapper that is immutable. It can be used to +# test the action of methods on immutable arrays. + +module ImmutableArrays + +export ImmutableArray + +"An immutable wrapper type for arrays." +struct ImmutableArray{T,N,A<:AbstractArray} <: AbstractArray{T,N} + data::A +end + +ImmutableArray(data::AbstractArray{T,N}) where {T,N} = ImmutableArray{T,N,typeof(data)}(data) + +# Minimal AbstractArray interface +Base.size(A::ImmutableArray) = size(A.data) +Base.size(A::ImmutableArray, d) = size(A.data, d) +Base.getindex(A::ImmutableArray, i...) = getindex(A.data, i...) + +# The immutable array remains immutable after conversion to AbstractArray +AbstractArray{T}(A::ImmutableArray) where {T} = ImmutableArray(AbstractArray{T}(A.data)) +AbstractArray{T,N}(A::ImmutableArray{S,N}) where {S,T,N} = ImmutableArray(AbstractArray{T,N}(A.data)) + +Base.copy(A::ImmutableArray) = ImmutableArray(copy(A.data)) +Base.zero(A::ImmutableArray) = ImmutableArray(zero(A.data)) + +end diff --git a/test/testhelpers/InfiniteArrays.jl b/test/testhelpers/InfiniteArrays.jl new file mode 100644 index 00000000..a5f77356 --- /dev/null +++ b/test/testhelpers/InfiniteArrays.jl @@ -0,0 +1,53 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# InfiniteArrays (arrays with infinite size) + +# This test file is designed to exercise support for generic sizing, +# even though infinite arrays aren't implemented in Base. + +module InfiniteArrays + +export OneToInf, Infinity + +""" + Infinity() + +represents infinite cardinality. Note that `Infinity <: Integer` to support +being treated as an index. +""" +struct Infinity <: Integer end + +Base.:(==)(::Infinity, ::Int) = false +Base.:(==)(::Int, ::Infinity) = false +Base.:(<)(::Int, ::Infinity) = true +Base.:(≤)(::Int, ::Infinity) = true +Base.:(<)(::Infinity, ::Int) = false +Base.:(≤)(::Infinity, ::Int) = false +Base.:(≤)(::Infinity, ::Infinity) = true +Base.:(-)(::Infinity, ::Int) = Infinity() +Base.:(+)(::Infinity, ::Int) = Infinity() +Base.:(:)(::Infinity, ::Infinity) = 1:0 +Base.max(::Infinity, ::Int) = Infinity() +Base.max(::Int, ::Infinity) = Infinity() + +""" + OneToInf(n) + +Define an `AbstractInfUnitRange` that behaves like `1:∞`, with the added +distinction that the limits are guaranteed (by the type system) to +be 1 and ∞. +""" +struct OneToInf{T<:Integer} <: Base.AbstractOneTo{T} end + +OneToInf() = OneToInf{Int}() + +Base.axes(r::OneToInf) = (r,) +Base.size(r::OneToInf) = (Infinity(),) +Base.first(r::OneToInf{T}) where {T} = oneunit(T) +Base.length(r::OneToInf) = Infinity() +Base.last(r::OneToInf) = Infinity() +Base.unitrange(r::OneToInf) = r +Base.oneto(::Infinity) = OneToInf() +Base.unchecked_oneto(::Infinity) = OneToInf() + +end diff --git a/test/testhelpers/OffsetArrays.jl b/test/testhelpers/OffsetArrays.jl new file mode 100644 index 00000000..5acaa880 --- /dev/null +++ b/test/testhelpers/OffsetArrays.jl @@ -0,0 +1,852 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# OffsetArrays (arrays with indexing that doesn't start at 1) + +# This test file is designed to exercise support for generic indexing, +# even though offset arrays aren't implemented in Base. + +# OffsetArrays v1.15.0 +# No compat patch and docstrings +module OffsetArrays + +using Base: tail, @propagate_inbounds +using Base: IdentityUnitRange + +export OffsetArray, OffsetMatrix, OffsetVector + +const IIUR = IdentityUnitRange{<:AbstractUnitRange{<:Integer}} + +######################################################################################################## +# axes.jl +######################################################################################################## + +struct IdOffsetRange{T<:Integer,I<:AbstractUnitRange{T}} <: AbstractUnitRange{T} + parent::I + offset::T + + function IdOffsetRange{T,I}(r::I, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}} + _bool_check(T, r, offset) + new{T,I}(r, offset) + end + + #= This method is necessary to avoid a StackOverflowError in IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer). + The type signature in that method is more specific than IdOffsetRange{T,I}(r::I, offset::T), + so it ends up calling itself if I <: IdOffsetRange. + =# + function IdOffsetRange{T,IdOffsetRange{T,I}}(r::IdOffsetRange{T,I}, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}} + _bool_check(T, r, offset) + new{T,IdOffsetRange{T,I}}(r, offset) + end +end + +function _bool_check(::Type{Bool}, r, offset) + # disallow the construction of IdOffsetRange{Bool, UnitRange{Bool}}(true:true, true) + if offset && (first(r) || last(r)) + throw(ArgumentError("values = $r and offset = $offset can not produce a boolean range")) + end + return nothing +end +_bool_check(::Type, r, offset) = nothing + +# Construction/coercion from arbitrary AbstractUnitRanges +function IdOffsetRange{T,I}(r::AbstractUnitRange, offset::Integer = 0) where {T<:Integer,I<:AbstractUnitRange{T}} + rc, o = offset_coerce(I, r) + return IdOffsetRange{T,I}(rc, convert(T, o+offset)::T) +end +function IdOffsetRange{T}(r::AbstractUnitRange, offset::Integer = 0) where T<:Integer + rc = convert(AbstractUnitRange{T}, r)::AbstractUnitRange{T} + return IdOffsetRange{T,typeof(rc)}(rc, convert(T, offset)::T) +end +IdOffsetRange(r::AbstractUnitRange{T}, offset::Integer = 0) where T<:Integer = + IdOffsetRange{T,typeof(r)}(r, convert(T, offset)::T) + +# Coercion from other IdOffsetRanges +IdOffsetRange{T,I}(r::IdOffsetRange{T,I}) where {T<:Integer,I<:AbstractUnitRange{T}} = r +function IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer = 0) where {T<:Integer,I<:AbstractUnitRange{T}} + rc, offset_rc = offset_coerce(I, r.parent) + return IdOffsetRange{T,I}(rc, convert(T, r.offset + offset + offset_rc)::T) +end +IdOffsetRange{T}(r::IdOffsetRange{T}) where {T<:Integer} = r +function IdOffsetRange{T}(r::IdOffsetRange, offset::Integer = 0) where T<:Integer + return IdOffsetRange{T}(r.parent, r.offset + offset) +end +IdOffsetRange(r::IdOffsetRange) = r + +# Constructor to make `show` round-trippable +# try to preserve typeof(values) if the indices are known to be 1-based +_subtractindexoffset(values, indices::Union{Base.OneTo, IdentityUnitRange{<:Base.OneTo}}, offset) = values +_subtractindexoffset(values, indices, offset) = _subtractoffset(values, offset) +function IdOffsetRange(; values::AbstractUnitRange{<:Integer}, indices::AbstractUnitRange{<:Integer}) + length(values) == length(indices) || throw(ArgumentError("values and indices must have the same length")) + values_nooffset = no_offset_view(values) + offset = first(indices) - 1 + values_minus_offset = _subtractindexoffset(values_nooffset, indices, offset) + return IdOffsetRange(values_minus_offset, offset) +end + +# Conversions to an AbstractUnitRange{Int} (and to an OrdinalRange{Int,Int} on Julia v"1.6") are necessary +# to evaluate CartesianIndices for BigInt ranges, as their axes are also BigInt ranges +Base.AbstractUnitRange{T}(r::IdOffsetRange) where {T<:Integer} = IdOffsetRange{T}(r) + +# TODO: uncomment these when Julia is ready +# # Conversion preserves both the values and the indices, throwing an InexactError if this +# # is not possible. +# Base.convert(::Type{IdOffsetRange{T,I}}, r::IdOffsetRange{T,I}) where {T<:Integer,I<:AbstractUnitRange{T}} = r +# Base.convert(::Type{IdOffsetRange{T,I}}, r::IdOffsetRange) where {T<:Integer,I<:AbstractUnitRange{T}} = +# IdOffsetRange{T,I}(convert(I, r.parent), r.offset) +# Base.convert(::Type{IdOffsetRange{T,I}}, r::AbstractUnitRange) where {T<:Integer,I<:AbstractUnitRange{T}} = +# IdOffsetRange{T,I}(convert(I, r), 0) + +offset_coerce(::Type{Base.OneTo{T}}, r::Base.OneTo) where T<:Integer = convert(Base.OneTo{T}, r), 0 +function offset_coerce(::Type{Base.OneTo{T}}, r::AbstractUnitRange) where T<:Integer + o = first(r) - 1 + return Base.OneTo{T}(last(r) - o), o +end +# function offset_coerce(::Type{Base.OneTo{T}}, r::IdOffsetRange) where T<:Integer +# rc, o = offset_coerce(Base.OneTo{T}, r.parent) + +# Fallback, specialize this method if `convert(I, r)` doesn't do what you need +offset_coerce(::Type{I}, r::AbstractUnitRange) where I<:AbstractUnitRange = + convert(I, r)::I, 0 + +@inline Base.parent(r::IdOffsetRange) = r.parent +@inline Base.axes(r::IdOffsetRange) = (Base.axes1(r),) +@inline Base.axes1(r::IdOffsetRange) = IdOffsetRange(Base.axes1(r.parent), r.offset) +@inline Base.unsafe_indices(r::IdOffsetRange) = (Base.axes1(r),) +@inline Base.length(r::IdOffsetRange) = length(r.parent) +@inline Base.isempty(r::IdOffsetRange) = isempty(r.parent) +#= We specialize on reduced_indices to work around cases where the parent axis type doesn't +support reduced_index, but the axes do support reduced_indices +The difference is that reduced_index expects the axis type to remain unchanged, +which may not always be possible, eg. for statically sized axes +See https://github.com/JuliaArrays/OffsetArrays.jl/issues/204 +=# +function Base.reduced_indices(inds::Tuple{IdOffsetRange, Vararg{IdOffsetRange}}, d::Int) + parents_reduced = Base.reduced_indices(map(parent, inds), d) + ntuple(i -> IdOffsetRange(parents_reduced[i], inds[i].offset), Val(length(inds))) +end +Base.reduced_index(i::IdOffsetRange) = typeof(i)(first(i):first(i)) +# Workaround for #92 on Julia < 1.4 +Base.reduced_index(i::IdentityUnitRange{<:IdOffsetRange}) = typeof(i)(first(i):first(i)) +for f in [:first, :last] + # coerce the type to deal with values that get promoted on addition (eg. Bool) + @eval @inline Base.$f(r::IdOffsetRange) = eltype(r)($f(r.parent) + r.offset) +end + +# Iteration for an IdOffsetRange +@inline Base.iterate(r::IdOffsetRange, i...) = _iterate(r, i...) +# In general we iterate over the parent term by term and add the offset. +# This might have some performance degradation when coupled with bounds-checking +# See https://github.com/JuliaArrays/OffsetArrays.jl/issues/214 +@inline function _iterate(r::IdOffsetRange, i...) + ret = iterate(r.parent, i...) + ret === nothing && return nothing + return (eltype(r)(ret[1] + r.offset), ret[2]) +end +# Base.OneTo(n) is known to be exactly equivalent to the range 1:n, +# and has no specialized iteration defined for it, +# so we may add the offset to the range directly and iterate over the result +# This gets around the performance issue described in issue #214 +# We use the helper function _addoffset to evaluate the range instead of broadcasting +# just in case this makes it easy for the compiler. +@inline _iterate(r::IdOffsetRange{<:Integer, <:Base.OneTo}, i...) = iterate(_addoffset(r.parent, r.offset), i...) + +@inline function Base.getindex(r::IdOffsetRange, i::Integer) + i isa Bool && throw(ArgumentError("invalid index: $i of type Bool")) + @boundscheck checkbounds(r, i) + @inbounds eltype(r)(r.parent[i - r.offset] + r.offset) +end + +# Logical indexing following https://github.com/JuliaLang/julia/pull/31829 +#= Helper function to perform logical indxeing for boolean ranges +The code implemented is a branch-free version of the following: + + range(first(s) ? first(r) : last(r), length=Int(last(s))) + +See https://github.com/JuliaArrays/OffsetArrays.jl/pull/224#discussion_r595635143 + +Logical indexing does not preserve indices, unlike other forms of vector indexing +=# +@inline function _getindex(r, s::AbstractUnitRange{Bool}) + range(first(r) * first(s) + last(r) * !first(s), length=Int(last(s))) +end +@inline function _getindex(r, s::StepRange{Bool}) + range(first(r) * first(s) + last(r) * !first(s), step = oneunit(step(s)), length=Int(last(s))) +end +@inline function _getindex(r, s::AbstractUnitRange) + @inbounds rs = r.parent[_subtractoffset(s, r.offset)] .+ r.offset + _indexedby(rs, axes(s)) +end +@inline function _getindex(r, s::StepRange) + rs = @inbounds r.parent[s .- r.offset] .+ r.offset + _indexedby(rs, axes(s)) +end + +for T in [:AbstractUnitRange, :StepRange] + @eval @inline function Base.getindex(r::IdOffsetRange, s::$T{<:Integer}) + @boundscheck checkbounds(r, s) + return _getindex(r, s) + end +end + +# These methods are necessary to avoid ambiguity +for R in [:IIUR, :IdOffsetRange] + @eval @inline function Base.getindex(r::IdOffsetRange, s::$R) + @boundscheck checkbounds(r, s) + return _getindex(r, s) + end +end + +# offset-preserve broadcasting +Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(-), r::IdOffsetRange, x::Integer) = + IdOffsetRange(r.parent .- x, r.offset) +Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), r::IdOffsetRange, x::Integer) = + IdOffsetRange(r.parent .+ x, r.offset) +Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), x::Integer, r::IdOffsetRange) = + IdOffsetRange(x .+ r.parent, r.offset) +Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(big), r::IdOffsetRange) = + IdOffsetRange(big.(r.parent), r.offset) + +Base.show(io::IO, r::IdOffsetRange) = print(io, IdOffsetRange, "(values=",first(r), ':', last(r),", indices=",first(eachindex(r)),':',last(eachindex(r)), ")") + +# Optimizations +@inline Base.checkindex(::Type{Bool}, inds::IdOffsetRange, i::Real) = Base.checkindex(Bool, inds.parent, i - inds.offset) +Base._firstslice(i::IdOffsetRange) = IdOffsetRange(Base._firstslice(i.parent), i.offset) + +######################################################################################################## +# origin.jl +######################################################################################################## + +struct Origin{T<:Union{Tuple{Vararg{Int}}, Int}} + index::T +end +Origin(I::Tuple{Vararg{Int}}) = Origin{typeof(I)}(I) +Origin(I::Tuple{Vararg{Number}}) = Origin(map(Int, I)) +Origin(I::CartesianIndex) = Origin(Tuple(I)) +Origin(I::Number...) = Origin(I) +# Origin(0) != Origin((0, )) but they work the same with broadcasting +Origin(n::Number) = Origin{Int}(Int(n)) + +Base.Broadcast.broadcastable(o::Origin) = Ref(o) + +_showidx(index::Integer) = "(" * string(index) * ")" +_showidx(index::Tuple) = string(index) +Base.show(io::IO, o::Origin) = print(io, "Origin", _showidx(o.index)) + +######################################################################################################## +# utils.jl +######################################################################################################## + +### Low-level utilities ### + +_indexoffset(r::AbstractRange) = first(r) - 1 +_indexoffset(i::Integer) = 0 +_indexlength(r::AbstractRange) = length(r) +_indexlength(i::Integer) = Int(i) +_indexlength(i::Colon) = Colon() + +# utility methods used in reshape +# we don't use _indexlength in this to avoid converting the arguments to Int +_checksize(ind::Integer, s) = ind == s +_checksize(ind::AbstractUnitRange, s) = length(ind) == s + +_toaxis(i::Integer) = Base.OneTo(i) +_toaxis(i) = i + +_strip_IdOffsetRange(r::IdOffsetRange) = parent(r) +_strip_IdOffsetRange(r) = r + +_offset(axparent::AbstractUnitRange, ax::AbstractUnitRange) = first(ax) - first(axparent) +_offset(axparent::AbstractUnitRange, ::Union{Integer, Colon}) = 1 - first(axparent) + +_offsets(A::AbstractArray) = map(ax -> first(ax) - 1, axes(A)) +_offsets(A::AbstractArray, B::AbstractArray) = map(_offset, axes(B), axes(A)) + +abstract type AxisConversionStyle end +struct SingleRange <: AxisConversionStyle end +struct TupleOfRanges <: AxisConversionStyle end + +AxisConversionStyle(::Type) = SingleRange() +AxisConversionStyle(::Type{<:CartesianIndices}) = TupleOfRanges() + +_convertTupleAbstractUnitRange(x) = _convertTupleAbstractUnitRange(AxisConversionStyle(typeof(x)), x) +_convertTupleAbstractUnitRange(::SingleRange, x) = (convert(AbstractUnitRange{Int}, x),) +_convertTupleAbstractUnitRange(::TupleOfRanges, x) = convert(Tuple{Vararg{AbstractUnitRange{Int}}}, x) + +_toAbstractUnitRanges(t::Tuple) = (_convertTupleAbstractUnitRange(first(t))..., _toAbstractUnitRanges(tail(t))...) +_toAbstractUnitRanges(::Tuple{}) = () + +# ensure that the indices are consistent in the constructor +_checkindices(A::AbstractArray, indices, label) = _checkindices(ndims(A), indices, label) +function _checkindices(N::Integer, indices, label) + throw_argumenterror(N, indices, label) = throw(ArgumentError(label*" $indices are not compatible with a $(N)D array")) + N == length(indices) || throw_argumenterror(N, indices, label) +end + +@inline _indexedby(r::AbstractVector, ax::Tuple{Any}) = _indexedby(r, ax[1]) +@inline _indexedby(r::AbstractUnitRange{<:Integer}, ::Base.OneTo) = no_offset_view(r) +@inline _indexedby(r::AbstractUnitRange{Bool}, ::Base.OneTo) = no_offset_view(r) +@inline _indexedby(r::AbstractVector, ::Base.OneTo) = no_offset_view(r) +@inline function _indexedby(r::AbstractUnitRange{<:Integer}, ax::AbstractUnitRange) + of = convert(eltype(r), first(ax) - 1) + IdOffsetRange(_subtractoffset(r, of), of) +end +@inline _indexedby(r::AbstractUnitRange{Bool}, ax::AbstractUnitRange) = OffsetArray(r, ax) +@inline _indexedby(r::AbstractVector, ax::AbstractUnitRange) = OffsetArray(r, ax) + +# These functions are equivalent to the broadcasted operation r .- of +# However these ensure that the result is an AbstractRange even if a specific +# broadcasting behavior is not defined for a custom type +@inline _subtractoffset(r::AbstractUnitRange, of) = UnitRange(first(r) - of, last(r) - of) +@inline _subtractoffset(r::AbstractRange, of) = range(first(r) - of, stop = last(r) - of, step = step(r)) + +# similar to _subtractoffset, except these evaluate r .+ of +@inline _addoffset(r::AbstractUnitRange, of) = UnitRange(first(r) + of, last(r) + of) +@inline _addoffset(r::AbstractRange, of) = range(first(r) + of, stop = last(r) + of, step = step(r)) + +_contiguousindexingtype(r::AbstractUnitRange{<:Integer}) = r + +_of_eltype(::Type{T}, M::AbstractArray{T}) where {T} = M +_of_eltype(T, M::AbstractArray) = map(T, M) + +# filter the arguments to reshape to check if there are any ranges +# If not, we may pop the parent array +_filterreshapeinds(t::Tuple{AbstractUnitRange, Vararg{Any}}) = t +_filterreshapeinds(t::Tuple) = _filterreshapeinds(tail(t)) +_filterreshapeinds(t::Tuple{}) = t +_popreshape(A::AbstractArray, ax::Tuple{Vararg{Base.OneTo}}, inds::Tuple{}) = no_offset_view(A) +_popreshape(A::AbstractArray, ax, inds) = A + +######################################################################################################## +# OffsetArrays.jl +######################################################################################################## + +# Technically we know the length of CartesianIndices but we need to convert it first, so here we +# don't put it in OffsetAxisKnownLength. +const OffsetAxisKnownLength = Union{Integer, AbstractUnitRange} +const OffsetAxis = Union{OffsetAxisKnownLength, Colon} +const ArrayInitializer = Union{UndefInitializer, Missing, Nothing} + +## OffsetArray +struct OffsetArray{T,N,AA<:AbstractArray{T,N}} <: AbstractArray{T,N} + parent::AA + offsets::NTuple{N,Int} + @inline function OffsetArray{T, N, AA}(parent::AA, offsets::NTuple{N, Int}; checkoverflow = true) where {T, N, AA<:AbstractArray{T,N}} + # allocation of `map` on tuple is optimized away + checkoverflow && map(overflow_check, axes(parent), offsets) + new{T, N, AA}(parent, offsets) + end +end + +const OffsetVector{T,AA<:AbstractVector{T}} = OffsetArray{T,1,AA} + +const OffsetMatrix{T,AA<:AbstractMatrix{T}} = OffsetArray{T,2,AA} + +# checks if the offset may be added to the range without overflowing +function overflow_check(r::AbstractUnitRange, offset::Integer) + Base.hastypemax(eltype(r)) || return nothing + # This gives some performance boost https://github.com/JuliaLang/julia/issues/33273 + throw_upper_overflow_error(val) = throw(OverflowError("offset should be <= $(typemax(Int) - val) corresponding to the axis $r, received an offset $offset")) + throw_lower_overflow_error(val) = throw(OverflowError("offset should be >= $(typemin(Int) - val) corresponding to the axis $r, received an offset $offset")) + + # With ranges in the picture, first(r) might not necessarily be < last(r) + # we therefore use the min and max of first(r) and last(r) to check for overflow + firstlast_min, firstlast_max = minmax(first(r), last(r)) + + if offset > 0 && firstlast_max > typemax(Int) - offset + throw_upper_overflow_error(firstlast_max) + elseif offset < 0 && firstlast_min < typemin(Int) - offset + throw_lower_overflow_error(firstlast_min) + end + return nothing +end + +# Tuples of integers are treated as offsets +# Empty Tuples are handled here +@inline function OffsetArray(A::AbstractArray, offsets::Tuple{Vararg{Integer}}; kw...) + _checkindices(A, offsets, "offsets") + OffsetArray{eltype(A), ndims(A), typeof(A)}(A, offsets; kw...) +end + +# These methods are necessary to disallow incompatible dimensions for +# the OffsetVector and the OffsetMatrix constructors +for (FT, ND) in ((:OffsetVector, :1), (:OffsetMatrix, :2)) + @eval @inline function $FT(A::AbstractArray{<:Any,$ND}, offsets::Tuple{Vararg{Integer}}; kw...) + _checkindices(A, offsets, "offsets") + OffsetArray{eltype(A), $ND, typeof(A)}(A, offsets; kw...) + end + FTstr = string(FT) + @eval @inline function $FT(A::AbstractArray, offsets::Tuple{Vararg{Integer}}; kw...) + throw(ArgumentError($FTstr*" requires a "*string($ND)*"D array")) + end +end + +## OffsetArray constructors +for FT in (:OffsetArray, :OffsetVector, :OffsetMatrix) + # Nested OffsetArrays may strip off the wrapper and collate the offsets + # empty tuples are handled here + @eval @inline function $FT(A::OffsetArray, offsets::Tuple{Vararg{Int}}; checkoverflow = true) + _checkindices(A, offsets, "offsets") + # ensure that the offsets may be added together without an overflow + checkoverflow && map(overflow_check, axes(A), offsets) + I = map(+, _offsets(A, parent(A)), offsets) + $FT(parent(A), I, checkoverflow = false) + end + @eval @inline function $FT(A::OffsetArray, offsets::Tuple{Integer,Vararg{Integer}}; kw...) + $FT(A, map(Int, offsets); kw...) + end + + # In general, indices get converted to AbstractUnitRanges. + # CartesianIndices{N} get converted to N ranges + @eval @inline function $FT(A::AbstractArray, inds::Tuple{Any,Vararg{Any}}; kw...) + $FT(A, _toAbstractUnitRanges(to_indices(A, axes(A), inds)); kw...) + end + + # convert ranges to offsets + @eval @inline function $FT(A::AbstractArray, inds::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}; kw...) + _checkindices(A, inds, "indices") + # Performance gain by wrapping the error in a function: see https://github.com/JuliaLang/julia/issues/37558 + throw_dimerr(lA, lI) = throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices")) + lA = size(A) + lI = map(length, inds) + lA == lI || throw_dimerr(lA, lI) + $FT(A, map(_offset, axes(A), inds); kw...) + end + + @eval @inline $FT(A::AbstractArray, inds...; kw...) = $FT(A, inds; kw...) + @eval @inline $FT(A::AbstractArray; checkoverflow = false) = $FT(A, ntuple(zero, Val(ndims(A))), checkoverflow = checkoverflow) + + @eval @inline $FT(A::AbstractArray, origin::Origin; checkoverflow = true) = $FT(A, origin.index .- first.(axes(A)); checkoverflow = checkoverflow) +end + +(o::Origin)(A::AbstractArray) = OffsetArray(no_offset_view(A), o) +Origin(A::AbstractArray) = Origin(first.(axes(A))) + +# conversion-related methods +@inline OffsetArray{T}(M::AbstractArray, I...; kw...) where {T} = OffsetArray{T,ndims(M)}(M, I...; kw...) + +@inline function OffsetArray{T,N}(M::AbstractArray{<:Any,N}, I...; kw...) where {T,N} + M2 = _of_eltype(T, M) + OffsetArray{T,N}(M2, I...; kw...) +end +@inline OffsetArray{T,N}(M::OffsetArray{T,N}, I...; kw...) where {T,N} = OffsetArray(M, I...; kw...) +@inline OffsetArray{T,N}(M::AbstractArray{T,N}, I...; kw...) where {T,N} = OffsetArray{T,N,typeof(M)}(M, I...; kw...) + +@inline OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I...; kw...) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, I; kw...) +@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::NTuple{N,Int}; checkoverflow = true) where {T,N,A<:AbstractArray{T,N}} + checkoverflow && map(overflow_check, axes(M), I) + Mv = no_offset_view(M) + MvA = convert(A, Mv)::A + Iof = map(+, _offsets(M), I) + OffsetArray{T,N,A}(MvA, Iof, checkoverflow = false) +end +@inline function OffsetArray{T, N, AA}(parent::AbstractArray{<:Any,N}, offsets::NTuple{N, Integer}; kw...) where {T, N, AA<:AbstractArray{T,N}} + OffsetArray{T, N, AA}(parent, map(Int, offsets)::NTuple{N,Int}; kw...) +end +@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}; kw...) where {T,N,A<:AbstractArray{T,N}} + _checkindices(M, I, "indices") + # Performance gain by wrapping the error in a function: see https://github.com/JuliaLang/julia/issues/37558 + throw_dimerr(lA, lI) = throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices")) + lM = size(M) + lI = map(length, I) + lM == lI || throw_dimerr(lM, lI) + OffsetArray{T,N,A}(M, map(_offset, axes(M), I); kw...) +end +@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple; kw...) where {T,N,A<:AbstractArray{T,N}} + OffsetArray{T,N,A}(M, _toAbstractUnitRanges(to_indices(M, axes(M), I)); kw...) +end +@inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}; kw...) where {T,N,A<:AbstractArray{T,N}} + Mv = no_offset_view(M) + MvA = convert(A, Mv)::A + OffsetArray{T,N,A}(MvA, _offsets(M); kw...) +end +@inline OffsetArray{T,N,A}(M::A; checkoverflow = false) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, ntuple(zero, Val(N)); checkoverflow = checkoverflow) + +Base.convert(::Type{T}, M::AbstractArray) where {T<:OffsetArray} = M isa T ? M : T(M) + +@inline AbstractArray{T,N}(M::OffsetArray{S,N}) where {T,S,N} = OffsetArray{T}(M) + +# array initialization +@inline function OffsetArray{T,N}(init::ArrayInitializer, inds::Tuple{Vararg{OffsetAxisKnownLength}}; kw...) where {T,N} + _checkindices(N, inds, "indices") + AA = Array{T,N}(init, map(_indexlength, inds)) + OffsetArray{T, N, typeof(AA)}(AA, map(_indexoffset, inds); kw...) +end +@inline function OffsetArray{T, N}(init::ArrayInitializer, inds::Tuple; kw...) where {T, N} + OffsetArray{T, N}(init, _toAbstractUnitRanges(inds); kw...) +end +@inline OffsetArray{T,N}(init::ArrayInitializer, inds...; kw...) where {T,N} = OffsetArray{T,N}(init, inds; kw...) + +@inline OffsetArray{T}(init::ArrayInitializer, inds::NTuple{N, OffsetAxisKnownLength}; kw...) where {T,N} = OffsetArray{T,N}(init, inds; kw...) +@inline function OffsetArray{T}(init::ArrayInitializer, inds::Tuple; kw...) where {T} + OffsetArray{T}(init, _toAbstractUnitRanges(inds); kw...) +end +@inline OffsetArray{T}(init::ArrayInitializer, inds...; kw...) where {T} = OffsetArray{T}(init, inds; kw...) + +Base.IndexStyle(::Type{OA}) where {OA<:OffsetArray} = IndexStyle(parenttype(OA)) +parenttype(::Type{OffsetArray{T,N,AA}}) where {T,N,AA} = AA +parenttype(A::OffsetArray) = parenttype(typeof(A)) + +Base.parent(A::OffsetArray) = A.parent + +# TODO: Ideally we would delegate to the parent's broadcasting implementation, but that +# is currently broken in sufficiently many implementation, namely RecursiveArrayTools, DistributedArrays +# and StaticArrays, that it will take concentrated effort to get this working across the ecosystem. +# The goal would be to have `OffsetArray(CuArray) .+ 1 == OffsetArray{CuArray}`. +# Base.Broadcast.BroadcastStyle(::Type{<:OffsetArray{<:Any, <:Any, AA}}) where AA = Base.Broadcast.BroadcastStyle(AA) + +@inline Base.size(A::OffsetArray) = size(parent(A)) +# specializing length isn't necessary, as length(A) = prod(size(A)), +# but specializing length enables constant-propagation for statically sized arrays +# see https://github.com/JuliaArrays/OffsetArrays.jl/pull/304 +@inline Base.length(A::OffsetArray) = length(parent(A)) + +@inline Base.axes(A::OffsetArray) = map(IdOffsetRange, axes(parent(A)), A.offsets) +@inline Base.axes(A::OffsetArray, d) = d <= ndims(A) ? IdOffsetRange(axes(parent(A), d), A.offsets[d]) : IdOffsetRange(axes(parent(A), d)) +@inline Base.axes1(A::OffsetArray{T,0}) where {T} = IdOffsetRange(axes(parent(A), 1)) # we only need to specialize this one + +# Utils to translate a function to the parent while preserving offsets +unwrap(x) = x, identity +unwrap(x::OffsetArray) = parent(x), data -> OffsetArray(data, x.offsets, checkoverflow = false) +function parent_call(f, x) + parent, wrap_offset = unwrap(x) + wrap_offset(f(parent)) +end + +Base.similar(A::OffsetArray, ::Type{T}, dims::Dims) where T = + similar(parent(A), T, dims) +function Base.similar(A::AbstractArray, ::Type{T}, shape::Tuple{OffsetAxisKnownLength,Vararg{OffsetAxisKnownLength}}) where T + # strip IdOffsetRanges to extract the parent range and use it to generate the array + new_shape = map(_strip_IdOffsetRange, shape) + # route through _similar_axes_or_length to avoid a stack overflow if map(_strip_IdOffsetRange, shape) === shape + # This tries to use new_shape directly in similar if similar(A, T, ::typeof(new_shape)) is defined + # If this fails, it calls similar(A, T, map(_indexlength, new_shape)) to use the size along each axis + # to generate the new array + P = _similar_axes_or_length(A, T, new_shape, shape) + return OffsetArray(P, map(_offset, axes(P), shape)) +end +Base.similar(::Type{A}, sz::Tuple{Vararg{Int}}) where {A<:OffsetArray} = similar(Array{eltype(A)}, sz) +function Base.similar(::Type{T}, shape::Tuple{OffsetAxisKnownLength,Vararg{OffsetAxisKnownLength}}) where {T<:AbstractArray} + new_shape = map(_strip_IdOffsetRange, shape) + P = _similar_axes_or_length(T, new_shape, shape) + OffsetArray(P, map(_offset, axes(P), shape)) +end +# Try to use the axes to generate the parent array type +# This is useful if the axes have special meanings, such as with static arrays +# This method is hit if at least one axis provided to similar(A, T, axes) is an IdOffsetRange +# For example this is hit when similar(A::OffsetArray) is called, +# which expands to similar(A, eltype(A), axes(A)) +_similar_axes_or_length(A, T, ax, ::Any) = similar(A, T, ax) +_similar_axes_or_length(AT, ax, ::Any) = similar(AT, ax) +# Handle the general case by resorting to lengths along each axis +# This is hit if none of the axes provided to similar(A, T, axes) are IdOffsetRanges, +# and if similar(A, T, axes::AX) is not defined for the type AX. +# In this case the best that we can do is to create a mutable array of the correct size +_similar_axes_or_length(A, T, ax::I, ::I) where {I} = similar(A, T, map(_indexlength, ax)) +_similar_axes_or_length(AT, ax::I, ::I) where {I} = similar(AT, map(_indexlength, ax)) + +# reshape accepts a single colon +# this method is limited to AbstractUnitRange{<:Integer} to avoid method overwritten errors if Base defines the same, +# see https://github.com/JuliaLang/julia/pull/56850 +Base.reshape(A::AbstractArray, inds::Union{Integer, Colon, AbstractUnitRange{<:Integer}}...) = reshape(A, inds) +function Base.reshape(A::AbstractArray, inds::Tuple{Vararg{OffsetAxis}}) + AR = reshape(no_offset_view(A), map(_indexlength, inds)) + O = OffsetArray(AR, map(_offset, axes(AR), inds)) + return _popreshape(O, axes(AR), _filterreshapeinds(inds)) +end + +# Reshaping OffsetArrays can "pop" the original OffsetArray wrapper and return +# an OffsetArray(reshape(...)) instead of an OffsetArray(reshape(OffsetArray(...))) +# Short-circuit for AbstractVectors if the axes are compatible to get around the Base restriction +# to 1-based vectors +function _reshape(A::AbstractVector, inds::Tuple{OffsetAxis}) + @noinline throw_dimerr(ind::Integer) = throw( + DimensionMismatch("parent has $(size(A,1)) elements, which is incompatible with length $ind")) + @noinline throw_dimerr(ind) = throw( + DimensionMismatch("parent has $(size(A,1)) elements, which is incompatible with indices $ind")) + _checksize(first(inds), size(A,1)) || throw_dimerr(first(inds)) + A +end +_reshape(A, inds) = _reshape2(A, inds) +_reshape2(A, inds) = reshape(A, inds) +# avoid a stackoverflow by relegating to the parent if no_offset_view returns an offsetarray +_reshape2(A::OffsetArray, inds) = reshape(parent(A), inds) +_reshape_nov(A, inds) = _reshape(no_offset_view(A), inds) + +# And for non-offset axes, we can just return a reshape of the parent directly +Base.reshape(A::OffsetArray, inds::Tuple{Integer,Vararg{Integer}}) = _reshape_nov(A, inds) +Base.reshape(A::OffsetArray, inds::Dims) = _reshape_nov(A, inds) + +# permutedims in Base does not preserve axes, and can not be fixed in a non-breaking way +# This is a stopgap solution +Base.permutedims(v::OffsetVector) = reshape(v, (1, axes(v, 1))) + +Base.zero(A::OffsetArray) = parent_call(zero, A) +Base.fill!(A::OffsetArray, x) = parent_call(Ap -> fill!(Ap, x), A) + +## Indexing + +# Note this gets the index of the parent *array*, not the index of the parent *range* +# Here's how one can think about this: +# Δi = i - first(r) +# i′ = first(r.parent) + Δi +# and one obtains the result below. +parentindex(r::IdOffsetRange, i) = i - r.offset + +@propagate_inbounds Base.getindex(A::OffsetArray{<:Any,0}) = A.parent[] + +@inline function Base.getindex(A::OffsetArray{<:Any,N}, I::Vararg{Int,N}) where N + @boundscheck checkbounds(A, I...) + J = map(parentindex, axes(A), I) + @inbounds parent(A)[J...] +end + +@propagate_inbounds Base.getindex(A::OffsetArray{<:Any,N}, c::Vararg{Colon,N}) where N = + parent_call(x -> getindex(x, c...), A) + +# With one Colon we use linear indexing. +# In this case we may forward the index to the parent, as the information about the axes is lost +# The exception to this is with OffsetVectors where the axis information is preserved, +# but that case is handled by getindex(::OffsetArray{<:Any,N}, ::Vararg{Colon,N}) +@propagate_inbounds Base.getindex(A::OffsetArray, c::Colon) = A.parent[:] + +@inline function Base.getindex(A::OffsetVector, i::Int) + @boundscheck checkbounds(A, i) + @inbounds parent(A)[parentindex(Base.axes1(A), i)] +end +@propagate_inbounds Base.getindex(A::OffsetArray, i::Int) = parent(A)[i] + +@inline function Base.setindex!(A::OffsetArray{T,N}, val, I::Vararg{Int,N}) where {T,N} + @boundscheck checkbounds(A, I...) + J = map(parentindex, axes(A), I) + @inbounds parent(A)[J...] = val + A +end + +@inline function Base.setindex!(A::OffsetVector, val, i::Int) + @boundscheck checkbounds(A, i) + @inbounds parent(A)[parentindex(Base.axes1(A), i)] = val + A +end +@propagate_inbounds function Base.setindex!(A::OffsetArray, val, i::Int) + parent(A)[i] = val + A +end + +@inline Base.iterate(a::OffsetArray, i...) = iterate(parent(a), i...) + +Base.in(x, A::OffsetArray) = in(x, parent(A)) +Base.copy(A::OffsetArray) = parent_call(copy, A) + +Base.strides(A::OffsetArray) = strides(parent(A)) +Base.elsize(::Type{OffsetArray{T,N,A}}) where {T,N,A} = Base.elsize(A) +Base.cconvert(P::Type{Ptr{T}}, A::OffsetArray{T}) where {T} = Base.cconvert(P, parent(A)) + +# For fast broadcasting: ref https://discourse.julialang.org/t/why-is-there-a-performance-hit-on-broadcasting-with-offsetarrays/32194 +Base.dataids(A::OffsetArray) = Base.dataids(parent(A)) +Broadcast.broadcast_unalias(dest::OffsetArray, src::OffsetArray) = parent(dest) === parent(src) ? src : Broadcast.unalias(dest, src) + +### Special handling for AbstractRange +const OffsetRange{T} = OffsetVector{T,<:AbstractRange{T}} +const OffsetUnitRange{T} = OffsetVector{T,<:AbstractUnitRange{T}} + +Base.step(a::OffsetRange) = step(parent(a)) + +Base.checkindex(::Type{Bool}, inds::AbstractUnitRange, or::OffsetRange) = Base.checkindex(Bool, inds, parent(or)) + +# Certain special methods for linear indexing with integer ranges (or OffsetRanges) +# These may bypass the default getindex(A, I...) pathway if the parent types permit this +# For example AbstractUnitRanges and Arrays have special linear indexing behavior defined + +# If both the arguments are offset, we may unwrap the indices to call (::OffsetArray)[::AbstractRange{Int}] +@propagate_inbounds function Base.getindex(A::OffsetArray, r::OffsetRange{Int}) + _indexedby(A[parent(r)], axes(r)) +end +# If the indices are offset, we may unwrap them and pass the parent to getindex +@propagate_inbounds function Base.getindex(A::AbstractRange, r::OffsetRange{Int}) + _indexedby(A[parent(r)], axes(r)) +end + +# An OffsetUnitRange might use the rapid getindex(::Array, ::AbstractUnitRange{Int}) for contiguous indexing +@propagate_inbounds function Base.getindex(A::Array, r::OffsetUnitRange{Int}) + B = A[_contiguousindexingtype(parent(r))] + OffsetArray(B, axes(r), checkoverflow = false) +end + +# Linear Indexing of OffsetArrays with AbstractUnitRanges may use the faster contiguous indexing methods +@inline function Base.getindex(A::OffsetArray, r::AbstractUnitRange{Int}) + @boundscheck checkbounds(A, r) + # nD OffsetArrays do not have their linear indices shifted, so we may forward the indices provided to the parent + @inbounds B = parent(A)[_contiguousindexingtype(r)] + _indexedby(B, axes(r)) +end +@inline function Base.getindex(A::OffsetVector, r::AbstractUnitRange{Int}) + @boundscheck checkbounds(A, r) + # OffsetVectors may have their linear indices shifted, so we subtract the offset from the indices provided + @inbounds B = parent(A)[_subtractoffset(r, A.offsets[1])] + _indexedby(B, axes(r)) +end + +# This method added mainly to index an OffsetRange with another range +@inline function Base.getindex(A::OffsetVector, r::AbstractRange{Int}) + @boundscheck checkbounds(A, r) + @inbounds B = parent(A)[_subtractoffset(r, A.offsets[1])] + _indexedby(B, axes(r)) +end + +# In general we would pass through getindex(A, I...) which calls to_indices(A, I) and finally to_index(I) +# An OffsetUnitRange{Int} has an equivalent IdOffsetRange with the same values and axes, +# something similar also holds for OffsetUnitRange{BigInt} +# We may replace the former with the latter in an indexing operation to obtain a performance boost +@inline function Base.to_index(r::OffsetUnitRange{<:Union{Int,BigInt}}) + of = first(axes(r,1)) - 1 + IdOffsetRange(_subtractoffset(parent(r), of), of) +end + +@inline function _boundscheck_index_retaining_axes(r, s) + @boundscheck checkbounds(r, s) + @inbounds pr = r[UnitRange(s)] + _indexedby(pr, axes(s)) +end +@inline _boundscheck_return(r, s) = (@boundscheck checkbounds(r, s); s) + +for OR in [:IIUR, :IdOffsetRange] + for R in [:StepRange, :StepRangeLen, :LinRange, :UnitRange] + @eval @inline Base.getindex(r::$R, s::$OR) = _boundscheck_index_retaining_axes(r, s) + end + + # this method is needed for ambiguity resolution + @eval @inline function Base.getindex(r::StepRangeLen{T,<:Base.TwicePrecision,<:Base.TwicePrecision}, s::$OR) where T + _boundscheck_index_retaining_axes(r, s) + end +end +Base.getindex(r::Base.OneTo, s::IdOffsetRange) = _boundscheck_index_retaining_axes(r, s) + +# These methods are added to avoid ambiguities with Base. +# The ones involving Base types should be ported to Base and version-limited here +@inline Base.getindex(r::IdentityUnitRange, s::IIUR) = _boundscheck_return(r, s) +@inline Base.getindex(r::IdentityUnitRange, s::IdOffsetRange) = _boundscheck_return(r, s) +if IdentityUnitRange !== Base.Slice + @inline Base.getindex(r::Base.Slice, s::IIUR) = _boundscheck_return(r, s) + @inline Base.getindex(r::Base.Slice, s::IdOffsetRange) = _boundscheck_return(r, s) +end + +# eltype conversion +# This may use specialized map methods for the parent +Base.map(::Type{T}, O::OffsetArray) where {T} = parent_call(x -> map(T, x), O) +Base.map(::Type{T}, r::IdOffsetRange) where {T<:Real} = _indexedby(map(T, UnitRange(r)), axes(r)) +if eltype(IIUR) === Int + # This is type-piracy, but there is no way to convert an IdentityUnitRange to a non-Int type in Base + Base.map(::Type{T}, r::IdentityUnitRange) where {T<:Real} = _indexedby(map(T, UnitRange(r)), axes(r)) +end + +# Optimize certain reductions that treat an OffsetVector as a list +for f in [:minimum, :maximum, :extrema, :sum] + @eval Base.$f(r::OffsetRange) = $f(parent(r)) +end + +function Base.show(io::IO, r::OffsetRange) + show(io, r.parent) + print(io, " with indices ", UnitRange(axes(r, 1))) +end +Base.show(io::IO, ::MIME"text/plain", r::OffsetRange) = show(io, r) + + +### Some mutating functions defined only for OffsetVector ### + +Base.resize!(A::OffsetVector, nl::Integer) = (resize!(A.parent, nl); A) +Base.push!(A::OffsetVector, x...) = (push!(A.parent, x...); A) +Base.pop!(A::OffsetVector) = pop!(A.parent) +Base.append!(A::OffsetVector, items) = (append!(A.parent, items); A) +Base.empty!(A::OffsetVector) = (empty!(A.parent); A) + +# These functions keep the summary compact +const OffsetIndices = Union{IdOffsetRange, IdentityUnitRange{<:IdOffsetRange}} +function Base.inds2string(inds::Tuple{OffsetIndices, Vararg{OffsetIndices}}) + Base.inds2string(map(UnitRange, inds)) +end +Base.showindices(io::IO, ind1::IdOffsetRange, inds::IdOffsetRange...) = Base.showindices(io, map(UnitRange, (ind1, inds...))...) + +function Base.showarg(io::IO, @nospecialize(a::OffsetArray), toplevel) + print(io, "OffsetArray(") + Base.showarg(io, parent(a), false) + Base.showindices(io, axes(a)...) + print(io, ')') + if toplevel + print(io, " with eltype ", eltype(a)) + end +end + +function Base.replace_in_print_matrix(A::OffsetArray{<:Any,2}, i::Integer, j::Integer, s::AbstractString) + J = map(parentindex, axes(A), (i,j)) + Base.replace_in_print_matrix(parent(A), J..., s) +end +function Base.replace_in_print_matrix(A::OffsetArray{<:Any,1}, i::Integer, j::Integer, s::AbstractString) + ip = parentindex(axes(A,1), i) + Base.replace_in_print_matrix(parent(A), ip, j, s) +end + +# Actual unsafe_wrap implementation +@inline function _unsafe_wrap(pointer::Ptr{T}, inds::NTuple{N, OffsetAxisKnownLength}; own = false, kw...) where {T,N} + _checkindices(N, inds, "indices") + AA = Base.unsafe_wrap(Array, pointer, map(_indexlength, inds); own=own) + OffsetArray{T, N, typeof(AA)}(AA, map(_indexoffset, inds); kw...) +end +const OffsetArrayUnion{T,N} = Union{Type{OffsetArray}, Type{OffsetArray{T}}, Type{OffsetArray{T,N}}, Type{OffsetArray{T1, N} where T1}} where {T,N} + +@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::NTuple{N, OffsetAxisKnownLength}; kw...) where {T,N} + _unsafe_wrap(pointer, inds; kw...) +end +# Avoid ambiguity +@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::NTuple{N, <:Integer}; kw...) where {T,N} + _unsafe_wrap(pointer, inds; kw...) +end +@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::Vararg{OffsetAxisKnownLength,N}; kw...) where {T,N} + _unsafe_wrap(pointer, inds; kw...) +end +# Avoid ambiguity +@inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::Vararg{Integer,N}; kw...) where {T,N} + _unsafe_wrap(pointer, inds; kw...) +end + +no_offset_view(A::OffsetArray) = no_offset_view(parent(A)) +no_offset_view(a::Base.Slice{<:Base.OneTo}) = a +no_offset_view(a::Base.Slice) = Base.Slice(UnitRange(a)) +no_offset_view(S::SubArray) = view(parent(S), map(no_offset_view, parentindices(S))...) +no_offset_view(a::Array) = a +no_offset_view(i::Number) = i +no_offset_view(A::AbstractArray) = _no_offset_view(axes(A), A) +_no_offset_view(::Tuple{}, A::AbstractArray{T,0}) where T = A +_no_offset_view(::Tuple{Base.OneTo, Vararg{Base.OneTo}}, A::AbstractArray) = A +# the following method is needed for ambiguity resolution +_no_offset_view(::Tuple{Base.OneTo, Vararg{Base.OneTo}}, A::AbstractUnitRange) = A +_no_offset_view(::Any, A::AbstractArray) = OffsetArray(A, Origin(1)) +_no_offset_view(::Any, A::AbstractUnitRange) = UnitRange(A) + +##### +# center/centered +# These two helpers are deliberately not exported; their meaning can be very different in +# other scenarios and will be very likely to cause name conflicts if exported. +##### + +_halfroundInt(v, r::RoundingMode) = div(v, 2, r) + +function center(A::AbstractArray, r::RoundingMode=RoundDown) + map(axes(A)) do inds + _halfroundInt(length(inds)-1, r) + first(inds) + end +end + +centered(A::AbstractArray, cp::Dims=center(A)) = OffsetArray(A, .-cp) + +centered(A::AbstractArray, i::CartesianIndex) = centered(A, Tuple(i)) + +## +# Deprecations +## + +# This is a bad API design as it introduces counter intuitive results (#250) +@deprecate centered(A::AbstractArray, r::RoundingMode) OffsetArray(A, .-center(A, r)) false + + +end # module diff --git a/test/testhelpers/Quaternions.jl b/test/testhelpers/Quaternions.jl new file mode 100644 index 00000000..b1a41426 --- /dev/null +++ b/test/testhelpers/Quaternions.jl @@ -0,0 +1,66 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module Quaternions +using LinearAlgebra +using Random + +export Quaternion + +# A custom Quaternion type with minimal defined interface and methods. +# Used to test mul and mul! methods to show non-commutativity. +struct Quaternion{T<:Real} <: Number + s::T + v1::T + v2::T + v3::T +end +Quaternion{T}(s::Real) where {T<:Real} = Quaternion{T}(T(s), zero(T), zero(T), zero(T)) +Quaternion(s::Real, v1::Real, v2::Real, v3::Real) = Quaternion(promote(s, v1, v2, v3)...) +Base.convert(::Type{Quaternion{T}}, s::Real) where {T <: Real} = + Quaternion{T}(convert(T, s), zero(T), zero(T), zero(T)) +Base.promote_rule(::Type{Quaternion{T}}, ::Type{S}) where {T <: Real, S <: Real} = + Quaternion{promote_type(T, S)} +Base.abs2(q::Quaternion) = q.s*q.s + q.v1*q.v1 + q.v2*q.v2 + q.v3*q.v3 +Base.float(z::Quaternion{T}) where T = Quaternion(float(z.s), float(z.v1), float(z.v2), float(z.v3)) +Base.abs(q::Quaternion) = sqrt(abs2(q)) +Base.real(::Type{Quaternion{T}}) where {T} = T +Base.real(q::Quaternion) = q.s +Base.conj(q::Quaternion) = Quaternion(q.s, -q.v1, -q.v2, -q.v3) +Base.isfinite(q::Quaternion) = isfinite(q.s) & isfinite(q.v1) & isfinite(q.v2) & isfinite(q.v3) +Base.isreal(q::Quaternion) = iszero(q.v1) & iszero(q.v2) & iszero(q.v3) +Base.zero(::Type{Quaternion{T}}) where T = Quaternion{T}(zero(T), zero(T), zero(T), zero(T)) +# avoid defining sqrt(::Quaternion) +LinearAlgebra.choltype(::AbstractArray{Quaternion{T}}) where T = Quaternion{promote_type(T, Float32)} + +Base.:(+)(ql::Quaternion, qr::Quaternion) = + Quaternion(ql.s + qr.s, ql.v1 + qr.v1, ql.v2 + qr.v2, ql.v3 + qr.v3) +Base.:(-)(ql::Quaternion, qr::Quaternion) = + Quaternion(ql.s - qr.s, ql.v1 - qr.v1, ql.v2 - qr.v2, ql.v3 - qr.v3) +Base.:(*)(q::Quaternion, w::Quaternion) = Quaternion(q.s*w.s - q.v1*w.v1 - q.v2*w.v2 - q.v3*w.v3, + q.s*w.v1 + q.v1*w.s + q.v2*w.v3 - q.v3*w.v2, + q.s*w.v2 - q.v1*w.v3 + q.v2*w.s + q.v3*w.v1, + q.s*w.v3 + q.v1*w.v2 - q.v2*w.v1 + q.v3*w.s) +Base.:(*)(q::Quaternion, r::Real) = Quaternion(q.s*r, q.v1*r, q.v2*r, q.v3*r) +Base.:(*)(q::Quaternion, r::Bool) = Quaternion(q.s*r, q.v1*r, q.v2*r, q.v3*r) # remove method ambiguity +Base.:(*)(r::Real, q::Quaternion) = q * r +Base.:(*)(r::Bool, q::Quaternion) = q * r # remove method ambiguity +Base.:(/)(q::Quaternion, w::Quaternion) = q * conj(w) * (1.0 / abs2(w)) +Base.:(\)(q::Quaternion, w::Quaternion) = conj(q) * w * (1.0 / abs2(q)) +Base.:(/)(q::Quaternion, r::Real) = Quaternion(q.s / r, q.v1 / r, q.v2 / r, q.v3 / r) +Base.:(==)(q::Quaternion, w::Quaternion) = + (q.s == w.s) & (q.v1 == w.v1) & (q.v2 == w.v2) & (q.v3 == w.v3) + +# adapted from https://github.com/JuliaGeometry/Quaternions.jl/pull/42 +function Base.rand(rng::AbstractRNG, ::Random.SamplerType{Quaternion{T}}) where {T<:Real} + return Quaternion{T}(rand(rng, T), rand(rng, T), rand(rng, T), rand(rng, T)) +end +function Base.randn(rng::AbstractRNG, ::Type{Quaternion{T}}) where {T<:AbstractFloat} + return Quaternion{T}( + randn(rng, T) / 2, + randn(rng, T) / 2, + randn(rng, T) / 2, + randn(rng, T) / 2, + ) +end + +end diff --git a/test/testhelpers/SizedArrays.jl b/test/testhelpers/SizedArrays.jl new file mode 100644 index 00000000..bd0272d7 --- /dev/null +++ b/test/testhelpers/SizedArrays.jl @@ -0,0 +1,106 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +# SizedArrays + +# This test file defines an array wrapper with statical size. It can be used to +# test the action of LinearAlgebra with non-number eltype. + +module SizedArrays + +import Base: +, *, == + +using LinearAlgebra +import LinearAlgebra: mul! + +export SizedArray + +struct SOneTo{N} <: Base.AbstractOneTo{Int} end +SOneTo(N) = SOneTo{N}() +Base.length(::SOneTo{N}) where {N} = N +Base.size(r::SOneTo) = (length(r),) +Base.axes(r::SOneTo) = (r,) +Base.first(::SOneTo) = 1 +Base.last(r::SOneTo) = length(r) +Base.show(io::IO, r::SOneTo) = print(io, "SOneTo(", length(r), ")") + +Broadcast.axistype(a::Base.OneTo, s::SOneTo) = s +Broadcast.axistype(s::SOneTo, a::Base.OneTo) = s + +struct SizedArray{SZ,T,N,A<:AbstractArray} <: AbstractArray{T,N} + data::A + function SizedArray{SZ}(data::AbstractArray{T,N}) where {SZ,T,N} + SZ == size(data) || throw(ArgumentError("size mismatch!")) + new{SZ,T,N,typeof(data)}(data) + end + function SizedArray{SZ,T,N,A}(data::AbstractArray{T,N}) where {SZ,T,N,A} + SZ == size(data) || throw(ArgumentError("size mismatch!")) + new{SZ,T,N,A}(A(data)) + end + function SizedArray{SZ,T,N}(data::A) where {SZ,T,N,A<:AbstractArray{T,N}} + SizedArray{SZ,T,N,A}(data) + end + function SizedArray{SZ,T}(data::A) where {SZ,T,N,A<:AbstractArray{T,N}} + SizedArray{SZ,T,N,A}(data) + end +end +SizedMatrix{SZ,T,A<:AbstractArray} = SizedArray{SZ,T,2,A} +SizedVector{SZ,T,A<:AbstractArray} = SizedArray{SZ,T,1,A} +Base.convert(::Type{S}, data::AbstractArray) where {S<:SizedArray} = data isa S ? data : S(data) + +# Minimal AbstractArray interface +Base.size(a::SizedArray) = size(typeof(a)) +Base.size(::Type{<:SizedArray{SZ}}) where {SZ} = SZ +Base.axes(a::SizedArray) = map(SOneTo, size(a)) +Base.getindex(A::SizedArray, i...) = getindex(A.data, i...) +Base.setindex!(A::SizedArray, v, i...) = setindex!(A.data, v, i...) +Base.zero(::Type{T}) where T <: SizedArray = SizedArray{size(T)}(zeros(eltype(T), size(T))) +function Base.one(::Type{SizedMatrix{SZ,T,A}}) where {SZ,T,A} + allequal(SZ) || throw(DimensionMismatch("multiplicative identity defined only for square matrices")) + D = diagm(fill(one(T), SZ[1])) + SizedArray{SZ}(convert(A, D)) +end +Base.parent(S::SizedArray) = S.data ++(S1::SizedArray{SZ}, S2::SizedArray{SZ}) where {SZ} = SizedArray{SZ}(S1.data + S2.data) +==(S1::SizedArray{SZ}, S2::SizedArray{SZ}) where {SZ} = S1.data == S2.data + +function Base.similar(::Type{A}, shape::Tuple{SOneTo, Vararg{SOneTo}}) where {A<:AbstractArray} + R = similar(A, length.(shape)) + SizedArray{length.(shape)}(R) +end +function Base.similar(x::SizedArray, ::Type{T}, shape::Tuple{SOneTo, Vararg{SOneTo}}) where {T} + sz = map(length, shape) + SizedArray{sz}(similar(parent(x), T, sz)) +end +function Base.reshape(x::AbstractArray, shape::Tuple{SOneTo, Vararg{SOneTo}}) + sz = map(length, shape) + SizedArray{length.(sz)}(reshape(x, length.(sz))) +end + +const SizedMatrixLike = Union{SizedMatrix, Transpose{<:Any, <:SizedMatrix}, Adjoint{<:Any, <:SizedMatrix}} + +_data(S::SizedArray) = S.data +_data(T::Transpose{<:Any, <:SizedArray}) = transpose(_data(parent(T))) +_data(T::Adjoint{<:Any, <:SizedArray}) = adjoint(_data(parent(T))) + +function *(S1::SizedMatrixLike, S2::SizedMatrixLike) + 0 < ndims(S1) < 3 && 0 < ndims(S2) < 3 && size(S1, 2) == size(S2, 1) || throw(ArgumentError("size mismatch!")) + data = _data(S1) * _data(S2) + SZ = ndims(data) == 1 ? (size(S1, 1), ) : (size(S1, 1), size(S2, 2)) + SizedArray{SZ}(data) +end + +# deliberately wide method definitions to test for method ambiguties in LinearAlgebra +*(S1::SizedMatrixLike, M::AbstractMatrix) = _data(S1) * M +mul!(dest::AbstractMatrix, S1::SizedMatrix, M::AbstractMatrix, α::Number, β::Number) = + mul!(dest, _data(S1), M, α, β) +mul!(dest::AbstractMatrix, M::AbstractMatrix, S2::SizedMatrix, α::Number, β::Number) = + mul!(dest, M, _data(S2), α, β) +mul!(dest::AbstractMatrix, S1::SizedMatrix, S2::SizedMatrix, α::Number, β::Number) = + mul!(dest, _data(S1), _data(S2), α, β) +mul!(dest::AbstractVector, M::AbstractMatrix, v::SizedVector, α::Number, β::Number) = + mul!(dest, M, _data(v), α, β) + +LinearAlgebra.zeroslike(::Type{S}, ax::Tuple{SizedArrays.SOneTo, Vararg{SizedArrays.SOneTo}}) where {S<:SizedArray} = + zeros(eltype(S), ax) + +end diff --git a/test/testhelpers/StructArrays.jl b/test/testhelpers/StructArrays.jl new file mode 100644 index 00000000..f03b07f4 --- /dev/null +++ b/test/testhelpers/StructArrays.jl @@ -0,0 +1,39 @@ +module StructArrays + +struct StructArray{T,N,C <: Tuple{Vararg{AbstractArray{<:Any,N}}}} <: AbstractArray{T,N} + components :: C + + function StructArray{T,N,C}(components::C) where {T,N,C} + fieldcount(T) == length(components) || throw(ArgumentError("number of components incompatible with eltype")) + allequal(axes.(components)) || throw(ArgumentError("component arrays must have the same axes")) + new{T,N,C}(components) + end +end + +function StructArray{T}(components::Tuple{Vararg{AbstractArray{<:Any,N}}}) where {T,N} + StructArray{T,N,typeof(components)}(components) +end + +Base.size(S::StructArray) = size(S.components[1]) +Base.axes(S::StructArray) = axes(S.components[1]) +function Base.getindex(S::StructArray{T,N}, inds::Vararg{Int,N}) where {T,N} + vals = map(x -> x[inds...], S.components) + T(vals...) +end +function Base.setindex!(S::StructArray{T,N}, val, inds::Vararg{Int,N}) where {T,N} + vals = getfield.(Ref(convert(T, val)), fieldnames(T)) + for (A,v) in zip(S.components, vals) + A[inds...] = v + end + S +end + +isnonemptystructtype(::Type{T}) where {T} = isstructtype(T) && fieldcount(T) != 0 + +function Base.similar(S::StructArray, ::Type{T}, dims::Tuple{Int, Vararg{Int}}) where {T} + isnonemptystructtype(T) || return similar(S.components[1], T, dims) + arrs = similar.(S.components, fieldtypes(T), Ref(dims)) + StructArray{T}(arrs) +end + +end diff --git a/test/triangular.jl b/test/triangular.jl index 0241427b..55b3bf4a 100644 --- a/test/triangular.jl +++ b/test/triangular.jl @@ -8,15 +8,16 @@ using Test, LinearAlgebra, Random using LinearAlgebra: errorbounds, transpose!, BandIndex using Test: GenericArray -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") -isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($TESTHELPERS, "SizedArrays.jl")) using .Main.SizedArrays -isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) +isdefined(Main, :FillArrays) || @eval Main include(joinpath($TESTHELPERS, "FillArrays.jl")) using .Main.FillArrays -isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "ImmutableArrays.jl")) +isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($TESTHELPERS, "ImmutableArrays.jl")) using .Main.ImmutableArrays n = 9 diff --git a/test/tridiag.jl b/test/tridiag.jl index effea2b0..0ff1279c 100644 --- a/test/tridiag.jl +++ b/test/tridiag.jl @@ -6,24 +6,25 @@ isdefined(Main, :pruned_old_LA) || @eval Main include("prune_old_LA.jl") using Test, LinearAlgebra, Random -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") -isdefined(Main, :Quaternions) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Quaternions.jl")) +isdefined(Main, :Quaternions) || @eval Main include(joinpath($TESTHELPERS, "Quaternions.jl")) using .Main.Quaternions -isdefined(Main, :InfiniteArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "InfiniteArrays.jl")) +isdefined(Main, :InfiniteArrays) || @eval Main include(joinpath($TESTHELPERS, "InfiniteArrays.jl")) using .Main.InfiniteArrays -isdefined(Main, :FillArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "FillArrays.jl")) +isdefined(Main, :FillArrays) || @eval Main include(joinpath($TESTHELPERS, "FillArrays.jl")) using .Main.FillArrays -isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) +isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($TESTHELPERS, "OffsetArrays.jl")) using .Main.OffsetArrays -isdefined(Main, :SizedArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "SizedArrays.jl")) +isdefined(Main, :SizedArrays) || @eval Main include(joinpath($TESTHELPERS, "SizedArrays.jl")) using .Main.SizedArrays -isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "ImmutableArrays.jl")) +isdefined(Main, :ImmutableArrays) || @eval Main include(joinpath($TESTHELPERS, "ImmutableArrays.jl")) using .Main.ImmutableArrays include("testutils.jl") # test_approx_eq_modphase diff --git a/test/uniformscaling.jl b/test/uniformscaling.jl index 047848de..6216ef6f 100644 --- a/test/uniformscaling.jl +++ b/test/uniformscaling.jl @@ -6,10 +6,12 @@ isdefined(Main, :pruned_old_LA) || @eval Main include("prune_old_LA.jl") using Test, LinearAlgebra, Random -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") -isdefined(Main, :Quaternions) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Quaternions.jl")) +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") + +isdefined(Main, :Quaternions) || @eval Main include(joinpath($TESTHELPERS, "Quaternions.jl")) using .Main.Quaternions -isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "OffsetArrays.jl")) +isdefined(Main, :OffsetArrays) || @eval Main include(joinpath($TESTHELPERS, "OffsetArrays.jl")) using .Main.OffsetArrays Random.seed!(1234543) diff --git a/test/unitful.jl b/test/unitful.jl index 87c153c2..477c9eae 100644 --- a/test/unitful.jl +++ b/test/unitful.jl @@ -6,8 +6,10 @@ using Test, LinearAlgebra, Random Random.seed!(1234321) -const BASE_TEST_PATH = joinpath(Sys.BINDIR, "..", "share", "julia", "test") -isdefined(Main, :Furlongs) || @eval Main include(joinpath($(BASE_TEST_PATH), "testhelpers", "Furlongs.jl")) +const BASE_TEST_PATH = joinpath(dirname(pathof(LinearAlgebra)), "..", "test") +const TESTHELPERS = joinpath(BASE_TEST_PATH, "testhelpers") + +isdefined(Main, :Furlongs) || @eval Main include(joinpath($TESTHELPERS, "Furlongs.jl")) using .Main.Furlongs LinearAlgebra.sylvester(a::Furlong,b::Furlong,c::Furlong) = -c / (a + b)