Skip to content

WIP: review before version 3 #119

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Dec 9, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 25 additions & 19 deletions src/LinearMaps.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ end

abstract type LinearMap{T} end

const MapOrMatrix{T} = Union{LinearMap{T},AbstractMatrix{T}}
const RealOrComplex = Union{Real,Complex}
const MapOrMatrix{T} = Union{LinearMap{T}, AbstractMatrix{T}}
const RealOrComplex = Union{Real, Complex}

Base.eltype(::LinearMap{T}) where {T} = T

Expand Down Expand Up @@ -45,7 +45,8 @@ LinearAlgebra.ishermitian(::LinearMap) = false # default assumptions
LinearAlgebra.isposdef(::LinearMap) = false # default assumptions

Base.ndims(::LinearMap) = 2
Base.size(A::LinearMap, n) = (n==1 || n==2 ? size(A)[n] : error("LinearMap objects have only 2 dimensions"))
Base.size(A::LinearMap, n) =
(n==1 || n==2 ? size(A)[n] : error("LinearMap objects have only 2 dimensions"))
Base.length(A::LinearMap) = size(A)[1] * size(A)[2]

"""
Expand Down Expand Up @@ -115,8 +116,9 @@ end
mul!(Y::AbstractVecOrMat, A::LinearMap, B::AbstractVector) -> Y
mul!(Y::AbstractMatrix, A::LinearMap, B::AbstractMatrix) -> Y

Calculates the action of the linear map `A` on the vector or matrix `B` and stores the result in `Y`,
overwriting the existing value of `Y`. Note that `Y` must not be aliased with either `A` or `B`.
Calculates the action of the linear map `A` on the vector or matrix `B` and stores the
result in `Y`, overwriting the existing value of `Y`. Note that `Y` must not be aliased
with either `A` or `B`.

## Examples
```jldoctest; setup=(using LinearAlgebra, LinearMaps)
Expand Down Expand Up @@ -144,26 +146,26 @@ end
mul!(C::AbstractVecOrMat, A::LinearMap, B::AbstractVector, α, β) -> C
mul!(C::AbstractMatrix, A::LinearMap, B::AbstractMatrix, α, β) -> C

Combined inplace multiply-add ``A B α + C β``. The result is stored in `C` by overwriting it.
Note that `C` must not be aliased with either `A` or `B`.
Combined inplace multiply-add ``A B α + C β``. The result is stored in `C` by overwriting
it. Note that `C` must not be aliased with either `A` or `B`.

## Examples
```jldoctest; setup=(using LinearAlgebra, LinearMaps)
julia> A=LinearMap([1.0 2.0; 3.0 4.0]); B=[1.0, 1.0]; C=[1.0, 3.0];

julia> mul!(C, A, B, 100.0, 10.0) === C
true

julia> C
2-element Array{Float64,1}:
310.0
730.0

julia> A=LinearMap([1.0 2.0; 3.0 4.0]); B=[1.0 1.0; 1.0 1.0]; C=[1.0 2.0; 3.0 4.0];

julia> mul!(C, A, B, 100.0, 10.0) === C
true

julia> C
2×2 Array{Float64,2}:
310.0 320.0
Expand Down Expand Up @@ -233,6 +235,8 @@ function _unsafe_mul!(y::AbstractMatrix, A::LinearMap, x::AbstractMatrix, α, β
return _generic_mapmat_mul!(y, A, x, α, β)
end

const LinearMapTuple = Tuple{Vararg{LinearMap}}

include("left.jl") # left multiplication by a transpose or adjoint vector
include("transpose.jl") # transposing linear maps
include("wrappedmap.jl") # wrap a matrix of linear map in a new type, thereby allowing to alter its properties
Expand All @@ -257,15 +261,17 @@ with the purpose of redefining its properties via the keyword arguments `kwargs`
a `UniformScaling` object `J` with specified (square) dimension `M`; or
from a function or callable object `f`. In the latter case, one also needs to specify
the size of the equivalent matrix representation `(M, N)`, i.e., for functions `f` acting
on length `N` vectors and producing length `M` vectors (with default value `N=M`). Preferably,
also the `eltype` `T` of the corresponding matrix representation needs to be specified, i.e.
whether the action of `f` on a vector will be similar to, e.g., multiplying by numbers of type `T`.
If not specified, the devault value `T=Float64` will be assumed. Optionally, a corresponding
function `fc` can be specified that implements the adjoint (=transpose in the real case) of `f`.
on length `N` vectors and producing length `M` vectors (with default value `N=M`).
Preferably, also the `eltype` `T` of the corresponding matrix representation needs to be
specified, i.e. whether the action of `f` on a vector will be similar to, e.g., multiplying
by numbers of type `T`. If not specified, the devault value `T=Float64` will be assumed.
Optionally, a corresponding function `fc` can be specified that implements the adjoint
(=transpose in the real case) of `f`.

The keyword arguments and their default values for the function-based constructor are:
* `issymmetric::Bool = false` : whether `A` or `f` acts as a symmetric matrix
* `ishermitian::Bool = issymmetric & T<:Real` : whether `A` or `f` acts as a Hermitian matrix
* `ishermitian::Bool = issymmetric & T<:Real` : whether `A` or `f` acts as a Hermitian
matrix
* `isposdef::Bool = false` : whether `A` or `f` acts as a positive definite matrix.
For existing linear maps or matrices `A`, the default values will be taken by calling
`issymmetric`, `ishermitian` and `isposdef` on the existing object `A`.
Expand All @@ -274,8 +280,8 @@ For the function-based constructor, there is one more keyword argument:
* `ismutating::Bool` : flags whether the function acts as a mutating matrix multiplication
`f(y,x)` where the result vector `y` is the first argument (in case of `true`),
or as a normal matrix multiplication that is called as `y=f(x)` (in case of `false`).
The default value is guessed by looking at the number of arguments of the first occurrence
of `f` in the method table.
The default value is guessed by looking at the number of arguments of the first
occurrence of `f` in the method table.
"""
LinearMap(A::MapOrMatrix; kwargs...) = WrappedMap(A; kwargs...)
LinearMap(J::UniformScaling, M::Int) = UniformScalingMap(J.λ, M)
Expand Down
49 changes: 30 additions & 19 deletions src/composition.jl
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
struct CompositeMap{T, As<:Tuple{Vararg{LinearMap}}} <: LinearMap{T}
struct CompositeMap{T, As<:LinearMapTuple} <: LinearMap{T}
maps::As # stored in order of application to vector
function CompositeMap{T, As}(maps::As) where {T, As}
N = length(maps)
for n in 2:N
check_dim_mul(maps[n], maps[n-1])
end
for n in eachindex(maps)
A = maps[n]
@assert promote_type(T, eltype(A)) == T "eltype $(eltype(A)) cannot be promoted to $T in CompositeMap constructor"
for TA in Base.Generator(eltype, maps)
promote_type(T, TA) == T ||
error("eltype $TA cannot be promoted to $T in CompositeMap constructor")
end
new{T, As}(maps)
end
end
CompositeMap{T}(maps::As) where {T, As<:Tuple{Vararg{LinearMap}}} = CompositeMap{T, As}(maps)
CompositeMap{T}(maps::As) where {T, As<:LinearMapTuple} = CompositeMap{T, As}(maps)

# basic methods
Base.size(A::CompositeMap) = (size(A.maps[end], 1), size(A.maps[1], 2))
Expand All @@ -27,7 +27,8 @@ for (f, _f, g) in ((:issymmetric, :_issymmetric, :transpose),
LinearAlgebra.$f(A::CompositeMap) = $_f(A.maps)
$_f(maps::Tuple{}) = true
$_f(maps::Tuple{<:LinearMap}) = $f(maps[1])
$_f(maps::Tuple{Vararg{<:LinearMap}}) = maps[end] == $g(maps[1]) && $_f(Base.front(Base.tail(maps)))
$_f(maps::Tuple{Vararg{<:LinearMap}}) =
maps[end] == $g(maps[1]) && $_f(Base.front(Base.tail(maps)))
# since the introduction of ScaledMap, the following cases cannot occur
# function $_f(maps::Tuple{Vararg{<:LinearMap}}) # length(maps) >= 2
# if maps[1] isa UniformScalingMap{<:RealOrComplex}
Expand Down Expand Up @@ -117,30 +118,40 @@ function Base.:(*)(A₁::CompositeMap, A₂::CompositeMap)
end

# special transposition behavior
LinearAlgebra.transpose(A::CompositeMap{T}) where {T} = CompositeMap{T}(map(transpose, reverse(A.maps)))
LinearAlgebra.adjoint(A::CompositeMap{T}) where {T} = CompositeMap{T}(map(adjoint, reverse(A.maps)))
LinearAlgebra.transpose(A::CompositeMap{T}) where {T} =
CompositeMap{T}(map(transpose, reverse(A.maps)))
LinearAlgebra.adjoint(A::CompositeMap{T}) where {T} =
CompositeMap{T}(map(adjoint, reverse(A.maps)))

# comparison of CompositeMap objects
Base.:(==)(A::CompositeMap, B::CompositeMap) = (eltype(A) == eltype(B) && A.maps == B.maps)

# multiplication with vectors
function _unsafe_mul!(y::AbstractVecOrMat, A::CompositeMap{<:Any,<:Tuple{LinearMap}}, x::AbstractVector)
return _unsafe_mul!(y, A.maps[1], x)
end
function _unsafe_mul!(y::AbstractVecOrMat, A::CompositeMap{<:Any,<:Tuple{LinearMap,LinearMap}}, x::AbstractVector)
_compositemul!(y, A, x, similar(y, size(A.maps[1], 1)))
end
function _unsafe_mul!(y::AbstractVecOrMat, A::CompositeMap{<:Any,<:Tuple{Vararg{LinearMap}}}, x::AbstractVector)
_compositemul!(y, A, x, similar(y, size(A.maps[1], 1)), similar(y, size(A.maps[2], 1)))
end
const CompositeMap1 = CompositeMap{<:Any,<:Tuple{LinearMap}}
const CompositeMap2 = CompositeMap{<:Any,<:Tuple{LinearMap,LinearMap}}
_unsafe_mul!(y::AbstractVecOrMat, A::CompositeMap1, x::AbstractVector) =
_unsafe_mul!(y, A.maps[1], x)
# function _unsafe_mul!(y::AbstractVecOrMat, A::CompositeMap2, x::AbstractVector)
# _compositemul!(y, A, x, similar(y, size(A.maps[1], 1)))
# end
# function _unsafe_mul!(y::AbstractVecOrMat, A::CompositeMap, x::AbstractVector)
# _compositemul!(y, A, x, similar(y, size(A.maps[1], 1)), similar(y, size(A.maps[2], 1)))
# end
_unsafe_mul!(y::AbstractVecOrMat, A::CompositeMap, x::AbstractVector) =
_compositemul!(y, A, x)

function _compositemul!(y::AbstractVecOrMat, A::CompositeMap{<:Any,<:Tuple{LinearMap,LinearMap}}, x::AbstractVector, z::AbstractVector)
function _compositemul!(y::AbstractVecOrMat, A::CompositeMap2, x::AbstractVector)
# was there any advantage in having this z an argument, this is not a public method?
z = similar(y, size(A.maps[1], 1))
# no size checking, will be done by individual maps
_unsafe_mul!(z, A.maps[1], x)
_unsafe_mul!(y, A.maps[2], z)
return y
end
function _compositemul!(y::AbstractVecOrMat, A::CompositeMap{<:Any,<:Tuple{Vararg{LinearMap}}}, x::AbstractVector, source::AbstractVector, dest::AbstractVector)
function _compositemul!(y::AbstractVecOrMat, A::CompositeMap, x::AbstractVector)
# was there any advantage in having these arguments, this is not a public method?
source = similar(y, size(A.maps[1], 1))
dest = similar(y, size(A.maps[2], 1))
# no size checking, will be done by individual maps
N = length(A.maps)
_unsafe_mul!(source, A.maps[1], x)
Expand Down
22 changes: 14 additions & 8 deletions src/functionmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,26 @@ function FunctionMap{T}(f::F1, fc::F2, M::Int, N::Int;
end

# additional constructors
FunctionMap{T}(f, M::Int; kwargs...) where {T} = FunctionMap{T}(f, nothing, M, M; kwargs...)
FunctionMap{T}(f, M::Int, N::Int; kwargs...) where {T} = FunctionMap{T}(f, nothing, M, N; kwargs...)
FunctionMap{T}(f, fc, M::Int; kwargs...) where {T} = FunctionMap{T}(f, fc, M, M; kwargs...)
FunctionMap{T}(f, M::Int; kwargs...) where {T} =
FunctionMap{T}(f, nothing, M, M; kwargs...)
FunctionMap{T}(f, M::Int, N::Int; kwargs...) where {T} =
FunctionMap{T}(f, nothing, M, N; kwargs...)
FunctionMap{T}(f, fc, M::Int; kwargs...) where {T} =
FunctionMap{T}(f, fc, M, M; kwargs...)

# properties
Base.size(A::FunctionMap) = (A.M, A.N)
Base.parent(A::FunctionMap) = (A.f, A.fc)
Base.parent(A::FunctionMap) = (A.f, A.fc) # is this meanigful/useful?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC the reason for parent was for manual inspection of wrapped maps, because show is now rather sparse. It may be used in the package only rarely, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps any parent-like method would be most useful if it returned all the arguments needed to recreate the object. at a minimum that would include size too. maybe a named tuple would be more useful too.
not sure i would use it much though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From Base:

help?> Base.parent
  parent(A)

  Return the underlying "parent array”. ...

So I see this making sense for WrappedMap, but other than that I am not entirely sure. Where is this used?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we use parent only somewhere in kronecker.jl, and then potentially in an inconsistent way. It's not obvious what parent should return given the wide range of LinearMap subtypes. I'd be fine with actually removing it. I introduced this when I cut down the show method to show much less details, and then I used it in one or two places as it was handy. But this is not really crucial.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another decision to be made: what shall we do with parent? Just remove? I think it's used internally only in one spot in the kronecker code, and could be worked around with a ternary if-clause, as I had it originally.

LinearAlgebra.issymmetric(A::FunctionMap) = A._issymmetric
LinearAlgebra.ishermitian(A::FunctionMap) = A._ishermitian
LinearAlgebra.isposdef(A::FunctionMap) = A._isposdef
ismutating(A::FunctionMap) = A._ismutating
_ismutating(f) = first(methods(f)).nargs == 3

# multiplication with vector
const TransposeFunctionMap = TransposeMap{<:Any, <:FunctionMap}
const AdjointFunctionMap = AdjointMap{<:Any, <:FunctionMap}

function Base.:(*)(A::FunctionMap, x::AbstractVector)
length(x) == A.N || throw(DimensionMismatch())
if ismutating(A)
Expand All @@ -41,7 +47,7 @@ function Base.:(*)(A::FunctionMap, x::AbstractVector)
end
return y
end
function Base.:(*)(A::AdjointMap{<:Any,<:FunctionMap}, x::AbstractVector)
function Base.:(*)(A::AdjointFunctionMap, x::AbstractVector)
Afun = A.lmap
ishermitian(Afun) && return Afun*x
length(x) == size(A, 2) || throw(DimensionMismatch())
Expand All @@ -67,7 +73,7 @@ function Base.:(*)(A::AdjointMap{<:Any,<:FunctionMap}, x::AbstractVector)
error("adjoint not implemented for $(A.lmap)")
end
end
function Base.:(*)(A::TransposeMap{<:Any,<:FunctionMap}, x::AbstractVector)
function Base.:(*)(A::TransposeFunctionMap, x::AbstractVector)
Afun = A.lmap
(issymmetric(Afun) || (isreal(A) && ishermitian(Afun))) && return Afun*x
length(x) == size(A, 2) || throw(DimensionMismatch())
Expand Down Expand Up @@ -105,7 +111,7 @@ function _unsafe_mul!(y::AbstractVecOrMat, A::FunctionMap, x::AbstractVector)
return y
end

function _unsafe_mul!(y::AbstractVecOrMat, At::TransposeMap{<:Any,<:FunctionMap}, x::AbstractVector)
function _unsafe_mul!(y::AbstractVecOrMat, At::TransposeFunctionMap, x::AbstractVector)
A = At.lmap
(issymmetric(A) || (isreal(A) && ishermitian(A))) && return _unsafe_mul!(y, A, x)
if A.fc !== nothing
Expand All @@ -126,7 +132,7 @@ function _unsafe_mul!(y::AbstractVecOrMat, At::TransposeMap{<:Any,<:FunctionMap}
end
end

function _unsafe_mul!(y::AbstractVecOrMat, Ac::AdjointMap{<:Any,<:FunctionMap}, x::AbstractVector)
function _unsafe_mul!(y::AbstractVecOrMat, Ac::AdjointFunctionMap, x::AbstractVector)
A = Ac.lmap
ishermitian(A) && return _unsafe_mul!(y, A, x)
if A.fc !== nothing
Expand Down
2 changes: 2 additions & 0 deletions src/left.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ end
function mul!(x::AbstractMatrix, y::AdjointAbsVecOrMat, A::LinearMap, α::Number, β::Number)
check_dim_mul(x, y, A)
_unsafe_mul!(x', conj(α)*A', y', true, conj(β))
# why not?: _unsafe_mul!(x', A', y', conj(α), conj(β))
return x
end

Expand All @@ -62,5 +63,6 @@ end
function mul!(x::AbstractMatrix, y::TransposeAbsVecOrMat, A::LinearMap, α::Number, β::Number)
check_dim_mul(x, y, A)
_unsafe_mul!(transpose(x), α*transpose(A), transpose(y), true, β)
# why not?: _unsafe_mul!(transpose(x), transpose(A), transpose(y), α, β)
return x
end
29 changes: 15 additions & 14 deletions src/linearcombination.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
struct LinearCombination{T, As<:Tuple{Vararg{LinearMap}}} <: LinearMap{T}
struct LinearCombination{T, As<:LinearMapTuple} <: LinearMap{T}
maps::As
function LinearCombination{T, As}(maps::As) where {T, As}
N = length(maps)
Expand All @@ -19,9 +19,10 @@ MulStyle(A::LinearCombination) = MulStyle(A.maps...)
# basic methods
Base.size(A::LinearCombination) = size(A.maps[1])
Base.parent(A::LinearCombination) = A.maps
LinearAlgebra.issymmetric(A::LinearCombination) = all(issymmetric, A.maps) # sufficient but not necessary
LinearAlgebra.ishermitian(A::LinearCombination) = all(ishermitian, A.maps) # sufficient but not necessary
LinearAlgebra.isposdef(A::LinearCombination) = all(isposdef, A.maps) # sufficient but not necessary
# following conditions are sufficient but not necessary
LinearAlgebra.issymmetric(A::LinearCombination) = all(issymmetric, A.maps)
LinearAlgebra.ishermitian(A::LinearCombination) = all(ishermitian, A.maps)
LinearAlgebra.isposdef(A::LinearCombination) = all(isposdef, A.maps)

# adding linear maps
"""
Expand Down Expand Up @@ -59,24 +60,24 @@ end
Base.:(-)(A₁::LinearMap, A₂::LinearMap) = +(A₁, -A₂)

# comparison of LinearCombination objects, sufficient but not necessary
Base.:(==)(A::LinearCombination, B::LinearCombination) = (eltype(A) == eltype(B) && A.maps == B.maps)
Base.:(==)(A::LinearCombination, B::LinearCombination) =
(eltype(A) == eltype(B) && A.maps == B.maps)

# special transposition behavior
LinearAlgebra.transpose(A::LinearCombination) = LinearCombination{eltype(A)}(map(transpose, A.maps))
LinearAlgebra.adjoint(A::LinearCombination) = LinearCombination{eltype(A)}(map(adjoint, A.maps))
LinearAlgebra.transpose(A::LinearCombination) =
LinearCombination{eltype(A)}(map(transpose, A.maps))
LinearAlgebra.adjoint(A::LinearCombination) =
LinearCombination{eltype(A)}(map(adjoint, A.maps))

# multiplication with vectors & matrices
for (intype, outtype) in ((AbstractVector, AbstractVecOrMat), (AbstractMatrix, AbstractMatrix))
for (In, Out) in ((AbstractVector, AbstractVecOrMat), (AbstractMatrix, AbstractMatrix))
@eval begin
function _unsafe_mul!(y::$outtype, A::LinearCombination, x::$intype)

function _unsafe_mul!(y::$Out, A::LinearCombination, x::$In)
_unsafe_mul!(y, first(A.maps), x)
_mul!(MulStyle(A), y, A, x, true)
return y
end

function _unsafe_mul!(y::$outtype, A::LinearCombination, x::$intype,
α::Number, β::Number)
function _unsafe_mul!(y::$Out, A::LinearCombination, x::$In, α::Number, β::Number)
if iszero(α) # trivial cases
iszero(β) && return fill!(y, zero(eltype(y)))
isone(β) && return y
Expand Down Expand Up @@ -109,7 +110,7 @@ function _mul!(::ThreeArg, y, A::LinearCombination, x, α)
return __mul!(y, Base.tail(A.maps), x, α, similar(y))
end

__mul!(y, As::Tuple{Vararg{LinearMap}}, x, α, z) =
__mul!(y, As::LinearMapTuple, x, α, z) =
__mul!(__mul!(y, first(As), x, α, z), Base.tail(As), x, α, z)
__mul!(y, A::Tuple{LinearMap}, x, α, z) = __mul!(y, first(A), x, α, z)
__mul!(y, A::LinearMap, x, α, z) = muladd!(MulStyle(A), y, A, x, α, z)
Expand Down
12 changes: 6 additions & 6 deletions src/scaledmap.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ ScaledMap(λ::S, lmap::A) where {S<:RealOrComplex,A<:LinearMap} =
Base.size(A::ScaledMap) = size(A.lmap)
Base.parent(A::ScaledMap) = (A.λ, A.lmap)
Base.isreal(A::ScaledMap) = isreal(A.λ) && isreal(A.lmap)
LinearAlgebra.issymmetric(A::ScaledMap) = issymmetric(A.lmap)
LinearAlgebra.ishermitian(A::ScaledMap) = ishermitian(A.lmap)
LinearAlgebra.issymmetric(A::ScaledMap) = isreal(A.λ) && issymmetric(A.lmap)
LinearAlgebra.ishermitian(A::ScaledMap) = isreal(A.λ) && ishermitian(A.lmap)
LinearAlgebra.isposdef(A::ScaledMap) = isposdef(A.λ) && isposdef(A.lmap)

Base.transpose(A::ScaledMap) = A.λ * transpose(A.lmap)
Base.adjoint(A::ScaledMap) = conj(A.λ) * adjoint(A.lmap)

# comparison (sufficient, not necessary)
Base.:(==)(A::ScaledMap, B::ScaledMap) =
(eltype(A) == eltype(B) && A.lmap == B.lmap) && A.λ == B.λ
eltype(A) == eltype(B) && A.lmap == B.lmap && A.λ == B.λ

# scalar multiplication and division
Base.:(*)(α::RealOrComplex, A::LinearMap) = ScaledMap(α, A)
Expand All @@ -43,12 +43,12 @@ Base.:(*)(A::ScaledMap, B::LinearMap) = A.λ * (A.lmap * B)
Base.:(*)(A::LinearMap, B::ScaledMap) = (A * B.lmap) * B.λ

# multiplication with vectors/matrices
for (intype, outtype) in ((AbstractVector, AbstractVecOrMat), (AbstractMatrix, AbstractMatrix))
for (In, Out) in ((AbstractVector, AbstractVecOrMat), (AbstractMatrix, AbstractMatrix))
@eval begin
function _unsafe_mul!(y::$outtype, A::ScaledMap, x::$intype)
function _unsafe_mul!(y::$Out, A::ScaledMap, x::$In)
return _unsafe_mul!(y, A.lmap, x, A.λ, false)
end
function _unsafe_mul!(y::$outtype, A::ScaledMap, x::$intype, α::Number, β::Number)
function _unsafe_mul!(y::$Out, A::ScaledMap, x::$In, α::Number, β::Number)
return _unsafe_mul!(y, A.lmap, x, A.λ * α, β)
end
end
Expand Down
2 changes: 1 addition & 1 deletion src/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function _show_typeof(A::LinearMap{T}) where {T}
split(string(typeof(A)), '{')[1] * '{' * string(T) * '}'
end

function print_maps(io::IO, maps::Tuple{Vararg{LinearMap}}, k)
function print_maps(io::IO, maps::LinearMapTuple, k)
n = length(maps)
str = ""
if get(io, :limit, true) && n > 10
Expand Down
Loading