# Polynomial and Sum-of-Squares variables

## Polynomial variables

While JuMP allows to create decision variables representing a number whose value needs to be optimized upon by the optimizer, PolyJuMP allows to create polynomial decision variables. In order to do that, we first need to create polynomial variables with the @polyvar macro:

julia> using DynamicPolynomials # or TypedPolynomials, pick your favorite

julia> @polyvar x y
(x, y)

Note that these should not be confused with JuMP's decision variables which are created using the @variable macro. The polynomial decision variable that will be created need to be parametrized by a polynomial basis of finite size. For instance, if we want to find a quadratic polynomial, we can parametrize it with all monomials of degree between 0 and 2. Generating a vector with such monomials can be achieved through the monomials function:

julia> X = monomials([x, y], 0:2)
1
y
x
y²
xy
x²

We can now create our polynomial variable p as follows:

julia> using SumOfSquares

julia> model = Model()
A JuMP Model
Feasibility problem with:
Variables: 0
Model mode: AUTOMATIC
CachingOptimizer state: NO_OPTIMIZER
Solver name: No optimizer attached.

julia> @variable(model, p, Poly(X))
(_) + (_)y + (_)x + (_)y² + (_)xy + (_)x²

This creates a vector of decision variables a and sets p as the scalar product between a and X.

Just like with classical JuMP's decision variables, containers of polynomial variables can be created as follows:

julia> @variable(model, [1:3, 1:4], Poly(X))       # Creates a Matrix
(_) + (_)y + (_)x + (_)y² + (_)xy + (_)x²     …  (_) + (_)y + (_)x + (_)y² + (_)xy + (_)x²
(_) + (_)y + (_)x + (_)y² + (_)xy + (_)x²     (_) + (_)y + (_)x + (_)y² + (_)xy + (_)x²
(_) + (_)y + (_)x + (_)y² + (_)xy + (_)x²     (_) + (_)y + (_)x + (_)y² + (_)xy + (_)x²

julia> @variable(model, [[:a, :b], -2:2], Poly(X)) # Creates a DenseAxisArray
2-dimensional DenseAxisArray{DynamicPolynomials.Polynomial{DynamicPolynomials.Commutative{DynamicPolynomials.CreationOrder}, MultivariatePolynomials.Graded{MultivariatePolynomials.LexOrder}, VariableRef},2,...} with index sets:
Dimension 1, [:a, :b]
Dimension 2, -2:2
And data, a 2×5 Matrix{DynamicPolynomials.Polynomial{DynamicPolynomials.Commutative{DynamicPolynomials.CreationOrder}, MultivariatePolynomials.Graded{MultivariatePolynomials.LexOrder}, VariableRef}}:
(_) + (_)y + (_)x + (_)y² + (_)xy + (_)x²  …  (_) + (_)y + (_)x + (_)y² + (_)xy + (_)x²
(_) + (_)y + (_)x + (_)y² + (_)xy + (_)x²     (_) + (_)y + (_)x + (_)y² + (_)xy + (_)x²

julia> @variable(model, [i=1:3, j=i:3], Poly(X))   # Creates a SparseAxisArray
JuMP.Containers.SparseAxisArray{Polynomial{DynamicPolynomials.Commutative{DynamicPolynomials.CreationOrder}, Graded{LexOrder}, VariableRef}, 2, Tuple{Int64, Int64}} with 6 entries:
[1, 1]  =  (_) + (_)*y + (_)*x + (_)*y^2 + (_)*x*y + (_)*x^2
[1, 2]  =  (_) + (_)*y + (_)*x + (_)*y^2 + (_)*x*y + (_)*x^2
[1, 3]  =  (_) + (_)*y + (_)*x + (_)*y^2 + (_)*x*y + (_)*x^2
[2, 2]  =  (_) + (_)*y + (_)*x + (_)*y^2 + (_)*x*y + (_)*x^2
[2, 3]  =  (_) + (_)*y + (_)*x + (_)*y^2 + (_)*x*y + (_)*x^2
[3, 3]  =  (_) + (_)*y + (_)*x + (_)*y^2 + (_)*x*y + (_)*x^2

For more flexibility, polynomials parametrized by decision variables can also be created "by hand" for instance as follows:

julia> @variable(model, α)
α

julia> @variable(model, β)
β

julia> p = α*x^2 + (α+β)*y^2*x + β*y^3
(α)x² + (β)y³ + (α + β)xy²

The coefficients of the polynomial can even be quadratic, e.g.

julia> p = (3α^2+β)*x^2 + (α*β+2β)*y^2*x + β*y^3
(3 α² + β)x² + (β)y³ + (α*β + 2 β)xy²

Let me stress again the distinction between α and β which are decision variables and x and y which are polynomial variables.

## Nonnegative polynomial variables

In order to create a sum-of-squares polynomial variable, the syntax is exactly the same except SOSPoly should be used instead of Poly. For instance, the following code creates a $3 \times 4$ matrix of sum-of-squares polynomial variables:

julia> @variable(model, [1:2], SOSPoly(X))
GramMatrix with row/column basis:
MonomialBasis([1, y, x, y^2, x*y, x^2])
And entries in a 6×6 SymMatrix{VariableRef}:
_  _  _  _  _  _
_  _  _  _  _  _
_  _  _  _  _  _
_  _  _  _  _  _
_  _  _  _  _  _
_  _  _  _  _  _
GramMatrix with row/column basis:
MonomialBasis([1, y, x, y^2, x*y, x^2])
And entries in a 6×6 SymMatrix{VariableRef}:
_  _  _  _  _  _
_  _  _  _  _  _
_  _  _  _  _  _
_  _  _  _  _  _
_  _  _  _  _  _
_  _  _  _  _  _

There is however an important difference between the meaning of the vector of monomials X between Poly and SOSPoly. For SOSPoly, it creates a positive semidefinite matrix of variables Q and sets p as the value of X' * Q * X. That is, for instance, if X contains all the monomials of degree 2, then all the monomials of p will have degree 4 (i.e. p will be a quartic form).

Similarly, to create diagonally-dominant-sum-of-squares polynomial variables (see [Definition 3.1, AM17]), use DSOSPoly(X). This creates a diagonally dominant matrix of variables Q and sets the polynomial variables as the value of X' * Q * X.

Finally, to create scaled-diagonally-dominant-sum-of-squares polynomial variables (see [Definition 3.2, AM17]), use SDSOSPoly(X). This creates a scaled diagonally dominant matrix of variables Q and sets the polynomial variables as the value of X' * Q * X.

## Choosing a polynomial basis

In the previous section, we show how to create polynomial variables from a monomial basis. However, the monomial basis is only a particular case of polynomial basis and while using a different basis of the same space of polynomial is would give an equivalent program, it might be more stable numerically (see [Section 3.1.5, BPT12]).

For instance, creating an univariate cubic polynomial variable p using the Chebyshev basis can be done as follows:

julia> cheby_basis = FixedPolynomialBasis([1, x, 2x^2-1, 4x^3-3x])
FixedPolynomialBasis([1, x, -1 + 2x², -3x + 4x³])

julia> @variable(model, variable_type=Poly(cheby_basis))
(_ - _) + (_ - 3 _)x + (2 _)x² + (4 _)x³

and to create a quadratic form variable q using the scaled monomial basis (see [Section 3.1.5, BPT12]), use the following:

julia> X = monomials([x, y], 2)
y²
xy
x²

julia> scaled_basis = ScaledMonomialBasis(X)
ScaledMonomialBasis([y², xy, x²])

julia> @variable(model, variable_type=Poly(scaled_basis))
(_)y² + (1.4142135623730951 _)xy + (_)x²

which is equivalent to

julia> scaled_basis = FixedPolynomialBasis([x^2, √2*x*y, y^2])
FixedPolynomialBasis([x², 1.4142135623730951xy, y²])

julia> @variable(model, variable_type=Poly(scaled_basis))
(_)y² + (1.4142135623730951 _)xy + (_)x²

[BPT12] Blekherman, G.; Parrilo, P. A. & Thomas, R. R. Semidefinite Optimization and Convex Algebraic Geometry. Society for Industrial and Applied Mathematics, 2012.

[AM17] Ahmadi, A. A. & Majumdar, A. DSOS and SDSOS Optimization: More Tractable Alternatives to Sum of Squares and Semidefinite Optimization ArXiv e-prints, 2017.

## Reference

PolyJuMP.PolyType
struct Poly{PB<:AbstractPolynomialBasis} <: AbstractPoly
polynomial_basis::PB
end

Polynomial variable $v^\top p$ where $v$ is a vector of new decision variables and $p$ is a vector of polynomials for the basis polynomial_basis.

source

Variable bridges:

SumOfSquares.Bridges.Variable.ScaledDiagonallyDominantBridgeType
struct ScaledDiagonallyDominantBridge{T} <: MOI.Bridges.Variable.AbstractBridge
side_dimension::Int
variables::Vector{Vector{MOI.VariableIndex}}
constraints::Vector{MOI.ConstraintIndex{
MOI.VectorOfVariables, SOS.PositiveSemidefinite2x2ConeTriangle}}
end

A matrix is SDD iff it is the sum of psd matrices Mij that are zero except for entries ii, ij and jj [Lemma 9, AM17]. This bridge substitute the constrained variables in SOS.ScaledDiagonallyDominantConeTriangle into a sum of constrained variables in SOS.PositiveSemidefinite2x2ConeTriangle.

[AM17] Ahmadi, A. A. & Majumdar, A. DSOS and SDSOS Optimization: More Tractable Alternatives to Sum of Squares and Semidefinite Optimization ArXiv e-prints, 2017.

source