Inner and outer characterization of the projection of polynomial equations

using symmetries, quotients and intervals

Luc Jaulin












This microsite is associated to the paper
L. Jaulin (2023). Inner and outer characterization of the projection of polynomial equations using symmetries, quotients and intervals, International Journal of Approximate Reasoning, Volume 159, August 2023.

Using symmetries, set quotient and interval analysis we can compute efficiently an inner and an outer approximation for the set P which corresponds to the projection of another set X defined by polynomial equations.







Code for handling sets using separators and symmetries


Click below to see/run/modify the online program :




from pyibex import *
from vibes import *

f = Function('x', 'y', '(x-2)^2 + (3*y+x-1)^2')
S1 = SepFwdBwd(f, sqr(Interval(0,4)))
s = Function('x', 'y', '(y, x)')
S2 = SepTransform(S1,s,s)
vibes.beginDrawing()
X0 = IntervalVector(2, [-3,7])
vibes.newFigure('X')
pySIVIA(X0, S1|S2, 0.01)









Code for computing sep(Bn(X)))


Click below to see/run/modify the online program :




from numpy import *
from math import factorial
import numpy as np
import sympy as sp
import itertools


def separable_only(Bn, m):
    S = [s for s in Bn if (np.abs(np.prod(s[0:m])) == factorial(m))]
    return (S)


def Bn(n):  # all_OctaSym
    N = [i for i in range(1, n + 1)]
    LX = [I for I in itertools.permutations(N)]
    LY = [I for I in list(itertools.product([-1, 1], repeat=n))]
    L = []
    for X in LX:
        for Y in LY:
            s = []
            for x, y in zip(X, Y):
                s.append(x * y)
            L.append(s)
    return L


def octasym(I):
    return lambda L: [
        np.sign(I[i]) * L[np.abs(I[i]) - 1] for i in range(0, len(I))
    ]


def diff(L1, L2):
    L = [x for x in L1 if not (x in L2)]
    return L


def vect2matsym(s):
    n = len(s)
    M = np.zeros([n, n])
    for i in range(n):
        e = np.sign(s[i])
        j = np.abs(s[i]) - 1
        M[j][i] = e
    return M


def mat2vectsym(M):
    n = len(M)
    v = []
    for j in range(n):
        for i in range(n):
            if not (M[i, j] == 0): v.append(np.int(np.sign(M[i, j]) * (i + 1)))
    return (v)



def invertsym(v):
    M = vect2matsym(v)
    N = np.linalg.inv(M)
    return mat2vectsym(N)


def mult2vectsym(v1, v2):
    return [np.sign(v2[i]) * v1[np.abs(v2[i]) - 1] for i in range(len(v1))]


def Test_solution(Eq, X, Lsymb):
    m = len(Eq)
    bb = True
    for j in range(m):
        E = Eq[j]
        for i in range(len(X)):
            E = E.subs(Lsymb[i], X[i])
        bb = bb and (sp.simplify(E) == 0)
    return bb


def Sep_BnX(Bn, m, x0, S, Eq,
            Lsymb):  # x0 : known solution, S : known symmetries
    U = []
    i = 0
    B = separable_only(Bn, m)
    for I in B:
        i = i + 1
        s = octasym(I)
        if Test_solution(Eq, s(x0), Lsymb):
            U.append(I)
            print("Sep_BnX in U, i=", i, "  ", I)
    U = diff(U, S)
    bb = True
    while bb == True:
        bb = False
        for s1 in S:
            Z = []
            for s2 in U:
                s3 = mult2vectsym(s1, s2)
                if s3 in S:
                    bb = True
                    Z.append(s2)
                    S.append(s2)
            U = diff(U, Z)
    if (len(U) > 0): print("Please, add solutions or symmetries")
    return S, U


def count_2disks(
):  #Mutiplication : ((x1-2)**2+(x2-3)**2)*((x1+2)**2+(x2+3)**2)
    n, m = 2, 1
    B2 = Bn(n)
    print("Number of all Hyperoctahedral symmetries found : ", len(B2))
    print("2**n*factorial(n) : ", 2**n * factorial(n))
    x0 = [2 + 1 / sp.sqrt(4),
          3 + sp.sqrt(3) / 2]  #  valid solutions to be given by the user
    S = [[-1, -2], [1, 2]]  # valid symmetries to be given by the user
    x1, x2 = sp.symbols("x1 x2")
    Lsymb = [x1, x2]
    eq1 = sp.sympify("((x1-2)**2+(x2-3)**2-1)*((x1+2)**2+(x2+3)**2-1)")
    Eq = [eq1]
    S, U = Sep_BnX(B2, 1, x0, S, Eq, Lsymb)
    return S


def count_rotate():  #rotate : X1*x2=x3
    # x3*x1-x4*x2=x5
    # x4*x1+x3*x2=x6
    # x3^2+x4^2=1
    # one solution (2,4,1/2,sqrt(3)/2,1-2*sqrt(3),2+sqrt(3)
    n, m = 6, 2
    B6 = Bn(n)
    print("len(B6)", len(B6))
    print(2**n * factorial(n))
    x0 = [
        2, 4, 1 / sp.sqrt(4),
        sp.sqrt(3) / 2, 1 - 2 * sp.sqrt(3),
        sp.sqrt(3) + 2
    ]
    Lsymb = list(sp.symbols("x1 x2 x3 x4 x5 x6"))
    eq1 = sp.sympify("x3*x1-x4*x2-x5")
    eq2 = sp.sympify("x4*x1+x3*x2-x6")
    eq3 = sp.sympify("x3^2+x4^2-1")
    Eq = [eq1, eq2, eq3]
    print(Eq)
    S = [[2, -1, -4, 3, 5, 6], [1, -2, 3, -4, 5, -6],
         [-1, -2, -4, 3, 6, -5]]  # valid symmetries to be given by the user
    S, U = Sep_BnX(B6, m, x0, S, Eq, Lsymb)
    return S



print("Generate the separable symmetries for two-disks")
S = count_2disks()

print("Generate the separable symmetries for rotate")
S = count_rotate()












Code for generating the function psi for the rotate constraint and for the two-disks constraint


Click below to see/run/modify the online program :


from findsym import count_rotate,count_2disks
import numpy as np

def gene_ψ(S,m):
    def φ(s): return [np.sign(s[i]) for i in range(m,l)]
    A=[]
    for s in S:
        l=len(s)
        a=φ(s)
        if not (a in A):
            A.append(a)
            Str=""
            for i in range(l-m-1): Str=Str+str(a[i])+ ","
            Str="(" + Str+ str(a[l-m-1]) +  ")   :   " + str(s)+ " ,"
            print(Str)


def gene_ψ_rotate():
    S=count_rotate()
    m=2
    gene_ψ(S,m)

def gene_ψ_2disks():
    S=count_2disks()
    m=1
    gene_ψ(S,m)


print("\n Generation of the function ψ for the constraint two_disks")
gene_ψ_2disks()

print("\n Generation of the function ψ for the constraint Rotate")
gene_ψ_rotate()












Code for the rotating rectangle

Click below to see/run/modify the online program :


from vibes import vibes
from roblib import *
from pyibex import *
from rot import sponge0, octasym
from findsym import invertsym
from itertools import product


class CtcSponge0(Ctc): #contract X1,X2 on the positive quarter
    def __init__(C,Q,outer):
        Ctc.__init__(C, 2)
        C.outer=outer
        C.Q=Q
    def contract(C,P):
        if not(P.is_empty()):
                P[0],P[1] = sponge0(P[0],P[1],*C.Q,C.outer)
        return P


class CtcSym(Ctc):
    def __init__(C,C0,s):
        C.m=len(s)-len(C0.Q)
        Ctc.__init__(C, C.m)
        C.outer=C0.outer
        C.sp=s[0:C.m]
        C.sq=[np.sign(s[i])*(np.abs(s[i])-C.m) for i in range(C.m,len(s))]
        C.C0=C0
        C.Q=C0.Q.copy()
    def contract(C,P):
        if not(P.is_empty()):
            C.C0.Q=octasym(C.sq)(C.Q)
            P1=octasym(C.sp)(P)
            P2=C.C0.contract(IntervalVector(P1))
            P=IntervalVector(octasym(invertsym(C.sp))(P2))
        return P

def sgn(Q): #interval extension of the vector sign
    def sgn(Q):   # return all possible signs an interval.
        L=[]
        if (Q.lb()<0): L.append(-1)
        if (Q.ub()>0): L.append(1)
        return L
    l=len(Q)
    L=[sgn(Q[i]) for i in range(l)]
    return L

ψ =	{  #obtained via the program gene_psi
    ( -1 , 1 , 1 , 1 )   :    [2, -1, -4, 3, 5, 6]  ,
    ( 1 , -1 , 1 , -1 )   :    [1, -2, 3, -4, 5, -6]  ,
    ( -1 , -1 , 1 , 1 )   :    [-1, -2, -3, -4, 5, 6]  ,
    ( 1 , -1 , -1 , 1 )   :    [-1, 2, 3, -4, -5, 6]  ,
    ( -1 , 1 , 1 , -1 )   :    [-1, -2, -4, 3, 6, -5]  ,
    ( 1 , 1 , 1 , 1 )   :    [1, 2, 3, 4, 5, 6]  ,
    ( 1 , 1 , 1 , -1 )   :    [2, -1, 3, 4, 6, -5]  ,
    ( 1 , -1 , 1 , 1 )   :    [-2, 1, 4, -3, 5, 6]  ,
    ( 1 , 1 , -1 , 1 )   :    [2, 1, 4, 3, -5, 6]  ,
    ( 1 , 1 , -1 , -1 )   :    [-1, -2, 3, 4, -5, -6]  ,
    ( 1 , -1 , -1 , -1 )   :    [-2, -1, 3, -4, -6, -5]  ,
    ( -1 , 1 , -1 , -1 )   :    [-2, 1, -4, 3, -5, -6]  ,
    ( -1 , -1 , 1 , -1 )   :    [2, 1, -4, -3, 5, -6]  ,
    ( -1 , 1 , -1 , 1 )   :    [1, -2, -3, 4, -5, 6]  ,
    ( -1 , -1 , -1 , -1 )   :    [1, 2, -3, -4, -5, -6]  ,
    ( -1 , -1 , -1 , 1 )   :    [-2, -1, -4, -3, -5, 6]
}


def sep_sym(CtcC0,Q,ψ):
    sym=[]
    for I in product(*sgn(Q)): sym.append(ψ[(I)])
    _C0=CtcC0(Q,True)
    C0=CtcC0(Q,False)
    Seps=[]
    for s in sym:
        _s=invertsym(s)
        C1=CtcSym(C0,_s)
        _C1=CtcSym(_C0,_s)
        S= SepCtcPair(_C1,C1)
        Seps.append(S)
    return SepUnion(Seps)

def sep_angle(Q):
    return sep_sym(CtcSponge0,Q,ψ)

def rot(x,y,t):
    x1=np.cos(t)*x-np.sin(t)*y
    y1=np.sin(t)*x+np.cos(t)*y
    return [x1,y1]


def draw_polygon_rot(P,t,col='darkblue'):
    R=[rot(A[0],A[1],t) for A in P]
    vibes.drawPolygon(R, col)

def RunsiviaP(S):
    p1min,p1max,p2min,p2max=-20,20,-20,20
    vibes.beginDrawing()
    vibes.newFigure('Solution')
    params = { "x": 0,"y": 0,"width": 800,"height": 800}
    vibes.setFigureProperties(params)
    pySIVIA([[p1min,p1max],[p2min,p2max]], S, 1.2)
    for i in range(-21,21,1):
        vibes.drawLine([[-0.3,i],[0.3,i]],'black')
        vibes.drawLine([[i,-0.3],[i,0.3]],'black')
    vibes.drawLine([[p1min,0],[p1max,0]],'black')
    vibes.drawLine([[0,p2min],[0,p2max]],'black')

def siviaP(Q):
    S=sep_angle(Q)
    RunsiviaP(S)
    A1=[Q[2].lb(),Q[3].lb()]
    A2=[Q[2].lb(),Q[3].ub()]
    A3=[Q[2].ub(),Q[3].ub()]
    A4=[Q[2].ub(),Q[3].lb()]
    P=[A1,A2,A3,A4]
    if True: #if we want to draw the polygon
        for t in arange(θ0.lb(),θ0.ub(),0.1):
            draw_polygon_rot(P,-t,'black')
        draw_polygon_rot(P,-θ0.ub(),'blue')
        draw_polygon_rot(P,0,'#111111[#3333FFAA]')
    vibes.endDrawing()


θ0,Y10,Y20=Interval(3,4),Interval(-10,10),Interval(5,12)
Q=[cos(θ0),sin(θ0),Y10,Y20]
print(Q)
siviaP(Q)







Code for the Workspace characterization

Click below to see/run/modify the online program :


from vibes import vibes
from roblib import *
from pyibex import *
from quotient import sep_angle,RunsiviaP,draw_polygon_rot

def siviaP(Q1,Q2):
    S1=sep_angle(Q1)
    S2=sep_angle(Q2)
    RunsiviaP(S1|S2)
    A1=[Q1[2].lb(),Q1[3].lb()]
    A2=[Q1[2].lb(),Q1[3].ub()]
    A3=[Q2[2].lb(),Q2[3].lb()]
    A4=[Q2[2].lb(),Q2[3].ub()]
    A5=[Q2[2].ub(),Q2[3].ub()]
    A6=[Q2[2].ub(),Q2[3].lb()]
    A7=[Q1[2].ub(),Q1[3].ub()]
    A8=[Q1[2].ub(),Q1[3].lb()]
    P=[A1,A2,A3,A4,A5,A6,A7,A8]
    print(P)
    if True:
        for t in arange(θ0.lb(),θ0.ub(),0.1):
            draw_polygon_rot(P,-t,'black')
        draw_polygon_rot(P,-θ0.ub(),'blue')
    draw_polygon_rot(P,0,'#FF1111[#FF11DDAA]')
    vibes.endDrawing()

θ0=Interval(0.5,1.5)
Y10a,Y20a=Interval(5,10),Interval(-8,8)
Y10b,Y20b=Interval(-4,11),Interval(8,10)
Qa=[cos(θ0),sin(θ0),Y10a,Y20a]
Qb=[cos(θ0),sin(θ0),Y10b,Y20b]
siviaP(Qa,Qb)






Code for the speed estimator

Click below to see/run/modify the online program :




from vibes import vibes
from roblib import *
from pyibex import *
from quotient import sep_angle,RunsiviaP,draw_polygon_rot,rot

def siviaP(θ0,Y10,Y20):
    S=[]
    for i in range(N):
        Q=[cos(θ0[i]),-sin(θ0[i]),Y10[i],Y20[i]]
        S1=sep_angle(Q)
        S.append(S1)
    Sq=SepQInter(S)
    Sq.q=3
    RunsiviaP(Sq)


def randround(x,e):
    return 2*e*round(x/(2*e))-e

ny,nθ=1,0.5
θtruth=[1.2,2.4,3.1,0.1,-1.3,-2.1]
N=len(θtruth)
θm=[randround(θtruth[i],nθ)  for i in range(N)]
print("θm",θm)
θ0=[θm[i]+Interval(-nθ,nθ) for i in range(N)]
print(θ0)
Pos=[[-3,5],[-13,-5],[5,-10],[-7,10],[-10,-10],[10,-5]]
Vearth=[10,10]
Y10,Y20=[],[]
Y1m,Y2m=[],[]
for i in range(N):
    Vi=rot(Vearth[0],Vearth[1],-θtruth[i])
    Y1m.append(randround(Vi[0],ny))
    Y2m.append(randround(Vi[1],ny))
Y10=[Y1m[i]+Interval(-ny,ny) for i in range(N)]
Y20=[Y2m[i]+Interval(-ny,ny) for i in range(N)]
print("\nY1m",Y1m)
print("Y10",Y10)
print("\nY2m",Y2m)
print("Y20",Y20)
siviaP(θ0,Y10,Y20)
a=180/3.14
for i in range(N):
    vibes.drawAUV(Pos[i][0], Pos[i][1],a*θtruth[i], 2, 'black[yellow]')
vibes.drawPoint(Vearth[0],Vearth[1],2,'red[red]')
vibes.endDrawing()