Skip to content

Commit 12002c5

Browse files
authored
Add partial multiplication matrices (#73)
* Add partial multiplication matrices * Fixes * Fixes * Fixes * fix format
1 parent 6658945 commit 12002c5

File tree

3 files changed

+223
-21
lines changed

3 files changed

+223
-21
lines changed

src/border.jl

Lines changed: 182 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,40 @@ function BorderBasis{StaircaseDependence}(b::BorderBasis{LinearDependence})
6262
return BorderBasis(d, b.matrix[rows, cols])
6363
end
6464

65+
struct StaircaseSolver{
66+
T,
67+
R<:RankCheck,
68+
M<:SemialgebraicSets.AbstractMultiplicationMatricesSolver,
69+
}
70+
max_partial_iterations::Int
71+
max_iterations::Int
72+
rank_check::R
73+
solver::M
74+
end
75+
function StaircaseSolver{T}(;
76+
max_partial_iterations::Int = 0,
77+
max_iterations::Int = -1,
78+
rank_check::RankCheck = LeadingRelativeRankTol(Base.rtoldefault(T)),
79+
solver = SS.ReorderedSchurMultiplicationMatricesSolver{T}(),
80+
) where {T}
81+
return StaircaseSolver{T,typeof(rank_check),typeof(solver)}(
82+
max_partial_iterations,
83+
max_iterations,
84+
rank_check,
85+
solver,
86+
)
87+
end
88+
6589
function solve(
6690
b::BorderBasis{LinearDependence,T},
67-
solver::SemialgebraicSets.AbstractMultiplicationMatricesSolver = MultivariateMoments.SemialgebraicSets.ReorderedSchurMultiplicationMatricesSolver{
68-
T,
69-
}(),
91+
solver::StaircaseSolver = StaircaseSolver{T}(),
7092
) where {T}
7193
return solve(BorderBasis{StaircaseDependence}(b), solver)
7294
end
7395

7496
function solve(
7597
b::BorderBasis{<:StaircaseDependence,T},
76-
solver::SemialgebraicSets.AbstractMultiplicationMatricesSolver = MultivariateMoments.SemialgebraicSets.ReorderedSchurMultiplicationMatricesSolver{
77-
T,
78-
}(),
98+
solver::StaircaseSolver{T} = StaircaseSolver{T}(),
7999
) where {T}
80100
d = b.dependence
81101
dependent = dependent_basis(d)
@@ -180,34 +200,70 @@ function solve(
180200
end
181201
end
182202
@assert o <= length(vars)
183-
if o < length(vars)
203+
partial = o < length(vars)
204+
if partial
184205
# Several things could have gone wrong here:
185206
# 1) We could be missing corners,
186207
# 2) We have all corners but we could not complete the border because
187208
# there is not topological order working
188-
# In any case, we don't have all multiplication matrices so we abort
189-
return
209+
# We now try to build new relation by comparing partial multiplication matrices
210+
# We store them in a vector and reshape in a matrix after as it's easy to append to a vector in-place.
211+
# a matrix after
212+
if solver.max_partial_iterations == 0
213+
return
214+
else
215+
com_fix = partial_commutation_fix(
216+
known_border_coefficients,
217+
border_coefficients,
218+
T,
219+
standard,
220+
vars,
221+
solver.rank_check,
222+
)
223+
end
224+
else
225+
if solver.max_iterations == 0
226+
Uperp = nothing
227+
else
228+
Uperp = commutation_fix(mult, solver.solver.ε)
229+
end
230+
com_fix = if isnothing(Uperp)
231+
nothing
232+
else
233+
Uperp, standard
234+
end
190235
end
191-
Uperp = commutation_fix(mult, solver.ε)
192-
if isnothing(Uperp)
236+
if isnothing(com_fix)
193237
# The matrices commute, let's simultaneously diagonalize them
194-
sols = SS.solve(SS.MultiplicationMatrices(mult), solver)
238+
sols = SS.solve(SS.MultiplicationMatrices(mult), solver.solver)
195239
return ZeroDimensionalVariety(sols)
196240
else
241+
Uperp, Ubasis = com_fix
197242
# The matrices don't commute, let's find the updated staircase and start again
198-
new_basis, I1, I2 = MB.merge_bases(standard, dependent)
243+
new_basis, I1, I2 = MB.merge_bases(Ubasis, dependent)
199244
new_matrix = Matrix{T}(undef, length(new_basis), size(Uperp, 2))
245+
I_nontrivial_standard = [
246+
_index(Ubasis, std) for
247+
std in standard_basis(b.dependence, trivial = false).monomials
248+
]
249+
Uperpstd = Uperp[I_nontrivial_standard, :]
200250
for i in axes(new_matrix, 1)
201251
if iszero(I1[i])
202252
@assert !iszero(I2[i])
203-
new_matrix[i, :] = b.matrix[:, I2[i]]' * Uperp
253+
new_matrix[i, :] = b.matrix[:, I2[i]]' * Uperpstd
204254
else
205255
@assert iszero(I2[i])
206256
new_matrix[i, :] = Uperp[I1[i], :]
207257
end
208258
end
209259
null = MacaulayNullspace(new_matrix, new_basis)
210-
return solve(null, ShiftNullspace{StaircaseDependence}())
260+
new_solver = StaircaseSolver{T}(;
261+
max_partial_iterations = solver.max_partial_iterations - partial,
262+
max_iterations = solver.max_iterations - !partial,
263+
solver.rank_check,
264+
solver.solver,
265+
)
266+
return solve(null, ShiftNullspace{StaircaseDependence}(new_solver))
211267
end
212268
end
213269

@@ -249,6 +305,117 @@ function commutation_fix(matrices, ε)
249305
end
250306
end
251307

308+
function partial_commutation_fix(
309+
known_border_coefficients,
310+
border_coefficients,
311+
::Type{T},
312+
standard,
313+
vars,
314+
rank_check::RankCheck,
315+
) where {T}
316+
function shifted_border_coefficients(mono, shift)
317+
coef = border_coefficients(mono)
318+
ret = zero(coef)
319+
unknown = zero(MP.polynomial_type(mono, T))
320+
for i in eachindex(coef)
321+
if iszero(coef)
322+
continue
323+
end
324+
shifted = shift * standard.monomials[i]
325+
j = _index(standard, shifted)
326+
if !isnothing(j)
327+
ret[j] += coef[i]
328+
elseif known_border_coefficients(shifted)
329+
ret .+= coef[i] .* border_coefficients(shifted)
330+
else
331+
MA.operate!(MA.add_mul, unknown, coef[i], shifted)
332+
end
333+
end
334+
return ret, unknown
335+
end
336+
new_relations = T[]
337+
unknowns = MP.polynomial_type(prod(vars), T)[]
338+
for std in standard.monomials
339+
for x in vars
340+
mono_x = x * std
341+
if !known_border_coefficients(mono_x)
342+
# FIXME what do we do if one of the two monomials is unknown
343+
# but the other one is known ?
344+
continue
345+
end
346+
for y in vars
347+
mono_y = y * std
348+
if !known_border_coefficients(mono_y)
349+
# FIXME what do we do if one of the two monomials is unknown
350+
# but the other one is known ?
351+
continue
352+
end
353+
if isnothing(_index(standard, mono_x))
354+
if isnothing(_index(standard, mono_y))
355+
coef_xy, unknowns_xy =
356+
shifted_border_coefficients(mono_y, x)
357+
else
358+
mono_xy = x * mono_y
359+
if known_border_coefficients(mono_xy)
360+
coef_xy = border_coefficients(mono_xy)
361+
unknowns_yx = zero(PT)
362+
else
363+
coef_xy = zeros(length(standard.monomials))
364+
unknowns_xy = mono_xy
365+
end
366+
end
367+
coef_yx, unknowns_yx =
368+
shifted_border_coefficients(mono_x, y)
369+
else
370+
if !isnothing(_index(standard, mono_y))
371+
# Let `f` be `known_border_coefficients`.
372+
# They are both standard so we'll get
373+
# `f(y * mono_x) - f(x * mono_y)`
374+
# which will give a zero column, let's just ignore it
375+
continue
376+
end
377+
mono_yx = y * mono_x
378+
if known_border_coefficients(mono_yx)
379+
coef_yx = border_coefficients(mono_yx)
380+
unknowns_yx = zero(PT)
381+
else
382+
coef_yx = zeros(length(standard.monomials))
383+
unknowns_yx = mono_yx
384+
end
385+
coef_xy, unknowns_xy =
386+
shifted_border_coefficients(mono_y, x)
387+
end
388+
append!(new_relations, coef_xy - coef_yx)
389+
push!(unknowns, unknowns_xy - unknowns_yx)
390+
end
391+
end
392+
end
393+
standard_part = reshape(
394+
new_relations,
395+
length(standard.monomials),
396+
div(length(new_relations), length(standard.monomials)),
397+
)
398+
unknown_monos = MP.merge_monomial_vectors(MP.monomials.(unknowns))
399+
unknown_part = Matrix{T}(undef, length(unknown_monos), length(unknowns))
400+
for i in eachindex(unknowns)
401+
unknown_part[:, i] = MP.coefficients(unknowns[i], unknown_monos)
402+
end
403+
basis, I1, I2 = MB.merge_bases(standard, MB.MonomialBasis(unknown_monos))
404+
M = Matrix{T}(undef, length(basis.monomials), size(standard_part, 2))
405+
for i in eachindex(basis.monomials)
406+
if iszero(I1[i])
407+
@assert !iszero(I2[i])
408+
M[i, :] = unknown_part[I2[i], :]
409+
else
410+
@assert iszero(I2[i])
411+
M[i, :] = standard_part[I1[i], :]
412+
end
413+
end
414+
F = LinearAlgebra.svd(M, full = true)
415+
r = rank_from_singular_values(F.S, rank_check)
416+
return F.U[:, (r+1):end], basis
417+
end
418+
252419
"""
253420
Base.@kwdef struct AlgebraicBorderSolver{
254421
D,

src/shift.jl

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ the row indices of the null space.
103103
*Back to the roots: Polynomial system solving, linear algebra, systems theory.*
104104
IFAC Proceedings Volumes 45.16 (2012): 1203-1208.
105105
"""
106-
struct ShiftNullspace{D,C<:RankCheck} <: MacaulayNullspaceSolver
106+
struct ShiftNullspace{D,S,C<:RankCheck} <: MacaulayNullspaceSolver
107+
solver::S
107108
check::C
108109
end
109110
# Because the matrix is orthogonal, we know the SVD of the whole matrix is
@@ -112,14 +113,30 @@ end
112113
# constant monomial) should be a standard monomial, `LeadingRelativeRankTol`
113114
# ensures that we will take it.
114115
function ShiftNullspace{D}(check::RankCheck) where {D}
115-
return ShiftNullspace{D,typeof(check)}(check)
116+
return ShiftNullspace{D,Nothing,typeof(check)}(nothing, check)
117+
end
118+
function ShiftNullspace{D}(solver::StaircaseSolver) where {D}
119+
check = solver.rank_check
120+
return ShiftNullspace{D,typeof(solver),typeof(check)}(solver, check)
121+
end
122+
function ShiftNullspace{D}() where {D}
123+
return ShiftNullspace{D}(LeadingRelativeRankTol(Base.rtoldefault(Float64)))
116124
end
117-
ShiftNullspace{D}() where {D} = ShiftNullspace{D}(LeadingRelativeRankTol(1e-8))
118125
ShiftNullspace(args...) = ShiftNullspace{StaircaseDependence}(args...)
119126

127+
function _solver(
128+
shift::ShiftNullspace{StaircaseDependence,Nothing},
129+
::Type{T},
130+
) where {T}
131+
return StaircaseSolver{T}(rank_check = shift.check)
132+
end
133+
function _solver(shift::ShiftNullspace, ::Type)
134+
return shift.solver
135+
end
136+
120137
function border_basis_and_solver(
121-
null::MacaulayNullspace,
138+
null::MacaulayNullspace{T},
122139
shift::ShiftNullspace{D},
123-
) where {D}
124-
return BorderBasis{D}(null, shift.check), nothing
140+
) where {T,D}
141+
return BorderBasis{D}(null, shift.check), _solver(shift, T)
125142
end

test/null.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,24 @@ b(x) = MB.MonomialBasis(x)
99

1010
include("utils.jl")
1111

12+
function test_partial_commutative_fix(x, y)
13+
matrix = Float64[
14+
1 0 0 0 # 1
15+
0 1 0 0 # y
16+
0 0 1 0 # x
17+
0 0 0 1 # y^2
18+
0 0 1 0 # x * y
19+
1 0 0 0 # x^2
20+
]
21+
basis = MB.MonomialBasis(MP.monomials((x, y), 0:2))
22+
null = MM.MacaulayNullspace(matrix, basis, 1e-8)
23+
D = MM.StaircaseDependence
24+
solver = MM.StaircaseSolver{Float64}(max_partial_iterations = 1)
25+
shift_solver = MM.ShiftNullspace{D}(solver)
26+
sol = MM.solve(null, shift_solver)
27+
return testelements(sol, [[1, 1], [-1, 1]], 1e-8)
28+
end
29+
1230
function test_dependent_border(x, y)
1331
matrix = Float64[
1432
1 0 0 0 0 # 1

0 commit comments

Comments
 (0)