|
| 1 | +# # Hypercube |
| 2 | + |
| 3 | +#md # [](@__BINDER_ROOT_URL__/generated/Extension/hypercube.ipynb) |
| 4 | +#md # [](@__NBVIEWER_ROOT_URL__/generated/Extension/hypercube.ipynb) |
| 5 | +# **Contributed by**: Benoît Legat |
| 6 | + |
| 7 | +# Given a Sum-of-Squares constraint on an algebraic set: |
| 8 | +# ```math |
| 9 | +# g_1(x) = , \ldots, g_m(x) = 0 \Rightarrow p(x) \ge 0. |
| 10 | +# ``` |
| 11 | +# We can either use the certificate: |
| 12 | +# ```math |
| 13 | +# p(x) = s(x) + \lambda_1(x) g_1(x) + \cdots + \lambda_m(x) g_m(x), s_0(x) \text{ is SOS}, |
| 14 | +# ``` |
| 15 | +# or |
| 16 | +# ```math |
| 17 | +# p(x) \equiv s(x) \pmod{\la g_1(x), \ldots, g_m(x) \ra}, s_0(x) \text{ is SOS}. |
| 18 | +# ``` |
| 19 | +# the second one leads to a *simpler* SDP but needs to compute a *Gr\"obner* basis: |
| 20 | +# * SemialgebraicSets implements Buchberger's algorithm. |
| 21 | +# * The `@set` macro recognizes variable fix, e.g., `x = 1` |
| 22 | +# and provides shortcut. |
| 23 | +# * If you know a \alert{better} way to take modulo, |
| 24 | +# better create your \alert{own} type of algebraic set! |
| 25 | + |
| 26 | +# We illustrate this in this example. |
| 27 | + |
| 28 | +using DynamicPolynomials |
| 29 | +@polyvar x[1:3] |
| 30 | +p = sum(x)^2 |
| 31 | +using SumOfSquares |
| 32 | +S = algebraicset([xi^2 - 1 for xi in x]) |
| 33 | + |
| 34 | +# We will now search for the minimum of `x` over `S` using Sum of Squares Programming. |
| 35 | +# We first need to pick an SDP solver, see [here](https://jump.dev/JuMP.jl/v0.21.6/installation/#Supported-solvers) for a list of the available choices. |
| 36 | + |
| 37 | +import CSDP |
| 38 | +solver = optimizer_with_attributes(CSDP.Optimizer, MOI.Silent() => true) |
| 39 | + |
| 40 | +function min_algebraic(S) |
| 41 | + model = SOSModel(solver) |
| 42 | + @variable(model, α) |
| 43 | + @objective(model, Max, α) |
| 44 | + @constraint(model, c, p >= α, domain = S) |
| 45 | + optimize!(model) |
| 46 | + @show termination_status(model) |
| 47 | + @show objective_value(model) |
| 48 | +end |
| 49 | + |
| 50 | +min_algebraic(S) |
| 51 | + |
| 52 | +# Note that the minimum is in fact `1`. |
| 53 | +# Indeed, since each variables is odd (it is either `-1` or `1`) |
| 54 | +# and there is an odd number of variables, their sum is odd. |
| 55 | +# Therefore it cannot be zero! |
| 56 | + |
| 57 | +# We can see that the Gröbner basis of `S` was computed |
| 58 | + |
| 59 | +@show S.I.gröbnerbasis |
| 60 | +S.I.algo |
| 61 | + |
| 62 | +# The Gröbner basis is simple to compute in this case as the vector |
| 63 | +# of `xi^2 - 1` is already a Gröbner basis. |
| 64 | +# However, we still need to divide polynomials by the Gröbner basis |
| 65 | +# which can be simplified in this case. |
| 66 | + |
| 67 | +const MP = MultivariatePolynomials |
| 68 | +const SS = SemialgebraicSets |
| 69 | +struct HypercubeIdeal{V} <: SS.AbstractPolynomialIdeal |
| 70 | + variables::Vector{V} |
| 71 | +end |
| 72 | +struct HypercubeSet{V} <: SS.AbstractAlgebraicSet |
| 73 | + ideal::HypercubeIdeal{V} |
| 74 | +end |
| 75 | +MP.changecoefficienttype(set::HypercubeSet, ::Type) = set |
| 76 | +SS.ideal(set::HypercubeSet) = set.ideal |
| 77 | +function Base.rem(p, set::HypercubeIdeal) |
| 78 | + return MP.polynomial(map(MP.terms(p)) do term |
| 79 | + mono = MP.monomial(term) |
| 80 | + new_mono = one(mono) |
| 81 | + for (var, exp) in powers(mono) |
| 82 | + if var in set.variables |
| 83 | + exp = rem(exp, 2) |
| 84 | + end |
| 85 | + new_mono *= var^exp |
| 86 | + end |
| 87 | + MP.coefficient(term) * new_mono |
| 88 | + end) |
| 89 | +end |
| 90 | + |
| 91 | +H = HypercubeSet(HypercubeIdeal(x)) |
| 92 | + |
| 93 | +min_algebraic(H) |
| 94 | + |
| 95 | +# Let's now try to find the correct lower bound: |
| 96 | + |
| 97 | +function min_algebraic_rational(S, d) |
| 98 | + model = SOSModel(solver) |
| 99 | + @variable(model, q, SOSPoly(MP.monomials(x, 0:d))) |
| 100 | + deno = q + 1 |
| 101 | + @constraint(model, c, deno * p >= deno, domain = S) |
| 102 | + optimize!(model) |
| 103 | + @show termination_status(model) |
| 104 | +end |
| 105 | + |
| 106 | +# With `d = 0`, it's the same as previously |
| 107 | + |
| 108 | +min_algebraic_rational(H, 0) |
| 109 | + |
| 110 | +# But with `d = 1`, we can find the correct lower bound |
| 111 | + |
| 112 | +min_algebraic_rational(H, 1) |
0 commit comments