diff --git a/NEWS.md b/NEWS.md index e76ddd4aa4eb3..db451ca61dbf3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -290,6 +290,14 @@ This section lists changes that do not have deprecation warnings. Its return value has been removed. Use the `process_running` function to determine if a process has already exited. + * `transpose` and `transpose!` no longer recursively transpose the elements of the + container. Similarly, `RowVector` no longer provides a transposed view of the elements. + Transposition now simply rearranges the elements of containers of data, such as arrays + of strings. Note that the renamed `adjoint` method (formerly `ctranspose`) does still + act in a recursive manner, and that (very occassionally) `conj(adjoint(...))` will be + preferrable to `transpose` for linear algebra problems using nested arrays as "block + matrices". ([#23424]) + Library improvements -------------------- diff --git a/base/abstractarraymath.jl b/base/abstractarraymath.jl index 5d8cfc5607ad8..741094145356d 100644 --- a/base/abstractarraymath.jl +++ b/base/abstractarraymath.jl @@ -83,6 +83,94 @@ end squeeze(A::AbstractArray, dim::Integer) = squeeze(A, (Int(dim),)) +## Transposition ## + +""" + transpose(A::AbstractMatrix) + +The transposition operator (`.'`). + +# Examples +```jldoctest +julia> A = [1 2 3; 4 5 6; 7 8 9] +3×3 Array{Int64,2}: +1 2 3 +4 5 6 +7 8 9 + +julia> transpose(A) +3×3 Array{Int64,2}: +1 4 7 +2 5 8 +3 6 9 +``` +""" +function transpose(A::AbstractMatrix) + ind1, ind2 = indices(A) + B = similar(A, (ind2, ind1)) + transpose!(B, A) +end + +transpose(a::AbstractArray) = error("transpose not defined for $(typeof(a)). Consider using `permutedims` for higher-dimensional arrays.") + +""" + transpose!(dest, src) + +Transpose array `src` and store the result in the preallocated array `dest`, which should +have a size corresponding to `(size(src,2),size(src,1))`. No in-place transposition is +supported and unexpected results will happen if `src` and `dest` have overlapping memory +regions. +""" +transpose!(B::AbstractMatrix, A::AbstractMatrix) = transpose_f!(identity, B, A) + +function transpose!(B::AbstractVector, A::AbstractMatrix) + indices(B,1) == indices(A,2) && indices(A,1) == 1:1 || throw(DimensionMismatch("transpose")) + copy!(B, A) +end +function transpose!(B::AbstractMatrix, A::AbstractVector) + indices(B,2) == indices(A,1) && indices(B,1) == 1:1 || throw(DimensionMismatch("transpose")) + copy!(B, A) +end + +const transposebaselength=64 +function transpose_f!(f, B::AbstractMatrix, A::AbstractMatrix) + inds = indices(A) + indices(B,1) == inds[2] && indices(B,2) == inds[1] || throw(DimensionMismatch(string(f))) + + m, n = length(inds[1]), length(inds[2]) + if m*n<=4*transposebaselength + @inbounds begin + for j = inds[2] + for i = inds[1] + B[j,i] = f(A[i,j]) + end + end + end + else + transposeblock!(f,B,A,m,n,first(inds[1])-1,first(inds[2])-1) + end + return B +end +function transposeblock!(f, B::AbstractMatrix, A::AbstractMatrix, m::Int, n::Int, offseti::Int, offsetj::Int) + if m*n<=transposebaselength + @inbounds begin + for j = offsetj+(1:n) + for i = offseti+(1:m) + B[j,i] = f(A[i,j]) + end + end + end + elseif m>n + newm=m>>1 + transposeblock!(f,B,A,newm,n,offseti,offsetj) + transposeblock!(f,B,A,m-newm,n,offseti+newm,offsetj) + else + newn=n>>1 + transposeblock!(f,B,A,m,newn,offseti,offsetj) + transposeblock!(f,B,A,m,n-newn,offseti,offsetj+newn) + end + return B +end ## Unary operators ## diff --git a/base/exports.jl b/base/exports.jl index 889910e51cdfc..ec07ae0e02361 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -74,6 +74,9 @@ export LinSpace, LowerTriangular, Irrational, + MappedArray, + MappedVector, + MappedMatrix, Matrix, MergeSort, NTuple, diff --git a/base/linalg/adjoint.jl b/base/linalg/adjoint.jl new file mode 100644 index 0000000000000..c82d80fde12d8 --- /dev/null +++ b/base/linalg/adjoint.jl @@ -0,0 +1,124 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +adjoint(a::AbstractArray) = error("adjoint not defined for $(typeof(a)). Consider using `permutedims` for higher-dimensional arrays.") + +## adjoint ## + +""" + adjoint(v::AbstractVector) + +Creates a `RowVector` from `v` where `adjoint` has been applied recursively to the elements. +Conceptually, this is intended create the "dual vector" of `v` (note however that the output +is strictly an `AbstractMatrix`). Note also that the output is a view of `v`. +""" +@inline adjoint(vec::AbstractVector) = RowVector(_map(adjoint, vec)) +@inline adjoint(rowvec::RowVector) = _map(adjoint, parent(rowvec)) + +""" + adjoint(m::AbstractMatrix) + +Returns the Hermitian adjoint of `m`, where `m` has been transposed and `adjoint` is applied +recursively to the elements. +""" +function adjoint(a::AbstractMatrix) + (ind1, ind2) = indices(a) + b = similar(a, promote_op(adjoint, eltype(a)), (ind2, ind1)) + adjoint!(b, a) +end + +""" +adjoint!(dest,src) + +Conjugate transpose array `src` and store the result in the preallocated array `dest`, which +should have a size corresponding to `(size(src,2),size(src,1))`. No in-place transposition +is supported and unexpected results will happen if `src` and `dest` have overlapping memory +regions. +""" +adjoint!(b::AbstractMatrix, a::AbstractMatrix) = transpose_f!(adjoint, b, a) +function adjoint!(b::AbstractVector, a::AbstractMatrix) + if indices(b, 1) != indices(a, 2) || indices(a, 1) != 1:1 + throw(DimensionMismatch("adjoint")) + end + adjointcopy!(b, a) +end +function adjoint!(b::AbstractMatrix, a::AbstractVector) + if indices(b, 2) != indices(a, 1) || indices(b, 1) != 1:1 + throw(DimensionMismatch("adjoint")) + end + adjointcopy!(b, a) +end + +function adjointcopy!(b, a) + ra = eachindex(a) + rb = eachindex(b) + if rb == ra + for i ∈ rb + b[i] = adjoint(a[i]) + end + else + for (i, j) ∈ zip(rb, ra) + b[i] = adjoint(a[j]) + end + end +end + +""" +adjoint(x::Number) + +The (complex) conjugate of `x`, `conj(x)`. +""" +adjoint(x::Number) = conj(x) + + +## conjadjoint ## + +""" +conjadjoint(a) + +Returns `conj(adjoint(a))`. +""" +conjadjoint(a::AbstractArray) = conj(adjoint(a)) + +∘(::typeof(conj), ::typeof(conj)) = identity +∘(::typeof(adjoint), ::typeof(adjoint)) = identity +∘(::typeof(conjadjoint), ::typeof(conjadjoint)) = identity + +∘(::typeof(conj), ::typeof(adjoint)) = conjadjoint +∘(::typeof(adjoint), ::typeof(conj)) = conjadjoint +∘(::typeof(conj), ::typeof(conjadjoint)) = adjoint +∘(::typeof(conjadjoint), ::typeof(conj)) = adjoint +∘(::typeof(adjoint), ::typeof(conjadjoint)) = conj +∘(::typeof(conjadjoint), ::typeof(adjoint)) = conj + +## mapped array aliases ## + +const ConjArray{T,N,A<:AbstractArray{<:Any,N}} = MappedArray{T,N,typeof(conj),typeof(conj),A} +const ConjVector{T,A<:AbstractVector} = MappedVector{T,typeof(conj),typeof(conj),A} +const ConjMatrix{T,A<:AbstractMatrix} = MappedMatrix{T,typeof(conj),typeof(conj),A} + +inv_func(::typeof(adjoint)) = adjoint +inv_func(::typeof(conjadjoint)) = conjadjoint + +@inline _map(f, a::AbstractArray) = MappedArray(f, a) +@inline _map(f, a::MappedArray) = map(f, a) + +## lazy conj ## + +""" +conj(v::RowVector) + +Returns a [`ConjArray`](@ref) lazy view of the input, where each element is conjugated. + +# Examples +```jldoctest +julia> v = [1+im, 1-im].' +1×2 RowVector{Complex{Int64},Array{Complex{Int64},1}}: +1+1im 1-1im + +julia> conj(v) +1×2 RowVector{Complex{Int64},ConjArray{Complex{Int64},1,Array{Complex{Int64},1}}}: +1-1im 1+1im +``` +""" +@inline conj(rowvec::RowVector) = RowVector(_map(conj, parent(rowvec))) + diff --git a/base/linalg/bidiag.jl b/base/linalg/bidiag.jl index 055c0c6310c6c..b619318ada347 100644 --- a/base/linalg/bidiag.jl +++ b/base/linalg/bidiag.jl @@ -243,7 +243,8 @@ broadcast(::typeof(floor), ::Type{T}, M::Bidiagonal) where {T<:Integer} = Bidiag broadcast(::typeof(ceil), ::Type{T}, M::Bidiagonal) where {T<:Integer} = Bidiagonal(ceil.(T, M.dv), ceil.(T, M.ev), M.uplo) transpose(M::Bidiagonal) = Bidiagonal(M.dv, M.ev, M.uplo == 'U' ? :L : :U) -adjoint(M::Bidiagonal) = Bidiagonal(conj(M.dv), conj(M.ev), M.uplo == 'U' ? :L : :U) +adjoint(M::Bidiagonal{<:Number}) = Bidiagonal(conj(M.dv), conj(M.ev), M.uplo == 'U' ? :L : :U) +adjoint(M::Bidiagonal) = Bidiagonal(adjoint.(M.dv), adjoint.(M.ev), M.uplo == 'U' ? :L : :U) istriu(M::Bidiagonal) = M.uplo == 'U' || iszero(M.ev) istril(M::Bidiagonal) = M.uplo == 'L' || iszero(M.ev) diff --git a/base/linalg/conjarray.jl b/base/linalg/conjarray.jl deleted file mode 100644 index c43bc0436b0b1..0000000000000 --- a/base/linalg/conjarray.jl +++ /dev/null @@ -1,61 +0,0 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -""" - ConjArray(array) - -A lazy-view wrapper of an `AbstractArray`, taking the elementwise complex conjugate. This -type is usually constructed (and unwrapped) via the [`conj`](@ref) function (or related -[`adjoint`](@ref)), but currently this is the default behavior for `RowVector` only. For -other arrays, the `ConjArray` constructor can be used directly. - -# Examples -```jldoctest -julia> [1+im, 1-im]' -1×2 RowVector{Complex{Int64},ConjArray{Complex{Int64},1,Array{Complex{Int64},1}}}: - 1-1im 1+1im - -julia> ConjArray([1+im 0; 0 1-im]) -2×2 ConjArray{Complex{Int64},2,Array{Complex{Int64},2}}: - 1-1im 0+0im - 0+0im 1+1im -``` -""" -struct ConjArray{T,N,A<:AbstractArray} <: AbstractArray{T,N} - parent::A -end - -@inline ConjArray(a::AbstractArray{T,N}) where {T,N} = ConjArray{conj_type(T),N,typeof(a)}(a) - -const ConjVector{T,V<:AbstractVector} = ConjArray{T,1,V} -@inline ConjVector(v::AbstractVector{T}) where {T} = ConjArray{conj_type(T),1,typeof(v)}(v) - -const ConjMatrix{T,M<:AbstractMatrix} = ConjArray{T,2,M} -@inline ConjMatrix(m::AbstractMatrix{T}) where {T} = ConjArray{conj_type(T),2,typeof(m)}(m) - -# This type can cause the element type to change under conjugation - e.g. an array of complex arrays. -@inline conj_type(x) = conj_type(typeof(x)) -@inline conj_type(::Type{T}) where {T} = promote_op(conj, T) - -@inline parent(c::ConjArray) = c.parent -@inline parent_type(c::ConjArray) = parent_type(typeof(c)) -@inline parent_type(::Type{ConjArray{T,N,A}}) where {T,N,A} = A - -@inline size(a::ConjArray) = size(a.parent) -IndexStyle(::CA) where {CA<:ConjArray} = IndexStyle(parent_type(CA)) -IndexStyle(::Type{CA}) where {CA<:ConjArray} = IndexStyle(parent_type(CA)) - -@propagate_inbounds getindex(a::ConjArray{T,N}, i::Int) where {T,N} = conj(getindex(a.parent, i)) -@propagate_inbounds getindex(a::ConjArray{T,N}, i::Vararg{Int,N}) where {T,N} = conj(getindex(a.parent, i...)) -@propagate_inbounds setindex!(a::ConjArray{T,N}, v, i::Int) where {T,N} = setindex!(a.parent, conj(v), i) -@propagate_inbounds setindex!(a::ConjArray{T,N}, v, i::Vararg{Int,N}) where {T,N} = setindex!(a.parent, conj(v), i...) - -@inline similar(a::ConjArray, ::Type{T}, dims::Dims{N}) where {T,N} = similar(parent(a), T, dims) - -# Currently, this is default behavior for RowVector only -@inline conj(a::ConjArray) = parent(a) - -# Helper functions, currently used by RowVector -@inline _conj(a::AbstractArray) = ConjArray(a) -@inline _conj(a::AbstractArray{T}) where {T<:Real} = a -@inline _conj(a::ConjArray) = parent(a) -@inline _conj(a::ConjArray{T}) where {T<:Real} = parent(a) diff --git a/base/linalg/diagonal.jl b/base/linalg/diagonal.jl index d767171537ac1..38d007a1f1368 100644 --- a/base/linalg/diagonal.jl +++ b/base/linalg/diagonal.jl @@ -197,7 +197,7 @@ function Ac_mul_B(A::AbstractMatrix, D::Diagonal) A_mul_B!(Ac, D) end -At_mul_B(D::Diagonal, B::Diagonal) = Diagonal(transpose.(D.diag) .* B.diag) +At_mul_B(D::Diagonal, B::Diagonal) = Diagonal(D.diag .* B.diag) At_mul_B(A::AbstractTriangular, D::Diagonal) = A_mul_B!(transpose(A), D) function At_mul_B(A::AbstractMatrix, D::Diagonal) At = similar(A, promote_op(*, eltype(A), eltype(D.diag)), (size(A, 2), size(A, 1))) @@ -214,7 +214,7 @@ function A_mul_Bc(D::Diagonal, A::AbstractMatrix) A_mul_B!(D, Ac) end -A_mul_Bt(D::Diagonal, B::Diagonal) = Diagonal(D.diag .* transpose.(B.diag)) +A_mul_Bt(D::Diagonal, B::Diagonal) = Diagonal(D.diag .* B.diag) A_mul_Bt(D::Diagonal, B::AbstractTriangular) = A_mul_B!(D, transpose(B)) function A_mul_Bt(D::Diagonal, A::AbstractMatrix) At = similar(A, promote_op(*, eltype(A), eltype(D.diag)), (size(A, 2), size(A, 1))) @@ -223,7 +223,7 @@ function A_mul_Bt(D::Diagonal, A::AbstractMatrix) end Ac_mul_Bc(D::Diagonal, B::Diagonal) = Diagonal(adjoint.(D.diag) .* adjoint.(B.diag)) -At_mul_Bt(D::Diagonal, B::Diagonal) = Diagonal(transpose.(D.diag) .* transpose.(B.diag)) +At_mul_Bt(D::Diagonal, B::Diagonal) = Diagonal(D.diag .* B.diag) A_mul_B!(A::Diagonal,B::Diagonal) = throw(MethodError(A_mul_B!, Tuple{Diagonal,Diagonal})) At_mul_B!(A::Diagonal,B::Diagonal) = throw(MethodError(At_mul_B!, Tuple{Diagonal,Diagonal})) @@ -239,14 +239,14 @@ A_mul_Bc!(A::AbstractMatrix,B::Diagonal) = scale!(A,conj(B.diag)) # Get ambiguous method if try to unify AbstractVector/AbstractMatrix here using AbstractVecOrMat A_mul_B!(out::AbstractVector, A::Diagonal, in::AbstractVector) = out .= A.diag .* in Ac_mul_B!(out::AbstractVector, A::Diagonal, in::AbstractVector) = out .= adjoint.(A.diag) .* in -At_mul_B!(out::AbstractVector, A::Diagonal, in::AbstractVector) = out .= transpose.(A.diag) .* in +At_mul_B!(out::AbstractVector, A::Diagonal, in::AbstractVector) = out .= A.diag .* in A_mul_B!(out::AbstractMatrix, A::Diagonal, in::AbstractMatrix) = out .= A.diag .* in Ac_mul_B!(out::AbstractMatrix, A::Diagonal, in::AbstractMatrix) = out .= adjoint.(A.diag) .* in -At_mul_B!(out::AbstractMatrix, A::Diagonal, in::AbstractMatrix) = out .= transpose.(A.diag) .* in +At_mul_B!(out::AbstractMatrix, A::Diagonal, in::AbstractMatrix) = out .= A.diag .* in # ambiguities with Symmetric/Hermitian -# RealHermSymComplex[Sym]/[Herm] only include Number; invariant to [c]transpose +# RealHermSymComplex[Sym]/[Herm] only include Number; invariant to transpose/adjoint A_mul_Bt(A::Diagonal, B::RealHermSymComplexSym) = A*B At_mul_B(A::RealHermSymComplexSym, B::Diagonal) = A*B A_mul_Bc(A::Diagonal, B::RealHermSymComplexHerm) = A*B @@ -317,8 +317,7 @@ Ac_ldiv_B(F::Factorization, D::Diagonal) = @inline A_mul_Bc(D::Diagonal, rowvec::RowVector) = D*adjoint(rowvec) conj(D::Diagonal) = Diagonal(conj(D.diag)) -transpose(D::Diagonal{<:Number}) = D -transpose(D::Diagonal) = Diagonal(transpose.(D.diag)) +transpose(D::Diagonal) = D adjoint(D::Diagonal{<:Number}) = conj(D) adjoint(D::Diagonal) = Diagonal(adjoint.(D.diag)) diff --git a/base/linalg/factorization.jl b/base/linalg/factorization.jl index 487221a20acac..e437b86f0a698 100644 --- a/base/linalg/factorization.jl +++ b/base/linalg/factorization.jl @@ -81,7 +81,7 @@ end # fallback methods for transposed solves At_ldiv_B(F::Factorization{<:Real}, B::AbstractVecOrMat) = Ac_ldiv_B(F, B) -At_ldiv_B(F::Factorization, B) = conj.(Ac_ldiv_B(F, conj.(B))) +At_ldiv_B(F::Factorization, B) = conj.(Ac_ldiv_B(F, conj.(B))) # TODO fix for array eltypes? """ A_ldiv_B!([Y,] A, B) -> Y diff --git a/base/linalg/generic.jl b/base/linalg/generic.jl index 82d6f7d2d2a78..cde3bc0990a6b 100644 --- a/base/linalg/generic.jl +++ b/base/linalg/generic.jl @@ -915,7 +915,7 @@ issymmetric(A::AbstractMatrix{<:Real}) = ishermitian(A) """ issymmetric(A) -> Bool -Test whether a matrix is symmetric. +Test whether a matrix is symmetric (such that `A == transpose(A)`). # Examples ```jldoctest @@ -942,7 +942,7 @@ function issymmetric(A::AbstractMatrix) return false end for i = first(indsn):last(indsn), j = (i):last(indsn) - if A[i,j] != transpose(A[j,i]) + if A[i,j] != A[j,i] return false end end @@ -954,7 +954,7 @@ issymmetric(x::Number) = x == x """ ishermitian(A) -> Bool -Test whether a matrix is Hermitian. +Test whether a matrix is Hermitian (such that `A == adjoint(A)`). # Examples ```jldoctest diff --git a/base/linalg/givens.jl b/base/linalg/givens.jl index de621983494d4..9e0ba92cb034b 100644 --- a/base/linalg/givens.jl +++ b/base/linalg/givens.jl @@ -3,7 +3,7 @@ abstract type AbstractRotation{T} end -transpose(R::AbstractRotation) = error("transpose not implemented for $(typeof(R)). Consider using conjugate transpose (') instead of transpose (.').") +transpose(R::AbstractRotation) = error("transpose not implemented for $(typeof(R)). Consider using adjoint (') instead of transpose (.').") function *(R::AbstractRotation{T}, A::AbstractVecOrMat{S}) where {T,S} TS = typeof(zero(T)*zero(S) + zero(T)*zero(S)) diff --git a/base/linalg/linalg.jl b/base/linalg/linalg.jl index a46682c8f354e..1eed4b11b042c 100644 --- a/base/linalg/linalg.jl +++ b/base/linalg/linalg.jl @@ -16,8 +16,9 @@ import Base: USE_BLAS64, abs, acos, acosh, acot, acoth, acsc, acsch, adjoint, as inv, isapprox, isone, IndexStyle, kron, length, log, map, ndims, oneunit, parent, power_by_squaring, print_matrix, promote_rule, real, round, sec, sech, setindex!, show, similar, sin, sincos, sinh, size, sqrt, tan, tanh, transpose, trunc, typed_hcat, vec +import Base.MappedArrays: inv_func using Base: hvcat_fill, iszero, IndexLinear, _length, promote_op, promote_typeof, - @propagate_inbounds, @pure, reduce, typed_vcat + @propagate_inbounds, @pure, reduce, transpose_f!, typed_vcat # We use `_length` because of non-1 indices; releases after julia 0.5 # can go back to `length`. `_length(A)` is equivalent to `length(linearindices(A))`. @@ -242,8 +243,32 @@ end copy_oftype(A::AbstractArray{T}, ::Type{T}) where {T} = copy(A) copy_oftype(A::AbstractArray{T,N}, ::Type{S}) where {T,N,S} = convert(AbstractArray{S,N}, A) -include("conjarray.jl") -include("transpose.jl") +function copy_transpose!(B::AbstractVecOrMat, ir_dest::AbstractRange{Int}, jr_dest::AbstractRange{Int}, + A::AbstractVecOrMat, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}) + if length(ir_dest) != length(jr_src) + throw(ArgumentError(string("source and destination must have same size (got ", + length(jr_src)," and ",length(ir_dest),")"))) + end + if length(jr_dest) != length(ir_src) + throw(ArgumentError(string("source and destination must have same size (got ", + length(ir_src)," and ",length(jr_dest),")"))) + end + @boundscheck checkbounds(B, ir_dest, jr_dest) + @boundscheck checkbounds(A, ir_src, jr_src) + idest = first(ir_dest) + for jsrc in jr_src + jdest = first(jr_dest) + for isrc in ir_src + B[idest,jdest] = A[isrc,jsrc] + jdest += step(jr_dest) + end + idest += step(ir_dest) + end + return B +end + + +include("adjoint.jl") include("rowvector.jl") include("exceptions.jl") @@ -276,7 +301,6 @@ include("bitarray.jl") include("ldlt.jl") include("schur.jl") - include("arpack.jl") include("arnoldi.jl") diff --git a/base/linalg/rowvector.jl b/base/linalg/rowvector.jl index 1c5472b1faf0a..f39b8426f04fe 100644 --- a/base/linalg/rowvector.jl +++ b/base/linalg/rowvector.jl @@ -1,155 +1,11 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -""" - RowVector(vector) - -A lazy-view wrapper of an [`AbstractVector`](@ref), which turns a length-`n` vector into a `1×n` -shaped row vector and represents the transpose of a vector (the elements are also transposed -recursively). This type is usually constructed (and unwrapped) via the [`transpose`](@ref) -function or `.'` operator (or related [`adjoint`](@ref) or `'` operator). - -By convention, a vector can be multiplied by a matrix on its left (`A * v`) whereas a row -vector can be multiplied by a matrix on its right (such that `v.' * A = (A.' * v).'`). It -differs from a `1×n`-sized matrix by the facts that its transpose returns a vector and the -inner product `v1.' * v2` returns a scalar, but will otherwise behave similarly. -""" -struct RowVector{T,V<:AbstractVector} <: AbstractMatrix{T} - vec::V - function RowVector{T,V}(v::V) where V<:AbstractVector where T - check_types(T,v) - new(v) - end -end - -@inline check_types(::Type{T1}, ::AbstractVector{T2}) where {T1,T2} = check_types(T1, T2) -@pure check_types(::Type{T1}, ::Type{T2}) where {T1,T2} = T1 === transpose_type(T2) ? nothing : - error("Element type mismatch. Tried to create a `RowVector{$T1}` from an `AbstractVector{$T2}`") - -const ConjRowVector{T,CV<:ConjVector} = RowVector{T,CV} - -# The element type may be transformed as transpose is recursive -@inline transpose_type(::Type{T}) where {T} = promote_op(transpose, T) - -# Constructors that take a vector -@inline RowVector(vec::AbstractVector{T}) where {T} = RowVector{transpose_type(T),typeof(vec)}(vec) -@inline RowVector{T}(vec::AbstractVector{T}) where {T} = RowVector{T,typeof(vec)}(vec) - -# Constructors that take a size and default to Array -@inline RowVector{T}(n::Int) where {T} = RowVector{T}(Vector{transpose_type(T)}(n)) -@inline RowVector{T}(n1::Int, n2::Int) where {T} = n1 == 1 ? - RowVector{T}(Vector{transpose_type(T)}(n2)) : - error("RowVector expects 1×N size, got ($n1,$n2)") -@inline RowVector{T}(n::Tuple{Int}) where {T} = RowVector{T}(Vector{transpose_type(T)}(n[1])) -@inline RowVector{T}(n::Tuple{Int,Int}) where {T} = n[1] == 1 ? - RowVector{T}(Vector{transpose_type(T)}(n[2])) : - error("RowVector expects 1×N size, got $n") - -# Conversion of underlying storage -convert(::Type{RowVector{T,V}}, rowvec::RowVector) where {T,V<:AbstractVector} = - RowVector{T,V}(convert(V,rowvec.vec)) - -# similar tries to maintain the RowVector wrapper and the parent type -@inline similar(rowvec::RowVector) = RowVector(similar(parent(rowvec))) -@inline similar(rowvec::RowVector, ::Type{T}) where {T} = RowVector(similar(parent(rowvec), transpose_type(T))) - -# Resizing similar currently loses its RowVector property. -@inline similar(rowvec::RowVector, ::Type{T}, dims::Dims{N}) where {T,N} = similar(parent(rowvec), T, dims) - -# Basic methods -""" - transpose(v::AbstractVector) - -The transposition operator (`.'`). - -# Examples -```jldoctest -julia> v = [1,2,3] -3-element Array{Int64,1}: - 1 - 2 - 3 - -julia> transpose(v) -1×3 RowVector{Int64,Array{Int64,1}}: - 1 2 3 -``` -""" -@inline transpose(vec::AbstractVector) = RowVector(vec) -@inline adjoint(vec::AbstractVector) = RowVector(_conj(vec)) - -@inline transpose(rowvec::RowVector) = rowvec.vec -@inline transpose(rowvec::ConjRowVector) = copy(rowvec.vec) # remove the ConjArray wrapper from any raw vector -@inline adjoint(rowvec::RowVector) = conj(rowvec.vec) -@inline adjoint(rowvec::RowVector{<:Real}) = rowvec.vec - -parent(rowvec::RowVector) = rowvec.vec -vec(rowvec::RowVector) = rowvec.vec - -""" - conj(v::RowVector) - -Returns a [`ConjArray`](@ref) lazy view of the input, where each element is conjugated. - -# Examples -```jldoctest -julia> v = [1+im, 1-im].' -1×2 RowVector{Complex{Int64},Array{Complex{Int64},1}}: - 1+1im 1-1im - -julia> conj(v) -1×2 RowVector{Complex{Int64},ConjArray{Complex{Int64},1,Array{Complex{Int64},1}}}: - 1-1im 1+1im -``` -""" -@inline conj(rowvec::RowVector) = RowVector(_conj(rowvec.vec)) -@inline conj(rowvec::RowVector{<:Real}) = rowvec - -# AbstractArray interface -@inline length(rowvec::RowVector) = length(rowvec.vec) -@inline size(rowvec::RowVector) = (1, length(rowvec.vec)) -@inline size(rowvec::RowVector, d) = ifelse(d==2, length(rowvec.vec), 1) -@inline indices(rowvec::RowVector) = (Base.OneTo(1), indices(rowvec.vec)[1]) -@inline indices(rowvec::RowVector, d) = ifelse(d == 2, indices(rowvec.vec)[1], Base.OneTo(1)) -IndexStyle(::RowVector) = IndexLinear() -IndexStyle(::Type{<:RowVector}) = IndexLinear() - -@propagate_inbounds getindex(rowvec::RowVector, i::Int) = transpose(rowvec.vec[i]) -@propagate_inbounds setindex!(rowvec::RowVector, v, i::Int) = setindex!(rowvec.vec, transpose(v), i) - -# Keep a RowVector where appropriate -@propagate_inbounds getindex(rowvec::RowVector, ::Colon, i::Int) = transpose.(rowvec.vec[i:i]) -@propagate_inbounds getindex(rowvec::RowVector, ::Colon, inds::AbstractArray{Int}) = RowVector(rowvec.vec[inds]) -@propagate_inbounds getindex(rowvec::RowVector, ::Colon, ::Colon) = RowVector(rowvec.vec[:]) - -# helper function for below -@inline to_vec(rowvec::RowVector) = map(transpose, transpose(rowvec)) -@inline to_vec(x::Number) = x -@inline to_vecs(rowvecs...) = (map(to_vec, rowvecs)...,) - -# map: Preserve the RowVector by un-wrapping and re-wrapping, but note that `f` -# expects to operate within the transposed domain, so to_vec transposes the elements -@inline map(f, rowvecs::RowVector...) = RowVector(map(transpose∘f, to_vecs(rowvecs...)...)) - -# broacast (other combinations default to higher-dimensional array) -@inline broadcast(f, rowvecs::Union{Number,RowVector}...) = - RowVector(broadcast(transpose∘f, to_vecs(rowvecs...)...)) - -# Horizontal concatenation # - -@inline hcat(X::RowVector...) = transpose(vcat(map(transpose, X)...)) -@inline hcat(X::Union{RowVector,Number}...) = transpose(vcat(map(transpose, X)...)) - -@inline typed_hcat(::Type{T}, X::RowVector...) where {T} = - transpose(typed_vcat(T, map(transpose, X)...)) -@inline typed_hcat(::Type{T}, X::Union{RowVector,Number}...) where {T} = - transpose(typed_vcat(T, map(transpose, X)...)) - # Multiplication # # inner product -> dot product specializations @inline *(rowvec::RowVector{T}, vec::AbstractVector{T}) where {T<:Real} = dot(parent(rowvec), vec) -@inline *(rowvec::ConjRowVector{T}, vec::AbstractVector{T}) where {T<:Real} = dot(rowvec', vec) -@inline *(rowvec::ConjRowVector, vec::AbstractVector) = dot(rowvec', vec) +#@inline *(rowvec::ConjRowVector{T}, vec::AbstractVector{T}) where {T<:Real} = dot(rowvec', vec) +#@inline *(rowvec::ConjRowVector, vec::AbstractVector) = dot(rowvec', vec) # Generic behavior @inline function *(rowvec::RowVector, vec::AbstractVector) diff --git a/base/linalg/symmetric.jl b/base/linalg/symmetric.jl index 20d587a6a1bc8..171e6700778be 100644 --- a/base/linalg/symmetric.jl +++ b/base/linalg/symmetric.jl @@ -236,7 +236,7 @@ function adjoint(A::Symmetric) AC = adjoint(A.data) return Symmetric(AC, ifelse(A.uplo == 'U', :L, :U)) end -function transpose(A::Hermitian) +function transpose(A::Hermitian{<:Number}) AT = transpose(A.data) return Hermitian(AT, ifelse(A.uplo == 'U', :L, :U)) end diff --git a/base/linalg/transpose.jl b/base/linalg/transpose.jl deleted file mode 100644 index d46e4a5d3a6b9..0000000000000 --- a/base/linalg/transpose.jl +++ /dev/null @@ -1,153 +0,0 @@ -# This file is a part of Julia. License is MIT: https://julialang.org/license - -adjoint(a::AbstractArray) = error("adjoint not defined for $(typeof(a)). Consider using `permutedims` for higher-dimensional arrays.") -transpose(a::AbstractArray) = error("transpose not defined for $(typeof(a)). Consider using `permutedims` for higher-dimensional arrays.") - -## Matrix transposition ## - -""" - transpose!(dest,src) - -Transpose array `src` and store the result in the preallocated array `dest`, which should -have a size corresponding to `(size(src,2),size(src,1))`. No in-place transposition is -supported and unexpected results will happen if `src` and `dest` have overlapping memory -regions. -""" -transpose!(B::AbstractMatrix, A::AbstractMatrix) = transpose_f!(transpose, B, A) - -""" - adjoint!(dest,src) - -Conjugate transpose array `src` and store the result in the preallocated array `dest`, which -should have a size corresponding to `(size(src,2),size(src,1))`. No in-place transposition -is supported and unexpected results will happen if `src` and `dest` have overlapping memory -regions. -""" -adjoint!(B::AbstractMatrix, A::AbstractMatrix) = transpose_f!(adjoint, B, A) -function transpose!(B::AbstractVector, A::AbstractMatrix) - indices(B,1) == indices(A,2) && indices(A,1) == 1:1 || throw(DimensionMismatch("transpose")) - copy!(B, A) -end -function transpose!(B::AbstractMatrix, A::AbstractVector) - indices(B,2) == indices(A,1) && indices(B,1) == 1:1 || throw(DimensionMismatch("transpose")) - copy!(B, A) -end -function adjoint!(B::AbstractVector, A::AbstractMatrix) - indices(B,1) == indices(A,2) && indices(A,1) == 1:1 || throw(DimensionMismatch("transpose")) - ccopy!(B, A) -end -function adjoint!(B::AbstractMatrix, A::AbstractVector) - indices(B,2) == indices(A,1) && indices(B,1) == 1:1 || throw(DimensionMismatch("transpose")) - ccopy!(B, A) -end - -const transposebaselength=64 -function transpose_f!(f, B::AbstractMatrix, A::AbstractMatrix) - inds = indices(A) - indices(B,1) == inds[2] && indices(B,2) == inds[1] || throw(DimensionMismatch(string(f))) - - m, n = length(inds[1]), length(inds[2]) - if m*n<=4*transposebaselength - @inbounds begin - for j = inds[2] - for i = inds[1] - B[j,i] = f(A[i,j]) - end - end - end - else - transposeblock!(f,B,A,m,n,first(inds[1])-1,first(inds[2])-1) - end - return B -end -function transposeblock!(f, B::AbstractMatrix, A::AbstractMatrix, m::Int, n::Int, offseti::Int, offsetj::Int) - if m*n<=transposebaselength - @inbounds begin - for j = offsetj .+ (1:n) - for i = offseti .+ (1:m) - B[j,i] = f(A[i,j]) - end - end - end - elseif m>n - newm=m>>1 - transposeblock!(f,B,A,newm,n,offseti,offsetj) - transposeblock!(f,B,A,m-newm,n,offseti+newm,offsetj) - else - newn=n>>1 - transposeblock!(f,B,A,m,newn,offseti,offsetj) - transposeblock!(f,B,A,m,n-newn,offseti,offsetj+newn) - end - return B -end - -function ccopy!(B, A) - RB, RA = eachindex(B), eachindex(A) - if RB == RA - for i = RB - B[i] = adjoint(A[i]) - end - else - for (i,j) = zip(RB, RA) - B[i] = adjoint(A[j]) - end - end -end - -""" - transpose(A::AbstractMatrix) - -The transposition operator (`.'`). - -# Examples -```jldoctest -julia> A = [1 2 3; 4 5 6; 7 8 9] -3×3 Array{Int64,2}: - 1 2 3 - 4 5 6 - 7 8 9 - -julia> transpose(A) -3×3 Array{Int64,2}: - 1 4 7 - 2 5 8 - 3 6 9 -``` -""" -function transpose(A::AbstractMatrix) - ind1, ind2 = indices(A) - B = similar(A, (ind2, ind1)) - transpose!(B, A) -end -function adjoint(A::AbstractMatrix) - ind1, ind2 = indices(A) - B = similar(A, (ind2, ind1)) - adjoint!(B, A) -end - -@inline adjoint(A::AbstractVector{<:Real}) = transpose(A) -@inline adjoint(A::AbstractMatrix{<:Real}) = transpose(A) - -function copy_transpose!(B::AbstractVecOrMat, ir_dest::AbstractRange{Int}, jr_dest::AbstractRange{Int}, - A::AbstractVecOrMat, ir_src::AbstractRange{Int}, jr_src::AbstractRange{Int}) - if length(ir_dest) != length(jr_src) - throw(ArgumentError(string("source and destination must have same size (got ", - length(jr_src)," and ",length(ir_dest),")"))) - end - if length(jr_dest) != length(ir_src) - throw(ArgumentError(string("source and destination must have same size (got ", - length(ir_src)," and ",length(jr_dest),")"))) - end - @boundscheck checkbounds(B, ir_dest, jr_dest) - @boundscheck checkbounds(A, ir_src, jr_src) - idest = first(ir_dest) - for jsrc in jr_src - jdest = first(jr_dest) - for isrc in ir_src - B[idest,jdest] = A[isrc,jsrc] - jdest += step(jr_dest) - end - idest += step(ir_dest) - end - return B -end diff --git a/base/linalg/tridiag.jl b/base/linalg/tridiag.jl index 891a51f9b8655..14863a754c36e 100644 --- a/base/linalg/tridiag.jl +++ b/base/linalg/tridiag.jl @@ -128,7 +128,7 @@ broadcast(::typeof(floor), ::Type{T}, M::SymTridiagonal) where {T<:Integer} = Sy broadcast(::typeof(ceil), ::Type{T}, M::SymTridiagonal) where {T<:Integer} = SymTridiagonal(ceil.(T, M.dv), ceil.(T, M.ev)) transpose(M::SymTridiagonal) = M #Identity operation -adjoint(M::SymTridiagonal) = conj(M) +adjoint(M::SymTridiagonal{<:Number}) = conj(M) function diag(M::SymTridiagonal, n::Integer=0) # every branch call similar(..., ::Int) to make sure the @@ -534,7 +534,7 @@ broadcast(::typeof(ceil), ::Type{T}, M::Tridiagonal) where {T<:Integer} = Tridiagonal(ceil.(T, M.dl), ceil.(T, M.d), ceil.(T, M.du)) transpose(M::Tridiagonal) = Tridiagonal(M.du, M.d, M.dl) -adjoint(M::Tridiagonal) = conj(transpose(M)) +adjoint(M::Tridiagonal) = Tridiagonal(adjoint.(M.du), adjoint.(M.d), adjoint.(M.dl)) function diag(M::Tridiagonal{T}, n::Integer=0) where T # every branch call similar(..., ::Int) to make sure the diff --git a/base/linalg/uniformscaling.jl b/base/linalg/uniformscaling.jl index a4800b031581c..d134ff2c33a54 100644 --- a/base/linalg/uniformscaling.jl +++ b/base/linalg/uniformscaling.jl @@ -63,7 +63,7 @@ end copy(J::UniformScaling) = UniformScaling(J.λ) transpose(J::UniformScaling) = J -adjoint(J::UniformScaling) = UniformScaling(conj(J.λ)) +adjoint(J::UniformScaling) = UniformScaling(adjoint(J.λ)) one(::Type{UniformScaling{T}}) where {T} = UniformScaling(one(T)) one(J::UniformScaling{T}) where {T} = one(UniformScaling{T}) diff --git a/base/mappedarray.jl b/base/mappedarray.jl new file mode 100644 index 0000000000000..f67d9c57205c3 --- /dev/null +++ b/base/mappedarray.jl @@ -0,0 +1,92 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +module MappedArrays + +export MappedArray, MappedVector, MappedMatrix + +""" + MappedArray(f, a) + MappedArray(f, f_inv, a) + +Returns a lazily mapped array where function `f` is applied the elements of array `a`. + +`f_inv` is the inverse function to `f`, and should satisfy `f(f_inv(x)) == x`. It is +optional and used to enable `setindex!` on the output array, so that the appropriate values +can be stored in `a`. Some inverse functions are known (e.g. `conj` is its own inverse) and +users may overload the `Base.MappedArrays.inv_func` function with their own definitions +(e.g. `Base.MappedArrays.inv_func(::typeof(conj)) = conj`) so that `f_inv` is created +automatically by the constructor. + +# Example + +```julia +julia> a = [1, 2, 3] +3-element Array{Int64,1}: + 1 + 2 + 3 + +julia> b = MappedArray(x -> x + 10, x -> x - 10, a) +3-element MappedArray{Int64,1,getfield(Main, Symbol("##3#5")),getfield(Main, Symbol("##4#6")),Array{Int64,1}}: + 11 + 12 + 13 + +julia> b[2] = 20 +20 + +julia> a[2] +10 +``` +""" +struct MappedArray{T, N, F, F_inv, A <: AbstractArray{<:Any, N}} <: AbstractArray{T, N} + f::F + f_inv::F_inv + parent::A +end + +MappedArray(f, a::AbstractArray) = MappedArray(f, inv_func(f), a) +function MappedArray(f, f_inv, a::AbstractArray) + MappedArray{Base.promote_op(f, eltype(a)), ndims(a), typeof(f), typeof(f_inv), typeof(a)}(f, f_inv, a) +end + +noinverse(x) = error("No inverse function defined") +∘(::typeof(noinverse), ::Any) = noinverse +∘(::Any, ::typeof(noinverse)) = noinverse +∘(::typeof(noinverse), ::typeof(noinverse)) = noinverse + +inv_func(f) = noinverse +inv_func(::typeof(identity)) = identity +inv_func(::typeof(conj)) = conj + +const MappedVector{T, F, F_inv, A <: AbstractVector} = MappedArray{T, 1, F, F_inv, A} +MappedVector(f, a::AbstractVector) = MappedVector(f, inv_func(f), a) +function MappedVector(f, f_inv, a::AbstractVector) + MappedArray{Base.promote_op(f, eltype(a)), 1, typeof(f), typeof(f_inv), typeof(a)}(f, f_inv, a) +end + +const MappedMatrix{T, F, F_inv, A <: AbstractMatrix} = MappedArray{T, 2, F, F_inv, A} +MappedMatrix(f, a::AbstractMatrix) = MappedMatrix(f, inv_func(f), a) +function MappedMatrix(f, f_inv, a::AbstractMatrix) + MappedArray{Base.promote_op(f, eltype(a)), 2, typeof(f), typeof(f_inv), typeof(a)}(f, f_inv, a) +end + +Base.parent(a::MappedArray) = a.parent +parent_type(::Type{<:MappedArray{<:Any, <:Any, <:Any, <:Any, A}}) where {A} = A + +Base.size(a::MappedArray) = size(parent(a)) +Base.indices(a::MappedArray) = indices(parent(a)) +Base.IndexStyle(::Type{MA}) where {MA <: MappedArray} = Base.IndexStyle(parent_type(MA)) + +Base.@propagate_inbounds Base.getindex(a::MappedArray, i::Int...) = a.f(a.parent[i...]) +Base.@propagate_inbounds function Base.setindex!(a::MappedArray{T}, v::T, i::Int...) where {T} + a.parent[i...] = a.f_inv(v) +end +Base.@propagate_inbounds function Base.setindex!(a::MappedArray{T}, v, i::Int...) where {T} + a[i...] = convert(T, v) +end + +# MappedArray preserves laziness under `map` +Base.map(f, a::MappedArray) = MappedArray(f ∘ a.f, inv_func(f) ∘ a.f_inv, a.parent) + +end # module diff --git a/base/rowvector.jl b/base/rowvector.jl new file mode 100644 index 0000000000000..a4f3142379fd2 --- /dev/null +++ b/base/rowvector.jl @@ -0,0 +1,101 @@ +# This file is a part of Julia. License is MIT: https://julialang.org/license + +""" + RowVector(vector) + +A lazy-view wrapper of an [`AbstractVector`](@ref), which turns a length-`n` vector into a `1×n` +shaped row vector and represents the transpose of a vector (the elements are also transposed +recursively). This type is usually constructed (and unwrapped) via the [`transpose`](@ref) +function or `.'` operator (or related [`adjoint`](@ref) or `'` operator). + +By convention, a vector can be multiplied by a matrix on its left (`A * v`) whereas a row +vector can be multiplied by a matrix on its right (such that `v.' * A = (A.' * v).'`). It +differs from a `1×n`-sized matrix by the facts that its transpose returns a vector and the +inner product `v1.' * v2` returns a scalar, but will otherwise behave similarly. +""" +struct RowVector{T,V<:AbstractVector{T}} <: AbstractMatrix{T} + vec::V +end + +parent(rowvec::RowVector) = rowvec.vec +vec(rowvec::RowVector) = rowvec.vec + +# Constructors that take a vector +@inline RowVector(vec::AbstractVector{T}) where {T} = RowVector{T,typeof(vec)}(vec) +@inline RowVector{T}(vec::AbstractVector{T}) where {T} = RowVector{T,typeof(vec)}(vec) + +# Conversion of underlying storage +function convert(::Type{RowVector{T,V}}, rowvec::RowVector) where {T,V<:AbstractVector{T}} + RowVector{T,V}(convert(V,parent(rowvec))) +end + +# similar tries to maintain the RowVector wrapper and the parent type +@inline similar(rowvec::RowVector) = RowVector(similar(parent(rowvec))) +@inline similar(rowvec::RowVector, ::Type{T}) where {T} = RowVector(similar(parent(rowvec), T)) + +# Resizing similar currently loses its RowVector property. +@inline similar(rowvec::RowVector, ::Type{T}, dims::Dims{N}) where {T,N} = similar(parent(rowvec), T, dims) + +# Basic methods +""" +transpose(v::AbstractVector) + +The transposition operator (`.'`). + +# Examples +```jldoctest +julia> v = [1,2,3] +3-element Array{Int64,1}: +1 +2 +3 + +julia> transpose(v) +1×3 RowVector{Int64,Array{Int64,1}}: +1 2 3 +``` +""" +@inline transpose(vec::AbstractVector) = RowVector(vec) +@inline transpose(rowvec::RowVector) = parent(rowvec) + +# AbstractArray interface +@inline length(rowvec::RowVector) = length(parent(rowvec)) +@inline size(rowvec::RowVector) = (1, length(parent(rowvec))) +@inline size(rowvec::RowVector, d) = ifelse(d==2, length(parent(rowvec)), 1) +@inline indices(rowvec::RowVector) = (Base.OneTo(1), indices(parent(rowvec))[1]) +@inline indices(rowvec::RowVector, d) = ifelse(d == 2, indices(parent(rowvec))[1], Base.OneTo(1)) +IndexStyle(::RowVector) = IndexLinear() +IndexStyle(::Type{<:RowVector}) = IndexLinear() + +@propagate_inbounds getindex(rowvec::RowVector, i::Int) = parent(rowvec)[i] +@propagate_inbounds setindex!(rowvec::RowVector, v, i::Int) = setindex!(parent(rowvec), v, i) + +# Keep a RowVector where appropriate +@propagate_inbounds getindex(rowvec::RowVector, ::Colon, i::Int) = parent(rowvec)[i] +@propagate_inbounds getindex(rowvec::RowVector, ::Colon, inds::AbstractArray{Int}) = RowVector(parent(rowvec)[inds]) +@propagate_inbounds getindex(rowvec::RowVector, ::Colon, ::Colon) = RowVector(parent(rowvec)[:]) + +# helper function for below +@inline to_vec(rowvec::RowVector) = parent(rowvec) +@inline to_vec(x::Number) = x +@inline to_vecs(rowvecs...) = (map(to_vec, rowvecs)...,) + +# map: Preserve the RowVector by un-wrapping and re-wrapping +@inline map(f, rowvecs::RowVector...) = RowVector(map(f, to_vecs(rowvecs...)...)) + +# broacast (other combinations default to higher-dimensional array) +# (in future, should use broadcast infrastructure to manage this?) +@inline function broadcast(f, rowvecs::Union{Number,RowVector}...) + RowVector(broadcast(f, to_vecs(rowvecs...)...)) +end + +# Horizontal concatenation # +@inline hcat(X::RowVector...) = transpose(vcat(map(transpose, X)...)) +@inline hcat(X::Union{RowVector,Number}...) = transpose(vcat(map(transpose, X)...)) + +@inline function typed_hcat(::Type{T}, X::RowVector...) where {T} + transpose(typed_vcat(T, map(transpose, X)...)) +end +@inline function typed_hcat(::Type{T}, X::Union{RowVector,Number}...) where {T} + transpose(typed_vcat(T, map(transpose, X)...)) +end diff --git a/base/sparse/linalg.jl b/base/sparse/linalg.jl index cbdee2c69b99b..3b608db9722b3 100644 --- a/base/sparse/linalg.jl +++ b/base/sparse/linalg.jl @@ -45,7 +45,7 @@ end for (f, op, transp) in ((:A_mul_B, :identity, false), (:Ac_mul_B, :adjoint, true), - (:At_mul_B, :transpose, true)) + (:At_mul_B, :identity, true)) @eval begin function $(Symbol(f,:!))(α::Number, A::SparseMatrixCSC, B::StridedVecOrMat, β::Number, C::StridedVecOrMat) if $transp @@ -308,7 +308,7 @@ function A_rdiv_B!(A::SparseMatrixCSC{T}, D::Diagonal{T}) where T A end -A_rdiv_Bc!(A::SparseMatrixCSC{T}, D::Diagonal{T}) where {T} = A_rdiv_B!(A, conj(D)) +A_rdiv_Bc!(A::SparseMatrixCSC{T}, D::Diagonal{T}) where {T} = A_rdiv_B!(A, adjoint(D)) A_rdiv_Bt!(A::SparseMatrixCSC{T}, D::Diagonal{T}) where {T} = A_rdiv_B!(A, D) ## triu, tril @@ -675,7 +675,7 @@ function normestinv(A::SparseMatrixCSC{T}, t::Integer = min(2,maximum(size(A)))) end end - # Use the conjugate transpose + # Use the conjugate transpose (adjoint) Z = F' \ S h_max = zero(real(eltype(Z))) h = zeros(real(eltype(Z)), n) diff --git a/base/sparse/sparsematrix.jl b/base/sparse/sparsematrix.jl index d9af41a9ab18b..c041c82df47fc 100644 --- a/base/sparse/sparsematrix.jl +++ b/base/sparse/sparsematrix.jl @@ -753,7 +753,7 @@ count (`length(q) == A.n`). This method is the parent of several methods performing transposition and permutation operations on [`SparseMatrixCSC`](@ref)s. As this method performs no argument checking, -prefer the safer child methods (`[c]transpose[!]`, `permute[!]`) to direct use. +prefer the safer child methods (`transpose[!]`, `adjoint[!]`, `permute[!]`) to direct use. This method implements the `HALFPERM` algorithm described in F. Gustavson, "Two fast algorithms for sparse matrices: multiplication and permuted transposition," ACM TOMS 4(3), @@ -826,14 +826,14 @@ function ftranspose!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}, f::Fu halfperm!(X, A, 1:A.n, f) end transpose!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = ftranspose!(X, A, identity) -adjoint!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = ftranspose!(X, A, conj) +adjoint!(X::SparseMatrixCSC{Tv,Ti}, A::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti} = ftranspose!(X, A, adjoint) function ftranspose(A::SparseMatrixCSC{Tv,Ti}, f::Function) where {Tv,Ti} X = SparseMatrixCSC(A.n, A.m, Vector{Ti}(A.m+1), Vector{Ti}(nnz(A)), Vector{Tv}(nnz(A))) halfperm!(X, A, 1:A.n, f) end transpose(A::SparseMatrixCSC) = ftranspose(A, identity) -adjoint(A::SparseMatrixCSC) = ftranspose(A, conj) +adjoint(A::SparseMatrixCSC) = ftranspose(A, adjoint) """ unchecked_noalias_permute!(X::SparseMatrixCSC{Tv,Ti}, @@ -3135,7 +3135,7 @@ end ## Structure query functions issymmetric(A::SparseMatrixCSC) = is_hermsym(A, identity) -ishermitian(A::SparseMatrixCSC) = is_hermsym(A, conj) +ishermitian(A::SparseMatrixCSC) = is_hermsym(A, adjoint) function is_hermsym(A::SparseMatrixCSC, check::Function) m, n = size(A) diff --git a/base/sparse/sparsevector.jl b/base/sparse/sparsevector.jl index dea981211f70b..4801ade39ad8b 100644 --- a/base/sparse/sparsevector.jl +++ b/base/sparse/sparsevector.jl @@ -1438,7 +1438,7 @@ vecnorm(x::SparseVectorUnion, p::Real=2) = vecnorm(nonzeros(x), p) # Transpose # (The only sparse matrix structure in base is CSC, so a one-row sparse matrix is worse than dense) @inline transpose(sv::SparseVector) = RowVector(sv) -@inline adjoint(sv::SparseVector) = RowVector(conj(sv)) +@inline adjoint(sv::SparseVector) = RowVector(Base.LinAlg._adjoint(sv)) ### BLAS Level-1 diff --git a/base/sysimg.jl b/base/sysimg.jl index 0a3963d4872a2..0be39254c4530 100644 --- a/base/sysimg.jl +++ b/base/sysimg.jl @@ -158,6 +158,7 @@ include("multinverses.jl") using .MultiplicativeInverses include("abstractarraymath.jl") include("arraymath.jl") +include("rowvector.jl") # define MIME"foo/bar" early so that we can overload 3-arg show struct MIME{mime} end @@ -231,6 +232,8 @@ using .Cartesian include("multidimensional.jl") include("permuteddimsarray.jl") using .PermutedDimsArrays +include("mappedarray.jl") +using .MappedArrays # nullable types include("nullable.jl") diff --git a/test/choosetests.jl b/test/choosetests.jl index cfb0a3d08a6f0..b7b45f8fcde83 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -49,7 +49,7 @@ function choosetests(choices = []) "checked", "bitset", "floatfuncs", "compile", "distributed", "inline", "boundscheck", "error", "ambiguous", "cartesian", "asmvariant", "osutils", "channels", "iostream", "specificity", "codegen", "codevalidation", - "reinterpretarray", "syntax" + "reinterpretarray", "syntax", "mappedarray" ] if isdir(joinpath(JULIA_HOME, Base.DOCDIR, "examples")) diff --git a/test/linalg/conjarray.jl b/test/linalg/conjarray.jl index 4ba4ac90ad5bb..ff4a783856d49 100644 --- a/test/linalg/conjarray.jl +++ b/test/linalg/conjarray.jl @@ -1,6 +1,6 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license -@testset "Core" begin +@testset "ConjArray core" begin m = [1+im 2; 2 4-im] cm = ConjMatrix(m) @test cm[1,1] == 1-im @@ -16,13 +16,9 @@ @test cv[1] == [1-im] end -@testset "RowVector conjugates" begin +@testset "RowVector conjugation" begin v = [1+im, 1-im] - rv = v' + rv = conj(v.') @test (parent(rv) isa ConjArray) @test rv' === v - - # Currently, view behavior defaults to only RowVectors. - @test isa((v').', Vector) - @test isa((v.')', Vector) end diff --git a/test/linalg/rowvector.jl b/test/linalg/rowvector.jl index bf265090d214b..063bdb5723a4d 100644 --- a/test/linalg/rowvector.jl +++ b/test/linalg/rowvector.jl @@ -4,18 +4,20 @@ v = [1,2,3] z = [1+im,2,3] - @test RowVector(v) == [1 2 3] + @test RowVector(v)::RowVector == [1 2 3] @test RowVector{Int}(v) == [1 2 3] @test size(RowVector{Int}(3)) === (1,3) @test size(RowVector{Int}(1,3)) === (1,3) @test size(RowVector{Int}((3,))) === (1,3) @test size(RowVector{Int}((1,3))) === (1,3) - @test_throws ErrorException RowVector{Float64, Vector{Int}}(v) + @test_throws TypeError RowVector{Float64, Vector{Int}}(v) @test (v.')::RowVector == [1 2 3] @test (v')::RowVector == [1 2 3] @test (z.')::RowVector == [1+im 2 3] + @test parent(z.') isa Vector @test (z')::RowVector == [1-im 2 3] + @test parent(z') isa Base.LinAlg.ConjVector rv = v.' tz = z.' @@ -54,6 +56,40 @@ @test vec(rv) === v end +@testset "Nested arrays" begin + v = [[1, 2]] + + @test v'::RowVector == reshape([[1 2]], (1,1)) + @test parent(v') isa Base.LinAlg.AdjointArray{<:RowVector} + @test conj(v')::RowVector == reshape([[1 2]], (1,1)) + @test parent(conj(v')) isa Base.LinAlg.AdjointArray{<:RowVector} + @test (v').'::Vector == [[1 2]] + @test (v')' === v + + @test v.'::RowVector == [[1, 2]] + @test parent(v.') isa Vector + @test conj(v.')::RowVector == [[1, 2]] + @test parent(conj(v.')) isa Vector{Int} + @test (v.').' === v + @test (v.')'::Vector == [[1 2]] + + z = [[1+im, 2]] + + @test z'::RowVector == reshape([[1-im 2]], (1,1)) + @test parent(z') isa Base.LinAlg.AdjointArray{<:RowVector} + @test conj(z')::RowVector == reshape([[1+im 2]], (1,1)) + @test parent(conj(z')) isa Base.LinAlg.ConjAdjointArray{<:RowVector} + @test (z').'::Vector{<:RowVector} == [[1-im 2]] + @test (z')' === z + + @test z.'::RowVector == [[1+im, 2]] + @test parent(z.') isa Vector + @test conj(z.')::RowVector == [[1-im, 2]] + @test parent(conj(z.')) isa ConjArray{<:Vector} + @test (z.').' === z + @test (z.')'::Vector{<:RowVector} == [[1-im 2]] +end + @testset "Diagonal ambiguity methods" begin d = Diagonal([1,2,3]) v = [2,3,4] diff --git a/test/mappedarray.jl b/test/mappedarray.jl new file mode 100644 index 0000000000000..90c38b8c162f7 --- /dev/null +++ b/test/mappedarray.jl @@ -0,0 +1,15 @@ +@test @inferred(MappedArray(identity, [1,2,3]))::MappedArray == [1,2,3] +@test @inferred(MappedVector(identity, [1,2,3]))::MappedArray == [1,2,3] +@test @inferred(MappedArray(identity, [1 2; 3 4]))::MappedArray == [1 2; 3 4] +@test @inferred(MappedMatrix(identity, [1 2; 3 4]))::MappedArray == [1 2; 3 4] + +a = [1,2,3] +b = @inferred(MappedArray(x -> x + 10, a))::MappedArray +@test b[2] === 12 +@test_throws ErrorException b[2] = 20 +c = @inferred(MappedArray(x -> x + 10, x -> x - 10, a))::MappedArray +@test c[2] === 12 +@test (c[2] = 20; a[2] === 10) +@test (c[3] = 30.0; a[3] === 20) + +@test Base.IndexStyle(b) === Base.IndexStyle(a) \ No newline at end of file