Python CPLEX API: Conditional iteration on binary variables - python

I'm working on a graph-theoretical problem. Suppose we want to find a Graph G=(V,E), such that there exists a partition X of V containing at most k equivalence classes. A variable p_S takes value 1 exactly when S is a member of partition X, and zero otherwise. So we have a constraint that the sum over all variables p_S is at most k, for all subsets S of V.
So what I want to do is to iterate over all p_S that have value 1 and define more constraints based on the elements I draw out of S. These constraints would preserve that members of an equivalence class share some mutual property.
Is it possible to access the p_S variables that way and how could I do it?
ALternatively I know I can do without iterating on my binary variables if I'm allowed to use binary variables as coefficients in my constraints. Is that possible?
Thanks in advance!

The CPLEX Python API is index based. To iterate over all binary variables with a solution value set to 1 we need to query the variables types and the solution values and filter accordingly. Here is a simple example:
import sys
import cplex
def main(modelfile):
# Read in a model file.
c = cplex.Cplex()
c.read(modelfile)
# Solve the model and print the solution and status.
c.solve()
print("Solution value:", c.solution.get_objective_value())
print("Solution status: {0} ({1})".format(
c.solution.get_status_string(),
c.solution.get_status()))
# Display all binary variables that have a solution value of 1.
types = c.variables.get_types()
nvars = c.variables.get_num()
binvars = [idx for idx, typ
in zip(range(nvars), c.variables.get_types())
if typ == c.variables.type.binary]
inttol = c.parameters.mip.tolerances.integrality.get()
binvars_at_one = [idx for idx, val
in zip(binvars, c.solution.get_values(binvars))
if abs(val - 1.0) <= inttol]
print("Binary variables with a solution value equal to one:")
for varname in c.variables.get_names(binvars_at_one):
print(" ", varname)
if __name__ == "__main__":
if len(sys.argv) != 2:
raise ValueError("usage: {0} <model>".format(sys.argv[0]))
main(sys.argv[1])
For more, see the documentation for Cplex.variables and Cplex.solution.

linearization example:
from docplex.mp.model import Model
mdl = Model(name='binaryproduct')
x = mdl.binary_var(name='x')
y = mdl.binary_var(name='y')
z = mdl.binary_var(name='z')
# z==x*y
mdl.add_constraint(x+y<=1+z, 'ct1')
mdl.add_constraint(z<=x, 'ct2')
mdl.add_constraint(z<=y, 'ct3')
#end of z==x*y
mdl.solve()
for v in mdl.iter_binary_vars():
print(v," = ",v.solution_value)

Related

Error while writing optimization constraint in cplex python api

My goal is to write the following model using docplex.mp.model in python. which ptj takes binary variable {0,1}.
[summation from of Ptj from j=1 to t][t = 1,.....,8]
here is the code I wrote:
N = 8
(period_list = [t for t in range(1, no_of_period+1)])
(j = period_list)
p = Mode.binary_var_dict(period_list, name = 'p')
for t in period_list:
for j in range(1,t+1):
Model.add_constraints(Model.sum(p[t,j]) == 1)
but I got an error. Could anyone help me with this problem please?
Your code has numerous issues.
First, you need to create one instance of docplex.mp.model.Model to add constraints to: all your calls to Model.<fn> should be rewritten as mdl.<fn> as they are instance methods.
Second, the variable dict you create has periods as keys, that is, 1,2,..P
so querying p[t,j] is sure to crash with KeyError. If you need a square matrix of variables for each couple of periods, use Model.binary_var_matrix.
Third: Model.add_constraints (with a final S) expects an iterable, but you are passing one constraint, this is also sure to crash.
Lastly, using ranges starting at 1 is not the simplest nor the safest choice with Docplex.
Here is a code, freely derived from your sample, which I guess is close to what you need:
pr = range(1, no_of_period+1)
from docplex.mp.model import Model
m = Model()
p = m.binary_var_matrix(pr, pr, name = 'p')
m.add_constraints( (m.sum(p[t,j] for j in pr) == 1) for t in pr)
print(m.lp_string)
and the output is:
Minimize
obj:
Subject To
c1: p_1_1 + p_1_2 + p_1_3 = 1
c2: p_2_1 + p_2_2 + p_2_3 = 1
c3: p_3_1 + p_3_2 + p_3_3 = 1
[..snip..]

Cannot reduce time taken to add constraints to pulp

I have the following code(python 3) for adding constraints to pulp(v 2.3). It needs to add up to 400000 constraints(100^2 S, 4 A).
def linearProgram(self, error = 1e-12):
lp_problem = p.LpProblem('best-Vpi', p.LpMinimize)
#create problem variables
V = p.LpVariable.dicts("V",range(self.S), cat = "Continuous")
#objective function
for i in range(self.S):
self.v.append(V[i])
lp_problem += p.lpSum(self.v)
#constraints
for s in range(self.S):
for a in range(self.A):
pv = p.LpAffineExpression([(V[x],self.T[s][a][x]) for x in range(self.S)])
constraint = p.lpSum([self.PR[s][a], self.gamma*pv ])
lp_problem += V[s] >= constraint
status = lp_problem.solve(p.PULP_CBC_CMD(msg = 0)) #solve
I can't seem to be able to optimise it further..
I even tried multiprocessing, but it gave a lot of errors-
def __addconstraints(self, S0, S1, lp_problem):
for s in range(S0, S1):
for a in range(self.A):
pv= p.lpDot(self.T[s][a],self.v)
lp_problem += self.v[s] >= p.lpSum([self.PR[s][a], self.gamma*pv])
..................
#in linearProgram
if self.S%4:
s0, s1 = 0, self.S//3
else:
s0, s1 = 0, self.S//4
incr = s1
processes = []
for x in range(4):
proc = multiprocessing.Process(target=self.__addconstraints, args=(s0, s1, lp_problem))
processes.append(proc)
proc.start()
s0 = s1
s1 = min(s1+incr, self.S)
for proc in processes:
proc.join()
hard code for episodic? no need (due to initialization of mdp)
if self.mdptype=="episodic":
for state in self.end:
lp_problem += V[state] == 0
I am new to both pulp and multiprocessing, so I don't really have an idea what I'm doing :p
Any kind of help is appreciated.
In your code, you first build a p.LpAffineExpression, then you apply a p.lpSum and finally you do a third operation on the result V[s] >= constraint. The two last operations may increase the time because the expression is being copied each time.
From my experience, the fastest times I've gotten are doing the following:
# _vars_tup is a list of (key, value) pairs where each key is a variable and each value is a coefficient.
# it's like initializing a dictionary.
# CONSTANT is a python number (not a pulp variable)
model += p.LpAffineExpression(_vars_tup, constant=CONSTANT) >= 0
The idea is to reduce the number of times you do operations with p.LpAffineExpression objects, because a copy is done at each operation. So, build the list of variables and coefficients (_vars_tup) for ALL the variables present in the constraint and then at the last step create the p.LpAffineExpression and compare it with a constant.
An equivalent way would be (although I haven't tried it):
const = p.LpConstraint(e=p.LpAffineExpression(_vars_tup, constant=_constant), sense = p.LpConstraintGE, rhs = -CONSTANT)
model.addConstraint(other)

Issue with accessing different types of variable values in cplex solution pool

I have this MILP model that I am solving by cplex python API:
def CModel():
mdl=Model('Generate')
#variable declaration
y=mdl.binary_var_dict(ijk,name='y')
Sum=mdl.integer_var_dict(ij,name='S')
#objective
mdl.minimize(0)
#constraints
#1
mdl.add_constraints(mdl.sum(y[(i,j,k)]+y[(j,i,k)] for j in T)==1 for i in T for k in K)
#2
mdl.add_constraints(mdl.sum(y[(i,j,k)]+y[(j,i,k)] for k in K1 )==1 for i in T for j in T if i!=j)
#3
mdl.add_constraints(mdl.sum(y[(i,j,k)]+y[(j,i,k)] for k in K2 )==1 for i in T for j in T if i!=j)
#4
mdl.add_constraints(mdl.sum(y[(i,j,k)] for k in K )==1 for i in T for j in T if i!=j)
#5
for i,j in ij:
for k in K4:
Sum=mdl.sum(y[(j,i,k1)] for k1 in range(k+1,k+8 ))
mdl.add(mdl.if_then(y[(i,j,k)]==1, Sum==0))
return mdl
Sum is a variable that I am using to make the 5th constraint (I'm just using it to restrict y on certain ranges to 0). In the solution pool I just need the indices of y where y==1. I have this solution pool for solving the model:
def soln_pool(mdl):
cpx = mdl.get_cplex()
cpx.parameters.parallel.set(1)
cpx.parameters.mip.pool.intensity.set(4)
cpx.populatelim=50
st2=time.time()
try:
cpx.populate_solution_pool()
except CplexSolverError:
print("Exception raised during populate")
return []
numsol = cpx.solution.pool.get_num() #max timing by 29 second
sol_pool = []
pool=[]
pool2=[]
if numsol!=0:
for i in range(numsol):
indices = [j for j, a in enumerate(cpx.solution.pool.get_values(i)) if a > 0.5]
for element in sol_pool:
for j in element:
v = mdl.get_var_by_index(j)
i1 = int(v.name.split('_')[1])
i2 = int(v.name.split('_')[2])
i3 = int(v.name.split('_')[3])
pool.append([i1,i2,i3])
pool2.append(pool)
Before I add constriant #5 there was no problem with the solution pool function but after adding #5 I get this error:
i1 = int(v.name.split('_')[1])
ValueError: invalid literal for int() with base 10: '{y'
Hiw can I access only variable y values on solution pool and convert then to integers?
You can just check v.name.startswith('y') to filter out anything that is not a plain y variable. My guess is that the offending variable is some auxiliary variable that was created under the hood. You could print the full v.name to see the name of the offending variable's name. If it is an autocreated variable then its name will tell for which constraint it was created.
Also note that you have
Sum=mdl.integer_var_dict(ij,name='S')
and later
Sum=mdl.sum(y[(j,i,k1)] for k1 in range(k+1,k+8 ))
The latter overwrites the former. That is probably not what you intend to do.
Daniel is right. Model.if_then generates a boolean variable. You can check this with this small code:
def is_gen(dv):
return 'yes' if dv.is_generated() else 'no'
for dv in cm.iter_variables():
print(f'-- variable: {dv.name}, index={dv.index}, generated={is_gen(dv)}')
which generates output like:
-- variable: _bool{y_10_2_3 == 1}, index=5887, generated=yes
which represents the truth variable for constraint y_20_2_3==1.
As you can see, calling is_generated() on a variable return True when the variable has been generated by Docplex, so you can just filter out those variables in you split code.

Extracting Gurobi Solution Index

I have a bunch of gurobi variables
y[0],y[1],...,y[n]
x[0],x[1],...,x[m].
I would like to be able to figure out the indices of the optimal y's that are not zero. In other words, if the optimal solution is y[0]=0, y[1]=5, y[2]=0, y[3]=1, I would like to return [1,3]. So far, I have
F = []
for v in model.getVars():
if v.varName[0]=='y' and v.x>0:
F.append[v.varName]
This, in the above example, would give me ['y[1]', 'y[3]']. Since this output is a string, I'm not sure how I can get the 1 and 3 out of it. Please help.
Thanks in advance!
I am using the following which works:
Index_List = []
for v in m.getVars():
if v.varName[0] == 'y' and v.x>0:
Index = int(v.varName[2])
for j in range(3,3+100)):
BOOL = False
try:
IndexNEW =int(v.varName[j])
Index = 10*Index+IndexNEW
BOOL = True
except ValueError:
()
if not BOOL:
break
Index_List.append(Index)
The resulting Index_List is as desired. There must be a better way though.
Assuming
from gurobipy import *
m = Model()
If you create a gurobi tupledict for your variables with
x = m.addVars(nx, vtype=GRB.INTEGER, name="x")
y = m.addVars(ny, vtype=GRB.INTEGER, name="y")
# ...your constraints and objective here..
then you can directly call the attributes for your variables (in your case the .X attribute for the variable value in the current solution). Using a list comprehension it could be done with:
m.optimize()
if m.status == GRB.OPTIMAL:
indices = [i for i in range(ny) if y[i].X > 0]
where nx and ny are the number of your variables.

Gurobi with python dictionary key value error

I am using Gurobi 7 with Python 2.7 and want to implement the following linear optimization problem:
I have translated the above to Python and Gurobi using the following code:
T = range(1,17520)
# Create variables - defined as dictionaries
p = {} # power
s = {} # SOC
b = {} # buy
for t in T:
p[t] = m.addVar(vtype = GRB.CONTINUOUS, lb = -R, ub = R, name = "power_{}".format(t))
s[t] = m.addVar(vtype = GRB.CONTINUOUS, lb = 0, ub = E, name = "SOC_{}".format(t))
b[t] = m.addVar(vtype = GRB.CONTINUOUS, lb = 0, name = "Buy_{}".format(t))
# constraints
for t in T:
m.addConstr(b[t] == demand[t] + p[t], name = "balance_{}".format(t))
if t == 0:
m.addConstr(s[t] == p[t], name = "charge_{}".format(t))
else:
m.addConstr(s[t] == s[t-1] + p[t], name = "charge_{}".format(t))
# integrate variables and constraints
m.update()
# Objective function
obj = quicksum(
b[t]*SBP[t]
for t in T
)
m.setObjective(obj,GRB.MINIMIZE)
# start optimization
m.optimize
The error message I get (shown below) is probably due to the [t-1] index; however I do not see why this is not accepted by the compiler. Do I need to define this constraints in a different way?
I have not found any other examples of gurobi optimization problems being defined with this structure (variable is a function of the preceding variable etc.) but this is a very typical structure for LP problems.
Any help you can provide is greatly appreciated.
OK turns out I was confused with Python's zero-indexing; I have defined the set T as a range from 1 to 17520 and yet I subsequently define constraints for variables indexed in 0.
My problem was fixed by defining the set T as
T = range(0,17519)

Categories

Resources