comparison src/LazyTensors/lazy_tensor_operations.jl @ 1101:1e8270c18edb feature/lazy_tensors/pretty_printing

Merge default
author Jonatan Werpers <jonatan@werpers.com>
date Thu, 12 May 2022 21:52:47 +0200
parents 67969bd7e642 2e606d4c0ab1
children
comparison
equal deleted inserted replaced
1014:67969bd7e642 1101:1e8270c18edb
1 """ 1 """
2 LazyTensorApplication{T,R,D} <: LazyArray{T,R} 2 TensorApplication{T,R,D} <: LazyArray{T,R}
3 3
4 Struct for lazy application of a LazyTensor. Created using `*`. 4 Struct for lazy application of a LazyTensor. Created using `*`.
5 5
6 Allows the result of a `LazyTensor` applied to a vector to be treated as an `AbstractArray`. 6 Allows the result of a `LazyTensor` applied to a vector to be treated as an `AbstractArray`.
7 With a mapping `m` and a vector `v` the LazyTensorApplication object can be created by `m*v`. 7 With a mapping `m` and a vector `v` the TensorApplication object can be created by `m*v`.
8 The actual result will be calcualted when indexing into `m*v`. 8 The actual result will be calcualted when indexing into `m*v`.
9 """ 9 """
10 struct LazyTensorApplication{T,R,D, TM<:LazyTensor{<:Any,R,D}, AA<:AbstractArray{<:Any,D}} <: LazyArray{T,R} 10 struct TensorApplication{T,R,D, TM<:LazyTensor{<:Any,R,D}, AA<:AbstractArray{<:Any,D}} <: LazyArray{T,R}
11 t::TM 11 t::TM
12 o::AA 12 o::AA
13 13
14 function LazyTensorApplication(t::LazyTensor{<:Any,R,D}, o::AbstractArray{<:Any,D}) where {R,D} 14 function TensorApplication(t::LazyTensor{<:Any,R,D}, o::AbstractArray{<:Any,D}) where {R,D}
15 @boundscheck check_domain_size(t, size(o)) 15 @boundscheck check_domain_size(t, size(o))
16 I = ntuple(i->1, range_dim(t)) 16 I = ntuple(i->1, range_dim(t))
17 T = typeof(apply(t,o,I...)) 17 T = typeof(apply(t,o,I...))
18 return new{T,R,D,typeof(t), typeof(o)}(t,o) 18 return new{T,R,D,typeof(t), typeof(o)}(t,o)
19 end 19 end
20 end 20 end
21 21
22 function Base.getindex(ta::LazyTensorApplication{T,R}, I::Vararg{Any,R}) where {T,R} 22 function Base.getindex(ta::TensorApplication{T,R}, I::Vararg{Any,R}) where {T,R}
23 @boundscheck checkbounds(ta, Int.(I)...) 23 @boundscheck checkbounds(ta, Int.(I)...)
24 return apply(ta.t, ta.o, I...) 24 return @inbounds apply(ta.t, ta.o, I...)
25 end 25 end
26 Base.getindex(ta::LazyTensorApplication{T,1} where T, I::CartesianIndex{1}) = ta[Tuple(I)...] # Would otherwise be caught in the previous method. 26 Base.@propagate_inbounds Base.getindex(ta::TensorApplication{T,1} where T, I::CartesianIndex{1}) = ta[Tuple(I)...] # Would otherwise be caught in the previous method.
27 Base.size(ta::LazyTensorApplication) = range_size(ta.t) 27 Base.size(ta::TensorApplication) = range_size(ta.t)
28 28
29 29
30 """ 30 """
31 LazyTensorTranspose{T,R,D} <: LazyTensor{T,D,R} 31 TensorTranspose{T,R,D} <: LazyTensor{T,D,R}
32 32
33 Struct for lazy transpose of a LazyTensor. 33 Struct for lazy transpose of a LazyTensor.
34 34
35 If a mapping implements the the `apply_transpose` method this allows working with 35 If a mapping implements the the `apply_transpose` method this allows working with
36 the transpose of mapping `m` by using `m'`. `m'` will work as a regular LazyTensor lazily calling 36 the transpose of mapping `m` by using `m'`. `m'` will work as a regular LazyTensor lazily calling
37 the appropriate methods of `m`. 37 the appropriate methods of `m`.
38 """ 38 """
39 struct LazyTensorTranspose{T,R,D, TM<:LazyTensor{T,R,D}} <: LazyTensor{T,D,R} 39 struct TensorTranspose{T,R,D, TM<:LazyTensor{T,R,D}} <: LazyTensor{T,D,R}
40 tm::TM 40 tm::TM
41 end 41 end
42 42
43 # # TBD: Should this be implemented on a type by type basis or through a trait to provide earlier errors? 43 # # TBD: Should this be implemented on a type by type basis or through a trait to provide earlier errors?
44 # Jonatan 2020-09-25: Is the problem that you can take the transpose of any LazyTensor even if it doesn't implement `apply_transpose`? 44 # Jonatan 2020-09-25: Is the problem that you can take the transpose of any LazyTensor even if it doesn't implement `apply_transpose`?
45 Base.adjoint(tm::LazyTensor) = LazyTensorTranspose(tm) 45 Base.adjoint(tm::LazyTensor) = TensorTranspose(tm)
46 Base.adjoint(tmt::LazyTensorTranspose) = tmt.tm 46 Base.adjoint(tmt::TensorTranspose) = tmt.tm
47 47
48 apply(tmt::LazyTensorTranspose{T,R,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,D} = apply_transpose(tmt.tm, v, I...) 48 apply(tmt::TensorTranspose{T,R,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,D} = apply_transpose(tmt.tm, v, I...)
49 apply_transpose(tmt::LazyTensorTranspose{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmt.tm, v, I...) 49 apply_transpose(tmt::TensorTranspose{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmt.tm, v, I...)
50 50
51 range_size(tmt::LazyTensorTranspose) = domain_size(tmt.tm) 51 range_size(tmt::TensorTranspose) = domain_size(tmt.tm)
52 domain_size(tmt::LazyTensorTranspose) = range_size(tmt.tm) 52 domain_size(tmt::TensorTranspose) = range_size(tmt.tm)
53 53
54 54
55 struct LazyTensorBinaryOperation{Op,T,R,D,T1<:LazyTensor{T,R,D},T2<:LazyTensor{T,R,D}} <: LazyTensor{T,D,R} 55 struct ElementwiseTensorOperation{Op,T,R,D,T1<:LazyTensor{T,R,D},T2<:LazyTensor{T,R,D}} <: LazyTensor{T,D,R}
56 tm1::T1 56 tm1::T1
57 tm2::T2 57 tm2::T2
58 58
59 function LazyTensorBinaryOperation{Op,T,R,D}(tm1::T1,tm2::T2) where {Op,T,R,D, T1<:LazyTensor{T,R,D},T2<:LazyTensor{T,R,D}} 59 function ElementwiseTensorOperation{Op,T,R,D}(tm1::T1,tm2::T2) where {Op,T,R,D, T1<:LazyTensor{T,R,D},T2<:LazyTensor{T,R,D}}
60 @boundscheck check_domain_size(tm2, domain_size(tm1)) 60 @boundscheck check_domain_size(tm2, domain_size(tm1))
61 @boundscheck check_range_size(tm2, range_size(tm1)) 61 @boundscheck check_range_size(tm2, range_size(tm1))
62 return new{Op,T,R,D,T1,T2}(tm1,tm2) 62 return new{Op,T,R,D,T1,T2}(tm1,tm2)
63 end 63 end
64 end 64 end
65 65
66 LazyTensorBinaryOperation{Op}(s,t) where Op = LazyTensorBinaryOperation{Op,eltype(s), range_dim(s), domain_dim(s)}(s,t) 66 ElementwiseTensorOperation{Op}(s,t) where Op = ElementwiseTensorOperation{Op,eltype(s), range_dim(s), domain_dim(s)}(s,t)
67 67
68 apply(tmBinOp::LazyTensorBinaryOperation{:+,T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmBinOp.tm1, v, I...) + apply(tmBinOp.tm2, v, I...) 68 apply(tmBinOp::ElementwiseTensorOperation{:+,T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmBinOp.tm1, v, I...) + apply(tmBinOp.tm2, v, I...)
69 apply(tmBinOp::LazyTensorBinaryOperation{:-,T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmBinOp.tm1, v, I...) - apply(tmBinOp.tm2, v, I...) 69 apply(tmBinOp::ElementwiseTensorOperation{:-,T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} = apply(tmBinOp.tm1, v, I...) - apply(tmBinOp.tm2, v, I...)
70 70
71 range_size(tmBinOp::LazyTensorBinaryOperation) = range_size(tmBinOp.tm1) 71 range_size(tmBinOp::ElementwiseTensorOperation) = range_size(tmBinOp.tm1)
72 domain_size(tmBinOp::LazyTensorBinaryOperation) = domain_size(tmBinOp.tm1) 72 domain_size(tmBinOp::ElementwiseTensorOperation) = domain_size(tmBinOp.tm1)
73 73
74 74
75 """ 75 """
76 LazyTensorComposition{T,R,K,D} 76 TensorComposition{T,R,K,D}
77 77
78 Lazily compose two `LazyTensor`s, so that they can be handled as a single `LazyTensor`. 78 Lazily compose two `LazyTensor`s, so that they can be handled as a single `LazyTensor`.
79 """ 79 """
80 struct LazyTensorComposition{T,R,K,D, TM1<:LazyTensor{T,R,K}, TM2<:LazyTensor{T,K,D}} <: LazyTensor{T,R,D} 80 struct TensorComposition{T,R,K,D, TM1<:LazyTensor{T,R,K}, TM2<:LazyTensor{T,K,D}} <: LazyTensor{T,R,D}
81 t1::TM1 81 t1::TM1
82 t2::TM2 82 t2::TM2
83 83
84 function LazyTensorComposition(t1::LazyTensor{T,R,K}, t2::LazyTensor{T,K,D}) where {T,R,K,D} 84 function TensorComposition(t1::LazyTensor{T,R,K}, t2::LazyTensor{T,K,D}) where {T,R,K,D}
85 @boundscheck check_domain_size(t1, range_size(t2)) 85 @boundscheck check_domain_size(t1, range_size(t2))
86 return new{T,R,K,D, typeof(t1), typeof(t2)}(t1,t2) 86 return new{T,R,K,D, typeof(t1), typeof(t2)}(t1,t2)
87 end 87 end
88 end 88 end
89 89
90 range_size(tm::LazyTensorComposition) = range_size(tm.t1) 90 range_size(tm::TensorComposition) = range_size(tm.t1)
91 domain_size(tm::LazyTensorComposition) = domain_size(tm.t2) 91 domain_size(tm::TensorComposition) = domain_size(tm.t2)
92 92
93 function apply(c::LazyTensorComposition{T,R,K,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,K,D} 93 function apply(c::TensorComposition{T,R,K,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,K,D}
94 apply(c.t1, c.t2*v, I...) 94 apply(c.t1, c.t2*v, I...)
95 end 95 end
96 96
97 function apply_transpose(c::LazyTensorComposition{T,R,K,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,K,D} 97 function apply_transpose(c::TensorComposition{T,R,K,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,K,D}
98 apply_transpose(c.t2, c.t1'*v, I...) 98 apply_transpose(c.t2, c.t1'*v, I...)
99 end 99 end
100 100
101 101
102 """ 102 """
103 LazyTensorComposition(tm, tmi::IdentityTensor) 103 TensorComposition(tm, tmi::IdentityTensor)
104 LazyTensorComposition(tmi::IdentityTensor, tm) 104 TensorComposition(tmi::IdentityTensor, tm)
105 105
106 Composes a `Tensormapping` `tm` with an `IdentityTensor` `tmi`, by returning `tm` 106 Composes a `Tensormapping` `tm` with an `IdentityTensor` `tmi`, by returning `tm`
107 """ 107 """
108 function LazyTensorComposition(tm::LazyTensor{T,R,D}, tmi::IdentityTensor{T,D}) where {T,R,D} 108 function TensorComposition(tm::LazyTensor{T,R,D}, tmi::IdentityTensor{T,D}) where {T,R,D}
109 @boundscheck check_domain_size(tm, range_size(tmi)) 109 @boundscheck check_domain_size(tm, range_size(tmi))
110 return tm 110 return tm
111 end 111 end
112 112
113 function LazyTensorComposition(tmi::IdentityTensor{T,R}, tm::LazyTensor{T,R,D}) where {T,R,D} 113 function TensorComposition(tmi::IdentityTensor{T,R}, tm::LazyTensor{T,R,D}) where {T,R,D}
114 @boundscheck check_domain_size(tmi, range_size(tm)) 114 @boundscheck check_domain_size(tmi, range_size(tm))
115 return tm 115 return tm
116 end 116 end
117 # Specialization for the case where tm is an IdentityTensor. Required to resolve ambiguity. 117 # Specialization for the case where tm is an IdentityTensor. Required to resolve ambiguity.
118 function LazyTensorComposition(tm::IdentityTensor{T,D}, tmi::IdentityTensor{T,D}) where {T,D} 118 function TensorComposition(tm::IdentityTensor{T,D}, tmi::IdentityTensor{T,D}) where {T,D}
119 @boundscheck check_domain_size(tm, range_size(tmi)) 119 @boundscheck check_domain_size(tm, range_size(tmi))
120 return tmi 120 return tmi
121 end 121 end
122 122
123 123 Base.:*(a::T, tm::LazyTensor{T}) where T = TensorComposition(ScalingTensor{T,range_dim(tm)}(a,range_size(tm)), tm)
124 """ 124 Base.:*(tm::LazyTensor{T}, a::T) where T = a*tm
125 InflatedLazyTensor{T,R,D} <: LazyTensor{T,R,D} 125
126 """
127 InflatedTensor{T,R,D} <: LazyTensor{T,R,D}
126 128
127 An inflated `LazyTensor` with dimensions added before and afer its actual dimensions. 129 An inflated `LazyTensor` with dimensions added before and afer its actual dimensions.
128 """ 130 """
129 struct InflatedLazyTensor{T,R,D,D_before,R_middle,D_middle,D_after, TM<:LazyTensor{T,R_middle,D_middle}} <: LazyTensor{T,R,D} 131 struct InflatedTensor{T,R,D,D_before,R_middle,D_middle,D_after, TM<:LazyTensor{T,R_middle,D_middle}} <: LazyTensor{T,R,D}
130 before::IdentityTensor{T,D_before} 132 before::IdentityTensor{T,D_before}
131 tm::TM 133 tm::TM
132 after::IdentityTensor{T,D_after} 134 after::IdentityTensor{T,D_after}
133 135
134 function InflatedLazyTensor(before, tm::LazyTensor{T}, after) where T 136 function InflatedTensor(before, tm::LazyTensor{T}, after) where T
135 R_before = range_dim(before) 137 R_before = range_dim(before)
136 R_middle = range_dim(tm) 138 R_middle = range_dim(tm)
137 R_after = range_dim(after) 139 R_after = range_dim(after)
138 R = R_before+R_middle+R_after 140 R = R_before+R_middle+R_after
139 141
144 return new{T,R,D,D_before,R_middle,D_middle,D_after, typeof(tm)}(before, tm, after) 146 return new{T,R,D,D_before,R_middle,D_middle,D_after, typeof(tm)}(before, tm, after)
145 end 147 end
146 end 148 end
147 149
148 """ 150 """
149 InflatedLazyTensor(before, tm, after) 151 InflatedTensor(before, tm, after)
150 InflatedLazyTensor(before,tm) 152 InflatedTensor(before,tm)
151 InflatedLazyTensor(tm,after) 153 InflatedTensor(tm,after)
152 154
153 The outer product of `before`, `tm` and `after`, where `before` and `after` are `IdentityTensor`s. 155 The outer product of `before`, `tm` and `after`, where `before` and `after` are `IdentityTensor`s.
154 156
155 If one of `before` or `after` is left out, a 0-dimensional `IdentityTensor` is used as the default value. 157 If one of `before` or `after` is left out, a 0-dimensional `IdentityTensor` is used as the default value.
156 158
157 If `tm` already is an `InflatedLazyTensor`, `before` and `after` will be extended instead of 159 If `tm` already is an `InflatedTensor`, `before` and `after` will be extended instead of
158 creating a nested `InflatedLazyTensor`. 160 creating a nested `InflatedTensor`.
159 """ 161 """
160 InflatedLazyTensor(::IdentityTensor, ::LazyTensor, ::IdentityTensor) 162 InflatedTensor(::IdentityTensor, ::LazyTensor, ::IdentityTensor)
161 163
162 function InflatedLazyTensor(before, itm::InflatedLazyTensor, after) 164 function InflatedTensor(before, itm::InflatedTensor, after)
163 return InflatedLazyTensor( 165 return InflatedTensor(
164 IdentityTensor(before.size..., itm.before.size...), 166 IdentityTensor(before.size..., itm.before.size...),
165 itm.tm, 167 itm.tm,
166 IdentityTensor(itm.after.size..., after.size...), 168 IdentityTensor(itm.after.size..., after.size...),
167 ) 169 )
168 end 170 end
169 171
170 InflatedLazyTensor(before::IdentityTensor, tm::LazyTensor{T}) where T = InflatedLazyTensor(before,tm,IdentityTensor{T}()) 172 InflatedTensor(before::IdentityTensor, tm::LazyTensor{T}) where T = InflatedTensor(before,tm,IdentityTensor{T}())
171 InflatedLazyTensor(tm::LazyTensor{T}, after::IdentityTensor) where T = InflatedLazyTensor(IdentityTensor{T}(),tm,after) 173 InflatedTensor(tm::LazyTensor{T}, after::IdentityTensor) where T = InflatedTensor(IdentityTensor{T}(),tm,after)
172 # Resolve ambiguity between the two previous methods 174 # Resolve ambiguity between the two previous methods
173 InflatedLazyTensor(I1::IdentityTensor{T}, I2::IdentityTensor{T}) where T = InflatedLazyTensor(I1,I2,IdentityTensor{T}()) 175 InflatedTensor(I1::IdentityTensor{T}, I2::IdentityTensor{T}) where T = InflatedTensor(I1,I2,IdentityTensor{T}())
174 176
175 function range_size(itm::InflatedLazyTensor) 177 # TODO: Implement some pretty printing in terms of ⊗. E.g InflatedTensor(I(3),B,I(2)) -> I(3)⊗B⊗I(2)
178
179 function range_size(itm::InflatedTensor)
176 return flatten_tuple( 180 return flatten_tuple(
177 range_size(itm.before), 181 range_size(itm.before),
178 range_size(itm.tm), 182 range_size(itm.tm),
179 range_size(itm.after), 183 range_size(itm.after),
180 ) 184 )
181 end 185 end
182 186
183 function domain_size(itm::InflatedLazyTensor) 187 function domain_size(itm::InflatedTensor)
184 return flatten_tuple( 188 return flatten_tuple(
185 domain_size(itm.before), 189 domain_size(itm.before),
186 domain_size(itm.tm), 190 domain_size(itm.tm),
187 domain_size(itm.after), 191 domain_size(itm.after),
188 ) 192 )
189 end 193 end
190 194
191 function apply(itm::InflatedLazyTensor{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D} 195 function apply(itm::InflatedTensor{T,R,D}, v::AbstractArray{<:Any,D}, I::Vararg{Any,R}) where {T,R,D}
192 dim_before = range_dim(itm.before) 196 dim_before = range_dim(itm.before)
193 dim_domain = domain_dim(itm.tm) 197 dim_domain = domain_dim(itm.tm)
194 dim_range = range_dim(itm.tm) 198 dim_range = range_dim(itm.tm)
195 dim_after = range_dim(itm.after) 199 dim_after = range_dim(itm.after)
196 200
198 202
199 v_inner = view(v, view_index...) 203 v_inner = view(v, view_index...)
200 return apply(itm.tm, v_inner, inner_index...) 204 return apply(itm.tm, v_inner, inner_index...)
201 end 205 end
202 206
203 function apply_transpose(itm::InflatedLazyTensor{T,R,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,D} 207 function apply_transpose(itm::InflatedTensor{T,R,D}, v::AbstractArray{<:Any,R}, I::Vararg{Any,D}) where {T,R,D}
204 dim_before = range_dim(itm.before) 208 dim_before = range_dim(itm.before)
205 dim_domain = domain_dim(itm.tm) 209 dim_domain = domain_dim(itm.tm)
206 dim_range = range_dim(itm.tm) 210 dim_range = range_dim(itm.tm)
207 dim_after = range_dim(itm.after) 211 dim_after = range_dim(itm.after)
208 212
210 214
211 v_inner = view(v, view_index...) 215 v_inner = view(v, view_index...)
212 return apply_transpose(itm.tm, v_inner, inner_index...) 216 return apply_transpose(itm.tm, v_inner, inner_index...)
213 end 217 end
214 218
215 function Base.show(io::IO, ::MIME"text/plain", tm::InflatedLazyTensor{T}) where T 219 function Base.show(io::IO, ::MIME"text/plain", tm::InflatedTensor{T}) where T
216 show(IOContext(io, :compact=>true), MIME("text/plain"), tm.before) 220 show(IOContext(io, :compact=>true), MIME("text/plain"), tm.before)
217 print(io, "⊗") 221 print(io, "⊗")
218 # if get(io, :compact, false) 222 # if get(io, :compact, false)
219 show(io, MIME("text/plain"), tm.tm) 223 show(io, MIME("text/plain"), tm.tm)
220 print(io, "⊗") 224 print(io, "⊗")
223 227
224 228
225 @doc raw""" 229 @doc raw"""
226 LazyOuterProduct(tms...) 230 LazyOuterProduct(tms...)
227 231
228 Creates a `LazyTensorComposition` for the outerproduct of `tms...`. 232 Creates a `TensorComposition` for the outerproduct of `tms...`.
229 This is done by separating the outer product into regular products of outer products involving only identity mappings and one non-identity mapping. 233 This is done by separating the outer product into regular products of outer products involving only identity mappings and one non-identity mapping.
230 234
231 First let 235 First let
232 ```math 236 ```math
233 \begin{aligned} 237 \begin{aligned}
260 ``` 264 ```
261 """ 265 """
262 function LazyOuterProduct end 266 function LazyOuterProduct end
263 267
264 function LazyOuterProduct(tm1::LazyTensor{T}, tm2::LazyTensor{T}) where T 268 function LazyOuterProduct(tm1::LazyTensor{T}, tm2::LazyTensor{T}) where T
265 itm1 = InflatedLazyTensor(tm1, IdentityTensor{T}(range_size(tm2))) 269 itm1 = InflatedTensor(tm1, IdentityTensor{T}(range_size(tm2)))
266 itm2 = InflatedLazyTensor(IdentityTensor{T}(domain_size(tm1)),tm2) 270 itm2 = InflatedTensor(IdentityTensor{T}(domain_size(tm1)),tm2)
267 271
268 return itm1∘itm2 272 return itm1∘itm2
269 end 273 end
270 274
271 LazyOuterProduct(t1::IdentityTensor{T}, t2::IdentityTensor{T}) where T = IdentityTensor{T}(t1.size...,t2.size...) 275 LazyOuterProduct(t1::IdentityTensor{T}, t2::IdentityTensor{T}) where T = IdentityTensor{T}(t1.size...,t2.size...)
272 LazyOuterProduct(t1::LazyTensor, t2::IdentityTensor) = InflatedLazyTensor(t1, t2) 276 LazyOuterProduct(t1::LazyTensor, t2::IdentityTensor) = InflatedTensor(t1, t2)
273 LazyOuterProduct(t1::IdentityTensor, t2::LazyTensor) = InflatedLazyTensor(t1, t2) 277 LazyOuterProduct(t1::IdentityTensor, t2::LazyTensor) = InflatedTensor(t1, t2)
274 278
275 LazyOuterProduct(tms::Vararg{LazyTensor}) = foldl(LazyOuterProduct, tms) 279 LazyOuterProduct(tms::Vararg{LazyTensor}) = foldl(LazyOuterProduct, tms)
276 280
277 281
278 function check_domain_size(tm::LazyTensor, sz) 282 function check_domain_size(tm::LazyTensor, sz)