Skip to content

Commit 988e2f5

Browse files
authored
Fix and document XORSpace (#314)
* Fix and document XORSpace * Add tests * Fix
1 parent c096b3d commit 988e2f5

File tree

6 files changed

+129
-27
lines changed

6 files changed

+129
-27
lines changed

docs/src/constraints.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ Types of sparsity
295295
SumOfSquares.Certificate.Sparsity.Variable
296296
SumOfSquares.Certificate.Sparsity.Monomial
297297
SumOfSquares.Certificate.Sparsity.SignSymmetry
298+
SumOfSquares.Certificate.Sparsity.XORSpace
298299
```
299300

300301
Ideal certificates:

src/Certificate/Sparsity/sign.jl

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,40 @@
22
struct SignSymmetry <: Sparsity.Pattern end
33
44
Sign symmetry as developed in [Section III.C, L09].
5+
Let `n` be the number of variables.
6+
A *sign-symmetry* is a binary vector `r` of `{0, 1}^n`
7+
such that `dot(r, e)` is even for all exponent `e`.
58
6-
[L09] Lofberg, Johan.
9+
Let `o(e)` be the binary vector of `{0, 1}^n` for which the `i`th bit is `i`
10+
iff the `i`th entry of `e` is odd.
11+
Let `O` be the set of `o(e)` for exponents of `e`.
12+
The sign symmetry of `r` is then equivalent to its orthogonality
13+
with all elements of `O`.
14+
15+
Since we are only interested in the orthogonal subspace, say `R`, of `O`,
16+
even if `O` is not a linear subspace (i.e., it is not invariant under `xor`),
17+
we compute its span.
18+
We start by creating row echelon form of the span of `O` using
19+
[`XORSpace`](@ref).
20+
We then compute a basis for `R`.
21+
The set `R` of sign symmetries will be the span of this basis.
22+
23+
Let `a`, `b` be exponents of monomials of the gram basis. For any `r in R`,
24+
```
25+
⟨r, o(a + b)⟩ = ⟨r, xor(o(a), o(b)⟩ = xor(⟨r, o(a)⟩, ⟨r, o(b)⟩)
26+
```
27+
For `o(a, b)` to be sign symmetric, this scalar product should be zero for all
28+
sign symmetry `r`.
29+
This is equivalent to saying that `⟨r, o(a)⟩` and `⟨r, o(b)⟩` are equal
30+
for all `r in R`.
31+
In other words, the projection of `o(a)` and `o(b)` to `R` have the same
32+
coordinates in the basis.
33+
If we order the monomials by grouping them by equal coordinates of projection,
34+
we see that the product that are sign symmetric form a block diagonal
35+
structure.
36+
This means that we can group the monomials by these coordinates.
37+
38+
[L09] Löfberg, Johan.
739
*Pre-and post-processing sum-of-squares programs in practice*.
840
IEEE transactions on automatic control 54, no. 5 (2009): 1007-1011.
941
"""
@@ -24,7 +56,9 @@ function buckets_sign_symmetry(monos, r, ::Type{T}, ::Type{U}) where {T,U}
2456
# We store vector of indices instead of vector of monos in the bucket
2557
# as `monos` is already sorted and the buckets content will be sorted
2658
# so it will remain sorted and `DynamicPolynomials` can keep the
27-
# `MonomialVector` struct in `monos[I]`.
59+
# `MonomialVector` struct in `monos[I]`. With MultivariateBases, the
60+
# sorted state will be ensured by the basis struct so it will also
61+
# serve other MultivariatePolynomials implementations
2862
buckets = Dict{U,Vector{Int}}()
2963
for (idx, mono) in enumerate(monos)
3064
exp = binary_exponent(MP.exponents(mono), T)
@@ -51,11 +85,8 @@ function sign_symmetry(
5185
end
5286
function sparsity(
5387
monos::AbstractVector{<:MP.AbstractMonomial},
54-
sp::SignSymmetry,
55-
gram_monos::AbstractVector = SumOfSquares.Certificate.monomials_half_newton_polytope(
56-
monos,
57-
tuple(),
58-
),
88+
::SignSymmetry,
89+
gram_monos::AbstractVector{<:MP.AbstractMonomial},
5990
)
6091
n = MP.nvariables(monos)
6192
return sign_symmetry(monos, n, appropriate_type(n), gram_monos)

src/Certificate/Sparsity/xor_space.jl

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,44 @@
1+
"""
2+
mutable struct XORSpace{T<:Integer}
3+
dimension::Int
4+
basis::Vector{T}
5+
end
6+
7+
Basis for a linear subspace of the Hamming space (i.e. set of binary string
8+
`{0, 1}^n` of length `n`) represented in the bits of an integer of type `T`.
9+
This is used to implement `Certificate.Sparsity.SignSymmetry`.
10+
11+
Consider the scalar product `⟨a, b⟩` which returns the
12+
the xor of the bits of `a & b`.
13+
It is a scalar product since `⟨a, b⟩ = ⟨b, a⟩` and
14+
`⟨a, xor(b, c)⟩ = xor(⟨a, b⟩, ⟨a, c⟩)`.
15+
16+
We have two options here to compute the orthogonal space.
17+
18+
The first one is to build an orthogonal basis
19+
with some kind of Gram-Schmidt process and then to obtain the orthogonal
20+
space by removing the projection from each vector of the basis.
21+
22+
The second option, which is the one we use here is to compute the row echelon
23+
form and then read out the orthogonal subspace directly from it.
24+
For instance, if the row echelon form is
25+
```
26+
1 a 0 c e
27+
b 1 d f
28+
```
29+
then the orthogonal basis is
30+
```
31+
a 1 b 0 0
32+
c 0 d 1 0
33+
e 0 f 0 1
34+
```
35+
36+
The `basis` vector has `dimension` nonzero elements. Any element added with
37+
`push!` can be obtained as the `xor` of some of the elements of `basis`.
38+
Moreover, the `basis` is kept in row echelon form in the sense that the first,
39+
second, ..., `i - 1`th bits of `basis[i]` are zero and `basis[i]` is zero if its
40+
`i`th bit is not one.
41+
"""
142
mutable struct XORSpace{T<:Integer}
243
dimension::Int
344
basis::Vector{T}
@@ -19,18 +60,29 @@ end
1960
function XORSpace(n)
2061
return XORSpace{appropriate_type(n)}(n)
2162
end
63+
b(x) = bitstring(x)[end-5:end]
64+
e_i(T, i) = (one(T) << (i - 1))
65+
has_bit(x, i) = !iszero(x & e_i(typeof(x), i))
2266
function Base.push!(xs::XORSpace{T}, x::T) where {T}
23-
for i in eachindex(xs.basis)
24-
if !iszero(x & (one(T) << (i - 1)))
25-
if iszero(xs.basis[i])
26-
xs.dimension += 1
27-
xs.basis[i] = x
28-
break
29-
else
30-
x = xor(x, xs.basis[i])
31-
end
67+
I = eachindex(xs.basis)
68+
for i in I
69+
if !iszero(xs.basis) && has_bit(x, i)
70+
x = xor(x, xs.basis[i])
71+
end
72+
end
73+
i = findfirst(Base.Fix1(has_bit, x), I)
74+
if isnothing(i)
75+
return
76+
end
77+
xs.dimension += 1
78+
# Clear `i`th column elements of the basis already added
79+
for j in I
80+
if has_bit(xs.basis[j], i)
81+
xs.basis[j] = xor(xs.basis[j], x)
3282
end
3383
end
84+
# `x` will be the row used as pivot for the `i`th column
85+
xs.basis[i] = x
3486
return xs
3587
end
3688
function orthogonal_complement(xs::XORSpace{T}) where {T}

src/Certificate/newton_polytope.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,11 @@ end
213213
# two different monomials of `monos` and `mono` is not in `X` then, the corresponding
214214
# diagonal entry of the Gram matrix will be zero hence the whole column and row
215215
# will be zero hence we can remove this monomial.
216-
# See [Theorem 2, L09] or [Section 2.4, BKP16].
216+
# See [Proposition 3.7, CLR95], [Theorem 2, L09] or [Section 2.4, BKP16].
217+
218+
# [CLR95] Choi, M. D. and Lam, T. Y. and Reznick, B.
219+
# *Sum of Squares of Real Polynomials*.
220+
# Proceedings of Symposia in Pure mathematics (1995)
217221
#
218222
# [L09] Lofberg, Johan.
219223
# *Pre-and post-processing sum-of-squares programs in practice*.

test/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
[deps]
2+
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
23
DynamicPolynomials = "7c1d4256-1411-5781-91ec-d7bc3513ac07"
34
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
45
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

test/sparsity.jl

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
11
using Test
2+
import Combinatorics
23
using SumOfSquares
34
import MultivariateBases as MB
45

6+
function _xor_complement_test(
7+
exps,
8+
expected,
9+
n = maximum(ndigits(exp, base = 2) for exp in exps; init = 1),
10+
)
11+
for e in Combinatorics.permutations(exps)
12+
@test Certificate.Sparsity.xor_complement(e, n) == expected
13+
end
14+
end
15+
516
function xor_complement_test()
6-
@test Certificate.Sparsity.xor_complement([1], 1) == Int[]
7-
@test Certificate.Sparsity.xor_complement(Int[], 1) == [1]
8-
@test Certificate.Sparsity.xor_complement([1], 2) == [2]
9-
@test Certificate.Sparsity.xor_complement([2], 2) == [1]
10-
@test Certificate.Sparsity.xor_complement([1, 2], 2) == Int[]
11-
@test Certificate.Sparsity.xor_complement([1, 3], 2) == Int[]
12-
@test Certificate.Sparsity.xor_complement(Int[], 2) == [1, 2]
13-
@test Certificate.Sparsity.xor_complement([7], 3) == [3, 5]
14-
@test Certificate.Sparsity.xor_complement([5, 6, 3], 3) == [7]
15-
@test Certificate.Sparsity.xor_complement([3], 3) == [3, 4]
17+
_xor_complement_test([1], Int[])
18+
_xor_complement_test(Int[], [1])
19+
_xor_complement_test([1], [2], 2)
20+
_xor_complement_test([2], [1])
21+
_xor_complement_test([1, 2], Int[])
22+
_xor_complement_test([1, 3], Int[])
23+
_xor_complement_test(Int[], [1, 2], 2)
24+
_xor_complement_test([7], [3, 5])
25+
_xor_complement_test([5, 6, 3], [7])
26+
_xor_complement_test([3], [3, 4], 3)
27+
_xor_complement_test([32, 3, 24, 14, 21, 56], [7, 27])
28+
return
1629
end
1730

1831
function set_monos(bases::Vector{<:MB.MonomialBasis})

0 commit comments

Comments
 (0)