Mercurial > repos > public > sbplib_julia
changeset 1002:271aa6ae1055 refactor/lazy_tensors
Split out a file for tensor types
author | Jonatan Werpers <jonatan@werpers.com> |
---|---|
date | Fri, 18 Mar 2022 22:18:04 +0100 |
parents | a3df203861d3 |
children | 7ef605b8f132 |
files | src/LazyTensors/LazyTensors.jl src/LazyTensors/lazy_tensor_operations.jl src/LazyTensors/tensor_types.jl test/LazyTensors/lazy_tensor_operations_test.jl test/LazyTensors/tensor_types_test.jl test/LazyTensors/tuple_manipulation_test.jl |
diffstat | 6 files changed, 194 insertions(+), 185 deletions(-) [+] |
line wrap: on
line diff
--- a/src/LazyTensors/LazyTensors.jl Fri Mar 18 22:01:25 2022 +0100 +++ b/src/LazyTensors/LazyTensors.jl Fri Mar 18 22:18:04 2022 +0100 @@ -12,6 +12,7 @@ export SizeMismatch include("lazy_tensor.jl") +include("tensor_types.jl") include("lazy_array.jl") include("lazy_tensor_operations.jl") include("tuple_manipulation.jl")
--- a/src/LazyTensors/lazy_tensor_operations.jl Fri Mar 18 22:01:25 2022 +0100 +++ b/src/LazyTensors/lazy_tensor_operations.jl Fri Mar 18 22:18:04 2022 +0100 @@ -1,8 +1,7 @@ -# TBD: Is there a good way to split this file? -# TODO: Split out functions for composition # TODO: We need to be really careful about good error messages. # TODO: Go over type parameters + """ LazyTensorApplication{T,R,D} <: LazyArray{T,R} @@ -54,7 +53,7 @@ range_size(tmt::LazyTensorTranspose) = domain_size(tmt.tm) domain_size(tmt::LazyTensorTranspose) = range_size(tmt.tm) -# TODO: Rename this + struct LazyTensorBinaryOperation{Op,T,R,D,T1<:LazyTensor{T,R,D},T2<:LazyTensor{T,R,D}} <: LazyTensor{T,D,R} tm1::T1 tm2::T2 @@ -102,66 +101,6 @@ """ - LazyLinearMap{T,R,D,...}(A, range_indicies, domain_indicies) - -LazyTensor defined by the AbstractArray A. `range_indicies` and `domain_indicies` define which indicies of A should -be considerd the range and domain of the LazyTensor. Each set of indices must be ordered in ascending order. - -For instance, if A is a m x n matrix, and range_size = (1,), domain_size = (2,), then the LazyLinearMap performs the -standard matrix-vector product on vectors of size n. -""" -struct LazyLinearMap{T,R,D, RD, AA<:AbstractArray{T,RD}} <: LazyTensor{T,R,D} - A::AA - range_indicies::NTuple{R,Int} - domain_indicies::NTuple{D,Int} - - function LazyLinearMap(A::AA, range_indicies::NTuple{R,Int}, domain_indicies::NTuple{D,Int}) where {T,R,D, RD, AA<:AbstractArray{T,RD}} - if !issorted(range_indicies) || !issorted(domain_indicies) - throw(DomainError("range_indicies and domain_indicies must be sorted in ascending order")) - end - - return new{T,R,D,RD,AA}(A,range_indicies,domain_indicies) - end -end - -range_size(llm::LazyLinearMap) = size(llm.A)[[llm.range_indicies...]] -domain_size(llm::LazyLinearMap) = size(llm.A)[[llm.domain_indicies...]] - -function apply(llm::LazyLinearMap{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} - view_index = ntuple(i->:,ndims(llm.A)) - for i ∈ 1:R - view_index = Base.setindex(view_index, Int(I[i]), llm.range_indicies[i]) - end - A_view = @view llm.A[view_index...] - return sum(A_view.*v) -end - -function apply_transpose(llm::LazyLinearMap{T,R,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,D} - apply(LazyLinearMap(llm.A, llm.domain_indicies, llm.range_indicies), v, I...) -end - - -""" - IdentityTensor{T,D} <: LazyTensor{T,D,D} - -The lazy identity LazyTensor for a given size. Usefull for building up higher dimensional tensor mappings from lower -dimensional ones through outer products. Also used in the Implementation for InflatedLazyTensor. -""" -struct IdentityTensor{T,D} <: LazyTensor{T,D,D} - size::NTuple{D,Int} -end - -IdentityTensor{T}(size::NTuple{D,Int}) where {T,D} = IdentityTensor{T,D}(size) -IdentityTensor{T}(size::Vararg{Int,D}) where {T,D} = IdentityTensor{T,D}(size) -IdentityTensor(size::Vararg{Int,D}) where D = IdentityTensor{Float64,D}(size) - -range_size(tmi::IdentityTensor) = tmi.size -domain_size(tmi::IdentityTensor) = tmi.size - -apply(tmi::IdentityTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = v[I...] -apply_transpose(tmi::IdentityTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = v[I...] - -""" LazyTensorComposition(tm, tmi::IdentityTensor) LazyTensorComposition(tmi::IdentityTensor, tm) @@ -182,21 +121,6 @@ return tmi end -""" - ScalingTensor{T,D} <: LazyTensor{T,D,D} - -A lazy tensor that scales its input with `λ`. -""" -struct ScalingTensor{T,D} <: LazyTensor{T,D,D} - λ::T - size::NTuple{D,Int} -end - -LazyTensors.apply(tm::ScalingTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = tm.λ*v[I...] -LazyTensors.apply_transpose(tm::ScalingTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = tm.λ*v[I...] - -LazyTensors.range_size(m::ScalingTensor) = m.size -LazyTensors.domain_size(m::ScalingTensor) = m.size """ InflatedLazyTensor{T,R,D} <: LazyTensor{T,R,D} @@ -221,6 +145,7 @@ return new{T,R,D,D_before,R_middle,D_middle,D_after, typeof(tm)}(before, tm, after) end end + """ InflatedLazyTensor(before, tm, after) InflatedLazyTensor(before,tm)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/LazyTensors/tensor_types.jl Fri Mar 18 22:18:04 2022 +0100 @@ -0,0 +1,76 @@ +""" + IdentityTensor{T,D} <: LazyTensor{T,D,D} + +The lazy identity LazyTensor for a given size. Usefull for building up higher dimensional tensor mappings from lower +dimensional ones through outer products. Also used in the Implementation for InflatedLazyTensor. +""" +struct IdentityTensor{T,D} <: LazyTensor{T,D,D} + size::NTuple{D,Int} +end + +IdentityTensor{T}(size::NTuple{D,Int}) where {T,D} = IdentityTensor{T,D}(size) +IdentityTensor{T}(size::Vararg{Int,D}) where {T,D} = IdentityTensor{T,D}(size) +IdentityTensor(size::Vararg{Int,D}) where D = IdentityTensor{Float64,D}(size) + +range_size(tmi::IdentityTensor) = tmi.size +domain_size(tmi::IdentityTensor) = tmi.size + +apply(tmi::IdentityTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = v[I...] +apply_transpose(tmi::IdentityTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = v[I...] + + +""" + ScalingTensor{T,D} <: LazyTensor{T,D,D} + +A lazy tensor that scales its input with `λ`. +""" +struct ScalingTensor{T,D} <: LazyTensor{T,D,D} + λ::T + size::NTuple{D,Int} +end + +LazyTensors.apply(tm::ScalingTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = tm.λ*v[I...] +LazyTensors.apply_transpose(tm::ScalingTensor{T,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,D}) where {T,D} = tm.λ*v[I...] + +LazyTensors.range_size(m::ScalingTensor) = m.size +LazyTensors.domain_size(m::ScalingTensor) = m.size + + +""" + LazyLinearMap{T,R,D,...}(A, range_indicies, domain_indicies) + +LazyTensor defined by the AbstractArray A. `range_indicies` and `domain_indicies` define which indicies of A should +be considerd the range and domain of the LazyTensor. Each set of indices must be ordered in ascending order. + +For instance, if A is a m x n matrix, and range_size = (1,), domain_size = (2,), then the LazyLinearMap performs the +standard matrix-vector product on vectors of size n. +""" +struct LazyLinearMap{T,R,D, RD, AA<:AbstractArray{T,RD}} <: LazyTensor{T,R,D} + A::AA + range_indicies::NTuple{R,Int} + domain_indicies::NTuple{D,Int} + + function LazyLinearMap(A::AA, range_indicies::NTuple{R,Int}, domain_indicies::NTuple{D,Int}) where {T,R,D, RD, AA<:AbstractArray{T,RD}} + if !issorted(range_indicies) || !issorted(domain_indicies) + throw(DomainError("range_indicies and domain_indicies must be sorted in ascending order")) + end + + return new{T,R,D,RD,AA}(A,range_indicies,domain_indicies) + end +end + +range_size(llm::LazyLinearMap) = size(llm.A)[[llm.range_indicies...]] +domain_size(llm::LazyLinearMap) = size(llm.A)[[llm.domain_indicies...]] + +function apply(llm::LazyLinearMap{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} + view_index = ntuple(i->:,ndims(llm.A)) + for i ∈ 1:R + view_index = Base.setindex(view_index, Int(I[i]), llm.range_indicies[i]) + end + A_view = @view llm.A[view_index...] + return sum(A_view.*v) +end + +function apply_transpose(llm::LazyLinearMap{T,R,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,D} + apply(LazyLinearMap(llm.A, llm.domain_indicies, llm.range_indicies), v, I...) +end
--- a/test/LazyTensors/lazy_tensor_operations_test.jl Fri Mar 18 22:01:25 2022 +0100 +++ b/test/LazyTensors/lazy_tensor_operations_test.jl Fri Mar 18 22:18:04 2022 +0100 @@ -24,6 +24,7 @@ @test domain_size(m') == :range_size end + @testset "TensorApplication" begin struct SizeDoublingMapping{T,R,D} <: LazyTensor{T,R,D} domain_size::NTuple{D,Int} @@ -108,6 +109,7 @@ end end + @testset "LazyTensor binary operations" begin A = ScalingTensor(2.0, (3,)) B = ScalingTensor(3.0, (3,)) @@ -155,111 +157,6 @@ @test ((Ã∘B̃)'*ComplexF64[1.,2.])[1] isa ComplexF64 end -@testset "LazyLinearMap" begin - # Test a standard matrix-vector product - # mapping vectors of size 4 to vectors of size 3. - A = rand(3,4) - à = LazyLinearMap(A, (1,), (2,)) - v = rand(4) - w = rand(3) - - @test à isa LazyLinearMap{T,1,1} where T - @test à isa LazyTensor{T,1,1} where T - @test range_size(Ã) == (3,) - @test domain_size(Ã) == (4,) - - @test Ã*ones(4) ≈ A*ones(4) atol=5e-13 - @test Ã*v ≈ A*v atol=5e-13 - @test Ã'*w ≈ A'*w - - A = rand(2,3,4) - @test_throws DomainError LazyLinearMap(A, (3,1), (2,)) - - # Test more exotic mappings - B = rand(3,4,2) - # Map vectors of size 2 to matrices of size (3,4) - B̃ = LazyLinearMap(B, (1,2), (3,)) - v = rand(2) - - @test range_size(B̃) == (3,4) - @test domain_size(B̃) == (2,) - @test B̃ isa LazyTensor{T,2,1} where T - @test B̃*ones(2) ≈ B[:,:,1] + B[:,:,2] atol=5e-13 - @test B̃*v ≈ B[:,:,1]*v[1] + B[:,:,2]*v[2] atol=5e-13 - - # Map matrices of size (3,2) to vectors of size 4 - B̃ = LazyLinearMap(B, (2,), (1,3)) - v = rand(3,2) - - @test range_size(B̃) == (4,) - @test domain_size(B̃) == (3,2) - @test B̃ isa LazyTensor{T,1,2} where T - @test B̃*ones(3,2) ≈ B[1,:,1] + B[2,:,1] + B[3,:,1] + - B[1,:,2] + B[2,:,2] + B[3,:,2] atol=5e-13 - @test B̃*v ≈ B[1,:,1]*v[1,1] + B[2,:,1]*v[2,1] + B[3,:,1]*v[3,1] + - B[1,:,2]v[1,2] + B[2,:,2]*v[2,2] + B[3,:,2]*v[3,2] atol=5e-13 - - - # TODO: - # @inferred (B̃*v)[2] -end - - -@testset "IdentityTensor" begin - @test IdentityTensor{Float64}((4,5)) isa IdentityTensor{T,2} where T - @test IdentityTensor{Float64}((4,5)) isa LazyTensor{T,2,2} where T - @test IdentityTensor{Float64}((4,5)) == IdentityTensor{Float64}(4,5) - - @test IdentityTensor(3,2) isa IdentityTensor{Float64,2} - - for sz ∈ [(4,5),(3,),(5,6,4)] - I = IdentityTensor{Float64}(sz) - v = rand(sz...) - @test I*v == v - @test I'*v == v - - v = rand(ComplexF64,sz...) - @test I*v == v - @test I'*v == v - - @test range_size(I) == sz - @test domain_size(I) == sz - end - - I = IdentityTensor{Float64}((4,5)) - v = rand(4,5) - @inferred (I*v)[3,2] - @inferred (I'*v)[3,2] - @inferred range_size(I) - - @inferred range_dim(I) - @inferred domain_dim(I) - - à = rand(4,2) - A = LazyLinearMap(Ã,(1,),(2,)) - I1 = IdentityTensor{Float64}(2) - I2 = IdentityTensor{Float64}(4) - @test A∘I1 == A - @test I2∘A == A - @test I1∘I1 == I1 - @test_throws SizeMismatch I1∘A - @test_throws SizeMismatch A∘I2 - @test_throws SizeMismatch I1∘I2 -end - -@testset "ScalingTensor" begin - st = ScalingTensor(2.,(3,4)) - @test st isa LazyTensor{Float64, 2, 2} - @test range_size(st) == (3,4) - @test domain_size(st) == (3,4) - - v = rand(3,4) - @test st*v == 2.0 .* v - @test st'*v == 2.0 .* v - - @inferred (st*v)[2,2] - @inferred (st'*v)[2,2] -end @testset "InflatedLazyTensor" begin I(sz...) = IdentityTensor(sz...) @@ -395,7 +292,6 @@ end @testset "LazyOuterProduct" begin - A = ScalingTensor(2.0, (5,)) B = ScalingTensor(3.0, (3,)) C = ScalingTensor(5.0, (3,2)) @@ -445,5 +341,4 @@ I2 = IdentityTensor(4) @test I1⊗Ã⊗I2 == InflatedLazyTensor(I1, Ã, I2) end - end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/LazyTensors/tensor_types_test.jl Fri Mar 18 22:18:04 2022 +0100 @@ -0,0 +1,109 @@ +using Test +using Sbplib.LazyTensors + +@testset "IdentityTensor" begin + @test IdentityTensor{Float64}((4,5)) isa IdentityTensor{T,2} where T + @test IdentityTensor{Float64}((4,5)) isa LazyTensor{T,2,2} where T + @test IdentityTensor{Float64}((4,5)) == IdentityTensor{Float64}(4,5) + + @test IdentityTensor(3,2) isa IdentityTensor{Float64,2} + + for sz ∈ [(4,5),(3,),(5,6,4)] + I = IdentityTensor{Float64}(sz) + v = rand(sz...) + @test I*v == v + @test I'*v == v + + v = rand(ComplexF64,sz...) + @test I*v == v + @test I'*v == v + + @test range_size(I) == sz + @test domain_size(I) == sz + end + + I = IdentityTensor{Float64}((4,5)) + v = rand(4,5) + @inferred (I*v)[3,2] + @inferred (I'*v)[3,2] + @inferred range_size(I) + + @inferred range_dim(I) + @inferred domain_dim(I) + + à = rand(4,2) + A = LazyLinearMap(Ã,(1,),(2,)) + I1 = IdentityTensor{Float64}(2) + I2 = IdentityTensor{Float64}(4) + @test A∘I1 == A + @test I2∘A == A + @test I1∘I1 == I1 + @test_throws SizeMismatch I1∘A + @test_throws SizeMismatch A∘I2 + @test_throws SizeMismatch I1∘I2 +end + + +@testset "ScalingTensor" begin + st = ScalingTensor(2.,(3,4)) + @test st isa LazyTensor{Float64, 2, 2} + @test range_size(st) == (3,4) + @test domain_size(st) == (3,4) + + v = rand(3,4) + @test st*v == 2.0 .* v + @test st'*v == 2.0 .* v + + @inferred (st*v)[2,2] + @inferred (st'*v)[2,2] +end + + +@testset "LazyLinearMap" begin + # Test a standard matrix-vector product + # mapping vectors of size 4 to vectors of size 3. + A = rand(3,4) + à = LazyLinearMap(A, (1,), (2,)) + v = rand(4) + w = rand(3) + + @test à isa LazyLinearMap{T,1,1} where T + @test à isa LazyTensor{T,1,1} where T + @test range_size(Ã) == (3,) + @test domain_size(Ã) == (4,) + + @test Ã*ones(4) ≈ A*ones(4) atol=5e-13 + @test Ã*v ≈ A*v atol=5e-13 + @test Ã'*w ≈ A'*w + + A = rand(2,3,4) + @test_throws DomainError LazyLinearMap(A, (3,1), (2,)) + + # Test more exotic mappings + B = rand(3,4,2) + # Map vectors of size 2 to matrices of size (3,4) + B̃ = LazyLinearMap(B, (1,2), (3,)) + v = rand(2) + + @test range_size(B̃) == (3,4) + @test domain_size(B̃) == (2,) + @test B̃ isa LazyTensor{T,2,1} where T + @test B̃*ones(2) ≈ B[:,:,1] + B[:,:,2] atol=5e-13 + @test B̃*v ≈ B[:,:,1]*v[1] + B[:,:,2]*v[2] atol=5e-13 + + # Map matrices of size (3,2) to vectors of size 4 + B̃ = LazyLinearMap(B, (2,), (1,3)) + v = rand(3,2) + + @test range_size(B̃) == (4,) + @test domain_size(B̃) == (3,2) + @test B̃ isa LazyTensor{T,1,2} where T + @test B̃*ones(3,2) ≈ B[1,:,1] + B[2,:,1] + B[3,:,1] + + B[1,:,2] + B[2,:,2] + B[3,:,2] atol=5e-13 + @test B̃*v ≈ B[1,:,1]*v[1,1] + B[2,:,1]*v[2,1] + B[3,:,1]*v[3,1] + + B[1,:,2]v[1,2] + B[2,:,2]*v[2,2] + B[3,:,2]*v[3,2] atol=5e-13 + + + # TODO: + # @inferred (B̃*v)[2] +end
--- a/test/LazyTensors/tuple_manipulation_test.jl Fri Mar 18 22:01:25 2022 +0100 +++ b/test/LazyTensors/tuple_manipulation_test.jl Fri Mar 18 22:18:04 2022 +0100 @@ -1,3 +1,6 @@ +using Test +using Sbplib.LazyTensors + @testset "split_index" begin @test LazyTensors.split_index(Val(2),Val(1),Val(2),Val(2),1,2,3,4,5,6) == ((1,2,:,5,6),(3,4)) @test LazyTensors.split_index(Val(2),Val(3),Val(2),Val(2),1,2,3,4,5,6) == ((1,2,:,:,:,5,6),(3,4))