Absolute value formulation for an optimization problem with PuLP - python

I shared below a simplified version of the problem I'm trying to solve. There must be something wrong in my formulation, perhaps regarding the decision variables. All of the flow is sent to Destination1 by the model, but I am attempting to build a model that would evenly distribute the flow. When I force Destination2 to receive flow with an additional constraint, the objective value improves, so I'm not sure why such a solution is not found instead of the less optimal.
I appreciate your thoughts and am happy to answer any questions about this model.
Warehouses = ["A","B","C","D"]
origin_supply = {"A": 53, "B": 62, "C": 45, "D": 65}
Destinations = ['Destination1','Destination2']
Routes = [(o,d) for o in origin_supply for d in destinations]
model = LpProblem("Testing-absolute-value-objective", LpMinimize)
supply = [53,62,45,65]
destination_mean = sum(supply) / len(destinations)
# decision variables
route_vars = LpVariable.dicts("Route",(Warehouses,Destinations),cat = "Integer", lowBound = 0)
sum_for_diff = LpVariable.dicts("sum",(Destinations),cat = "Continuous")
sum_for_diff_abs = LpVariable.dicts("sum_abs",(Destinations),cat = "Continuous", lowBound = 0)
# objective function is to minimize the absolute value of the difference supplied to the two destinations
obj_func = lpSum(sum_for_diff_abs)
# constraints
# absolute value constraints for the difference
for d in destinations:
model += sum_for_diff_abs[d] >= sum_for_diff[d]
model += sum_for_diff_abs[d] >= -sum_for_diff[d]
# The supply constraints (in this case all supply must be sent)
for w in Warehouses:
model += lpSum([route_vars[w][d] for d in Destinations]) == origin_supply[w]
# calculate the difference from the average amount sent to each destination
# the reasoning is that in the full model there will be many destinations, so this logic could scale
for d in Destinations:
model += sum_for_diff[d] == lpSum( route_vars[w][d] for w in Warehouses) - destination_mean
model.solve()
print(LpStatus[model.status])
print(pulp.value(obj_func))
for v in model.variables():
print (v.name + " = " + str(v.varValue))

You are not setting the objective function.
This line
obj_func = lpSum(sum_for_diff_abs)
should be
model+= lpSum(sum_for_diff_abs)

Related

What is the difference using Pulp LpSum vs LpAffineExpression? What is pro and con?

I can't find any document to describe the difference between LpSum and LpAffineExpression. ButI saw there are some using either methods online.
Can anyone explain the pro and con for me?
I solved this problem using both methods, but can't see any difference in performance.
Using LpSum
from pulp import *
# Creates the 'trans' variable to contain the problem data
trans = LpProblem("Transporation_problem", LpMinimize)
# Creates a list of all the supply nodes
plants = ["A", "B"]
# Creates a dictionary for the number of units of supply for each supply node
supply = {"A": 60,
"B": 60}
# Creates a list of all demand nodes
centers = ["1", "2", "3"]
# Creates a dictionary for the number of units of demand for each demand node
demand = {"1":40,
"2":40,
"3":40}
# Creates a list of costs of each transportation path
costs = [ #Bars
#1 2 3
[4,6,4],#A Warehouses
[6,5,2] #B
]
# The cost data is made into a dictionary
costs = makeDict([plants,centers],costs,0)
# Creates a list of tuples containing all the possible routes for transport
Routes = [(w,b) for w in plants for b in centers]
# A dictionary called 'Vars' is created to contain the referenced variables(the routes)
vars = LpVariable.dicts("Route",(plants,centers),0,None,LpInteger)
# The objective function is added to 'prob' first
trans += lpSum([vars[w][b]*costs[w][b] for (w,b) in Routes]), "Sum_of_Transporting_Costs"
# The supply maximum constraints are added to prob for each supply node (warehouse)
for w in plants:
trans += lpSum([vars[w][b] for b in centers])<=supply[w], "Sum_of_Products_out_of_plants_%s"%w
# The demand minimum constraints are added to prob for each demand node (bar)
for b in centers:
trans += lpSum([vars[w][b] for w in plants])>=demand[b], "Sum_of_Products_into_centers%s"%b
# The problem is solved using PuLP's choice of Solver
trans.solve()
Using LpAffineExpression
model = pulp.LpProblem("Transporation_problem", LpMinimize)
# Define ojective function and add it to the model
x = LpVariable.dicts("Route",(plants,centers),0,None,LpInteger)
objective_function = LpAffineExpression(e = [( x[w][b],costs[w][b]) for (w,b) in Routes], name = 'Objective function')
model += objective_function
#Define constraints and add it to the model
# The supply maximum constraints are added to prob for each supply node (warehouse)
for w in plants:
supplyC = LpAffineExpression(e = [ ( x[w][b],1) for b in centers])
model.addConstraint(supplyC <=supply[w])
# The demand minimum constraints are added to prob for each demand node (bar)
for b in centers:
demandC = LpAffineExpression( e= [ (x[w][b],1) for w in plants])
model.addConstraint(demandC >=demand[b])
model.solve()

Uncapacited Facility location covering

I am trying to solve this problem using pulp :
This is my code, There is a problem, because the result should be to only keep the second Location :
# Import PuLP modeler functions
from pulp import *
# Set of locations J
Locations = ["A", "B","C"]
# Set of demands I
Demands = ["1", "2", "3", "4", "5"]
# Set of distances ij
dt = [ # Demands I
# 1 2 3 4 5
[2, 23, 30, 54, 1], # A Locations J
[3, 1, 2, 2, 3], # B
[50,65,80,90,100] # C distances are very long
]
# Max value to get covered
s = 5
# Theses binaries values should be generated by code from the dt array ... I write it down directly for simplification.
# Demand I is served by location J If distance is <= 5 ( 0 = KO , 1 = OK)
covered = [
[1,0,0,0,1],
[1,1,1,1,1] # This shows that we only need Location B , not A
[0,0,0,0,0] # This shows we can't use Location C, it's too far
]
# Creates the 'prob' variable to contain the problem data
prob = LpProblem("Set covering", LpMinimize)
# # Problem variables
J = LpVariable.dicts("location", Locations, cat='Binary')
# The distance data is made into a dictionary
distances = makeDict([Locations, Demands], covered, 0)
# The objective function
# Minimize J, which is the number of locations
prob += lpSum(J["A"]+J["B"]+J["C"])
# The constraint
# Is it covered or not ?
for w in Locations:
for b in Demands:
if(distances[w][b] > 0):
prob += int(distances[w][b]) * J[w] >= 1
# Or eventually this instead :
#for w in Locations:
# prob += (lpSum([distances[w][b] * J[w] for b in Demands]) >= 1)
# or that :
# prob += 1 * J["A"] >= 1
# prob += 1 * J["A"] >= 1
# prob += 1 * J["B"] >= 1
# prob += 1 * J["B"] >= 1
# prob += 1 * J["B"] >= 1
# prob += 1 * J["B"] >= 1
# prob += 1 * J["B"] >= 1
# The problem data is written to an .lp file
prob.writeLP("SetCovering.lp")
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print("Status:", LpStatus[prob.status])
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print(v.name, "=", v.varValue)
# The optimised objective function value is printed to the screen
print("Total Locations = ", value(prob.objective))
# Show constraints
constraints = prob.constraints
print(constraints)
#Status: Optimal
#location_A = 1.0
#location_B = 1.0
#location_C = 0.0
#Total Locations = 2.0
The result should be :
location_A = 0.0
location_B = 1.0
location_C = 0.0
because location B covers all of our needs.
I wonder where is the problem, there is the maths code , I hope I wrote enough:
Thanks , it's nice if you have a solution, I have also tried lpSum with no luck
Edit : Modified the code a few, you can see 'optimal solution', but It's not the solution I want + Added a "Location_C"
EDIT : This is my new code, added a secondary continuous pulp dict for arcs(links) generation (ser_customer) . The solver should only pick Fac-2 in this case, because it's near all of the customers, and other facilities are way too far:
# Lists (sets / Array) of Customers and Facilities
Customer = [1,2,3,4,5]
Facility = ['Fac-1', 'Fac-2', 'Fac-3']
# Dictionary of distances in kms
distance = {'Fac-1' : {1 : 54, 2 : 76, 3 : 5, 4 : 76, 5 : 76},
'Fac-2' : {1 : 1, 2 : 3, 3 : 1, 4 : 8, 5 : 1},
'Fac-3' : {1 : 45, 2 : 23, 3 : 54, 4 : 87, 5 : 88}
}
# Setting the Problem
prob = LpProblem("pb", LpMinimize)
# Defining our Decision Variables
use_facility = LpVariable.dicts("Use Facility", Facility, 0, 1, LpBinary)
ser_customer = LpVariable.dicts("Service", [(i,j) for i in Customer for j in Facility], 0)
# Setting the Objective Function = Minimize amount of facilities and arcs
prob += lpSum(use_facility['Fac-1']+use_facility['Fac-2']+use_facility['Fac-3']) + lpSum(distance[j][i]*ser_customer[(i,j)] for j in Facility for i in Customer)
# Constraints,At least 1 arc must exist between facilities and customers
for i in Customer:
prob += lpSum(ser_customer[(i,j)] for j in Facility) >= 1
prob.solve()
# Print the solution of Decision Variables
for v in prob.variables():
print(v.name, "=", v.varValue)
# Print the solution of Binary Decision Variables
Tolerance = 0.0001
for j in Facility:
if use_facility[j].varValue > Tolerance:
print("Establish Facility at site = ", j)
The result seems to show good arcs(links), but there is no facility selection, I wonder if somebody have any idea, is there any way to force use_facility[index] to be > 0 , Is adding arcs decisions variables a good idea ? I have tried to moove the arcs as a constraint too instead of being into the objective function, with no luck. :
Service_(1,_'Fac_1') = 0.0
Service_(1,_'Fac_2') = 1.0
Service_(1,_'Fac_3') = 0.0
Service_(2,_'Fac_1') = 0.0
Service_(2,_'Fac_2') = 1.0
Service_(2,_'Fac_3') = 0.0
Service_(3,_'Fac_1') = 0.0
Service_(3,_'Fac_2') = 1.0
Service_(3,_'Fac_3') = 0.0
Service_(4,_'Fac_1') = 0.0
Service_(4,_'Fac_2') = 1.0
Service_(4,_'Fac_3') = 0.0
Service_(5,_'Fac_1') = 0.0
Service_(5,_'Fac_2') = 1.0
Service_(5,_'Fac_3') = 0.0
Use_Facility_Fac_1 = 0.0
Use_Facility_Fac_2 = 0.0
Use_Facility_Fac_3 = 0.0
I also have tried the AirSquid solution, ,I think I maybe miss sources decisions variables who should be minimized but don' t know how to do, I guess covered are arcs (links), anyway It is a good exercise, harder than a simple product mix, hi hi :
prob = LpProblem('source minimzer', LpMinimize)
dist_limit = 5
sources = ['A', 'B','C'] # the source locations
# note this is zero-indexed to work with the list indexes in dist dictionary...
destinations = list(range(5)) # the demand locations 0, 1, 2, 3, 4
dist = { 'A': [2, 23, 30, 54, 1],
'B': [3, 1, 2, 2, 3],
'C':[24,54,12,56,76]}
covered = LpVariable.dicts('covered', [(s, d) for s in sources for d in destinations], cat='Binary')
# The objective function
# Minimize the number of sources
prob += lpSum(covered[s, d])
# set up constraint to limit covered if the destination is "reachable"
for s in sources:
for d in destinations:
prob += covered[s, d] * dist[s][d] <= dist_limit
# add one more constraint to make sure that every destination is "covered"...
# The problem is solved using PuLP's choice of Solver
prob.solve()
# The status of the solution is printed to the screen
print("Status:", LpStatus[prob.status])
# The optimised objective function value is printed to the screen
print("Location Selection = ", prob.objective)
The solution displayed, while it should print "B" :
Status: Optimal
Total Locations = covered_('C',_4)
You are on the right track! A couple things will help...
First, you overlooked a key piece of information in your output in that the solver says your formulation is INFEASIBLE!
Status: Infeasible
So whatever came out in the variables is gibberish and you must figure that part out first.
So, why is it infeasible? Take a look at your constraint. You are trying to force the impossible if your distance value is zero this cannot be true:
prob += int(distances[w][b]) * J[w] >= 1
So, you need to reformulate! You are missing a concept here. You actually need 2 constraints for this problem.
You need to constrain the selection of a source-destination if the route is too long
You need to enforce that every destination is covered.
You also need a double-indexed decision variable. Why? Well, lets say that source 'A' covers destination 1, 2; and 'B' covers 2, 3, 4, 5.... You will be able to know that all the destinations are "covered" with one variable, but you will not know which sources were used, so you need to keep track of both to get the full picture.
Here is a start, along with a couple edits. I'd suggest the variable names source and destination as that is kinda standard. You do not have a specific demand in this particular problem, just the need for a connection. You might also want to use dictionaries more than nested lists, I think it is clearer. Below is an example start with the first constraint. Note the trick here in limiting the covered variable. If the distance is less than the limit, s, then this constraint is satisfiable. For instance, if the distance is 3:
3 * 1 <= s
Anyhow, here is a recommended start. The other constraint is not implemented. You will need to sum across all the sources to ensure the destination is "covered". Comment back if your are stuck.
prob = LpProblem('source minimzer', LpMinimize)
dist_limit = 5
sources = ['A', 'B'] # the source locations
# note this is zero-indexed to work with the list indexes in dist dictionary...
destinations = list(range(5)) # the demand locations 0, 1, 2, 3, 4
dist = { 'A': [2, 23, 30, 54, 1],
'B': [3, 1, 2, 2, 3]}
covered = LpVariable.dicts('covered', [(s, d) for s in sources for d in destinations], cat='Binary')
# set up constraint to limit covered if the destination is "reachable"
for s in sources:
for d in destinations:
prob += covered[s, d] * dist[s][d] <= dist_limit
# add one more constraint to make sure that every destination is "covered"...

Python CPLEX API: Conditional iteration on binary variables

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)

Finding the optimal location for router placement

I am looking for an optimization algorithm that takes a text file encoded with 0s, 1s, and -1s:
1's denoting target cells that requires Wi-Fi coverage
0's denoting cells that are walls
1's denoting cells that are void (do not require Wi-Fi coverage)
Example of text file:
I have created a solution function along with other helper functions, but I can't seem to get the optimal positions of the routers to be placed to ensure proper coverage. There is another file that does the printing, I am struggling with finding the optimal location. I basically need to change the get_random_position function to get the optimal one, but I am unsure how to do that. The area covered by the various routers are:
This is the kind of output I am getting:
Each router covers a square area of at most (2S+1)^2
Type 1: S=5; Cost=180
Type 2: S=9; Cost=360
Type 3: S=15; Cost=480
My code is as follows:
import numpy as np
import time
from random import randint
def is_taken(taken, i, j):
for coords in taken:
if coords[0] == i and coords[1] == j:
return True
return False
def get_random_position(floor, taken , nrows, ncols):
i = randint(0, nrows-1)
j = randint(0, ncols-1)
while floor[i][j] == 0 or floor[i][j] == -1 or is_taken(taken, i, j):
i = randint(0, nrows-1)
j = randint(0, ncols-1)
return (i, j)
def solution(floor):
start_time = time.time()
router_types = [1,2,3]
nrows, ncols = floor.shape
ratio = 0.1
router_scale = int(nrows*ncols*0.0001)
if router_scale == 0:
router_scale = 1
row_ratio = int(nrows*ratio)
col_ratio = int(ncols*ratio)
print('Row : ',nrows, ', Col: ', ncols, ', Router scale :', router_scale)
global_best = [0, ([],[],[])]
taken = []
while True:
found_better = False
best = [global_best[0], (list(global_best[1][0]), list(global_best[1][1]), list(global_best[1][2]))]
for times in range(0, row_ratio+col_ratio):
if time.time() - start_time > 27.0:
print('Time ran out! Using what I got : ', time.time() - start_time)
return global_best[1]
fit = []
for rtype in router_types:
interim = (list(global_best[1][0]), list(global_best[1][1]), list(global_best[1][2]))
for i in range(0, router_scale):
pos = get_random_position(floor, taken, nrows, ncols)
interim[0].append(pos[0])
interim[1].append(pos[1])
interim[2].append(rtype)
fit.append((fitness(floor, interim), interim))
highest_fitness = fit[0]
for index in range(1, len(fit)):
if fit[index][0] > highest_fitness[0]:
highest_fitness = fit[index]
if highest_fitness[0] > best[0]:
best[0] = highest_fitness[0]
best[1] = (highest_fitness[1][0],highest_fitness[1][1], highest_fitness[1][2])
found_better = True
global_best = best
taken.append((best[1][0][-1],best[1][1][-1]))
break
if found_better == False:
break
print('Best:')
print(global_best)
end_time = time.time()
run_time = end_time - start_time
print("Run Time:", run_time)
return global_best[1]
def available_cells(floor):
available = 0
for i in range(0, len(floor)):
for j in range(0, len(floor[i])):
if floor[i][j] != 0:
available += 1
return available
def fitness(building, args):
render = np.array(building, dtype=int, copy=True)
cov_factor = 220
cost_factor = 22
router_types = { # type: [coverage, cost]
1: {'size' : 5, 'cost' : 180},
2: {'size' : 9, 'cost' : 360},
3: {'size' : 15, 'cost' : 480},
}
routers_used = args[-1]
for r, c, t in zip(*args):
size = router_types[t]['size']
nrows, ncols = render.shape
rows = range(max(0, r-size), min(nrows, r+size+1))
cols = range(max(0, c-size), min(ncols, c+size+1))
walls = []
for ri in rows:
for ci in cols:
if building[ri, ci] == 0:
walls.append((ri, ci))
def blocked(ri, ci):
for w in walls:
if min(r, ri) <= w[0] and max(r, ri) >= w[0]:
if min(c, ci) <= w[1] and max(c, ci) >= w[1]:
return True
return False
for ri in rows:
for ci in cols:
if blocked(ri, ci):
continue
if render[ri, ci] == 2:
render[ri, ci] = 4
if render[ri, ci] == 1:
render[ri, ci] = 2
render[r, c] = 5
return (
cov_factor * np.sum(render > 1) -
cost_factor * np.sum([router_types[x]['cost'] for x in routers_used])
)
Here's a suggestion on how to solve the problem; however I don't affirm this is the best approach, and it's certainly not the only one.
Main idea
Your problem can be modelised as a weighted minimum set cover problem.
Good news, this is a well known optimization problem:
It is easy to find algorithm descriptions for approximate solutions
A quick search on the web shows many implementations of approximation algorithms in Python.
Bad news, this is a NP-hard optimization problem:
If you need an exact solution: algorithms will work only for "small" sized problems in a reasonable amount of time(in your case: size of the problem <=> number of "1" cells).
Approximate (a.k.a greedy) algorithms are trade-off between computation requirements, and a risk do deliver far from optimal solutions in certain cases.
Note that the following part does not prove that your problem is NP-hard. The general minimum set cover problem is NP-hard. In your case the subsets have several properties that might help to design a better algorithm. I have no idea how though.
Translating into a cover set problem
Let's define some sets:
U: the set of "1" cells (requiring Wifi).
P(U): the power set of U (the set of subsets of U).
P: the set of cells on which you can place a router (not sure if P=U in your original post).
T: the set of router type (3 values in your case).
R+: positive Real number (used to describe prices).
Let's define a function (pseudo Python):
# Domain of definition : T,P --> R+,P(U)
# This function takes a router type and a position, and returns
# a tuple containing:
# - the price of a router of the given type.
# - the subset of U containing all the position covered by a router
# of the given type placed at the given position.
def weighted_subset(routerType, position):
pass # TODO: implementation
Now, we define a last set, as the image of the function we've just described: S=weighted_subset(T,P). Each element of this set is a subset of U, weighted by a price in R+.
With all this formalism, finding the router types & positions that:
gives coverage to all the desirable locations
minimize the cost
Is equivalent to finding a sub-collection of S:
whose union of their P(U) is equal to U
which minimise the sum of the associated weights
Which is the weighted minimal set cover problem.

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