Skip to content

Commit 8f564bb

Browse files
authored
Add Newton-type simultaneous diagonalization (#41)
* Add Newton-type simultaneous diagonalization * Fix format
1 parent cfabacf commit 8f564bb

File tree

6 files changed

+254
-84
lines changed

6 files changed

+254
-84
lines changed

src/SemialgebraicSets.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ module SemialgebraicSets
22

33
using Random
44

5-
import MutableArithmetics
6-
const MA = MutableArithmetics
5+
import MutableArithmetics as MA
76

87
using MultivariatePolynomials
98
const MP = MultivariatePolynomials
@@ -58,6 +57,7 @@ Base.intersect(set::AbstractSemialgebraicSet; kws...) = set
5857

5958
include("groebner.jl")
6059
include("ideal.jl")
60+
include("multiplication_matrices.jl")
6161
include("solve.jl")
6262
include("variety.jl")
6363
include("basic.jl")

src/multiplication_matrices.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
export ReorderedSchurMultiplicationMatricesSolver
2+
3+
struct MultiplicationMatrices{Ms}
4+
matrices::Ms
5+
end
6+
7+
"""
8+
AbstractMultiplicationMatricesSolver
9+
10+
Solver of algebraic equations using multiplication matrices.
11+
"""
12+
abstract type AbstractMultiplicationMatricesSolver end
13+
14+
function solve(
15+
Ms::MultiplicationMatrices,
16+
solver::AbstractMultiplicationMatricesSolver,
17+
)
18+
λ = rand(solver.rng, length(Ms.matrices))
19+
λ /= sum(λ)
20+
return _solve_multiplication_matrices(Ms.matrices, λ, solver)
21+
end
22+
23+
include("schur.jl")
24+
include("newton_type.jl")
25+
26+
function default_multiplication_matrices_solver(
27+
::AbstractVector{PT},
28+
) where {T,PT<:_APL{T}}
29+
return default_multiplication_matrices_solver(T)
30+
end
31+
32+
function default_multiplication_matrices_solver(::Type{T}) where {T}
33+
return ReorderedSchurMultiplicationMatricesSolver{T}()
34+
end

src/newton_type.jl

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# This file is largely inspired from Bernard Mourrain's MultivariateSeries/diagonalization.jl
2+
3+
export NewtonTypeDiagonalization
4+
5+
# norm of off diagonal terms of a square matrix
6+
function norm_off(M)
7+
if size(M[1], 1) > 1
8+
return sqrt(
9+
sum(
10+
abs2(M[i, j]) + abs2(M[j, i]) for i in 1:size(M, 1) for
11+
j in i+1:size(M, 1)
12+
),
13+
)
14+
else
15+
return 0.0
16+
end
17+
end
18+
19+
"""
20+
diagonalization_iter(D::Vector{<:AbstractMatrix{T}}) where {T}
21+
22+
Given the vector `D` of `[F_i * E_i, F_i * M_1 * E_i, ..., F_i * M_p * E_i]`,
23+
computes the matrix `X` (resp. `Y`) corresponding to ``(I_n + X_i)``
24+
(resp. ``(I_n + Y_i)``) of [KMY22, Theorem 5] that solves equations
25+
[KMY22, (26)-(29)].
26+
"""
27+
function diagonalization_iter(D::Vector{<:AbstractMatrix{T}}) where {T}
28+
n = LinearAlgebra.checksquare(D[1])
29+
s = length(D)
30+
31+
X = fill(zero(T), n, n)
32+
Y = fill(zero(T), n, n)
33+
34+
A = fill(zero(T), s, 2)
35+
b = fill(zero(T), s)
36+
for i in 1:n
37+
for j in 1:n
38+
if i != j
39+
for k in 1:s
40+
A[k, 1] = D[k][i, i]
41+
A[k, 2] = D[k][j, j]
42+
b[k] = -D[k][i, j]
43+
end
44+
v = A \ b
45+
X[i, j] = v[1]
46+
Y[i, j] = v[2]
47+
end
48+
end
49+
end
50+
for i in 1:n
51+
X[i, i] = 1
52+
Y[i, i] = 1
53+
end
54+
return X, Y
55+
end
56+
57+
"""
58+
struct NewtonTypeDiagonalization{T,RNGT} <: AbstractMultiplicationMatricesSolver
59+
max_iter::Int
60+
ε::T
61+
tol::T
62+
rng::RNGT
63+
end
64+
65+
Simultaneous diagonalization of commuting matrices using the method of [KMY22, Theorem 5].
66+
67+
[KMY22] Khouja, Rima, Mourrain, Bernard, and Yakoubsohn, Jean-Claude.
68+
*Newton-type methods for simultaneous matrix diagonalization.*
69+
Calcolo 59.4 (2022): 38.
70+
"""
71+
struct NewtonTypeDiagonalization{T,RNGT} <: AbstractMultiplicationMatricesSolver
72+
max_iter::Int
73+
ε::T
74+
tol::T
75+
rng::RNGT
76+
end
77+
# These were the values in MultivariateSeries/diagonalization.jl
78+
function NewtonTypeDiagonalization(max_iter, ε, tol)
79+
return NewtonTypeDiagonalization(max_iter, ε, tol, Random.GLOBAL_RNG)
80+
end
81+
NewtonTypeDiagonalization() = NewtonTypeDiagonalization(10, 1e-3, 5e-2)
82+
83+
function _eigvecs(M::AbstractMatrix{BigFloat})
84+
ev = LinearAlgebra.schur(Float64.(M)).vectors
85+
# `eigvecs` is failing some tests with a non-invertible `ev`
86+
#ev = LinearAlgebra.eigvecs(Float64.(M))
87+
return convert(Matrix{BigFloat}, ev)
88+
end
89+
_eigvecs(M::AbstractMatrix) = LinearAlgebra.eigvecs
90+
91+
function _solve_multiplication_matrices(M, λ, solver::NewtonTypeDiagonalization)
92+
@assert length(M) == length(λ)
93+
n = length(λ)
94+
r = LinearAlgebra.checksquare(M[1])
95+
96+
M1 = sum.* M)
97+
E = _eigvecs(M1)
98+
99+
# With `eigvecs`, we should do `inv` but with `schur` we can just transpose
100+
#F = inv(E)
101+
F = E'
102+
103+
D = vcat(
104+
# Add one matrix for the equation `F_i * E_i = I`
105+
# constraining `E_i` to be invertible
106+
[Matrix{eltype(M[1])}(I, r, r)],
107+
[F * M[i] * E for i in eachindex(M)],
108+
)
109+
err = sum(norm_off.(D))
110+
Δ = sum(norm.(D))
111+
112+
nit = 0
113+
114+
if err / Δ > solver.tol
115+
Δ = err
116+
while nit < solver.max_iter && Δ > solver.ε
117+
err0 = err
118+
X, Y = diagonalization_iter(D)
119+
# From [KMY22, Theorem 5]
120+
# Z_{i,k} + ∑_{i,k}
121+
# = F_i * M_k * E_i
122+
# = (I_n * Y_i) * (F_{i-1} * M_k * E_{i-1}) * (I_n * X_i)
123+
# = (I_n * Y_i) * D[i] * (I_n * X_i)
124+
# = Y * D[i] * X
125+
D = [Y * D[i] * X for i in eachindex(D)]
126+
# E_{i+1} = E_i * (I_n * X_i) from [KMY22, Theorem 5]
127+
E = E * X
128+
# F_{i+1} = (I_n * Y_i) * F_i from [KMY22, Theorem 5]
129+
F = Y * F
130+
nit += 1
131+
err = sum(norm_off.(D))
132+
Δ = err0 - err
133+
end
134+
end
135+
136+
return [[D[j+1][i, i] / D[1][i, i] for j in 1:n] for i in 1:r]
137+
end

src/schur.jl

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function _clusterordschur(M::AbstractMatrix{<:Real}, ɛ)
5151
atol = A[]
5252
# condition_number requires that conjugate pair need to be treated together so we first need to handle them
5353
# If they are in the same cluster then pair them, otherwise it is complex solution so we reject them
54-
i = 1
54+
i = firstindex(v)
5555
while i <= lastindex(v)
5656
if isreal(v[i])
5757
push!(clusters, [i])
@@ -107,3 +107,51 @@ function _clusterordschur(M::AbstractMatrix{<:Real}, ɛ)
107107
end
108108
return Z, clusters
109109
end
110+
111+
"""
112+
struct ReorderedSchurMultiplicationMatricesSolver{T,RNGT<:Random.AbstractRNG} <:
113+
AbstractMultiplicationMatricesSolver
114+
ɛ::T
115+
rng::RNGT
116+
end
117+
118+
Simultaneous diagonalization of commuting matrices using the method of [CGT97].
119+
120+
[CGT97] Corless, R. M.; Gianni, P. M. & Trager, B. M.
121+
*A reordered Schur factorization method for zero-dimensional polynomial systems with multiple roots*
122+
Proceedings of the 1997 international symposium on Symbolic and algebraic computation, 1997, 133-140
123+
"""
124+
struct ReorderedSchurMultiplicationMatricesSolver{T,RNGT<:Random.AbstractRNG} <:
125+
AbstractMultiplicationMatricesSolver
126+
ɛ::T
127+
rng::RNGT
128+
end
129+
function ReorderedSchurMultiplicationMatricesSolver(ɛ)
130+
return ReorderedSchurMultiplicationMatricesSolver(ɛ, Random.GLOBAL_RNG)
131+
end
132+
function ReorderedSchurMultiplicationMatricesSolver{T}() where {T}
133+
return ReorderedSchurMultiplicationMatricesSolver(Base.rtoldefault(real(T)))
134+
end
135+
136+
# Deterministic part
137+
function _solve_multiplication_matrices(
138+
Ms::AbstractVector{<:AbstractMatrix{T}},
139+
λ,
140+
solver::ReorderedSchurMultiplicationMatricesSolver,
141+
) where {T<:Real}
142+
@assert length(Ms) == length(λ)
143+
n = length(λ)
144+
Z, clusters = clusterordschur(sum.* Ms), solver.ɛ)
145+
r = length(clusters)
146+
vals = [zeros(T, n) for k in 1:r]
147+
for k in 1:r
148+
nk = length(clusters[k])
149+
for j in clusters[k]
150+
q = Z[:, j]
151+
for i in 1:n
152+
vals[k][i] += dot(q, Ms[i] * q) / nk
153+
end
154+
end
155+
end
156+
return vals
157+
end

src/solve.jl

Lines changed: 1 addition & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export algebraic_solver, ReorderedSchurMultiplicationMatricesSolver
1+
export algebraic_solver
22

33
"""
44
AbstractAlgebraicSolver
@@ -34,13 +34,6 @@ Returns a nullable which is `null` if `V` is not zero-dimensional and is the lis
3434
"""
3535
function multiplication_matrices end
3636

37-
"""
38-
AbstractMultiplicationMatricesSolver
39-
40-
Solver of algebraic equations using multiplication matrices.
41-
"""
42-
abstract type AbstractMultiplicationMatricesSolver end
43-
4437
struct SolverUsingMultiplicationMatrices{
4538
A<:AbstractMultiplicationMatricesAlgorithm,
4639
S<:AbstractMultiplicationMatricesSolver,
@@ -58,10 +51,6 @@ function solve(V, solver::SolverUsingMultiplicationMatrices)
5851
end
5952
end
6053

61-
struct MultiplicationMatrices{Ms}
62-
matrices::Ms
63-
end
64-
6554
struct GröbnerBasisMultiplicationMatricesAlgorithm <:
6655
AbstractMultiplicationMatricesAlgorithm end
6756

@@ -94,55 +83,6 @@ function multiplication_matrices(
9483
end
9584
end
9685

97-
include("schur.jl")
98-
99-
"""
100-
Corless, R. M.; Gianni, P. M. & Trager, B. M. A reordered Schur factorization method for zero-dimensional polynomial systems with multiple roots Proceedings of the 1997 international symposium on Symbolic and algebraic computation, 1997, 133-140
101-
"""
102-
struct ReorderedSchurMultiplicationMatricesSolver{T,RNGT<:Random.AbstractRNG} <:
103-
AbstractMultiplicationMatricesSolver
104-
ɛ::T
105-
rng::RNGT
106-
end
107-
function ReorderedSchurMultiplicationMatricesSolver(ɛ)
108-
return ReorderedSchurMultiplicationMatricesSolver(ɛ, Random.GLOBAL_RNG)
109-
end
110-
function ReorderedSchurMultiplicationMatricesSolver{T}() where {T}
111-
return ReorderedSchurMultiplicationMatricesSolver(Base.rtoldefault(real(T)))
112-
end
113-
114-
function solve(
115-
Ms::MultiplicationMatrices,
116-
solver::ReorderedSchurMultiplicationMatricesSolver,
117-
)
118-
λ = rand(solver.rng, length(Ms.matrices))
119-
λ /= sum(λ)
120-
return _solve_multiplication_matrices(Ms.matrices, λ, solver)
121-
end
122-
123-
# Deterministic part
124-
function _solve_multiplication_matrices(
125-
Ms::AbstractVector{<:AbstractMatrix{T}},
126-
λ,
127-
solver::ReorderedSchurMultiplicationMatricesSolver,
128-
) where {T<:Real}
129-
@assert length(Ms) == length(λ)
130-
n = length(λ)
131-
Z, clusters = clusterordschur(sum.* Ms), solver.ɛ)
132-
r = length(clusters)
133-
vals = [zeros(T, n) for k in 1:r]
134-
for k in 1:r
135-
nk = length(clusters[k])
136-
for j in clusters[k]
137-
q = Z[:, j]
138-
for i in 1:n
139-
vals[k][i] += dot(q, Ms[i] * q) / nk
140-
end
141-
end
142-
end
143-
return vals
144-
end
145-
14686
function algebraic_solver(
14787
algo::AbstractMultiplicationMatricesAlgorithm,
14888
solver::AbstractMultiplicationMatricesSolver,
@@ -160,14 +100,6 @@ end
160100
function default_multiplication_matrices_algorithm(p)
161101
return GröbnerBasisMultiplicationMatricesAlgorithm()
162102
end
163-
function default_multiplication_matrices_solver(::Type{T}) where {T}
164-
return ReorderedSchurMultiplicationMatricesSolver{T}()
165-
end
166-
function default_multiplication_matrices_solver(
167-
::AbstractVector{PT},
168-
) where {T,PT<:_APL{T}}
169-
return default_multiplication_matrices_solver(T)
170-
end
171103

172104
function default_algebraic_solver(p)
173105
return algebraic_solver(

0 commit comments

Comments
 (0)