changeset 498:5a600ec40ccc feature/outer_product

Merge in default
author Jonatan Werpers <jonatan@werpers.com>
date Thu, 05 Nov 2020 15:27:04 +0100
parents 2dc2eac27f75 (current diff) d8075fb14418 (diff)
children 7b550c714f3f
files src/LazyTensors/lazy_tensor_operations.jl test/testLazyTensors.jl
diffstat 4 files changed, 85 insertions(+), 6 deletions(-) [+]
line wrap: on
line diff
--- a/TODO.md	Thu Nov 05 12:48:30 2020 +0100
+++ b/TODO.md	Thu Nov 05 15:27:04 2020 +0100
@@ -13,6 +13,7 @@
  - [ ] Fix indexing signatures. We should make sure we are not too specific. For the "inbetween" layers we don't know what type of index is coming so we should use `I...` instead of `I::Vararg{Int,R}` or probably better `I::Vararg{Any,R}`
  - [ ] Use `@inferred` in a lot of tests.
  - [ ] Make sure we are setting tolerances in tests in a consistent way
+ - [ ] Add check for correct domain sizes to lazy tensor operations using SizeMismatch
 
 ## Repo
  - [ ] Add Vidar to the authors list
--- a/src/LazyTensors/lazy_tensor_operations.jl	Thu Nov 05 12:48:30 2020 +0100
+++ b/src/LazyTensors/lazy_tensor_operations.jl	Thu Nov 05 15:27:04 2020 +0100
@@ -86,12 +86,9 @@
     t2::TM2
 
     @inline function TensorMappingComposition(t1::TensorMapping{T,R,K}, t2::TensorMapping{T,K,D}) where {T,R,K,D}
-        @boundscheck if domain_size(t1) != range_size(t2)
-            throw(DimensionMismatch("the first argument has domain size $(domain_size(t1)) while the second has range size $(range_size(t2)) "))
-        end
+        @boundscheck check_domain_size(t1, range_size(t2))
         return new{T,R,K,D, typeof(t1), typeof(t2)}(t1,t2)
     end
-    # Add check for matching sizes as a boundscheck
 end
 export TensorMappingComposition
 
@@ -170,6 +167,27 @@
 apply(tmi::IdentityMapping{T,D}, v::AbstractArray{T,D}, I::Vararg{Any,D}) where {T,D} = v[I...]
 apply_transpose(tmi::IdentityMapping{T,D}, v::AbstractArray{T,D}, I::Vararg{Any,D}) where {T,D} = v[I...]
 
+"""
+Base.:∘(tm, tmi)
+Base.:∘(tmi, tm)
+
+Composes a `Tensormapping` `tm` with an `IdentityMapping` `tmi`, by returning `tm`
+"""
+@inline function Base.:∘(tm::TensorMapping{T,R,D}, tmi::IdentityMapping{T,D}) where {T,R,D}
+    @boundscheck check_domain_size(tm, range_size(tmi))
+    return tm
+end
+
+@inline function Base.:∘(tmi::IdentityMapping{T,R}, tm::TensorMapping{T,R,D}) where {T,R,D}
+    @boundscheck check_domain_size(tmi, range_size(tm))
+    return tm
+end
+# Specialization for the case where tm is an IdentityMapping. Required to resolve ambiguity.
+@inline function Base.:∘(tm::IdentityMapping{T,D}, tmi::IdentityMapping{T,D}) where {T,D}
+    @boundscheck check_domain_size(tm, range_size(tmi))
+    return tmi
+end
+
 
 """
     InflatedTensorMapping{T,R,D} <: TensorMapping{T,R,D}
@@ -203,8 +221,20 @@
 The outer product of `before`, `tm` and `after`, where `before` and `after` are `IdentityMapping`s.
 
 If one of `before` or `after` is left out, a 0-dimensional `IdentityMapping` is used as the default value.
+
+If `tm` already is an `InflatedTensorMapping`, `before` and `after` will be extended instead of
+creating a nested `InflatedTensorMapping`.
 """
 InflatedTensorMapping(::IdentityMapping, ::TensorMapping, ::IdentityMapping)
+
+function InflatedTensorMapping(before, itm::InflatedTensorMapping, after)
+    return InflatedTensorMapping(
+        IdentityMapping(before.size...,  itm.before.size...),
+        itm.tm,
+        IdentityMapping(itm.after.size..., after.size...),
+    )
+end
+
 InflatedTensorMapping(before::IdentityMapping, tm::TensorMapping{T}) where T = InflatedTensorMapping(before,tm,IdentityMapping{T}())
 InflatedTensorMapping(tm::TensorMapping{T}, after::IdentityMapping) where T = InflatedTensorMapping(IdentityMapping{T}(),tm,after)
 # Resolve ambiguity between the two previous methods
@@ -280,7 +310,6 @@
 flatten_tuple(t::Tuple) = ((flatten_tuple.(t)...)...,) # simplify?
 flatten_tuple(ts::Vararg) = flatten_tuple(ts)
 
-
 """
    LazyOuterProduct(tms...)
 
@@ -334,3 +363,20 @@
 export ⊗
 
 # TBD: Should we implement simplifications for outer products of LazyIdentities other LazyIdentities or Inflated tensormappings?
+
+function check_domain_size(tm::TensorMapping, sz)
+    if domain_size(tm) != sz
+        throw(SizeMismatch(tm,sz))
+    end
+end
+
+struct SizeMismatch <: Exception
+    tm::TensorMapping
+    sz
+end
+export SizeMismatch
+
+function Base.showerror(io::IO, err::SizeMismatch)
+    print(io, "SizeMismatch: ")
+    print(io, "domain size $(domain_size(err.tm)) of TensorMapping not matching size $(err.sz)")
+end
--- a/src/LazyTensors/tensor_mapping.jl	Thu Nov 05 12:48:30 2020 +0100
+++ b/src/LazyTensors/tensor_mapping.jl	Thu Nov 05 15:27:04 2020 +0100
@@ -64,4 +64,11 @@
 
 export range_size, domain_size
 
+"""
+    eltype(::TensorMapping{T})
+
+The type of elements the TensorMapping acts on.
+"""
+Base.eltype(::TensorMapping{T}) where T = T
+
 # TODO: Think about boundschecking!
--- a/test/testLazyTensors.jl	Thu Nov 05 12:48:30 2020 +0100
+++ b/test/testLazyTensors.jl	Thu Nov 05 15:27:04 2020 +0100
@@ -12,6 +12,8 @@
     @test range_dim(DummyMapping{Int,2,3}()) == 2
     @test domain_dim(DummyMapping{Int,2,3}()) == 3
     @test apply(DummyMapping{Int,2,3}(), zeros(Int, (0,0,0)),(Index{Unknown}(0),Index{Unknown}(0))) == :apply
+    @test eltype(DummyMapping{Int,2,3}()) == Int
+    @test eltype(DummyMapping{Float64,2,3}()) == Float64
 end
 
 @testset "Mapping transpose" begin
@@ -177,6 +179,7 @@
     @test_throws BoundsError (v1 +̃  v2)[4]
     v2 = [1., 2, 3, 4]
     # Test that size of arrays is asserted when not specified inbounds
+    # TODO: Replace these errors with SizeMismatch
     @test_throws DimensionMismatch v1 +̃ v2
 
     # Test operations on LazyArray
@@ -193,6 +196,7 @@
     @test_throws BoundsError (v1 + v2)[4]
     v2 = [1., 2, 3, 4]
     # Test that size of arrays is asserted when not specified inbounds
+    # TODO: Replace these errors with SizeMismatch
     @test_throws DimensionMismatch v1 + v2
 end
 
@@ -226,7 +230,7 @@
     @test Ã∘B̃ isa TensorMappingComposition
     @test range_size(Ã∘B̃) == (2,)
     @test domain_size(Ã∘B̃) == (4,)
-    @test_throws DimensionMismatch B̃∘Ã
+    @test_throws SizeMismatch B̃∘Ã
 
     # @test @inbounds B̃∘Ã # Should not error even though dimensions don't match. (Since ]test runs with forced boundschecking this is currently not testable 2020-10-16)
 
@@ -312,6 +316,17 @@
 
     @inferred range_dim(I)
     @inferred domain_dim(I)
+
+    Ã = rand(4,2)
+    A = LazyLinearMap(Ã,(1,),(2,))
+    I1 = IdentityMapping{Float64}(2)
+    I2 = IdentityMapping{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 "InflatedTensorMapping" begin
@@ -381,6 +396,16 @@
     @inferred apply(tm,v,Index{Unknown}.((1,2,3,2,2,4))...)
     @inferred (tm*v)[1,2,3,2,2,4]
 
+    @testset "InflatedTensorMapping of InflatedTensorMapping" begin
+        A = ScalingOperator(2.0,(2,3))
+        itm = InflatedTensorMapping(I(3,2), A, I(4))
+        @test  InflatedTensorMapping(I(4), itm, I(2)) == InflatedTensorMapping(I(4,3,2), A, I(4,2))
+        @test  InflatedTensorMapping(itm, I(2)) == InflatedTensorMapping(I(3,2), A, I(4,2))
+        @test  InflatedTensorMapping(I(4), itm) == InflatedTensorMapping(I(4,3,2), A, I(4))
+
+        @test InflatedTensorMapping(I(2), I(2), I(2)) isa InflatedTensorMapping # The constructor should always return its type.
+    end
+
 end
 
 @testset "slice_tuple" begin