3

This problem is an extension of an earlier question I aksed Python pyomo : how and where to store sumproduct involving decision variables (1d array) and fixed data (matrix) (i have solved this piece).

Brief background : I am trying to solve an optimization problem where I need to select the best store from where an order can be fulfilled. For this illustration I have 2 orders (O1, O2) and 3 stores (str_1, str_2, str_3). While selecting the best store to fulfill an order, there are 4 factors : A, B, C and D. So for fulfilling order 1, each store will have 4 set of scores corresponding to each factor. Score will be between 0 and 1.

I need to determine the optimal weights for 4 factors (wtA, wtB, wtC, wtD - decision variables) such that sumproduct of weights and the score is maximum. (Weights should be between 0 and 100). For instance, say if we check if store 1 can service order 1, then sumproduct = wtA * score_O1_str_1_A + wtB * score_O1_str_1_B + wtC * score_O1_str_1_C + wtD * score_O1_str_1_D

I am holding the sumproduct in a dictionary now. The idea is to pick for each order the store with maximum score, and to maximize the sum of scores across all orders. E.g.

O1 -> str_1 = 88 ; O1 -> str_2 = 90 ; O1 -> str_3 = 86 ; O2 -> str_1 = 82 ; O2 -> str_2 = 92 ; O2 -> str_3 = 85 ;

Above are the weighted scores - sumproduct we get by multiplying weights (decision variables) and the scores (given to us). From above I would want to pick weights such that the sum of maximum score from fulfilling each order is maximized. So in this case O1 -> str_2 (90) and O2 -> str_2 (92). Sum of maximums = 90 + 92 = 182. I need to maximize this. I have written the code but doesn't seem to give me the right answer. Your help is much appreciated!

Please see the below code to see what I have done and where I am stuck:

from pyomo.environ import *

model = ConcreteModel(name="(weights)")

# scores for factors A, B, C and D for each order and store combination
order_str_scores = {
('O1', 'str_1') : [0.88, 0.85, 0.88, 0.93], # if order 1 is fulfilled from store 2 then these are the scores
('O1', 'str_2'): [0.93, 0.91, 0.95, 0.86],
('O1', 'str_3') : [0.83, 0.83, 0.87, 0.9],
('O2', 'str_1') : [0.85, 0.86, 0.84, 0.98],
('O2', 'str_2') : [0.87, 0.8, 0.85, 0.87],
('O2', 'str_3') : [0.91, 0.87, 0.95, 0.83],
}

model.orders = list(set([i[0] for i in order_str_scores.keys()]))
model.stores = list(set([i[1] for i in order_str_scores.keys()]))

# 4 factors (A, B, C & D) whose scores are mentioned in 'order_str_wts' dictionary
model.factors = ['A', 'B', 'C','D']

# below 4 decision variables (one for each factor) will hold the optimal number between 0 - 100
def dv_bounds(m, i):
    return (0, 100)
model.x1 = Var(model.factors, within=NonNegativeReals, bounds=dv_bounds)

#Sum of these 4 decision variables should be equal to 100
def sum_wts(m):
    return sum(m.x1[i] for i in model.factors) == 100
model.sum_wts = Constraint(rule=sum_wts)


# here I hold the sumproduct of scores and corresponding weights for each factor
D = {}
model.k = [('O1', 'str_1'), ('O1', 'str_2'), ('O1', 'str_3'), ('O2', 'str_1'), ('O2', 'str_2'), ('O2', 'str_3')]
for i in model.k:
    D[i] = sum(model.x1[n] * order_str_scores[i][q] for n,q in zip(model.factors,range(4)))

# BELOW I GET STUCK. IDEA IS TO SELECT FOR EACH ORDER THE STORE WHICH HAS THE SUM OF WEIGHTED SCORES AND THE SUM OF SCORES ACROSS ORDERS SHOULD BE MAXIMUM

# create decision variable for each order, which will hold the maximum weighted score
model.x3 = Var(model.orders, within=NonNegativeReals, bounds=dv_bounds)

# add constraints
model.cons = ConstraintList()
for i in model.k:
    model.cons.add(model.x3[i[0]] >= D[i])


model.obj = Objective(rule=obj_rule, sense=maximize)

opt = SolverFactory('glpk')
result_obj = opt.solve(model, tee=True)
model.display()

What solver does is - and it makes sense is to pick a value of model.x3['O1'] and model.x3['O2'] = 100. This is because the upper bound I have set is 100 for each and since it needs to maximize the sum it picks 100 for each.

However, what I want is model.x3['O1'] and model.x3['O2'] to pick the actual maximum value corresponding to the store from dictionary 'D' where I hold the sumproducts (weighted scores). And pick optimal weights such that the sum of the weighted scores - corresponding to the store with maximum weights is maximixed.

UPDATE : I was able to solve the problem, please look at the below complete code listing

from pyomo.environ import *
import itertools
model = ConcreteModel(name="(weights)")

# scores for factors 'A', 'B', 'C','D' for each order - store combination
order_str_scores = {
('O1', 'str_1') : [0.90, 0.90, 0.71, 0.93],
('O1', 'str_2'): [0.83, 0.91, 0.95, 0.86],
('O1', 'str_3') : [0.83, 0.83, 0.87, 0.9],
('O2', 'str_1') : [0.71, 0.56, 0.84, 0.55],
('O2', 'str_2') : [0.97, 0.9, 0.95, 0.87],
('O2', 'str_3') : [0.91, 0.87, 0.95, 0.83],
('O3', 'str_1') : [0.81, 0.86, 0.84, 0.85],
('O3', 'str_2') : [0.89, 0.84, 0.95, 0.87],
('O3', 'str_3') : [0.97, 0.87, 0.95, 0.86],
('O4', 'str_1') : [0.95, 0.96, 0.84, 0.85],
('O4', 'str_2') : [0.89, 0.74, 0.95, 0.87],
('O4', 'str_3') : [0.87, 0.77, 0.85, 0.83],
('O5', 'str_1') : [0.61, 0.86, 0.94, 0.85],
('O5', 'str_2') : [0.99, 0.84, 0.98, 0.97],
('O5', 'str_3') : [0.77, 0.87, 0.95, 0.83],
}

# bounds for each of the factors
factor_bounds = {
'A' : [80, 99],
'B' : [0, 20],
'C': [0, 20],
'D' : [0, 20],
}

# list of unique orders and stores. These will act as indices
model.orders = sorted(list(set([i[0] for i in order_str_scores.keys()])))
model.stores = sorted(list(set([i[1] for i in order_str_scores.keys()])))

# 4 factors 'availability', 'distance', 'storeCapacity','smoothing' whose scores are mentioned in 'order_str_wts' dictionary
model.factors = ['A', 'B', 'C','D']

# below 4 decision variables (one for each factor) will hold the optimal number between 0 - 100
def dv_bounds(m, i):
    return (factor_bounds[i][0], factor_bounds[i][1])
model.x1 = Var(model.factors, within=NonNegativeReals, bounds=dv_bounds)

#Sum of these 4 decision variables should be equal to 100
def sum_wts(m):
    return sum(m.x1[i] for i in model.factors) == 100
model.sum_wts = Constraint(rule=sum_wts)

# Hold the sumproduct of scores and corresponding weights for each factor
D = {}
model.k = list(itertools.product(model.orders, model.stores))
for i in model.k:
    D[i] = sum(model.x1[n] * order_str_scores[i][q] for n,q in zip(model.factors,range(4)))

# DV : Binary auxiliary variable to help find the store with max weighted score
model.is_max = Var(model.orders, model.stores, within=Binary)

# DV : Variable to hold the maximum weighted score for each order
model.max_value = Var(model.orders, model.stores, within=NonNegativeReals)

# 1st helper constraint : or each order sum of binary DV variable == 1
def is_max_1(m, i):
    return sum(m.is_max[i, j] for j in model.stores) == 1
model.is_max_1 = Constraint(model.orders, rule=is_max_1)

# 1st helper constraint to find the maximum weighted score and the corresponding store for each order
def is_max_const(m,i,j):
    return m.max_value[i,j] <= 1000 * m.is_max[i,j]
model.is_max_const = Constraint(model.orders, model.stores, rule=is_max_const)

# 2nd helper constraint to find the maximum weighted score and the corresponding store for each order
def const_2(m,i,j,k):
    return m.max_value[i, j] + (1000 * (1 - m.is_max[i, j])) >= D[i, k]
model.const_2 = Constraint(model.orders, model.stores, model.stores, rule=const_2)

# 3rd helper constraint to ensure that the selected max_value is greater than the D
def const_3(m,i,j):
    return m.max_value[i,j] <= D[i, j]
model.const_3 = Constraint(model.orders, model.stores,rule=const_3)

# Define the objective function
def obj_rule(m):
    return sum(m.max_value[i,j] for i in m.orders for j in m.stores)

model.obj = Objective(rule=obj_rule, sense=maximize)

opt = SolverFactory('glpk')
result_obj = opt.solve(model, tee=True)
model.display()
  • Was able to solve the problem that I had posted. Please look at the **UPDATE** section in my above post. It has the complete code listing – Bhartendu Awasthi Oct 23 '19 at 04:59

0 Answers0