#!/usr/bin/python

#generate data

from __future__ import print_function

import cvxopt as cvx
import picos as pic
import sys
import traceback

A=[ cvx.matrix([[1,0,0,0,0],
                [0,3,0,0,0],
                [0,0,1,0,0]]),
    cvx.matrix([[0,0,2,0,0],
                [0,1,0,0,0],
                [0,0,0,1,0]]),
    cvx.matrix([[0,0,0,2,0],
                [4,0,0,0,0],
                [0,0,1,0,0]]),
    cvx.matrix([[1,0,0,0,0],
                [0,0,2,0,0],
                [0,0,0,0,4]]),
    cvx.matrix([[1,0,2,0,0],
                [0,3,0,1,2],
                [0,0,1,2,0]]),
    cvx.matrix([[0,1,1,1,0],
                [0,3,0,1,0],
                [0,0,2,2,0]]),
    cvx.matrix([[1,2,0,0,0],
                [0,3,3,0,5],
                [1,0,0,2,0]]),
    cvx.matrix([[1,0,3,0,1],
                [0,3,2,0,0],
                [1,0,0,2,0]])
  ]
  
c = cvx.matrix([1,2,3,4,5])

#create the problems

#--------------------------------------#
#         D-optimal design             #
#--------------------------------------#
prob_D = pic.Problem()
AA=[cvx.sparse(a,tc='d') for a in A]
s=len(AA)
m=AA[0].size[0]
AA=pic.new_param('A',AA)
mm=pic.new_param('m',m)
L=prob_D.add_variable('L',(m,m))
V=[prob_D.add_variable('V['+str(i)+']',AA[i].T.size) for i in range(s)]
w=prob_D.add_variable('w',s)
u={}
for k in ['01','23','4.','0123','4...','01234']:
        u[k] = prob_D.add_variable('u['+k+']',1)
prob_D.add_constraint(
                pic.sum([AA[i]*V[i]
                    for i in range(s)],'i','[s]')
                 == L)
#L lower inferior
prob_D.add_list_of_constraints( [L[i,j] == 0
                                for i in range(m)
                                for j in range(i+1,m)],['i','j'],'upper triangle')
prob_D.add_list_of_constraints([abs(V[i])<(mm**0.5)*w[i]
                                for i in range(s)],'i','[s]')
prob_D.add_constraint(1|w<1)
#SOC constraints to define u['01234'] such that u['01234']**8 < t[0] t[1] t[2] t[3] t[4]
prob_D.add_constraint(u['01']**2   <L[0,0]*L[1,1])
prob_D.add_constraint(u['23']**2   <L[2,2]*L[3,3])
prob_D.add_constraint(u['4.']**2   <L[4,4])
prob_D.add_constraint(u['0123']**2 <u['01']*u['23'])
prob_D.add_constraint(u['4...']**2 <u['4.'])
prob_D.add_constraint(u['01234']**2<u['0123']*u['4...'])

prob_D.set_objective('max',u['01234'])

#--------------------------------------#
# multiresponse case c-optimality SOCP #
#--------------------------------------#
prob_multiresponse_c=pic.Problem()
AA=[cvx.sparse(a,tc='d').T for a in A]
s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
u=prob_multiresponse_c.add_variable('u',c.size)
prob_multiresponse_c.add_list_of_constraints(
        [abs(AA[i]*u)<1 for i in range(s)], #constraints
        #[abs((AA[i]*u)//np.sqrt(3))<2 for i in range(s)], #constraints
        'i', #index
        '[s]' #set to which the index belongs
        )
prob_multiresponse_c.set_objective('max', cc|u)
#--------------------------------------------#
# multiresponse case c-optimality, dual SOCP #
#--------------------------------------------#
prob_dual_c=pic.Problem()
AA=[cvx.sparse(a,tc='d').T for a in A]
s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
z=[prob_dual_c.add_variable('z['+str(i)+']',AA[i].size[0]) for i in range(s)]
mu=prob_dual_c.add_variable('mu',s)
prob_dual_c.add_list_of_constraints(
        [abs(z[i])<mu[i] for i in range(s)], #constraints
        'i', #index
        '[s]' #set to which the index belongs
        )
prob_dual_c.add_constraint( 
        pic.sum(
        [AA[i].T*z[i] for i in range(s)], #summands
        'i', #index
        '[s]' #set to which the index belongs
        )  
        == cc )
prob_dual_c.set_objective('min',1|mu)

#----------------------------------------------------------------#
# multiresponse case exacr c-optimality, Lagrangian bound MISOCP #
#----------------------------------------------------------------#
prob_exact_c=pic.Problem()
AA=[cvx.sparse(a,tc='d').T for a in A]
s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
z=[prob_exact_c.add_variable('z['+str(i)+']',AA[i].size[0]) for i in range(s)]
w=prob_exact_c.add_variable('w',s,vtype='integer')
t=prob_exact_c.add_variable('t',1)
prob_exact_c.add_list_of_constraints(
        [abs(z[i])<w[i] for i in range(s)], #constraints
        'i', #index
        '[s]' #set to which the index belongs
        )
prob_exact_c.add_constraint( 
        pic.sum(
        [AA[i].T*z[i] for i in range(s)], #summands
        'i', #index
        '[s]' #set to which the index belongs
        )  
        == t*cc )
        
prob_exact_c.add_constraint( 1|w < 20 )
        
prob_exact_c.set_objective('max',t)


#----------------------------------------------#
# single case c-optimality, exact-int dual MIP #
#----------------------------------------------#
prob_exact_single_c=pic.Problem()
AA=[cvx.sparse(a[:,i],tc='d').T for i in range(3) for a in A[4:]]
s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
z=[prob_exact_single_c.add_variable('z['+str(i)+']',AA[i].size[0]) for i in range(s)]
w=prob_exact_single_c.add_variable('w',s,vtype='integer')
t=prob_exact_single_c.add_variable('t',1)
prob_exact_single_c.add_list_of_constraints(
        [abs(z[i])<w[i] for i in range(s)], #constraints
        'i', #index
        '[s]' #set to which the index belongs
        )
prob_exact_single_c.add_constraint( 
        pic.sum(
        [AA[i].T*z[i] for i in range(s)], #summands
        'i', #index
        '[s]' #set to which the index belongs
        )  
        == t*cc )
        
prob_exact_single_c.add_constraint( 1|w < 20 )
        
prob_exact_single_c.set_objective('max',t)

#--------------------------------------#
# multiresponse case A-optimality SOCP #
#--------------------------------------#
prob_multiresponse_A=pic.Problem()
AA=[cvx.sparse(a,tc='d').T for a in A]
s=len(AA)
m=AA[0].size[1]
AA=pic.new_param('A',AA)
U=prob_multiresponse_A.add_variable('U',(m,m))
prob_multiresponse_A.add_list_of_constraints(
        [abs(AA[i]*U)<1 for i in range(s)], #constraints
        'i', #index
        '[s]' #set to which the index belongs
        )
prob_multiresponse_A.set_objective('max', 'I'|U)

#--------------------------------------------#
# multiresponse case A-optimality, dual SOCP #
#--------------------------------------------#
prob_dual_A=pic.Problem()
AA=[cvx.sparse(a,tc='d').T for a in A]
s=len(AA)
AA=pic.new_param('A',AA)
Z=[prob_dual_A.add_variable('Z['+str(i)+']',AA[i].size) for i in range(s)]
mu=prob_dual_A.add_variable('mu',s)
prob_dual_A.add_list_of_constraints(
        [abs(Z[i])<mu[i] for i in range(s)], #constraints
        'i', #index
        '[s]' #set to which the index belongs
        )
prob_dual_A.add_constraint( 
        pic.sum(
        [AA[i].T*Z[i] for i in range(s)], #summands
        'i', #index
        '[s]' #set to which the index belongs
        )  
        == 'I' )
prob_dual_A.set_objective('min',1|mu)

#---------------------------------------#
# single response case, c-optimality LP #
#---------------------------------------#
prob_LP_c=pic.Problem()
#AA=[cvx.sparse(a[:,i],tc='d').T for i in range(3) for a in A[4:]]
AA=[cvx.sparse(a[:,0],tc='d').T for a in A]#new version to have variable bounds
s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
u=prob_LP_c.add_variable('u',c.size)
prob_LP_c.add_list_of_constraints(
        [abs(AA[i]*u)<1 for i in range(s)], #constraints
        'i', #index
        '[s]' #set to which the index belongs
        )
#prob_LP_c.set_objective('max', cc|u)
prob_LP_c.set_objective('min', cc|u)#new version to have a minimization LP

#--------------------------------------------#
# single response case, c-optimality dual LP #
#--------------------------------------------#
prob_LP_dual_c=pic.Problem()
#AA=[cvx.sparse(a[:,i],tc='d').T for i in range(3) for a in A[4:]]
AA = [cvx.sparse(a[:,0],tc='d').T for a in A] #new version to have variable bounds
s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
z=[prob_LP_dual_c.add_variable('z['+str(i)+']',AA[i].size[0]) for i in range(s)]
mu=prob_LP_dual_c.add_variable('mu',s)
prob_LP_dual_c.add_list_of_constraints(
        [abs(z[i])<mu[i] for i in range(s)], #constraints
        'i', #index
        '[s]' #set to which the index belongs
        )
prob_LP_dual_c.add_constraint( 
        pic.sum(
        [AA[i].T*z[i] for i in range(s)], #summands
        'i', #index
        '[s]' #set to which the index belongs
        )  
        == cc )
prob_LP_dual_c.set_objective('min',1|mu)

#--------------------------------------#
# multiresponse case c-optimality SDP  #
#--------------------------------------#
prob_SDP_c=pic.Problem()
AA=[cvx.sparse(a,tc='d').T for a in A]
s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
X=prob_SDP_c.add_variable('X',(c.size[0],c.size[0]),vtype='symmetric')
prob_SDP_c.add_list_of_constraints(
        [(AA[i].T*AA[i] | X ) <1 for i in range(s)], #constraints
        'i', #index
        '[s]' #set to which the index belongs
        )
prob_SDP_c.add_constraint(X>>0)
prob_SDP_c.set_objective('max', cc.T*X*cc)

#--------------------------------------------#
# multiresponse case c-optimality, dual SDP  #
#--------------------------------------------#
prob_SDP_c_dual=pic.Problem()
AA=[cvx.sparse(a,tc='d').T for a in A]
s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
mu=prob_SDP_c_dual.add_variable('mu',s)
prob_SDP_c_dual.add_constraint( 
        pic.sum(
        [mu[i]*AA[i].T*AA[i] for i in range(s)], #summands
        'i', #index
        '[s]' #set to which the index belongs
        )  
        >> cc*cc.T )
prob_SDP_c_dual.add_constraint(mu>0)
prob_SDP_c_dual.set_objective('min',1|mu)

#--------------------------------------#
# multiresponse case A-optimality SDP  #
#--------------------------------------#

prob_SDP_A=pic.Problem()
AA=[cvx.sparse(a,tc='d').T for a in A]
#s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
X=prob_SDP_A.add_variable('X',(c.size[0],c.size[0]),vtype='symmetric')
w=prob_SDP_A.add_variable('w',s)
prob_SDP_A.add_constraint(
        (   (pic.sum(
        [w[i]*AA[i].T*AA[i] for i in range(s)], 
        'i','[s]')                            &        'I') //
        (          'I'                        &         X))
        >> 0)
prob_SDP_A.add_constraint(w>0)
prob_SDP_A.add_constraint(1|w==1)
prob_SDP_A.set_objective('min', 'I'|X)

#-------------------------------------------#
#  multiresponse SOCP, multiple constraints #
#  (1|w_0 ... w_3) <1 , (1|w_4 ... w_7) <1  #
#-------------------------------------------#

#solve min <K,U>+b' l+ l0
                                #s.t.   ||Ai U||2< ri'l
                                #       ||A0 U||2<l0
                                #       l,l0>0
                                
prob_multiresponse_multiconstraints=pic.Problem()
AA=[cvx.sparse(a,tc='d').T for a in A]
s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
u=prob_multiresponse_multiconstraints.add_variable('u',c.size)
lbd=prob_multiresponse_multiconstraints.add_variable('lbd',2)
prob_multiresponse_multiconstraints.add_list_of_constraints(
        [abs(AA[i]*u)**2<lbd[0] for i in range(s//2)], #constraints
        'i', #index
        '0..3' #set to which the index belongs
        )
prob_multiresponse_multiconstraints.add_list_of_constraints(
        [abs(AA[i]*u)**2<lbd[1] for i in range(s//2,s)], #constraints
        'i', #index
        '4..7' #set to which the index belongs
        )
prob_multiresponse_multiconstraints.add_constraint(lbd>0)
prob_multiresponse_multiconstraints.add_constraint((1|lbd)<1)
prob_multiresponse_multiconstraints.set_objective('max', (cc|u))

multiconstraints_dual=pic.Problem()
alpha=multiconstraints_dual.add_variable('alpha',s)
mu=multiconstraints_dual.add_variable('mu',s)
t=multiconstraints_dual.add_variable('t',1)
z=[multiconstraints_dual.add_variable('z['+str(i)+']',AA[i].size[0]) for i in range(s)]

multiconstraints_dual.add_constraint( 
        pic.sum(
        [AA[i].T*z[i] for i in range(s)], #summands
        'i', #index
        '[s]' #set to which the index belongs
        )  
        == cc )
        
multiconstraints_dual.add_constraint((1|mu[:4])<t)
multiconstraints_dual.add_constraint((1|mu[4:])<t)
multiconstraints_dual.add_list_of_constraints(
        [abs(z[i])**2<4*mu[i]*alpha[i]
        for i in range(s)],'i','[s]')
multiconstraints_dual.set_objective('min',(1|alpha)+t)


#test QCQP
S=cvx.matrix([[ 4e-2,  6e-3, -4e-3,    0.0 ],
             [ 6e-3,  1e-2,  0.0,     0.0 ],
             [-4e-3,  0.0,   2.5e-3,  0.0 ],
             [ 0.0,   0.0,   0.0,     0.0 ]])
pbar=cvx.matrix([.12, .10, .07, .03])
qcqp=pic.Problem()
x=qcqp.add_variable('x',4)
z=qcqp.add_variable('z',2)
qcqp.add_constraint(1|x<6)
qcqp.add_constraint(x>0)
qcqp.add_constraint(z[0]**2+2*z[1]**2-2*z[0]*z[1]+x[2]**2+0.3*x[2]*z[0]-x[3]<x[0])
pbar=pic.new_param('pbar',pbar)
S=pic.new_param('S',S)
qcqp.add_constraint(z<0)
qcqp.add_constraint((1|z)>-6)
qcqp.set_objective('min',-(pbar|x)+10*x.T*S*x+3*z[0]+z[1])

#----------

#S=cvx.matrix([[ 4e-2,  6e-3, -4e-3,    0.0 ],
             #[ 6e-3,  1e-2,  0.0,     0.0 ],
             #[-4e-3,  0.0,   2.5e-3,  0.0 ],
             #[ 0.0,   0.0,   0.0,     0.0 ]])
#pbar=cvx.matrix([.12, .10, .07, .03])
#qcqp=pic.Problem()
#x01=qcqp.add_variable('x01',2)
#x2=qcqp.add_variable('x2',1)#,'integer')
#x3=qcqp.add_variable('x3',1)
#z=qcqp.add_variable('z',2)
#qcqp.add_constraint((1|(x01//x2//x3))<6)
#qcqp.add_constraint((x01//x2//x3)>0)
#qcqp.add_constraint(z[0]**2+2*z[1]**2-2*z[0]*z[1]+x2**2+0.3*x2*z[0]-x3<x01[0])
#pbar=pic.new_param('pbar',pbar)
#S=pic.new_param('S',S)
#qcqp.add_constraint(z<0)
#qcqp.add_constraint((1|z)>-6)
#qcqp.set_objective('min',-(pbar|(x01//x2//x3))+10*(x01//x2//x3).T*S*(x01//x2//x3)+3*z[0]+z[1])
##qcqp.add_constraint(z[0]**2+z[1]**2 < 10)
#qcqp.add_constraint(abs(z)**2 < 10)
#qcqp.solve(solver='mosek')

#------------

#QP+SOCP
soqcqp=qcqp.copy()
z=soqcqp.get_variable('z') #variable handle
soqcqp.add_constraint(abs(z)<x[0])

#test MIQCQP
miqcqp=qcqp.copy()
z=miqcqp.get_variable('z') #variable handle
miqcqp.add_constraint(z[0]**2+z[1]**2 < 10)
#we add the constraint x[2] integer
x=miqcqp.get_variable('x') #handle to the variable x
i=miqcqp.add_variable('i',1,vtype='integer')
miqcqp.add_constraint(x[2]==i)

#------------#
#standard LP #
#------------#

lp = pic.Problem()
AA=[cvx.sparse(a,tc='d').T for a in A]
AA.append(cvx.sparse([0,3,0,0,0]).T)
s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
x=lp.add_variable('x',5)

lp.add_constraint(AA[5]*x < 3)
lp.add_constraint(AA[6]*x > 2)
lp.add_constraint(AA[7]*x == 2.5)


lp.add_constraint(x>0)
        
lp.set_objective('max',cc.T*x)


dualp=pic.Problem()
mu5=dualp.add_variable('mu5',3)
mu6=dualp.add_variable('mu6',3)
mu7=dualp.add_variable('mu7',3)

dualp.add_constraint(mu5>0)
dualp.add_constraint(mu6>0)
dualp.add_constraint(AA[5].T*mu5-AA[6].T*mu6+AA[7].T*mu7==cc)

dualp.set_objective('min',(2.5|mu7)-(2|mu6)+(3|mu5))

#--------------#
#standard SOCP #
#--------------#
socp=pic.Problem()
AA=[cvx.sparse(a,tc='d').T for a in A]
s=len(AA)
AA=pic.new_param('A',AA)
cc=pic.new_param('c',c)
x=socp.add_variable('x',5)
socp.add_list_of_constraints(
        [abs(AA[i]*x+AA[i]*'|1|(5,1)') < '|2|(1,3)'*AA[i]*x - 1
        for i in range(s)],'i','[s]')
socp.add_constraint(1|x==3)
socp.add_constraint(x>0)
socp.set_objective('max',cc.T*x)

dsocp=pic.Problem()
z=[dsocp.add_variable('z['+str(i)+']',AA[i].size[0]) for i in range(s)]
lbda=dsocp.add_variable('lbda',s)
mu=dsocp.add_variable('mu',1)

dsocp.add_list_of_constraints(
        [abs(z[i]) < lbda[i] for i in range(s)],
        'i','[s]')

dsocp.add_constraint(
        cc+pic.sum([
        lbda[i]*AA[i].T*'|2|(3,1)'
        -AA[i].T*z[i]
        for i in range(s)],'i','[s]') < mu)
        
dsocp.set_objective('min',3*mu-pic.sum(
        [lbda[i]+z[i].T*AA[i]*'|1|(5,1)'
        for i in range(s)],'i','[s]')
        )

dsocp4=pic.Problem() #variant for the problem in TestSOCP4 with bounds on variables
z=[dsocp4.add_variable('z['+str(i)+']',AA[i].size[0]) for i in range(s)]
lbda=dsocp4.add_variable('lbda',s)
mu=dsocp4.add_variable('mu',1)
mu1=dsocp4.add_variable('mu1',1,lower = 0)
mu2=dsocp4.add_variable('mu2',1,lower = 0)

dsocp4.add_list_of_constraints(
        [abs(z[i]) < lbda[i] for i in range(s)],
        'i','[s]')

dsocp4.add_constraint(
        -cc+pic.sum([
        lbda[i]*AA[i].T*'|2|(3,1)'
        -AA[i].T*z[i]
        for i in range(s)],'i','[s]') < mu +mu1 * 'e_1(5,1)' -mu2 * 'e_3(5,1)')
        
dsocp4.set_objective('min',3*mu+0.58*mu1-0.59*mu2-pic.sum(
        [lbda[i]+z[i].T*AA[i]*'|1|(5,1)'
        for i in range(s)],'i','[s]')
        )
#-----------------#
#   SOCP + SDP    #
#-----------------#
coneP=pic.Problem()
y=coneP.add_variable('y',3)
x=coneP.add_variable('x',3)
AAA=[cvx.matrix([[ 1.30,-0.23, 1.81, 2.27],
               [-0.23, 2.37, 2.07,-0.42],
               [ 1.81, 2.07, 5.02, 3.15],
               [ 2.27,-0.42, 3.15, 8.22]
              ]),
   cvx.matrix([[ 5.08, 1.53, 2.34, 2.92],
               [ 1.53, 2.28,-0.74, 0.46],
               [ 2.34,-0.74, 2.76, 1.87],
               [ 2.92, 0.46, 1.87, 2.06]
              ]),
   cvx.matrix([[ 4.10,-0.71, 0.86, 1.26],
               [-0.71, 1.85,-1.33, 0.30],
               [ 0.86,-1.33, 2.24,-0.07],
               [ 1.26, 0.30,-0.07, 0.82]
              ]),
   cvx.matrix([[ 0.56,-0.06, 0.32,-1.03],
               [-0.06, 2.43, 1.57,-0.22],
               [ 0.32, 1.57, 2.71, 2.10],
               [-1.03,-0.22, 2.10, 7.84]
              ])
   ]
           
AA=pic.new_param('A',AAA)
        
coneP.add_constraint(abs(x)<y[0]-0.1)
coneP.add_constraint(1|x==0.3)
coneP.add_constraint(0.1<y[1]*x[2])
coneP.add_constraint(pic.sum([x[i]*AA[i] for i in range(3)],'i') 
                 + y[2]*AA[3] >> 'I')
coneP.set_objective('min',1|y)


coneQP=coneP.copy()
x=coneQP.get_variable('x')
y=coneQP.get_variable('y')
coneQP.add_constraint(y[0]**2+y[1]<2*y[2]-0.1)
coneQP.set_objective('min',(1|y)+0.6*y[0]**2)

from math import sqrt
dual_coneP=pic.Problem()
z=dual_coneP.add_variable('z',3)
w=dual_coneP.add_variable('w',1)
u=dual_coneP.add_variable('u',1)
mu=dual_coneP.add_variable('mu',1)
X=dual_coneP.add_variable('X',(4,4),'symmetric')

dual_coneP.add_constraint(abs(z)<1)
dual_coneP.add_constraint(w**2<4*u)
dual_coneP.add_constraint(X>>0)

dual_coneP.add_constraint(z[0]+mu==(AA[0]|X))
dual_coneP.add_constraint(z[1]+mu==(AA[1]|X))
dual_coneP.add_constraint(z[2]+mu-u==(AA[2]|X))

dual_coneP.add_constraint(AA[3]|X==1)

dual_coneP.set_objective('max',0.1+sqrt(0.1)*w-0.3*mu+('I'|X))

#--------------------------------------#
#       test a geometric program:
#--------------------------------------#

# min x/y+2y/x
#       x*y*y=1
#       (x,y>0)
#X=ln x, Y=ln y 
#  <=>
# exp max lse[X-Y,Y-X+ln(2)]
#       X+2Y=0

from math import log
gp=pic.Problem()
X=gp.add_variable('X',1)
Y=gp.add_variable('Y',1)
#gp.add_constraint(X+2*Y==0) #marche moins bien que les 2 LSE equivalentes ci-dessous (?)
gp.add_constraint(pic.lse(X+2*Y)<0)
gp.add_constraint(pic.lse(-X-2*Y)<0)
gp.set_objective('min',pic.lse((X-Y) & (Y-X+log(2))))


#----------------------------------------#
#  non-convex QP (bimatrix game)
#----------------------------------------#
import picos as pic
bim=pic.Problem()
AA=pic.new_param('A',[[ 1.21e+00,  5.90e-01, -1.45e+00],[-5.64e-01,  1.01e+00,  4.22e-01]])
BB=pic.new_param('B',[[ 1.30e-01, -1.59e+00,  2.06e+00],[ 1.06e+00, -5.56e-01, -1.76e-01]])
x=bim.add_variable('x',2)
y=bim.add_variable('y',3)
x.T*(AA+BB)*y
alpha=bim.add_variable('alpha',1)
beta=bim.add_variable('beta',1)
bim.add_constraint(AA*y<alpha)
bim.add_constraint(BB.T*x<beta)
bim.add_constraint(1|x==1)
bim.add_constraint(1|y==1)
bim.add_constraint(x>0)
bim.add_constraint(y>0)
bim.set_objective('max',x.T*(AA+BB)*y-alpha-beta)


avs=pic.tools.available_solvers()

def LP1Test(solver_to_test):
        #1st test: c optimality single response
        primal=prob_LP_c.copy()
        try:
                primal.solve(solver=solver_to_test,timelimit=1,maxit=50)
        except Exception as ex:
                traceback.print_exc()
                return (False,repr(ex))
        
        primf = primal.check_current_value_feasibility()
        if not(primf[0]):
                return (False,'not primal feasible|{0:1.0e}'.format(primf[1]))
        obj=-14.
        pgap = abs(primal.obj_value()-obj)/abs(obj)
        if pgap>0.1:
                return (False,'failed')        
        elif pgap>1e-5:
                return (False,'not primal optimal|{0:1.0e}'.format(pgap))
                
        try:
                z=[(cs.dual[1]-cs.dual[0]) for cs in primal.constraints]
                mu=[abs(zi) for zi in z]
        except TypeError:
                return (False,'no dual computed')
        
        zvar=prob_LP_dual_c.get_variable('z')
        muvar=prob_LP_dual_c.get_variable('mu')
        
        muvar.value=mu
        for i,zi in enumerate(z):
                zvar[i].value=zi

        dualf = prob_LP_dual_c.check_current_value_feasibility() 
        if not(dualf[0]):
                return (False,'not dual feasible|{0:1.0e}'.format(dualf[1]))
        dgap = abs(prob_LP_dual_c.obj_value()+obj)/abs(obj)
        if dgap >1e-5:
                return (False,'not dual optimal|{0:1.0e}'.format(dgap))
        return (True,primal.status)
        
def LP2Test(solver_to_test):
        #1st test: LP in standard form
        primal=lp.copy()
        try:
                primal.solve(solver=solver_to_test,timelimit=1,maxit=50)
        except Exception as ex:
                traceback.print_exc(file=sys.stdout)
                return (False,repr(ex))
        
        primf = primal.check_current_value_feasibility()
        if not(primf[0]):
                return (False,'not primal feasible|{0:1.0e}'.format(primf[1]))
        obj=12.5
        
        pgap = abs(primal.obj_value()-obj)/abs(obj)
        if pgap>0.1:
                return (False,'failed')        
        elif pgap>1e-5:
                return (False,'not primal optimal|{0:1.0e}'.format(pgap))
                
        mu5 = primal.constraints[0].dual
        mu6 = primal.constraints[1].dual
        mu7 = primal.constraints[2].dual
        
        if (mu5 is None) or (mu6 is None) or (mu7 is None):
                return (False,'no dual computed')
                
        mu5var=dualp.get_variable('mu5')
        mu6var=dualp.get_variable('mu6')
        mu7var=dualp.get_variable('mu7')
        
        mu5var.value=mu5
        mu6var.value=mu6
        mu7var.value=mu7

        dualf=dualp.check_current_value_feasibility()
        if not(dualf[0]):
                return (False,'not dual feasible|{0:1.0e}'.format(dualf[1]))
        dgap = abs(dualp.obj_value()-obj)/abs(obj)
        if dgap >1e-5:
                return (False,'not dual optimal|{0:1.0e}'.format(dgap))
        return (True,primal.status)

def SOCP1Test(solver_to_test):
        #first test (A optimality)
        primal=prob_multiresponse_A.copy()
        try:
                primal.solve(solver=solver_to_test,timelimit=1,maxit=50)
        except Exception as ex:
                traceback.print_exc(file=sys.stdout)
                return (False,repr(ex))
        
        primf = primal.check_current_value_feasibility()
        if not(primf[0]):
                return (False,'not primal feasible|{0:1.0e}'.format(primf[1]))
        obj=1.0759874194855403
        
        pgap = abs(primal.obj_value()-obj)/abs(obj)
        if pgap>0.1:
                return (False,'failed')        
        elif pgap>1e-5:
                return (False,'not primal optimal|{0:1.0e}'.format(pgap))
        
        Zvar=prob_dual_A.get_variable('Z')
        muvar=prob_dual_A.get_variable('mu')
        
        try:
                Z=[cvx.matrix(cs.dual[1:],Zvar[i].size) for i,cs in enumerate(primal.constraints)]
                mu=[cs.dual[0] for cs in primal.constraints]
        except TypeError:
                return (False,'no dual computed')
        
        muvar.value=mu
        for i,zi in enumerate(Z):
                Zvar[i].value=zi
                
        dualf=prob_dual_A.check_current_value_feasibility(tol=1e-5)
        if not(dualf[0]):
                return (False,'not dual feasible|{0:1.0e}'.format(dualf[1]))
        dgap = abs(prob_dual_A.obj_value()-obj)/abs(obj)
        if dgap >1e-5:
                return (False,'not dual optimal|{0:1.0e}'.format(dgap))
        return (True,primal.status)

def SOCP2Test(solver_to_test):        
        #2d test (socp in standard form)
        primal=socp.copy()
        try:
                primal.solve(solver=solver_to_test,timelimit=1,maxit=50)
        except Exception as ex:
                traceback.print_exc(file=sys.stdout)
                return (False,repr(ex))
             
        primf = primal.check_current_value_feasibility()
        if not(primf[0]):
                return (False,'not primal feasible|{0:1.0e}'.format(primf[1]))
        obj=8.921914163181004
        
        pgap = abs(primal.obj_value()-obj)/abs(obj)
        if pgap>0.1:
                return (False,'failed')        
        elif pgap>1e-5:
                return (False,'not primal optimal|{0:1.0e}'.format(pgap))
        
                
        zvar=dsocp.get_variable('z')
        lbdavar=dsocp.get_variable('lbda')
        muvar=dsocp.get_variable('mu')
        try:
                z=[cvx.matrix(cs.dual[1:],zvar[i].size)
                for i,cs in enumerate(primal.constraints[:8])]
                lbda=[cs.dual[0] for cs in primal.constraints[:8]]
                mu=primal.get_constraint((1,)).dual[0]
        except TypeError:
                return (False,'no dual computed')
        
        lbdavar.value=lbda
        muvar.value=mu
        for i,zi in enumerate(z):
                zvar[i].value=zi
                
        dualf=dsocp.check_current_value_feasibility(tol=1e-5)
        if not(dualf[0]):
                return (False,'not dual feasible|{0:1.0e}'.format(dualf[1]))
        dgap = abs(dsocp.obj_value()-obj)/abs(obj)
        if dgap >1e-5:
                return (False,'not dual optimal|{0:1.0e}'.format(dgap))
        return (True,primal.status)

def SOCP3Test(solver_to_test):    
        #3d test (socp with rotated cones)
        primal=prob_multiresponse_multiconstraints.copy()
        try:
                primal.solve(solver=solver_to_test,timelimit=1,maxit=100)
        except Exception as ex:
                traceback.print_exc(file=sys.stdout)
                return (False,repr(ex))
        
        primf = primal.check_current_value_feasibility()
        if not(primf[0]):
                return (False,'not primal feasible|{0:1.0e}'.format(primf[1]))
        obj=1.7997245328947509
        
        pgap = abs(primal.obj_value()-obj)/abs(obj)
        if pgap>0.1:
                return (False,'failed')        
        elif pgap>1e-5:
                return (False,'not primal optimal|{0:1.0e}'.format(pgap))
        
        zvar=multiconstraints_dual.get_variable('z')
        alphavar=multiconstraints_dual.get_variable('alpha')
        muvar=multiconstraints_dual.get_variable('mu')
        tvar=multiconstraints_dual.get_variable('t')
        
        try:
                z=[2*cs.dual[1:4]
                for i,cs in enumerate(primal.constraints[:8])]
                alpha=[cs.dual[0]+cs.dual[-1] for cs in primal.constraints[:8]]
                mu   =[cs.dual[0]-cs.dual[-1] for cs in primal.constraints[:8]]
                t = primal.get_constraint((3,)).dual
        except TypeError:
                return (False,'no dual computed')
        
        alphavar.value=alpha
        muvar.value=mu
        tvar.value=t
        for i,zi in enumerate(z):
                zvar[i].value=zi
                
        dualf=multiconstraints_dual.check_current_value_feasibility(tol=1e-5)
        if not(dualf[0]):
                return (False,'not dual feasible|{0:1.0e}'.format(dualf[1]))
        dgap = abs(multiconstraints_dual.obj_value()-obj)/abs(obj)
        if dgap >1e-5:
                return (False,'not dual optimal|{0:1.0e}'.format(dgap))
          
        return (True,primal.status)        

def SOCP4Test(solver_to_test,with_hardcoded_bound = True):
        #4th test (socp in standard form, with additional variable bounds)
        primal=socp.copy()
        #solve the problem a first time to check if constaints can be added afterwards
        #try:
                #primal.solve()
        #except:
                #pass
        x = primal.get_variable('x')
        primal.add_constraint(x[1]<0.58)
        if with_hardcoded_bound:
                x.set_sparse_lower([3],[0.59])
        else:
                primal.add_constraint(x[3]>0.59)
        primal.set_objective('min',primal.objective[1])
        try:
                sol=primal.solve(solver=solver_to_test,timelimit=1,maxit=50)
        except Exception as ex:
                traceback.print_exc(file=sys.stdout)
                return (False,repr(ex))

        primf = primal.check_current_value_feasibility()
        if not(primf[0]):
                return (False,'not primal feasible|{0:1.0e}'.format(primf[1]))
        obj=8.88848803874566
        
        pgap = abs(primal.obj_value()-obj)/abs(obj)
        if pgap>0.1:
                return (False,'failed')        
        elif pgap>1e-5:
                return (False,'not primal optimal|{0:1.0e}'.format(pgap))
        
        zvar=dsocp4.get_variable('z')
        lbdavar=dsocp4.get_variable('lbda')
        muvar=dsocp4.get_variable('mu')
        mu1var=dsocp4.get_variable('mu1')
        mu2var=dsocp4.get_variable('mu2')
        try:
                z=[cvx.matrix(cs.dual[1:],zvar[i].size)
                for i,cs in enumerate(primal.constraints[:8])]
                lbda=[cs.dual[0] for cs in primal.constraints[:8]]
                mu=primal.get_constraint((1,)).dual[0]
                mu1=primal.get_constraint((3,)).dual[0]
                if not(with_hardcoded_bound):
                        mu2=primal.get_constraint((4,)).dual[0]
        except TypeError:
                return (False,'no dual computed')
        
        lbdavar.value=lbda
        muvar.value=mu
        mu1var.value = mu1
        if with_hardcoded_bound:#TODO with reduced cost ?
                if solver_to_test=='cplex':
                        mu2var.value = primal.cplex_Instance.solution.get_reduced_costs(3)
                elif solver_to_test=='mosek7':
                        rc=[0]
                        try:
                                import mosek7 as mosek
                        except:
                                import mosek
                        primal.msk_task.getreducedcosts(mosek.soltype.itr,3,4,rc)
                        mu2var.value = rc[0]
                elif solver_to_test=='mosek6':
                        rc=[0]
                        import mosek
                        primal.msk_task.getreducedcosts(mosek.soltype.itr,3,4,rc)
                        mu2var.value = rc[0]
                        #mu2var.value = 10.338035946292555
                elif solver_to_test=='cvxopt':
                        mu2var.value = sol['cvxopt_sol']['z'][6]
                else: #'gurobi'
                        mu2var.value = primal.grbvar[3].RC
        else:
                mu2var.value = mu2
        
        for i,zi in enumerate(z):
                zvar[i].value=zi
        
        dualf=dsocp4.check_current_value_feasibility(tol=1e-5)
        if not(dualf[0]):
                return (False,'not dual feasible|{0:1.0e}'.format(dualf[1]))
        dgap = abs(dsocp4.obj_value()+obj)/abs(obj)
        if dgap >1e-5:
                return (False,'not dual optimal|{0:1.0e}'.format(dgap))
        
        return (True,primal.status)
        
def SDPTest(solver_to_test):
        primal=prob_SDP_c.copy()
        try:
                primal.solve(solver=solver_to_test,timelimit=1,maxit=50)
        except Exception as ex:
                traceback.print_exc(file=sys.stdout)
                return (False,repr(ex))
        
        primf = primal.check_current_value_feasibility()
        if not(primf[0]):
                return (False,'not primal feasible|{0:1.0e}'.format(primf[1]))
        obj=5.366615677650481
        
        pgap = abs(primal.obj_value()-obj)/abs(obj)
        if pgap>0.1:
                return (False,'failed')        
        elif pgap>1e-5:
                return (False,'not primal optimal|{0:1.0e}'.format(pgap))
                
        muvar=prob_SDP_c_dual.get_variable('mu')
        try:
                mu=[cs.dual[0] for cs in primal.constraints[:8]]
                Z=primal.constraints[8].dual
        except TypeError:
                return (False,'no dual computed')
        if Z is None:
                return (False,'no dual computed')
                
        muvar.value=mu
        
        dualf=prob_SDP_c_dual.check_current_value_feasibility()
        if not(dualf[0]):
                return (False,'not dual feasible|{0:1.0e}'.format(dualf[1]))
        
        S = (prob_SDP_c_dual.constraints[0].Exp1-prob_SDP_c_dual.constraints[0].Exp2)
        dfinf = (abs(Z-S).value[0]/abs(S).value[0])
        if dfinf>1e-5:
                return (False,'not dual feasible|{0:1.0e}'.format(dfinf))
        dgap = abs(prob_SDP_c_dual.obj_value()-obj)/abs(obj)
        if dgap >1e-5:
                return (False,'not dual optimal|{0:1.0e}'.format(dgap))
        return (True,primal.status)

def CONEPTest(solver_to_test):
        primal=coneP.copy()
        try:
                primal.solve(solver=solver_to_test,tol=1e-7,timelimit=1,maxit=50)
        except Exception as ex:
                traceback.print_exc(file=sys.stdout)
                return (False,repr(ex))
        
        primf = primal.check_current_value_feasibility()
        if not(primf[0]):
                return (False,'not primal feasible|{0:1.0e}'.format(primf[1]))
        obj=1.0159165875250857
        
        pgap = abs(primal.obj_value()-obj)/abs(obj)
        if pgap>0.1:
                return (False,'failed')        
        elif pgap>1e-5:
                return (False,'not primal optimal|{0:1.0e}'.format(pgap))
                
        zvar=dual_coneP.get_variable('z')
        muvar=dual_coneP.get_variable('mu')
        Xvar=dual_coneP.get_variable('X')
        uvar=dual_coneP.get_variable('u')
        wvar=dual_coneP.get_variable('w')
        
        try:
                mu=primal.constraints[1].dual
                z =primal.constraints[0].dual[1:]
                X =primal.constraints[3].dual        
                du=primal.constraints[2].dual       
                u =du[0] + du[-1]
                w = 2 * du[1]
        except TypeError:
                return (False,'no dual computed')
        
        if X is None:
                return (False,'no dual computed')
        
        muvar.value=mu
        zvar.value =z
        Xvar.value =X
        wvar.value =w
        uvar.value =u

        dualf=dual_coneP.check_current_value_feasibility()
        if not(dualf[0]):
                return (False,'not dual feasible|{0:1.0e}'.format(dualf[1]))
        dgap = abs(dual_coneP.obj_value()-obj)/abs(obj)
        if dgap >1e-5:
                return (False,'not dual optimal|{0:1.0e}'.format(dgap))
        return (True,primal.status)
        
def testOnlyPrimal(solver_to_test,primal,obj,tol=1e-6,maxit=50):
        primal2=primal.copy()
        try:
                primal2.solve(solver=solver_to_test,timelimit=1,maxit=maxit)
        except Exception as ex:
                traceback.print_exc(file=sys.stdout)
                return (False,repr(ex))

        primf = primal2.check_current_value_feasibility(tol=10*tol)
        if not(primf[0]):
                return (False,'not primal feasible|{0:1.0e}'.format(primf[1]))
        
        if obj==0:
                denom=1.
        else:
                denom=abs(obj)
        
        pgap = abs(primal2.obj_value()-obj)/denom
        if pgap>0.1:
                return (False,'failed')        
        elif pgap>1e-5:
                return (False,'not primal optimal|{0:1.0e}'.format(pgap))

        return (True,primal2.status)
                
def QCQPTest(solver_to_test):
        return testOnlyPrimal(solver_to_test,qcqp,
                                -12.433985877219854)
        
def MIXED_SOCP_QPTest(solver_to_test):
        return testOnlyPrimal(solver_to_test,soqcqp,
                                -6.8780810803741055)
        
def MIQCQPTest(solver_to_test):
        return testOnlyPrimal(solver_to_test,miqcqp,
                                -10.21427246841899,tol=1e-4,maxit=500)
         
def GPTest(solver_to_test):
        return testOnlyPrimal(solver_to_test,gp,
                                1.0397207708399179)
def MISOCPTest(solver_to_test):
        return testOnlyPrimal(solver_to_test,prob_exact_c,
                                8.601831095537415,tol=1e-4,maxit=None)

def MIPTest(solver_to_test):
        return testOnlyPrimal(solver_to_test,prob_exact_single_c,
                                5.48076923076923,tol=1e-4,maxit=500)
def CONEQCPTest(solver_to_test):
        return testOnlyPrimal(solver_to_test,coneQP,
                1.1541072108276682)

def NON_CONVEX_QPTest(solver_to_test):
        return testOnlyPrimal(solver_to_test,bim,0.)

def header(message, bold=False):
        if bold:
                print("#"*35+"\n"+"#{:^33}#".format(message)+"\n"+"#"*35)
        else:
                print("+"+"-"*33+"+"+"\n"+"|{:^33}|".format(message)+"\n"+"+"+"-"*33+"+")
                
prob_classes = ['LP1','LP2','SOCP1',
                'SOCP2','SOCP3','SOCP4','SDP','coneP','coneQCP',
                'QCQP','Mixed_SOCP_QP','non_convex_Qp','GP',
                'MIP','MISOCP','MIQCQP']

conic_classes = ['LP1','LP2','SOCP1','SOCP2','SOCP3','SOCP4','SDP','coneP']

header("Testing PICOS "+pic.__version__, bold=True)

# Run all tests.
results={}
for solver in avs:
        results[solver]={}
        for pclas in prob_classes:
                print()
                header("Testing "+pclas+" on "+solver)
                print()
                results[solver][pclas]=eval(pclas.upper()+'Test')(solver)
print()

header("Test results", bold=True)
print()

print('Version')
print('-------')
print('  PICOS  '+pic.__version__)
print('  Python '+sys.version.split()[0])
print()

print('Available solvers')
print('-----------------')
for solv in avs:
        print('  '+solv)
print()

print('Exceptions')
print('----------')
for solver in avs:
    for pclas in prob_classes:
            if not results[solver][pclas][0] \
            and results[solver][pclas][1].endswith(')') \
            and "NotAppropriateSolverError" not in results[solver][pclas][1] \
            and "NonConvexError" not in results[solver][pclas][1] \
            and "InappropriateSolverError" not in results[solver][pclas][1]:
                print("  " + str(solver) + " @ " + str(pclas) + ": " + \
                    results[solver][pclas][1])

print()

linesep='+---------------+'+'----------+'*len(avs)
emptyln='|               |'+'          |'*len(avs)
header= '| problem class |'
for solver in avs:
        #if solver == 'smcp':continue#TODO TMP
        header+='{0:^10}|'.format(solver)

print(linesep)
print(emptyln)
print(header)
print(emptyln)
print(linesep)

for pclas in prob_classes:
        clasln='|{0:^15}|'.format(pclas)
        for solver in avs:
                #if solver == 'smcp':continue#TODO TMP
                if results[solver][pclas][0]:
                        if pclas in conic_classes:
                                clasln+='  OK, OK  |'
                        else:
                                clasln+='    OK    |'
                else:
                        err=results[solver][pclas][1]
                        if 'NotAppropriateSolverError' in err:
                                clasln+='          |'
                        elif 'InappropriateSolverError' in err:
                                clasln+='          |'
                        elif 'NonConvexError' in err:
                                clasln+='not convex|'
                        elif 'DualizationError' in err:
                                clasln+='dualiz.err|'
                        elif 'no Primals' in err:
                                clasln+=' noprimal |'
                        elif 'primal' in err:
                                try:
                                    inf = err.split('|')[1]
                                    if 'feasible' in err:
                                            clasln+='Pinf:'+inf+'|'
                                    else:
                                            clasln+='Pgap:'+inf+'|'
                                except:
                                    clasln+='  failed  |'
                        elif 'no dual' in err:
                                clasln+='OK, nodual|'
                        elif 'dual' in err:
                                try:
                                    inf = err.split('|')[1]
                                    if 'feasible' in err:
                                            clasln+='Dinf:'+inf+'|'
                                    else:
                                            clasln+='Dgap:'+inf+'|'
                                except:
                                    clasln+='  failed  |'
                        else:
                                clasln+='  failed  |'
        print(clasln)
        print(linesep)
        
print()
print('Legend')
print('------')
print('  <blank>    = Problem class is not handled by the solver.')
print('  OK/OK      = Test passed (optimal primal and dual variables computed).')
print('  OK         = Test passed (only primal solution expected for problems class).')
print('  noprimal   = No primal solution was computed.')
print('  Pinf:err   = Primal solution has an infeasibility in the order of (err).')
print('  Pgap:err   = Feasible but suboptimal solution (gap in the order of (err).')
print('  OK/nodual  = Optimal primal solution, but no dual variables were computed.')
print('  Dinf:err   = Optimal primal solution, but dual infeasibility in the order of (err).')
print('  Dgap:err   = Optimal primal solution, but duality gap in the order of (err).')
print('  not convex = Problem was not solved because it is not convex.')
print('  dualiz.err = An error occured during dualization.')
print('  failed     = An error occured during search.')