diff --git a/.idea/Model.iml b/.idea/Model.iml
index 0ded8e60..05b9012b 100644
--- a/.idea/Model.iml
+++ b/.idea/Model.iml
@@ -7,7 +7,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index ae87bfde..3b05c6ac 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,6 +1,6 @@
-
+
diff --git a/model_data/optimiser/CostOptimiser.py b/model_data/optimiser/CostOptimiser.py
index e9ef9313..de5a9e11 100644
--- a/model_data/optimiser/CostOptimiser.py
+++ b/model_data/optimiser/CostOptimiser.py
@@ -1,4 +1,7 @@
-from mip import Model, xsum, minimize, BINARY
+from mip import Model, xsum, minimize, BINARY, OptimizationStatus
+from utils.logger import setup_logger
+
+logger = setup_logger()
class CostOptimiser:
@@ -9,6 +12,7 @@ class CostOptimiser:
def __init__(self, components, min_gain):
self.components = components
self.min_gain = min_gain
+ self.gain_constraint = None
self.m = None
self.variables = []
self.solution = []
@@ -42,21 +46,51 @@ class CostOptimiser:
# This constrain ensures that sum of gain_ig * x_ig >= min_gain, where gain_ig represents the gain for the ith
# component
# in group g, and x_ig is the binary decision variable for the ith component in group g
- self.m += xsum(
+ gain_expression = xsum(
item['gain'] * var for group, group_vars in zip(self.components, self.variables) for item, var in
zip(group, group_vars)
) >= self.min_gain
+ self.gain_constraint = self.m.add_constr(gain_expression)
+
# At most one item from each group
# This constraint ensures that at most one item from each group is selected
# This is expressed by summing up the decision variables for each group and ensuring that the sum is <= 1
for group_vars in self.variables:
self.m += xsum(var for var in group_vars) <= 1
+ def setup_slack(self):
+
+ # Remove the original gain constraint
+ self.m.remove(self.gain_constraint)
+ # Add slack variable
+ s = self.m.add_var(lb=0)
+
+ # Modify the constraint
+ self.m += xsum(
+ item['gain'] * var for group, group_vars in zip(self.components, self.variables) for item, var in
+ zip(group, group_vars)
+ ) + s >= self.min_gain
+
+ # Modify the objective to penalize the use of slack
+ penalty = 10000 # you can adjust this based on how much you want to penalize the use of slack
+ self.m.objective = minimize(
+ xsum(
+ component['cost'] * var for group, group_vars in zip(self.components, self.variables) for component, var
+ in
+ zip(group, group_vars)
+ ) + penalty * s
+ )
+
def solve(self):
# Solve the problem
self.m.optimize()
+ if self.m.status == OptimizationStatus.INFEASIBLE:
+ logger.info("We have an infeasible model, setting up slack model")
+ self.setup_slack()
+ self.m.optimize()
+
self.solution = [
item for group, group_vars in zip(self.components, self.variables) for item, var in zip(group, group_vars)
if
diff --git a/model_data/optimiser/GainOptimiser.py b/model_data/optimiser/GainOptimiser.py
index 08484774..d5c8a5af 100644
--- a/model_data/optimiser/GainOptimiser.py
+++ b/model_data/optimiser/GainOptimiser.py
@@ -1,18 +1,21 @@
-from mip import Model, xsum, maximize, BINARY
+from mip import Model, xsum, maximize, BINARY, OptimizationStatus
+from utils.logger import setup_logger
+
+logger = setup_logger()
class GainOptimiser:
"""
- This class is used maximise gain, given a constrained cost
+ This class is used to maximise gain, given a constrained cost
"""
def __init__(self, components, max_cost):
self.components = components
self.max_cost = max_cost
+ self.cost_constraint = None
self.m = None
self.variables = []
self.solution = []
-
self.solution_gain = None
self.solution_cost = None
@@ -26,7 +29,6 @@ class GainOptimiser:
self.components
]
- # Set objective
# This objective is the sum
# gain_ig * x_ig, where gain_ig represents the gain for ith part in group g
# and x_ig is the binary decision variable for the ith part in group g
@@ -38,33 +40,58 @@ class GainOptimiser:
)
)
- # Add constraints
# This constrain ensures that sum of cost_ig * x_ig <= C, where cost_ig represents the cost for the ith
# component
# in group g, and x_ig is the binary decision variable for the ith component in group g
- self.m += xsum(
+ cost_expression = xsum(
item['cost'] * var for group, group_vars in zip(self.components, self.variables) for item, var in
zip(group, group_vars)
) <= self.max_cost
- # At most one item from each group
+ self.cost_constraint = self.m.add_constr(cost_expression)
+
# This constraint ensures that at most one item from each group is selected
# This is expressed by summing up the decision variables for each group and ensuring that the sum is <= 1
for group_vars in self.variables:
self.m += xsum(var for var in group_vars) <= 1
+ def setup_slack(self):
+ # Remove the original cost constraint
+ self.m.remove(self.cost_constraint)
+
+ # Add slack variable
+ s = self.m.add_var(lb=0)
+
+ # Modify the constraint
+ self.m += xsum(
+ item['cost'] * var for group, group_vars in zip(self.components, self.variables) for item, var in
+ zip(group, group_vars)
+ ) + s <= self.max_cost
+
+ # Modify the objective to penalize the use of slack
+ penalty = -10000 # Negative penalty because we are maximizing
+ self.m.objective = maximize(
+ xsum(
+ component['gain'] * var for group, group_vars in zip(self.components, self.variables) for component, var
+ in
+ zip(group, group_vars)
+ ) + penalty * s
+ )
+
def solve(self):
# Solve the problem
self.m.optimize()
+ if self.m.status == OptimizationStatus.INFEASIBLE:
+ logger.info("We have an infeasible model, setting up slack model")
+ self.setup_slack()
+ self.m.optimize()
+
self.solution = [
item for group, group_vars in zip(self.components, self.variables) for item, var in zip(group, group_vars)
if
var.x >= 0.99
]
- # Get the selected items
-
self.solution_gain = self.m.objective.x
self.solution_cost = sum([component['cost'] for component in self.solution])
-
diff --git a/recommendations/recommendation_utils.py b/recommendations/recommendation_utils.py
index 9b7dbd4e..d35befd7 100644
--- a/recommendations/recommendation_utils.py
+++ b/recommendations/recommendation_utils.py
@@ -1,6 +1,7 @@
from copy import deepcopy
from backend.Property import Property
from statistics import mean
+import random
def estimate_sap_points():
@@ -9,7 +10,7 @@ def estimate_sap_points():
:return:
"""
- return 999
+ return random.sample(range(4, 12), 1)[0]
def r_value_per_mm_to_u_value(depth_mm: int, r_value_per_mm: float):