@@ -79,6 +79,45 @@ for k in 3:8 # is 8 sufficient?
79
79
kron ($ (mapargs... ), $ (Symbol (:A , k)), convert_to_lmaps (As... )... )
80
80
end
81
81
82
+ @doc raw """
83
+ squarekron(A₁::MapOrMatrix, A₂::MapOrMatrix, A₃::MapOrMatrix, Aᵢ::MapOrMatrix...)::CompositeMap
84
+
85
+ Construct a (lazy) representation of the Kronecker product `⨂ᵢ₌₁ⁿ Aᵢ` of at least 3 _square_
86
+ Kronecker factors. In contrast to [`kron`](@ref), this function assumes that all Kronecker
87
+ factors are square, and makes use of the following identity[^1]:
88
+
89
+ ```math
90
+ \b igotimes_{i=1}^n A_i = \p rod_{i=1}^n I_1 \o times \l dots \o times I_{i-1} \o times A_i \o times I_{i+1} \o times \l dots \o times I_n
91
+ ```
92
+
93
+ where ``I_k`` is an identity matrix of the size of ``A_k``. By associativity, the
94
+ Kronecker product of the identity operators may be combined to larger identity operators
95
+ ``I_{1:i-1}`` and ``I_{i+1:n}``, which yields
96
+
97
+ ```math
98
+ \b igotimes_{i=1}^n A_i = \p rod_{i=1}^n I_{1:i-1} \o times A_i \o times I_{i+1:n}
99
+ ```
100
+
101
+ i.e., a `CompositeMap` where each factor is a Kronecker product consisting of three maps:
102
+ outer `UniformScalingMap`s and the respective Kronecker factor. This representation is
103
+ expected to yield significantly faster multiplication (and reduce memory allocation)
104
+ compared to [`kron`](@ref), but benchmarking intended use cases is highly recommended.
105
+
106
+ [^1]: Fernandes, P. and Plateau, B. and Stewart, W. J. ["Efficient Descriptor-Vector Multiplications in Stochastic Automata Networks"](https://doi.org/10.1145/278298.278303), _Journal of the ACM_, 45(3), 381–414, 1998.
107
+ """
108
+ function squarekron (A:: MapOrMatrix , B:: MapOrMatrix , C:: MapOrMatrix , Ds:: MapOrMatrix... )
109
+ maps = (A, B, C, Ds... )
110
+ T = promote_type (map (eltype, maps)... )
111
+ all (_issquare, maps) || throw (ArgumentError (" operators need to be square in Kronecker sums" ))
112
+ ns = map (a -> size (a, 1 ), maps)
113
+ firstmap = first (maps) ⊗ UniformScalingMap (true , prod (ns[2 : end ]))
114
+ lastmap = UniformScalingMap (true , prod (ns[1 : end - 1 ])) ⊗ last (maps)
115
+ middlemaps = prod (enumerate (maps[2 : end - 1 ])) do (i, map)
116
+ UniformScalingMap (true , prod (ns[1 : i])) ⊗ map ⊗ UniformScalingMap (true , prod (ns[i+ 2 : end ]))
117
+ end
118
+ return firstmap * middlemaps * lastmap
119
+ end
120
+
82
121
struct KronPower{p}
83
122
function KronPower (p:: Integer )
84
123
p > 1 || throw (ArgumentError (" the Kronecker power is only defined for exponents larger than 1, got $k " ))
@@ -114,74 +153,90 @@ Base.:(==)(A::KroneckerMap, B::KroneckerMap) =
114
153
# multiplication helper functions
115
154
# ################
116
155
117
- @inline function _kronmul! (y, B, x, A, T)
118
- ma, na = size (A)
119
- mb, nb = size (B)
120
- X = reshape (x, (nb, na))
121
- Y = reshape (y, (mb, ma))
122
- if B isa UniformScalingMap
123
- _unsafe_mul! (Y, X, transpose (A))
124
- lmul! (B. λ, y)
156
+ @inline function _kronmul! (Y, B, X, A)
157
+ # minimize intermediate memory allocation
158
+ if size (B, 2 ) * size (A, 1 ) <= size (B, 1 ) * size (A, 2 )
159
+ temp = similar (Y, (size (B, 2 ), size (A, 1 ) ))
160
+ _unsafe_mul! (temp, X, transpose (A))
161
+ _unsafe_mul! (Y, B, temp)
125
162
else
126
- temp = similar (Y, (ma, nb ))
127
- _unsafe_mul! (temp, A, copy ( transpose (X)) )
128
- _unsafe_mul! (Y, B , transpose (temp ))
163
+ temp = similar (Y, (size (B, 1 ), size (A, 2 ) ))
164
+ _unsafe_mul! (temp, B, X )
165
+ _unsafe_mul! (Y, temp , transpose (A ))
129
166
end
130
- return y
167
+ return Y
131
168
end
132
- @inline function _kronmul! (y , B, x, A :: UniformScalingMap , _ )
133
- ma, na = size (A )
134
- mb, nb = size (B )
135
- iszero (A . λ) && return fill! (y, zero ( eltype (y)))
136
- X = reshape (x, (nb, na))
137
- Y = reshape (y, (mb, ma) )
169
+ @inline function _kronmul! (Y , B:: UniformScalingMap , X, A )
170
+ _unsafe_mul! (Y, X, transpose (A) )
171
+ ! isone (B . λ) && lmul! (B . λ, Y )
172
+ return Y
173
+ end
174
+ @inline function _kronmul! (Y, B, X, A :: UniformScalingMap )
138
175
_unsafe_mul! (Y, B, X)
139
- ! isone (A. λ) && rmul! (y , A. λ)
140
- return y
176
+ ! isone (A. λ) && rmul! (Y , A. λ)
177
+ return Y
141
178
end
142
- @inline function _kronmul! (y, B, x, A:: VecOrMatMap , _)
143
- ma, na = size (A)
144
- mb, nb = size (B)
145
- X = reshape (x, (nb, na))
146
- Y = reshape (y, (mb, ma))
179
+ # disambiguation (cannot occur)
180
+ @inline function _kronmul! (Y, B:: UniformScalingMap , X, A:: UniformScalingMap )
181
+ mul! (parent (Y), A. λ * B. λ, parent (X))
182
+ return Y
183
+ end
184
+ @inline function _kronmul! (Y, B, X, A:: VecOrMatMap )
147
185
At = transpose (A. lmap)
148
- if B isa UniformScalingMap
149
- # the following is (perhaps due to the reshape?) faster than
150
- # _unsafe_mul!(Y, B * X, At)
151
- _unsafe_mul! (Y, X, At)
152
- lmul! (B. λ, y)
153
- elseif nb* ma <= mb* na
186
+ if size (B, 2 ) * size (A, 1 ) <= size (B, 1 ) * size (A, 2 )
154
187
_unsafe_mul! (Y, B, X * At)
155
188
else
156
189
_unsafe_mul! (Y, Matrix (B * X), At)
157
190
end
158
- return y
191
+ return Y
192
+ end
193
+ @inline function _kronmul! (Y, B:: UniformScalingMap , X, A:: VecOrMatMap )
194
+ _unsafe_mul! (Y, X, transpose (A. lmap))
195
+ ! isone (B. λ) && lmul! (B. λ, Y)
196
+ return Y
159
197
end
198
+
160
199
const VectorMap{T} = WrappedMap{T,<: AbstractVector }
161
200
const AdjOrTransVectorMap{T} = WrappedMap{T,<: LinearAlgebra.AdjOrTransAbsVec }
162
- @inline _kronmul! (y, B:: AdjOrTransVectorMap , x, a:: VectorMap , _) = mul! (y, a. lmap, B. lmap * x)
163
201
164
202
# ################
165
203
# multiplication with vectors
166
204
# ################
167
205
168
206
const KroneckerMap2{T} = KroneckerMap{T, <: Tuple{LinearMap, LinearMap} }
169
-
207
+ const OuterProductMap{T} = KroneckerMap{T, <: Tuple{VectorMap, AdjOrTransVectorMap} }
208
+ function _unsafe_mul! (y, L:: OuterProductMap , x:: AbstractVector )
209
+ a, bt = L. maps
210
+ mul! (y, a. lmap, bt. lmap * x)
211
+ end
170
212
function _unsafe_mul! (y, L:: KroneckerMap2 , x:: AbstractVector )
171
213
require_one_based_indexing (y)
172
214
A, B = L. maps
173
- _kronmul! (y, B, x, A, eltype (L))
215
+ ma, na = size (A)
216
+ mb, nb = size (B)
217
+ X = reshape (x, (nb, na))
218
+ Y = reshape (y, (mb, ma))
219
+ _kronmul! (Y, B, X, A)
174
220
return y
175
221
end
176
222
function _unsafe_mul! (y, L:: KroneckerMap , x:: AbstractVector )
177
223
require_one_based_indexing (y)
178
224
maps = L. maps
179
225
if length (maps) == 2 # reachable only for L.maps::Vector
180
- @inbounds _kronmul! (y, maps[2 ], x, maps[1 ], eltype (L))
226
+ A, B = maps
227
+ ma, na = size (A)
228
+ mb, nb = size (B)
229
+ X = reshape (x, (nb, na))
230
+ Y = reshape (y, (mb, ma))
231
+ _kronmul! (Y, B, X, A)
181
232
else
182
233
A = first (maps)
183
234
B = KroneckerMap {eltype(L)} (_tail (maps))
184
- _kronmul! (y, B, x, A, eltype (L))
235
+ ma, na = size (A)
236
+ mb, nb = size (B)
237
+ X = reshape (x, (nb, na))
238
+ Y = reshape (y, (mb, ma))
239
+ _kronmul! (Y, B, X, A)
185
240
end
186
241
return y
187
242
end
@@ -225,7 +280,7 @@ struct KroneckerSumMap{T, As<:Tuple{LinearMap, LinearMap}} <: LinearMap{T}
225
280
maps:: As
226
281
function KroneckerSumMap {T} (maps:: Tuple{LinearMap,LinearMap} ) where {T}
227
282
A1, A2 = maps
228
- (size (A1, 1 ) == size (A1, 2 ) && size (A2, 1 ) == size (A2, 2 )) ||
283
+ (_issquare (A1) && _issquare (A2)) ||
229
284
throw (ArgumentError (" operators need to be square in Kronecker sums" ))
230
285
for TA in Base. Iterators. map (eltype, maps)
231
286
promote_type (T, TA) == T ||
@@ -269,6 +324,68 @@ kronsum(A::MapOrMatrix, B::MapOrMatrix) =
269
324
kronsum (A:: MapOrMatrix , B:: MapOrMatrix , C:: MapOrMatrix , Ds:: MapOrMatrix... ) =
270
325
kronsum (A, kronsum (B, C, Ds... ))
271
326
327
+ @doc raw """
328
+ sumkronsum(A, B)::LinearCombination
329
+ sumkronsum(A, B, Cs...)::LinearCombination
330
+
331
+ Construct a (lazy) representation of the Kronecker sum `A⊕B` of two or more square
332
+ objects of type `LinearMap` or `AbstractMatrix`. This function makes use of the following
333
+ representation of Kronecker sums[^1]:
334
+
335
+ ```math
336
+ \b igoplus_{i=1}^n A_i = \s um_{i=1}^n I_1 \o times \l dots \o times I_{i-1} \o times A_i \o times I_{i+1} \o times \l dots \o times I_n
337
+ ```
338
+
339
+ where ``I_k`` is the identity operator of the size of ``A_k``. By associativity, the
340
+ Kronecker product of the identity operators may be combined to larger identity operators
341
+ ``I_{1:i-1}`` and ``I_{i+1:n}``, which yields
342
+
343
+ ```math
344
+ \b igoplus_{i=1}^n A_i = \s um_{i=1}^n I_{1:i-1} \o times A_i \o times I_{i+1:n},
345
+ ```
346
+
347
+ i.e., a `LinearCombination` where each summand is a Kronecker product consisting of three
348
+ maps: outer `UniformScalingMap`s and the respective Kronecker factor. This representation is
349
+ expected to yield significantly faster multiplication (and reduce memory allocation)
350
+ compared to [`kronsum`](@ref), especially for 3 or more Kronecker summands, but
351
+ benchmarking intended use cases is highly recommended.
352
+
353
+ # Examples
354
+ ```jldoctest; setup=(using LinearAlgebra, SparseArrays, LinearMaps)
355
+ julia> J = LinearMap(I, 2) # 2×2 identity map
356
+ 2×2 LinearMaps.UniformScalingMap{Bool} with scaling factor: true
357
+
358
+ julia> E = spdiagm(-1 => trues(1)); D = LinearMap(E + E' - 2I);
359
+
360
+ julia> Δ₁ = kron(D, J) + kron(J, D); # discrete 2D-Laplace operator, Kronecker sum
361
+
362
+ julia> Δ₂ = sumkronsum(D, D);
363
+
364
+ julia> Δ₃ = D^⊕(2);
365
+
366
+ julia> Matrix(Δ₁) == Matrix(Δ₂) == Matrix(Δ₃)
367
+ true
368
+ ```
369
+
370
+ [^1]: Fernandes, P. and Plateau, B. and Stewart, W. J. ["Efficient Descriptor-Vector Multiplications in Stochastic Automata Networks"](https://doi.org/10.1145/278298.278303), _Journal of the ACM_, 45(3), 381–414, 1998.
371
+ """
372
+ function sumkronsum (A:: MapOrMatrix , B:: MapOrMatrix )
373
+ LinearAlgebra. checksquare (A, B)
374
+ A ⊗ UniformScalingMap (true , size (B,1 )) + UniformScalingMap (true , size (A,1 )) ⊗ B
375
+ end
376
+ function sumkronsum (A:: MapOrMatrix , B:: MapOrMatrix , C:: MapOrMatrix , Ds:: MapOrMatrix... )
377
+ maps = (A, B, C, Ds... )
378
+ all (_issquare, maps) || throw (ArgumentError (" operators need to be square in Kronecker sums" ))
379
+ ns = map (a -> size (a, 1 ), maps)
380
+ n = length (maps)
381
+ firstmap = first (maps) ⊗ UniformScalingMap (true , prod (ns[2 : end ]))
382
+ lastmap = UniformScalingMap (true , prod (ns[1 : end - 1 ])) ⊗ last (maps)
383
+ middlemaps = sum (enumerate (Base. front (Base. tail (maps)))) do (i, map)
384
+ UniformScalingMap (true , prod (ns[1 : i])) ⊗ map ⊗ UniformScalingMap (true , prod (ns[i+ 2 : end ]))
385
+ end
386
+ return firstmap + middlemaps + lastmap
387
+ end
388
+
272
389
struct KronSumPower{p}
273
390
function KronSumPower (p:: Integer )
274
391
p > 1 || throw (ArgumentError (" the Kronecker sum power is only defined for exponents larger than 1, got $k " ))
@@ -280,14 +397,26 @@ end
280
397
⊕(k::Integer)
281
398
282
399
Construct a lazy representation of the `k`-th Kronecker sum power `A^⊕(k) = A ⊕ A ⊕ ... ⊕ A`,
283
- where `A` can be a square `AbstractMatrix` or a `LinearMap`.
400
+ where `A` can be a square `AbstractMatrix` or a `LinearMap`. This calls [`sumkronsum`](@ref)
401
+ on the `k`-tuple `(A, ..., A)` for `k ≥ 3`.
402
+
403
+ # Example
404
+ ```jldoctest
405
+ julia> Matrix([1 0; 0 1]^⊕(2))
406
+ 4×4 Matrix{Int64}:
407
+ 2 0 0 0
408
+ 0 2 0 0
409
+ 0 0 2 0
410
+ 0 0 0 2
284
411
"""
285
412
⊕ (k:: Integer ) = KronSumPower (k)
286
413
287
414
⊕ (a, b, c... ) = kronsum (a, b, c... )
288
415
416
+ Base.:(^ )(A:: MapOrMatrix , :: KronSumPower{2} ) =
417
+ kronsum (convert (LinearMap, A), convert (LinearMap, A))
289
418
Base.:(^ )(A:: MapOrMatrix , :: KronSumPower{p} ) where {p} =
290
- kronsum (ntuple (n -> convert (LinearMap, A), Val (p))... )
419
+ sumkronsum (ntuple (_ -> convert (LinearMap, A), Val (p))... )
291
420
292
421
Base. size (A:: KroneckerSumMap , i) = prod (size .(A. maps, i))
293
422
Base. size (A:: KroneckerSumMap ) = (size (A, 1 ), size (A, 2 ))
@@ -305,10 +434,10 @@ Base.:(==)(A::KroneckerSumMap, B::KroneckerSumMap) =
305
434
306
435
function _unsafe_mul! (y, L:: KroneckerSumMap , x:: AbstractVector )
307
436
A, B = L. maps
308
- ma, na = size (A)
309
- mb, nb = size (B)
310
- X = reshape (x, (nb, na ))
311
- Y = reshape (y, (nb, na ))
437
+ a = size (A, 1 )
438
+ b = size (B, 1 )
439
+ X = reshape (x, (b, a ))
440
+ Y = reshape (y, (b, a ))
312
441
_unsafe_mul! (Y, X, transpose (A))
313
442
_unsafe_mul! (Y, B, X, true , true )
314
443
return y
0 commit comments