SOS Decomposition
This example demonstrates how to find an SOS decomposition for the polynomial:
An SOS decomposition represents \(p(x)\) as \(p(x) = \sum_k q_k(x)^2\), where \(q_k(x)\) are polynomials. This is achieved through Square Matricial Representation (SMR), where the polynomial is expressed in quadratic form:
where the monomial basis is given by monomial vector \(Z(x) = [1 \quad x_1 \quad x_2 \quad x_1 x_2 \quad x_2^2]^\top\), and the Gram matrix is a symmetric matrix \(Q_p \in \mathbb R^{5 \times 5}\). Once such a Gram matrix \(Q_p\) is found, we compute its Cholesky factorization \(Q_p = L L^\top\). The SOS terms are then obtained as:
The SOS decomposition is found by solving:
import numpy as np
import polymat
import sosopt
state = sosopt.init_state(sparse_smr=False)
# Define polynomial variables
state_variables = tuple(polymat.define_variable(name) for name in ('x1', 'x2'))
x1, x2 = state_variables
x = polymat.v_stack(state_variables)
# Create polynomial and SOS constraint
p = x1**2 - x1*x2**2 + x2**4 + 1
state, constraint = sosopt.sos_constraint(
name="p",
greater_than_zero=p,
).apply(state)
# Formulate SOS problem (feasibility formulation)
sos_problem = sosopt.sos_problem(
constraints=(constraint,),
solver=sosopt.cvxopt_solver,
)
# No decision variables are defined so far
# Output: empty tuple
print(sos_problem.decision_variable_symbols)
# Solve SOS problem
state, sos_result = sos_problem.solve().apply(state)
# Retrieve monomial basis and SMR from the SOS constraint
# Evaluate the decomposition variables with the results of the SOS problem
Z = constraint.sos_monomial_basis
Qp = constraint.gram_matrix.eval(sos_result.symbol_values)
# Output matrix as nested tuples
state, Qp_np = polymat.to_tuple(Qp).map(np.array).apply(state)
# Use SVD decomposition instead of Cholesky decomposition
_, S, V = np.linalg.svd(Qp_np)
# Vector of polynomials
q = polymat.from_(np.diag(np.sqrt(S)) @ V) @ Z
state, q_sympy = polymat.to_sympy(q).apply(state)
print(q_sympy)
state, p_sympy = polymat.to_sympy(q.T @ q).apply(state)
print(p_sympy)
The resulting Gram matrix is given as:
The recovered polynomial \(p(x) = q(x)^\top q(x)\) is given as: