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):