Formulating constraints of a LP in Pulp Python - python

I have this LP problem, and I'm trying to solve it using PuLP in Python-3. One option that I can think of is to write all variable explicitly, but I want to avoid it. Is there a way that I can use lists/dicts in this problem? (I did refer to https://pythonhosted.org/PuLP/CaseStudies/a_sudoku_problem.html where dicts were being used, but didn't quite understand the entire solution)
Assume wt{i,j,type} denotes the number of traded goods between person[i] and person[j] of type.
LP Problem:
(Here, cost{i,j} is a known cost of pairing for all (i,j) pairs.
subject to:
I would be really grateful for any help, as I'm a beginner to both optimization and python/pulp.

The 'lists/dicts' is a way to define variables over domains (indexed variables).
The indexs argument of LpVariable.dicts() defines the domain - cartesian product of the supplied sets. See also documentation of PuLP - LpVariable.
The code sample below does not contain all your constraints, but I believe you can easily fill-in the remaining ones. Constraint 1 (with const1 and const2) is treated via the lower and upper bound of the wt variable instead.
from pulp import LpProblem, LpVariable, LpMaximize, LpInteger, lpSum, value
prob = LpProblem("problem", LpMaximize)
# define 'index sets'
I = range(10) # [0, 1, ..., 9]
J = range(10)
T = range(3)
# define parameter cost[i,j]
cost = {}
for i in I:
for j in J:
cost[i,j] = i + j # whatever
# define wt[i,j,t]
const1 = 0 # lower bound for w[i,j,t]
const2 = 100 # upper bound for w[i,j,t]
wt = LpVariable.dicts(name="wt", indexs=(I, J, T), lowBound=const1, upBound=const2, cat=LpInteger)
# define assign[i,j]
assign = LpVariable.dicts(name="assign", indexs=(I, J))
# contraint
for i in I:
for j in J:
prob += assign[i][j] == lpSum(wt[i][j][t] for t in T), ""
# objective
prob += lpSum(cost[i,j] * assign[i][j] for i in I for j in J)
prob.solve()
for i in I:
for j in J:
for t in T:
print "wt(%s, %s, %s) = %s" % (i, j, t, value(wt[i][j][t]))
for i in I:
for j in J:
print "assign(%s, %s) = %s" % (i, j, value(assign[i][j]))

Related

How to sum up a two dimensional pulp solution

I have following code cutouts:
a = range(0, 30)
b = range (0, 88)
Power_consumption = LpVariable.dicts('Power_consumption', (a,b), lowBound=0, upBound=5, cat='Continuous')
...
result= [[]]
for i in a:
for j in b:
print("i:",3, "j:",3, " = ", Power_consumption[i][j].varValue)
result[i][j] = Power_consumption[i][j].varValue
After getting the optmimal solution of the Linear Programming Model, I would like to sum up the "Power_consumption" on the index i ( i = timestep, j = device) so that I have for every timestep "i" the power consumption of all devices. But when trying I get all the time "IndexError: list assignment index out of range". So I am wondering what I did wrong and whats the best way of doing so ?
I don’t think you are constructing your variable correctly with respect to a and b. Be more explicit there…
a = list(range(30))
b = list(range(88))
ab = [(i, j) for i in a for j in b]
Power = LpVariable.dicts(‘power’, ab, . . . )

How to Formulate a Pulp Objective Function with a max operator

I am trying to formulate an objective function for cost optimization in PuLP wherein the maximum of an array is added to the objective function. Please ignore the indentation.
#Decision Variables
allocation_vars = LpVariable.dicts(
'Allocation',
[(i,j,k) for i in TruckTypes for j in Days for k in RS],
0,
LpInteger
)
#Objective Function
for i in TruckTypes:
for j in Days:
prob += max(allocation_vars[(i, j, k)] * TransCost[i][k] for k in RS)
I am getting the following error when trying to run the above :
prob += max(allocation_vars[(i, j, k)] * TransCost[i][k] for k in RS)
TypeError: '>' not supported between instances of 'LpAffineExpression' and 'LpAffineExpression'
You should reformulate, as #AirSquid said.
Try instead the following:
Create a dummy variable m[i][j], add that to the objective function;
m = LpVariable.dicts(
'maxCosts',
[(i,j) for i in TruckTypes for j in Days],
0,
LpInteger
)
prob += lpSum([m[i][j] for j in Days for j in TruckTypes])
Add the following constraint:
for i in TruckTypes:
for j in Days:
for k in RS:
prob += allocation_vars[(i,j,k)]*TransCost[i][k] <= m[i][j]
Supposing you have a minimisation problem, this will work exactly the same as a max: it will reduce m[i][j] as much as possible, and to reduce it more, it will try to reduce the maximum of all allocation_vars[(i,j,k)]*TransCost[i][k].

VRP heterogeneous site-dependency

In my code, I managed to implement different vehicle types (I think) and to indicate the site-dependency. However, it seems that in the output of my optimization, vehicles can drive more then one route. I would like to implement that my vehicle, once it returns to the depot (node 0), that a new vehicle is assigned to perform another route. Could you help me with that? :)
I'm running on Python Jupyter notebook with the Docplex solver
all_units = [0,1,2,3,4,5,6,7,8,9]
ucp_raw_unit_data = {
"customer": all_units,
"loc_x": [40,45,45,42,42,42,40,40,38,38],
"loc_y" : [50,68,70,66,68,65,69,66,68,70],
"demand": [0,10,30,10,10,10,20,20,20,10],
"req_vehicle":[[0,1,2], [0], [0], [0],[0], [0], [0], [0], [0], [0]],
}
df_units = DataFrame(ucp_raw_unit_data, index=all_units)
# Display the 'df_units' Data Frame
df_units
Q = 50
N = list(df_units.customer[1:])
V = [0] + N
k = 15
# n.o. vehicles
K = range(1,k+1)
# vehicle 1 = type 1 vehicle 6 = type 2 and vehicle 11 = type 0
vehicle_types = {1:[1],2:[1],3:[1],4:[1],5:[2],6:[2],7:[2],8:[2],9:
[2],10:[2],11:[0],12:[0],13:[0],14:[0],15:[0]}
lf = 0.5
R = range(1,11)
# Create arcs and costs
A = [(i,j,k,r) for i in V for j in V for k in K for r in R if i!=j]
Y = [(k,r) for k in K for r in R]
c = {(i,j):np.hypot(df_units.loc_x[i]-df_units.loc_x[j],
df_units.loc_y[i]-df_units.loc_y[j]) for i,j,k,r in A}
from docplex.mp.model import Model
import docplex
mdl = Model('SDCVRP')
# decision variables
x = mdl.binary_var_dict(A, name = 'x')
u = mdl.continuous_var_dict(df_units.customer, ub = Q, name = 'u')
y = mdl.binary_var_dict(Y, name = 'y')
# objective function
mdl.minimize(mdl.sum(c[i,j]*x[i,j,k,r] for i,j,k,r in A))
#constraint 1 each node only visited once
mdl.add_constraints(mdl.sum(x[i,j,k,r] for k in K for r in R for j in V
if j != i and vehicle_types[k][0] in df_units.req_vehicle[j]) == 1 for i
in N)
##contraint 2 each node only exited once
mdl.add_constraints(mdl.sum(x[i,j,k, r] for k in K for r in R for i in V
if i != j and vehicle_types[k][0] in df_units.req_vehicle[j]) == 1 for j
in N )
##constraint 3 -- Vehicle type constraint (site-dependency)
mdl.add_constraints(mdl.sum(x[i,j,k,r] for k in K for r in R for i in V
if i != j and vehicle_types[k][0] not in
df_units.req_vehicle[j]) == 0 for j in N)
#Correcte constraint 4 -- Flow constraint
mdl.add_constraints((mdl.sum(x[i, j, k,r] for j in V if j != i) -
mdl.sum(x[j, i, k,r] for j in V if i != j)) == 0 for i in
N for k in K for r in R)
#constraint 5 -- Cumulative load of visited nodes
mdl.add_indicator_constraints([mdl.indicator_constraint(x[i,j,k,r],u[i] +
df_units.demand[j]==u[j]) for i,j,k,r in A if i!=0 and j!=0])
## constraint 6 -- one vehicle to one route
mdl.add_constraints(mdl.sum(y[k,r] for r in R) <= 1 for k in K)
mdl.add_indicator_constraints([mdl.indicator_constraint(x[i,j,k,r],y[k,r]
== 1) for i,j,k,r in A if i!=0 and j!=0])
##constraint 7 -- cumulative load must be equal or higher than demand in
this node
mdl.add_constraints(u[i] >=df_units.demand[i] for i in N)
##constraint 8 minimum load factor
mdl.add_indicator_constraints([mdl.indicator_constraint(x[j,0,k,r],u[j]
>= lf*Q) for j in N for k in K for r in R if j != 0])
mdl.parameters.timelimit = 15
solution = mdl.solve(log_output=True)
print(solution)
I expect every route to be visited with another vehicle, however the same vehicles perform multiple routes. Also, now the cumulative load is calculated for visited nodes, I would like to have this for the vehicle on the routes so that the last constraint (minimum load factor) can be performed.
I understand K indices are for vehicles and R are for routes. I ran your code and got the follwing assignments:
y_11_9=1
y_12_4=1
y_13_7=1
y_14_10=1
y_15_10=1
which seem to show many vehicles share the same route.
This is not forbidden by the sum(y[k,r] for r in R) <=1) constraint,
as it forbids one vehicle from working several routes.
Do you want to limit the number of assigned vehicles to one route to 1, as this is the symmetrical constraint from constraint #6?
If I got it wrong, plese send the solution you get and the constraint you want to add.
If I add the symmetrical constraint, that is, limit assignments vehicles to routes to 1 (no two vehicles on the same route), by:
mdl.add_constraints(mdl.sum(y[k, r] for r in R) <= 1 for k in K)
mdl.add_constraints(mdl.sum(y[k, r] for k in K) <= 1 for r in R)
I get a solution with the same cost, and only three vehicle-route assignments:
y_11_3=1
y_12_7=1
y_15_9=1
Still, I guess the best solution would be to add some cost factor of using a vehicle, and introducing this into the final objective. This might also reduce the symmetries in the problem.
Philippe.

Gurobi Error Divisor must be a constant when making a more complex objective function

When I use a fairly straight forward cost function for my optimization objective function, gurobi gives back an answer but when I complicate things with math.log() functions or even with i**2 instead of i*i it produces an error similar to one of the following:
GurobiError: Divisor must be a constant
TypeError: a float is required
TypeError: unsupported operand type(s) for ** or pow(): 'Var' and 'int'
I tried to reformulate math.log((m-i)/i) to math.log(m-i)- math.log(i) this produces the float is required error. changing i*i to i**2 produces unsupported error.
Now my question is: is it just impossible to make a more complex function within Gurobi? or am I making an mistake elsewhere.
Here is a snippit of my model
from gurobipy import *
import pandas as pd
import numpy as np
import time
import math
start_time = time.time()
# example NL (i, 20, 0.08, -6.7, 301)
def cost(i, j, k, l, m):
cost = (j - l)*i + k*i*i - l*(m - i) * (math.log((m - i) / i ))
return cost
def utility(i, j, k, l):
utility = j + k*i + l*i*i
return utility
"""
def cost(i, j, k, l):
cost = j + k*i + .5*l*i*i
return cost
"""
# assign files to use as input and as output
outputfile = 'model1nodeoutput.csv'
inputfile = 'marketclearinginput.xlsx'
# define dataframes
dfdemand = pd.read_excel(inputfile, sheetname="demand", encoding='utf8')
dfproducer = pd.read_excel(inputfile, sheetname="producer", encoding='utf8')
m = Model("1NodeMultiPeriod")
dofprod = [m.addVar(lb=3.0, ub=300, name=h) for h in dfproducer['name']]
dofdem = [m.addVar(lb=3.0, ub=300, name=h) for h in dfdemand['name']]
# Integrate new variables
m.update()
# Set objective
m.setObjective(quicksum([utility(i, j, k, l) for i, j, k, l
in zip(dofdem, dfdemand['c'], dfdemand['a'], dfdemand['b'])]) -
quicksum([cost(i, j, k, l, m) for i, j, k, l, m
in zip(dofprod, dfproducer['c'], dfproducer['a'], dfproducer['b'], dfproducer['Pmax'])]),
GRB.MAXIMIZE)
# Set constraints
# Set constraints for producers
for i, j, k in zip(dofprod, dfproducer['Pmin'], dfproducer['Pmax']):
m.addConstr(i >= j)
m.addConstr(i <= k)
# Set constraints for demand
for i, j, k in zip(dofdem, dfdemand['Pmin'], dfdemand['Pmax']):
m.addConstr(i >= j)
m.addConstr(i <= k)
# Build the timestamp list, pd or np unique both possible, pd faster and preserves order
# Timestamps skips the first 3 symbols (example L1T2034 becomes 2034)
timestamps = pd.unique([i.varName[3:] for i in dofprod])
# Set constraint produced >= demanded (this should be te last constraint added for shadow variables)
for h in timestamps:
m.addConstr(quicksum([i for i in dofprod if i.varName.endswith(h)]) >=
quicksum([i for i in dofdem if i.varName.endswith(h)]))
m.optimize()
Your problem might have to do with the Gurobi quicksum() function. Perhaps try sum().

Dynamic programming solution to maximizing an expression by placing parentheses

I'm trying to implement an algorithm from Algorithmic Toolbox course on Coursera that takes an arithmetic expression such as 5+8*4-2 and computes its largest possible value. However, I don't really understand the choice of indices in the last part of the shown algorithm; my implementation fails to compute values using the ones initialized in 2 tables (which are used to store maximized and minimized values of subexpressions).
The evalt function just takes the char, turns it into the operand and computes a product of two digits:
def evalt(a, b, op):
if op == '+':
return a + b
#and so on
MinMax computes the minimum and the maximum values of subexpressions
def MinMax(i, j, op, m, M):
mmin = 10000
mmax = -10000
for k in range(i, j-1):
a = evalt(M[i][k], M[k+1][j], op[k])
b = evalt(M[i][k], m[k+1][j], op[k])
c = evalt(m[i][k], M[k+1][j], op[k])
d = evalt(m[i][k], m[k+1][j], op[k])
mmin = min(mmin, a, b, c, d)
mmax = max(mmax, a, b, c, d)
return(mmin, mmax)
And this is the body of the main function
def get_maximum_value(dataset):
op = dataset[1:len(dataset):2]
d = dataset[0:len(dataset)+1:2]
n = len(d)
#iniitializing matrices/tables
m = [[0 for i in range(n)] for j in range(n)] #minimized values
M = [[0 for i in range(n)] for j in range(n)] #maximized values
for i in range(n):
m[i][i] = int(d[i]) #so that the tables will look like
M[i][i] = int(d[i]) #[[i, 0, 0...], [0, i, 0...], [0, 0, i,...]]
for s in range(n): #here's where I get confused
for i in range(n-s):
j = i + s
m[i][j], M[i][j] = MinMax(i,j,op,m,M)
return M[0][n-1]
Sorry to bother, here's what had to be improved:
for s in range(1,n)
in the main function, and
for k in range(i, j):
in MinMax function. Now it works.
The following change should work.
for s in range(1,n):
for i in range(0,n-s):

Categories

Resources