yuanchenyang / SumOfSquares.py

Python implementation of Sum-of-Squares optimization built on picos
MIT License
28 stars 4 forks source link

varying solution #6

Open JianqiangDing opened 1 year ago

JianqiangDing commented 1 year ago

Hi, yuanchen,

I am using SumOfSquares to reproduce the results of a paper, the problem is when I finish the program locally, the solution may be varying each time when I run it, Is this a bug of SumOfSquares or just some numerical issue? In my own opinion, It is unreasonable this shall be a numerical issue because of you may get some solution deviate to the exact answer, but you shall not get different wrong answer each time without introducing randomness.

I only notice that you control the numerical tolerance manually at https://github.com/yuanchenyang/SumOfSquares.py/blob/5edf9ca7ebfc3478802d900ede4408bbad07b467/src/SumOfSquares/util.py#L35

and I changed this locally, I guess this probably not the core of the problem.

And if you change the variable from ds=2 to ds=6, the results seems more stable each time, but according to the printed result, you can also known they are not all same each running.

Any suggestion would helpful, thank you.

Here is the code

import sympy as sp
from SumOfSquares import SOSProblem, Basis, prod
from sympy import Matrix, sin
import numpy as np

def polynomial(name, variables, deg, hom=False):
    """Returns a (possibly homogeneous) degree DEG polynomial in VARIABLES,
    with a variable (a sympy symbol) named using NAME for each coefficient. Used
    in Sum-of-Squares relaxations for polynomial optimization.
    """
    variables = sorted(variables, key=str)  # use lex order
    basis = Basis.from_degree(len(variables), deg, hom=hom)
    coeffs = sp.symbols(f"{name}_:{len(basis)}")
    p = sum(
        coeff * prod(var ** power for var, power in zip(variables, monom))
        for monom, coeff in zip(basis, coeffs)
    )

    return p, coeffs, basis

if __name__ == "__main__":
    x, y, u = sp.symbols("x y u")
    f = Matrix(
        [
            [x - 0.01 * (0.5 * x + 0.5 * y - 0.5 * x * y)],
            [y + 0.01 * (-0.5 * y + 1 + sin(u))],
        ]
    )

    h = x ** 2 + y ** 2 - 1
    h0 = x ** 2 + y ** 2 - 1.1
    g = 10 * x ** 2 + 10 * (y - 0.5) ** 2 - 1

    point = [0, -0.5]

    epsilon = 1e-8
    lambda_ = 1.01
    degree = 6
    ds = 2

    prob = SOSProblem()

    var_xy = [x, y]

    V, VC, VB = polynomial("coe", var_xy, degree)
    VC = [prob.sym_to_var(c) for c in VC]

    v1 = V.subs([(x, f[0, 0]), (y, f[1, 0])])

    VE = sp.integrate(v1, (u, -sp.pi, sp.pi)) / (2 * sp.pi)

    V0 = VE.subs([(x, point[0]), (y, point[1])])

    s0, coe0, _ = polynomial("coe0", var_xy, ds)
    s1, coe1, _ = polynomial("coe1", var_xy, ds)
    s2, coe2, _ = polynomial("coe2", var_xy, ds)
    s3, coe3, _ = polynomial("coe3", var_xy, ds)
    s4, coe4, _ = polynomial("coe4", var_xy, ds)

    prob.add_sos_constraint(VE - lambda_ * V + s0 * h - s1 * g, var_xy)
    prob.add_sos_constraint(-V + s2 * h0 - s3 * h, var_xy)
    prob.add_sos_constraint(V0 - epsilon, var_xy)
    prob.add_sos_constraint(s0, var_xy)
    prob.add_sos_constraint(s1, var_xy)
    prob.add_sos_constraint(s2, var_xy)
    prob.add_sos_constraint(s3, var_xy)
    prob.add_sos_constraint(s4, var_xy)

    prob.solve(solver="mosek")

    variables = sorted(var_xy, key=str)  # use lex order

    coef = [c.value for c in VC]

    p = sum(
        coeff * prod(var ** power for var, power in zip(variables, monom))
        for monom, coeff in zip(VB, coef)
    )

    print(p)

    import matplotlib.pyplot as plt

    delta = 0.005
    x = np.arange(-1.5, 1.5, delta)
    y = np.arange(-1.5, 1.5, delta)
    X, Y = np.meshgrid(x, y)
    p = sp.simplify(p)
    f = sp.lambdify(var_xy, p, "numpy")
    Z = f(X, Y)

    px = 1 / plt.rcParams["figure.dpi"]
    width, height = 800, 800
    fig, ax = plt.subplots(figsize=(width * px, height * px), layout="constrained")
    CS = ax.contour(X, Y, Z, [0], colors="black")

    f = sp.lambdify(var_xy, h, "numpy")
    Z = f(X, Y)

    CS = ax.contour(X, Y, Z, [0], colors="red")

    f = sp.lambdify(var_xy, g, "numpy")
    Z = f(X, Y)

    CS = ax.contour(X, Y, Z, [0], colors="green")

    ax.autoscale_view()
    ax.axis("equal")
    ax.axis("off")
    plt.show()

    # -0.148235093934226 * x ** 6 - 0.9139775824331 * x ** 5 * y - 0.592441913951806 * x ** 5
    # + 0.599122818712417 * x ** 4 * y ** 2 - 1.68210789622914 * x ** 4 * y - 6.66936332500185 * x ** 4
    # - 0.94731156081864 * x ** 3 * y ** 3 + 1.41821099997065 * x ** 3 * y ** 2 - 2.13140878408021 * x ** 3 * y
    # - 6.22855389829745 * x ** 3 - 2.68276981528723 * x ** 2 * y ** 4 - 2.82722211174006 * x ** 2 * y ** 3
    # - 3.14008146077077 * x ** 2 * y ** 2 + 5.82937763065515 * x ** 2 * y - 10.3135531880385 * x ** 2
    # + 0.400510950754091 * x * y ** 5 - 3.51420747189266 * x * y ** 4 - 1.10276873618979 * x * y ** 3
    # - 4.22251678242741 * x * y ** 2 - 0.0159961370282599 * x * y + 1.20594367210914 * x - 0.276674698891551 * y ** 6
    # + 0.590331979929915 * y ** 5 - 1.2739367322919 * y ** 4 - 0.71889747048312 * y ** 3 + 0.347300210524911 * y ** 2
    # + 0.162007963073248 * y + 0.00547554608091932

    # -0.1482223747096 * x ** 6 - 0.914027566470188 * x ** 5 * y - 0.592473286339536 * x ** 5
    # + 0.599163719362793 * x ** 4 * y ** 2 - 1.68224324545745 * x ** 4 * y - 6.66957712098864 * x ** 4
    # - 0.947372387805594 * x ** 3 * y ** 3 + 1.41827305257054 * x ** 3 * y ** 2 - 2.13146283990509 * x ** 3 * y
    # - 6.22886542691445 * x ** 3 - 2.68289032884015 * x ** 2 * y ** 4 - 2.82741631498913 * x ** 2 * y ** 3
    # - 3.1402655242855 * x ** 2 * y ** 2 + 5.82961805932735 * x ** 2 * y - 10.3138904527 * x ** 2
    # + 0.400548015483008 * x * y ** 5 - 3.51439801041485 * x * y ** 4 - 1.10285786331151 * x * y ** 3
    # - 4.2226512156696 * x * y ** 2 - 0.0159813113400924 * x * y + 1.20598932084955 * x - 0.276668369848645 * y ** 6
    # + 0.590330913340758 * y ** 5 - 1.27399290398017 * y ** 4 - 0.71891680911021 * y ** 3 + 0.347313847695148 * y ** 2
    # + 0.162014714259263 * y + 0.00547638974701935
yuanchenyang commented 1 year ago

Can you check that the inputs from picos into mosek remain the same between runs? If your problem has multiple solutions, any difference in the inputs to mosek may result in a different solution. Another way to force an unique solution is to add an objective to the problem.

JianqiangDing commented 1 year ago

Hi, yuanchen,

I just finish the tests you suggested, I add an objective as sum of all coefficient squares name gamma, and write out the problem into files before and after calling prob.solve, you may check these these files, and there are some small numerical differences between them, like

image

I also tried to write out the problem by mosek, but it failed (error code 4000) because of

MSK_RES_ERR_INVALID_FILE_FORMAT_FOR_SYM_MAT (4000)
The file format does not support a problem with symmetric matrix variables.

Here is the code, at the end of this code are two results, and you may notices coefficients of these two polynomials are different in some degree, especially the constant term.

import picos
import sympy as sp
from SumOfSquares import SOSProblem, Basis, prod
from sympy import Matrix, sin
from picos import norm
import numpy as np

def polynomial(name, variables, deg, hom=False):
    """Returns a (possibly homogeneous) degree DEG polynomial in VARIABLES,
    with a variable (a sympy symbol) named using NAME for each coefficient. Used
    in Sum-of-Squares relaxations for polynomial optimization.
    """
    variables = sorted(variables, key=str)  # use lex order
    basis = Basis.from_degree(len(variables), deg, hom=hom)
    coeffs = sp.symbols(f"{name}_:{len(basis)}")
    p = sum(
        coeff * prod(var ** power for var, power in zip(variables, monom))
        for monom, coeff in zip(basis, coeffs)
    )

    return p, coeffs, basis

if __name__ == "__main__":
    x, y, u = sp.symbols("x y u")
    f = Matrix(
        [
            [x - 0.01 * (0.5 * x + 0.5 * y - 0.5 * x * y)],
            [y + 0.01 * (-0.5 * y + 1 + sin(u))],
        ]
    )

    h = x ** 2 + y ** 2 - 1
    h0 = x ** 2 + y ** 2 - 1.1
    g = 10 * x ** 2 + 10 * (y - 0.5) ** 2 - 1

    point = [0, -0.5]

    epsilon = 1e-8
    lambda_ = 1.01
    degree = 6
    ds = 2

    prob = SOSProblem()

    var_xy = [x, y]

    V, VC, VB = polynomial("coe", var_xy, degree)
    VC = [prob.sym_to_var(c) for c in VC]
    gamma = 0
    for c in VC:
        gamma += c ** 2

    v1 = V.subs([(x, f[0, 0]), (y, f[1, 0])])

    VE = sp.integrate(v1, (u, -sp.pi, sp.pi)) / (2 * sp.pi)

    V0 = VE.subs([(x, point[0]), (y, point[1])])

    s0, coe0, _ = polynomial("coe0", var_xy, ds)
    s1, coe1, _ = polynomial("coe1", var_xy, ds)
    s2, coe2, _ = polynomial("coe2", var_xy, ds)
    s3, coe3, _ = polynomial("coe3", var_xy, ds)
    s4, coe4, _ = polynomial("coe4", var_xy, ds)

    prob.add_sos_constraint(VE - lambda_ * V + s0 * h - s1 * g, var_xy)
    prob.add_sos_constraint(-V + s2 * h0 - s3 * h, var_xy)
    prob.add_sos_constraint(V0 - epsilon, var_xy)
    prob.add_sos_constraint(s0, var_xy)
    prob.add_sos_constraint(s1, var_xy)
    prob.add_sos_constraint(s2, var_xy)
    prob.add_sos_constraint(s3, var_xy)
    prob.add_sos_constraint(s4, var_xy)

    prob.set_objective(direction='min', expression=gamma)

    prob.write_to_file('test_pre_result01.dat-s', writer='picos')

    prob.solve(solver="mosek")

    prob.write_to_file('test_after_result01.dat-s', writer='picos')

    variables = sorted(var_xy, key=str)  # use lex order

    coef = [c.value for c in VC]

    p = sum(
        coeff * prod(var ** power for var, power in zip(variables, monom))
        for monom, coeff in zip(VB, coef)
    )

    print(p)

    import matplotlib.pyplot as plt

    delta = 0.005
    x = np.arange(-1.5, 1.5, delta)
    y = np.arange(-1.5, 1.5, delta)
    X, Y = np.meshgrid(x, y)
    p = sp.simplify(p)
    f = sp.lambdify(var_xy, p, "numpy")
    Z = f(X, Y)

    px = 1 / plt.rcParams["figure.dpi"]
    width, height = 800, 800
    fig, ax = plt.subplots(figsize=(width * px, height * px), layout="constrained")
    CS = ax.contour(X, Y, Z, [0], colors="black")

    f = sp.lambdify(var_xy, h, "numpy")
    Z = f(X, Y)

    CS = ax.contour(X, Y, Z, [0], colors="red")

    f = sp.lambdify(var_xy, g, "numpy")
    Z = f(X, Y)

    CS = ax.contour(X, Y, Z, [0], colors="green")

    ax.autoscale_view()
    ax.axis("equal")
    ax.axis("off")
    plt.show()

    # -9.78540636846771e-6 * x ** 6 - 6.54459698023813e-6 * x ** 5 * y - 5.49648030440343e-6 * x ** 5
    # - 3.31036623986582e-6 * x ** 4 * y ** 2 + 4.99430964535994e-6 * x ** 4 * y - 6.78898583577452e-6 * x ** 4
    # - 2.59906609471962e-6 * x ** 3 * y ** 3 - 3.42960814456312e-6 * x ** 3 * y ** 2
    # - 2.41911575511092e-6 * x ** 3 * y - 1.39066673181138e-6 * x ** 3 - 8.44765011180873e-6 * x ** 2 * y ** 4
    # + 3.81630450700703e-6 * x ** 2 * y ** 3 - 6.33074859235012e-6 * x ** 2 * y ** 2
    # + 1.85576039236168e-6 * x ** 2 * y - 2.20676546027284e-5 * x ** 2 - 5.74773409048156e-6 * x * y ** 5
    # - 5.94900095136526e-6 * x * y ** 4 - 2.16945782913507e-6 * x * y ** 3 - 6.83476880990713e-6 * x * y ** 2
    # + 4.82373411338204e-9 * x * y + 1.77024516308475e-6 * x - 5.95443197187209e-6 * y ** 6
    # + 3.94073215280344e-6 * y ** 5 - 3.82728277687708e-7 * y ** 4 - 1.57335448618497e-6 * y ** 3
    # + 5.22005453462501e-7 * y ** 2 - 2.83448824424391e-7 * y - 1.89368929498165e-7

    # -9.80079936955942e-6 * x ** 6 - 6.51463408446183e-6 * x ** 5 * y - 5.46705161393525e-6 * x ** 5
    # - 3.32552055316688e-6 * x ** 4 * y ** 2 + 4.96683338395406e-6 * x ** 4 * y - 6.81459176944389e-6 * x ** 4
    # - 2.60042011878491e-6 * x ** 3 * y ** 3 - 3.38991519093161e-6 * x ** 3 * y ** 2
    # - 2.34912922720159e-6 * x ** 3 * y - 1.34615560524517e-6 * x ** 3 - 8.38689792279441e-6 * x ** 2 * y ** 4
    # + 3.78287202437611e-6 * x ** 2 * y ** 3 - 6.38212953812153e-6 * x ** 2 * y ** 2
    # + 1.89315055407056e-6 * x ** 2 * y - 2.2003050952949e-5 * x ** 2 - 5.76312748873915e-6 * x * y ** 5
    # - 5.89388305555828e-6 * x * y ** 4 - 2.17212703069132e-6 * x * y ** 3 - 6.79204268662201e-6 * x * y ** 2
    # - 1.4294930220735e-8 * x * y + 1.74598287887084e-6 * x - 6.07836513027998e-6 * y ** 6
    # + 4.07426303039097e-6 * y ** 5 - 4.50033444220666e-7 * y ** 4 - 1.65486376120298e-6 * y ** 3
    # + 6.60483794235288e-7 * y ** 2 - 2.99434193124444e-7 * y - 2.27658259624662e-7
yuanchenyang commented 1 year ago

I looked into this again, seems like the non-determinism might be caused by some floating point issues in sympy, as a lot of your values are very close to zero. They don't happen for any of my test cases.

JianqiangDing commented 1 year ago

Hi, chenyang,

yea, I agree with you, your cases work perfectly and this may be caused by sympy.

I also checked YALMIP later, and the outputs look all same (according to the final plots), this may heavily rely on MATLAB's powerful symbolic computation. And I also tried setting picos numerical tolerance to be compatible with the backend solver as described in Numeric Tolerances, but it failed.

I suggest you may leave this issue open for now for anyone who may be facing the same problems, and we may fix this issue in the near future:-).