Converting combination of classes into a single value
Awaiting user inputI am trying to maximise the overall product score (sum over score of all product types over all locations).
TOTALSCORE = m.addVar(vtype=GRB.CONTINUOUS, name='TOTALSCORE') # added
obj = TOTALSCORE
m.setObjective(obj, GRB.MAXIMIZE)
The score of each product is dependent on the values of a set of variables characterising the product. These values are in turn dependent on the treatment that is done at the location where the product is made and are known (dictionary ProductVariable_values with keys location (i) and treatment type (h), and values the variable values v1, v2 and v3.
Only one treatment per location is possible and the total cost of treatments is limited.
treatment_open = m.addVars(Source_Location, SourceTreatment_Type, vtype=GRB.BINARY, name='treatment_open')
cost_treatments = m.addVar(vtype=GRB.CONTINUOUS, name='cost_treatments')
#constraint #1 cost <= available budget
available_budget = 100000
m.addConstr(quicksum(SourceLocation_TreatmentType_cost[i, h] * treatment_open[i, h] for i, h in SourceLocation_TreatmentType) <= available_budget)
# constraint #2 not more than one treatment per location
for i in Source_Location:
m.addConstr((quicksum(treatment_open[i,h] for h in SourceTreatment_Type)) == 1)
I created three variables to derive the variable values per location
# constraint #3 calculate variable values
value_v1 = {}
value_v2 = {}
value_v3 = {}
for i in Source_Location:
value_v1[(i)] = m.addVar(vtype=GRB.CONTINUOUS, name=f"value_v1[({i})]")
value_v2[(i)] = m.addVar(vtype=GRB.CONTINUOUS, name=f"value_v2[({i})]")
value_v3[(i)] = m.addVar(vtype=GRB.CONTINUOUS, name=f"value_v3[({i})]")
for i in Source_Location:
m.addConstr(value_v1[i] = quicksum(ProductVariable_values[i, h][0] * treatment_open[i, h] for h in SourceTreatment_Type))
m.addConstr(value_v2[i] == quicksum(ProductVariable_values[i, h][1] * treatment_open[i, h] for h in SourceTreatment_Type))
m.addConstr(value_v3[i] == quicksum(ProductVariable_values[i, h][2] * treatment_open[i, h] for h in SourceTreatment_Type))
The eventual score each product receives depends on the combination of all variable values. These variable values are converted into classes and I have a lookup table with a score for each possible class combination (lookuptable_scores --> {('ProductA', 1, 1, 1): 0.74, ('ProductB', 1, 1, 1): 0.03, ('ProductA', 2, 1, 1): 0.73, ('ProductB', 2, 1, 1): 0.15, ...})
To convert the variable values into classes I had to create binary variables:
# constraint #4 convert variable values into classes
v1_class = {}
v2_class = {}
v3_class = {}
binary_v1 = {}
binary_v2 = {}
binary_v3 = {}
for i in Source_Location:
for product in product_list:
for binnumb in range(data['v1_binscount'][product]):
binary_v1[(i, product, binnumb)] = m.addVar(vtype=GRB.BINARY, name=f"binary_v1[({i},{product},{binnumb})]")
for binnumb in range(data['v2_binscount'][product]):
binary_v2[(i, product, binnumb)] = m.addVar(vtype=GRB.BINARY, name=f"binary_v2[({i},{product},{binnumb})]")
for binnumb in range(data['v3_binscount'][product]):
binary_v3[(i, product, binnumb)] = m.addVar(vtype=GRB.BINARY, name=f"binary_v3[({i},{product},{binnumb})]")
for i in Source_Location:
for product in product_list:
v1_class[(i, product)] = m.addVar(vtype=GRB.CONTINUOUS, name=f"v1_class[({i},{product})]")
v2_class[(i, product)] = m.addVar(vtype=GRB.CONTINUOUS, name=f"v2_class[({i},{product})]")
v3_class[(i, product)] = m.addVar(vtype=GRB.CONTINUOUS, name=f"v3_class[({i},{product})]")
for i in Source_Location:
for product in product_list:
#Calculate v1 class per location
for binnumb in range(data['v1_binscount'][product]):
m.addConstr((binary_v1[(i, product, binnumb)] == 0) >> (v1_intervals[product, binnumb + 1][0] >= value_v1[i] + 0.00000001))
m.addConstr((binary_v1[(i, product, binnumb)] == 1) >> (v1_intervals[product, binnumb + 1][0] <= value_v1[i]))
m.addConstr(v1_class[i, product] == quicksum((binary_v1[(i, product, binnumb)]) for binnumb in range(data['v1_binscount'][product])))
#Calculate v2 class per location
for binnumb in range(data['v2_binscount'][product]):
m.addConstr((binary_v2[(i, product, binnumb)] == 0) >> (v2_intervals[product, binnumb + 1][0] >= value_v2[i] + 0.00000001))
m.addConstr((binary_v2[(i, product, binnumb)] == 1) >> (v2_intervals[product, binnumb + 1][0] <= value_v2[i]))
m.addConstr(v2_class[i, product] == quicksum((binary_v2[(i, product, binnumb)]) for binnumb in range(data['v2_binscount'][product])))
#Calculate v3 class per location
for binnumb in range(data['v3_binscount'][product]):
m.addConstr((binary_v3[(i, product, binnumb)] == 0) >> (v3_intervals[product, binnumb + 1][0] >= value_v3[i] + 0.00000001))
m.addConstr((binary_v3[(i, product, binnumb)] == 1) >> (v3_intervals[product, binnumb + 1][0] <= value_v3[i]))
m.addConstr(v3_class[i, product] == quicksum((binary_v3[(i, product, binnumb)]) for binnumb in range(data['v3_binscount'][product])))
I tried to use v1_class, v2_class, v3_class variables directly to look up what the corresponding score is per location but cannot use variables as keys values in a dictionary (gurobipy.GurobiError: Variable has not yet been added to the model)
local_score = {}
for i in Source_Location:
for product in product_list:
local_score[(i, product)] = moov.addVar(vtype=GRB.CONTINUOUS, name=f"local_score[({i},{product})]")
moov.addConstr(local_score[i,product] == (lookuptable_scores[product, v1_class[i, product], v2_class[i,product], v3_class[i,product]]))
Therefore, I tried to solve it by again using binary variables:
#Constraint #5 calculate TOTALSCORE
local_score = {}
local_scorebin = {}
for i in Source_Location:
for (product, class_v1, class_v2, class_v3) in lookuptable_scores:
local_scorebin[(i, product, class_v1, class_v2, class_v3)] = m.addVar(vtype=GRB.BINARY,name=f"local_score_bin[({i},{product},{class_v1},{class_v2},{class_v3}")
for i in Source_Location:
for product in product_list:
local_score[(i, product)] = m.addVar(vtype=GRB.CONTINUOUS, name=f"local_score[({i},{product})]")
for i in Source_Location:
for (product, class_v1_lookup, class_v2_lookup, class_v3_lookup) in lookuptable_scores:
m.addConstr(
((local_scorebin[(i, product, class_v1_lookup, class_v2_lookup, class_v3_lookup)] == 1) >>
(100*class_v1_lookup + 10*class_v2_lookup + class_v3_lookup
== 100*v1_class[i, product] + 10*v2_class[i, product] + v3_class[i, product]))
)
for i in Source_Location:
for product in product_list:
m.addConstr(
local_score[i, product] ==
quicksum((local_scorebin[(i, product, (binnumb_v1+1), (binnumb_v2+1), (binnumb_v3+1))]
* lookuptable_scores[product, (binnumb_v1+1), (binnumb_v2+1), (binnumb_v3+1)])
for binnumb_v1 in range(data['v1_binscount'][product])
for binnumb_v2 in range(data['v2_binscount'][product])
for binnumb_v3 in range(data['v3_binscount'][product])
)
)
m.addConstr(
TOTALSCORE
==
quicksum(local_score[i, product] for i in Source_Location for product in product_list))
The constraint calculating the binary variable 'local_scorebin' in the code above generated the warning message "<input>:59: DeprecationWarning: elementwise comparison failed; this will raise an error in the future." using python 3.8, gurobi 9.1.1., numpy 1.23.0. However after updating gurobi to version 11.0.0 (python 3.12.1, numpy 1.26.3), this generates an error: "gurobipy.GurobiError: Constraint has no bool value (are you trying "lb <= expr <= ub"?)"
Is there a way to convert the combination of variable classes into the product score without using binary variables? In that way, I could avoid comparing an integer with a sum of gurobi variables which I believe is causing this error. Further, this would also resolve the issue of creating too many binary variables. Eventually, I want to run the code on approximately 100000 locations, 10 product types and 15 variables that each have ca. 10 classes
-
Hi Ine,
It looks like you might be one of our commercial customers. Did you want to open a Support Request?
- Riley
0
Please sign in to leave a comment.
Comments
1 comment