#!/bin/env python

#
# Construct a problem from scratch with variables of various
# types. Adds indicator constraints, Special Ordered Sets (SOSs), and
# shows how to retrieve such data once it has been added to the
# problem using the API functions
#

from __future__ import print_function
import xpress as xp

N = 40
S = range (N)

m = xp.problem ("test restriction")

xp.controls.miprelstop = 0

#
# All variables used in this example
#

v1 = xp.var (lb=0, ub=10, threshold=5, vartype=xp.continuous)
v2 = xp.var (lb=1, ub=7,  threshold=5, vartype=xp.continuous)
v3 = xp.var (lb=7, ub=10, threshold=5, vartype=xp.semicontinuous)
v4 = xp.var (lb=1, ub=7,  threshold=3, vartype=xp.semiinteger)
vb = xp.var (vartype = xp.integer, lb = 0, ub = 1)

v = [xp.var (name = "y{0}".format (i), lb = 0, ub = 2*N) for i in S] # set name of a variable as 

cc = xp.constraint (body=v1 - v2, lb = 2, ub = 15)
cc0 = xp.constraint (body=v1 + v2, lb = 2, ub = 15)

m.addVariable (vb, v, v1, v2, v3, v4) # adds both v, a vector (list) of variables, and v1 and v2, two scalar variables.
m.addConstraint (cc)

# Indices of variables can be retrieved both using their name and
# their Python object.

print ("index of v[0] from name: ", m.getIndexFromName (2, "y0"))
print ("index of v[0]:           ", m.getIndex (v[0]))

# Indicator constraints consist of a tuple with a condition on a
# binary variable and a constraint)

ind1 = (vb == 1, v1 + v2 >= 6)
ind2 = (vb == 1, v1 + v3 >= 7)
m.addIndicator (ind1)                           # adds the first indicator constraint
m.addIndicator ((vb == 1, v1 + v3 <= 10), ind2) # adds another indicator constraint and the second one defined above

s = xp.sos ([v1,v2],[2,4], name = "mynewsos", type=2)

m.addSOS (s)

# Showcases the use of getIndex()

print ("get index: var v1 -->", m.getIndex (v1), "; con cc -->", m.getIndex (cc), "; sos -->", m.getIndex (s))

ii_inds  = []
ii_comps = []

m.getindicators (ii_inds, ii_comps, 1, 3)

print ("getind: ", ii_inds, ii_comps)

print ("SOS:", s.name, s)

# Objects such as SOSs, variables, constraints, etc. can be copied with the copy() method.

sos2 = s.copy ()

m.setObjective (xp.Sum ([i*v[i] for i in S])) # objective overwritten at each setObjective ()

m.solve ()

# Retrieve a solution: first declare an empty string, then call the getmipsol() function to fill it up.

mipsol = []

m.getmipsol (mipsol)

s1 = m.getSolution (v1, v2, v [10:30]) # get a subset of the solutions
s2 = m.getSolution (S)                 # can get it with indices as well

print ("v1: ",   m.getSolution (v1), 
       ", v2: ", m.getSolution (v2), 
       "; sol vector: ", m.getSolution (),
       "; obj: ", m.getObjVal (), 
       sep="") # default separator between strings is " "

# Adds yet another constraint to the problem and saves it, then removes an SOS and saves another version

m.addConstraint ((1.25 * v1 - 2.5*v2 + 4.3) * (3.1 * v2 - 2 * v1 - 5.2) + 72.5 * v1**2 + 73 * v2**2 <= 1950)

m.write ("restriction", "lp")

m.delSOS (s)
m.write ("restriction-noSOS", "lp")

m.solve ()

#
# We create another problem, but can continue to use the objects
# created originally for the first problem. Note that the constraints
# must have been defined here as otherwise a constraint obtained from
# a previously read problem can't be added to another problem.
#

m2 = xp.problem ();

cc2 = cc.copy ()

print ("name of copy:", cc2.name, ", orig:", cc.name)

m2.addVariable (v1,v2)
m2.addConstraint (cc2)

m2.addConstraint (xp.Sum (2*v1) <= 100)
m2.addConstraint (xp.Sum (44) <= 100)

m2.addSOS (sos2)

m2.addSOS (xp.sos ([1,v1],[2,4], name = "mynewsos2", type=2))

m2.write ("example3", "lp")
m2.read ("example3.lp")

# This is how you can retrieve a SOS using its index
sss = m2.getSOS (0)

print ("solution of the restricted problem: ", m.getSolution ())
