Skip to content

Commit e990e21

Browse files
committed
Implement flat matrix-vector multiplication
Some packages (ExponentialUtilities, in this case) require the ability to multiply a `GradgenOperator` to a "flat" version of a `GradVector`. This implements the corresponding multiplication method and test
1 parent 5aa0ec1 commit e990e21

2 files changed

Lines changed: 100 additions & 19 deletions

File tree

src/linalg.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,37 @@ function LinearAlgebra.mul!(Φ::GradVector, G::GradgenOperator, Ψ::GradVector)
1717
end
1818

1919

20+
# Flat-vector dispatch used by ExponentialUtilities' Arnoldi iteration, which
21+
# builds its Krylov matrix via similar(b, T, (n, m+1)) → plain Matrix{T} and
22+
# then calls mul!(view(V,:,j+1), A, view(V,:,j)). The GradVector is treated as
23+
# a packed flat vector: blocks [grad_1 | grad_2 | … | state], each of length N.
24+
function LinearAlgebra.mul!(
25+
Φ::AbstractVector,
26+
G::GradgenOperator{num_controls,GT,CGT},
27+
Ψ::AbstractVector,
28+
α,
29+
β
30+
) where {num_controls,GT,CGT}
31+
N = size(G.G, 1)
32+
L = num_controls
33+
Ψ_state = view(Ψ, (L*N+1):((L+1)*N))
34+
for i = 1:L
35+
Φ_grad_i = view(Φ, ((i-1)*N+1):(i*N))
36+
Ψ_grad_i = view(Ψ, ((i-1)*N+1):(i*N))
37+
LinearAlgebra.mul!(Φ_grad_i, G.G, Ψ_grad_i, α, β)
38+
LinearAlgebra.mul!(Φ_grad_i, G.control_deriv_ops[i], Ψ_state, α, 1)
39+
end
40+
Φ_state = view(Φ, (L*N+1):((L+1)*N))
41+
LinearAlgebra.mul!(Φ_state, G.G, Ψ_state, α, β)
42+
return Φ
43+
end
44+
45+
46+
function LinearAlgebra.mul!::AbstractVector, G::GradgenOperator, Ψ::AbstractVector)
47+
return LinearAlgebra.mul!(Φ, G, Ψ, true, false)
48+
end
49+
50+
2051
function LinearAlgebra.lmul!(c, Ψ::GradVector)
2152
LinearAlgebra.lmul!(c, Ψ.state)
2253
for i eachindex.grad_states)

test/test_interface.jl

Lines changed: 69 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ using QuantumPropagators.Interfaces:
88
using QuantumGradientGenerators: GradGenerator, GradVector, GradgenOperator
99
using StaticArrays: SVector, SMatrix, MVector
1010
using LinearAlgebra: norm, dot, mul!, I
11+
using StableRNGs: StableRNG
1112

1213

1314
@testset "GradVector Interface" begin
1415

16+
rng = StableRNG(1179926107)
1517
N = 10
16-
Ψ = random_state_vector(N)
18+
Ψ = random_state_vector(N; rng)
1719
Ψ̃ = GradVector(Ψ, 2)
1820
@test check_state(Ψ̃)
1921

@@ -24,8 +26,9 @@ end
2426

2527
@testset "GradVector Interface (Static)" begin
2628

29+
rng = StableRNG(2188051723)
2730
N = 10
28-
Ψ = SVector{N,ComplexF64}(random_state_vector(N))
31+
Ψ = SVector{N,ComplexF64}(random_state_vector(N; rng))
2932
Ψ̃ = GradVector(Ψ, 2)
3033
@test check_state(Ψ̃)
3134

@@ -48,10 +51,11 @@ end
4851

4952
@testset "GradGenerator Interface" begin
5053

54+
rng = StableRNG(3031820470)
5155
N = 10
52-
Ĥ₀ = random_matrix(N, hermitian = true)
53-
Ĥ₁ = random_matrix(N, hermitian = true)
54-
Ĥ₂ = random_matrix(N, hermitian = true)
56+
Ĥ₀ = random_matrix(N; hermitian = true, rng)
57+
Ĥ₁ = random_matrix(N; hermitian = true, rng)
58+
Ĥ₂ = random_matrix(N; hermitian = true, rng)
5559
ϵ₁(t) = 1.0
5660
ϵ₂(t) = 1.0
5761
Ĥ_of_t = hamiltonian(Ĥ₀, (Ĥ₁, ϵ₁), (Ĥ₂, ϵ₂))
@@ -60,7 +64,7 @@ end
6064

6165
G̃_of_t = GradGenerator(Ĥ_of_t)
6266

63-
Ψ = random_state_vector(N)
67+
Ψ = random_state_vector(N; rng)
6468
Ψ̃ = GradVector(Ψ, length(get_controls(G̃_of_t)))
6569

6670
@test check_generator(G̃_of_t; state = Ψ̃, tlist, for_gradient_optimization = false)
@@ -70,10 +74,11 @@ end
7074

7175
@testset "GradGenerator Interface (Static)" begin
7276

77+
rng = StableRNG(1911203795)
7378
N = 10
74-
Ĥ₀ = SMatrix{N,N,ComplexF64}(random_matrix(N, hermitian = true))
75-
Ĥ₁ = SMatrix{N,N,ComplexF64}(random_matrix(N, hermitian = true))
76-
Ĥ₂ = SMatrix{N,N,ComplexF64}(random_matrix(N, hermitian = true))
79+
Ĥ₀ = SMatrix{N,N,ComplexF64}(random_matrix(N; hermitian = true, rng))
80+
Ĥ₁ = SMatrix{N,N,ComplexF64}(random_matrix(N; hermitian = true, rng))
81+
Ĥ₂ = SMatrix{N,N,ComplexF64}(random_matrix(N; hermitian = true, rng))
7782
ϵ₁(t) = 1.0
7883
ϵ₂(t) = 1.0
7984
Ĥ_of_t = hamiltonian(Ĥ₀, (Ĥ₁, ϵ₁), (Ĥ₂, ϵ₂))
@@ -82,7 +87,7 @@ end
8287

8388
G̃_of_t = GradGenerator(Ĥ_of_t)
8489

85-
Ψ = SVector{N,ComplexF64}(random_state_vector(N))
90+
Ψ = SVector{N,ComplexF64}(random_state_vector(N; rng))
8691
Ψ̃ = GradVector(Ψ, length(get_controls(G̃_of_t)))
8792

8893
@test check_generator(G̃_of_t; state = Ψ̃, tlist, for_gradient_optimization = false)
@@ -92,12 +97,13 @@ end
9297

9398
@testset "GradgenOperator Matrix Interface" begin
9499

100+
rng = StableRNG(3317751223)
95101
N = 5
96102
L = 2
97103
G = Matrix{ComplexF64}(I, N, N)
98-
mu = [rand(ComplexF64, N, N) for _ = 1:L]
104+
mu = [rand(rng, ComplexF64, N, N) for _ = 1:L]
99105
op = GradgenOperator{L,Matrix{ComplexF64},Matrix{ComplexF64}}(G, mu)
100-
state = GradVector(rand(ComplexF64, N), L)
106+
state = GradVector(rand(rng, ComplexF64, N), L)
101107

102108
# supports_matrix_interface reports true for matrix-backed GradgenOperator
103109
@test supports_matrix_interface(typeof(op))
@@ -116,15 +122,15 @@ end
116122
@test all(collect(op) .≈ vec(dense))
117123

118124
# 3-arg mul! agrees with 5-arg mul!(Phi, G, Psi, true, false)
119-
Psi = GradVector(rand(ComplexF64, N), L)
125+
Psi = GradVector(rand(rng, ComplexF64, N), L)
120126
Phi1 = GradVector(zeros(ComplexF64, N), L)
121127
Phi2 = GradVector(zeros(ComplexF64, N), L)
122128
mul!(Phi1, op, Psi)
123129
mul!(Phi2, op, Psi, true, false)
124130
@test norm(Phi1 - Phi2) < 1e-14
125131

126132
# 3-arg dot(Psi, op, Phi) matches dot(Psi, op * Phi)
127-
Psi2 = GradVector(rand(ComplexF64, N), L)
133+
Psi2 = GradVector(rand(rng, ComplexF64, N), L)
128134
@test dot(state, op, Psi2) dot(state, op * Psi2)
129135

130136
# similar(op) returns a dense Array of the same eltype and size (matching Operator pattern)
@@ -147,11 +153,52 @@ end
147153
end
148154

149155

156+
@testset "GradgenOperator flat-vector mul!" begin
157+
158+
rng = StableRNG(1602052280)
159+
N = 5
160+
L = 2
161+
G = rand(rng, ComplexF64, N, N)
162+
mu = [rand(rng, ComplexF64, N, N) for _ = 1:L]
163+
op = GradgenOperator{L,Matrix{ComplexF64},Matrix{ComplexF64}}(G, mu)
164+
dense = Array(op)
165+
166+
# Flat-vector layout: [grad_1 | grad_2 | ... | grad_L | state]
167+
Ψ_flat = rand(rng, ComplexF64, N * (L + 1))
168+
Φ_ref = dense * Ψ_flat
169+
170+
# 3-arg mul! agrees with the dense matrix-vector product
171+
Φ1 = zeros(ComplexF64, N * (L + 1))
172+
mul!(Φ1, op, Ψ_flat)
173+
@test norm(Φ1 - Φ_ref) < 1e-14
174+
175+
# 5-arg mul! with α=true, β=false matches the 3-arg result
176+
Φ2 = zeros(ComplexF64, N * (L + 1))
177+
mul!(Φ2, op, Ψ_flat, true, false)
178+
@test norm(Φ2 - Φ_ref) < 1e-14
179+
180+
# 5-arg mul! with non-trivial α, β: Φ ← α * G * Ψ + β * Φ
181+
α = 2.3 + 0.7im
182+
β = 1.5 - 0.3im
183+
Φ3 = rand(rng, ComplexF64, N * (L + 1))
184+
Φ3_init = copy(Φ3)
185+
mul!(Φ3, op, Ψ_flat, α, β)
186+
@test norm(Φ3 -* Φ_ref + β * Φ3_init)) < 1e-14
187+
188+
# works with views (the actual ExponentialUtilities use case)
189+
V = rand(rng, ComplexF64, N * (L + 1), 4)
190+
mul!(view(V, :, 2), op, view(V, :, 1))
191+
@test norm(view(V, :, 2) - dense * view(V, :, 1)) < 1e-14
192+
193+
end
194+
195+
150196
@testset "GradVector Vector Interface" begin
151197

198+
rng = StableRNG(2618946253)
152199
N = 5
153200
L = 2
154-
Psi = rand(ComplexF64, N)
201+
Psi = rand(rng, ComplexF64, N)
155202
gradvec = GradVector(Psi, L)
156203

157204
# supports_vector_interface is true for Vector-backed GradVector
@@ -192,9 +239,10 @@ end
192239

193240
@testset "GradVector Vector Interface (Static)" begin
194241

242+
rng = StableRNG(167987434)
195243
N = 5
196244
L = 2
197-
Psi = SVector{N,ComplexF64}(rand(ComplexF64, N))
245+
Psi = SVector{N,ComplexF64}(rand(rng, ComplexF64, N))
198246
gradvec = GradVector(Psi, L)
199247

200248
# SVector-backed GradVector: supports_vector_interface follows the component type
@@ -212,10 +260,11 @@ end
212260

213261
@testset "GradVector without Vector Interface" begin
214262

263+
rng = StableRNG(4252840018)
215264
N = 5
216265
L = 2
217266
# Matrix is not an AbstractVector, so supports_vector_interface returns false
218-
Psi = rand(ComplexF64, N, N)
267+
Psi = rand(rng, ComplexF64, N, N)
219268
gradvec = GradVector(Psi, L)
220269

221270
@test !supports_vector_interface(typeof(gradvec))
@@ -241,10 +290,11 @@ end
241290

242291
@testset "GradgenOperator without Matrix Interface" begin
243292

293+
rng = StableRNG(1276996367)
244294
N = 5
245295
L = 2
246-
G = NonMatrixOp(rand(ComplexF64, N, N))
247-
mu = [NonMatrixOp(rand(ComplexF64, N, N)) for _ = 1:L]
296+
G = NonMatrixOp(rand(rng, ComplexF64, N, N))
297+
mu = [NonMatrixOp(rand(rng, ComplexF64, N, N)) for _ = 1:L]
248298
op = GradgenOperator{L,NonMatrixOp,NonMatrixOp}(G, mu)
249299

250300
@test !supports_matrix_interface(typeof(op))

0 commit comments

Comments
 (0)