From 3dc5405b74d6f4ef82b340f53dfc715abeca2fdc Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Tue, 20 Feb 2024 13:52:28 +0000 Subject: [PATCH] optimisation bugs fixed and reviewed with kieran --- backend/Property.py | 1 - backend/app/plan/router.py | 1 - recommendations/HeatingRecommender.py | 11 ++++--- recommendations/optimiser/CostOptimiser.py | 4 ++- recommendations/optimiser/GainOptimiser.py | 38 ++++++++++++++++++---- recommendations/recommendation_utils.py | 10 ++---- 6 files changed, 44 insertions(+), 21 deletions(-) diff --git a/backend/Property.py b/backend/Property.py index efde7c18..45ed5198 100644 --- a/backend/Property.py +++ b/backend/Property.py @@ -623,7 +623,6 @@ class Property: floor_height=self.floor_height, perimeter=self.perimeter, built_form=self.data["built-form"], - property_type=self.data["property-type"], ) self.insulation_floor_area = self.floor_area / self.number_of_floors diff --git a/backend/app/plan/router.py b/backend/app/plan/router.py index a3f975be..375a551a 100644 --- a/backend/app/plan/router.py +++ b/backend/app/plan/router.py @@ -134,7 +134,6 @@ async def trigger_plan(body: PlanTriggerRequest): p.get_spatial_data(uprn_filenames) logger.info("Getting components and epc recommendations") - recommendations = {} recommendations_scoring_data = [] representative_recommendations = {} diff --git a/recommendations/HeatingRecommender.py b/recommendations/HeatingRecommender.py index 68fed382..d77e6b16 100644 --- a/recommendations/HeatingRecommender.py +++ b/recommendations/HeatingRecommender.py @@ -18,8 +18,9 @@ class HeatingRecommender: # This first iteration of the recommender will provide very basic recommendation # We recommend heating controls based on the main heating system if self.property.main_heating["clean_description"] == "Room heaters, electric": - self.recommend_room_heaters_electric(phase=phase, system_change=False, heating_controls_only=True) - self.recommend_electric_storage_heaters(phase=phase, system_change=True, heating_controls_only=False) + # Recommend high heat retention storage heaters + # self.recommend_room_heaters_electric(phase=phase, system_change=False, heating_controls_only=True) + # self.recommend_electric_storage_heaters(phase=phase, system_change=True, heating_controls_only=False) return if self.property.main_heating["clean_description"] == "Electric storage heaters, radiators": @@ -164,7 +165,8 @@ class HeatingRecommender: costs={}, description="", phase=phase, - heating_controls_only=heating_controls_only + heating_controls_only=heating_controls_only, + system_change=system_change ) ) return @@ -221,7 +223,8 @@ class HeatingRecommender: costs={}, description="", phase=phase, - heating_controls_only=heating_controls_only + heating_controls_only=heating_controls_only, + system_change=system_change ) ) return diff --git a/recommendations/optimiser/CostOptimiser.py b/recommendations/optimiser/CostOptimiser.py index 622d5b47..294a6bba 100644 --- a/recommendations/optimiser/CostOptimiser.py +++ b/recommendations/optimiser/CostOptimiser.py @@ -30,7 +30,9 @@ class CostOptimiser: :param min_gain: Numerical value for the minimum gain :return: """ - if min_gain <= 5: + if min_gain == 0: + return min_gain + elif min_gain <= 5: return min_gain + 0.5 elif min_gain <= 20: return min_gain + 1.5 diff --git a/recommendations/optimiser/GainOptimiser.py b/recommendations/optimiser/GainOptimiser.py index 0db6b4ea..6652ffbf 100644 --- a/recommendations/optimiser/GainOptimiser.py +++ b/recommendations/optimiser/GainOptimiser.py @@ -9,11 +9,15 @@ class GainOptimiser: This class is used to maximise gain, given a constrained cost """ - def __init__(self, components, max_cost, max_gain=None): + def __init__(self, components, max_cost, max_gain): """ This function will try and maximise the gain, given a constrained cost. If we specific a max_gain, then the optimisation routine is constained to try not to exceed a maximum increase + + If the maximum gain (`max_gain`) is explicitly set to 0, the optimization routine interprets this as an + instruction not to perform any optimization. + :param components: List of components, where each component is a dictionary with keys "id", "cost" and "gain" :param max_cost: Maximum cost constraint :param max_gain: Maximum gain constraint @@ -78,6 +82,10 @@ class GainOptimiser: # Remove the original cost constraint self.m.remove(self.cost_constraint) + if self.max_gain is not None: + # Remove the original max gain constraint + self.m.remove(self.max_gain_constraint) + # Add slack variable s = self.m.add_var(lb=0) @@ -99,18 +107,34 @@ class GainOptimiser: def solve(self): # Solve the problem + + if self.max_gain == 0: + logger.info("Max gain is set to 0, no optimisation will be performed") + # Nothing to do + return + 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 = [ + 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 ] + if (self.m.status == OptimizationStatus.INFEASIBLE) or ( + (self.m.status == OptimizationStatus.OPTIMAL) and not len(solution) + ): + logger.info("We have an infeasible model, setting up slack model") + self.setup_slack() + self.m.optimize() + 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 + ] + + self.solution = solution + 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 21f704f8..0d5f9743 100644 --- a/recommendations/recommendation_utils.py +++ b/recommendations/recommendation_utils.py @@ -544,7 +544,7 @@ def get_wall_type( return None -def estimate_external_wall_area(num_floors, floor_height, perimeter, built_form, property_type): +def estimate_external_wall_area(num_floors, floor_height, perimeter, built_form): """ This method estimates the external wall area based on fundamental assumptions about the home @@ -553,7 +553,6 @@ def estimate_external_wall_area(num_floors, floor_height, perimeter, built_form, :param floor_height: Height of one floor in meters. :param perimeter: Total perimeter of the building on one floor in meters. :param built_form: The built form of the property. This is used to determine the number of exposed walls. - :param property_type: The type of the property. This is used to determine the number of exposed walls. :return: """ wall_area_one_floor = perimeter * floor_height @@ -566,11 +565,8 @@ def estimate_external_wall_area(num_floors, floor_height, perimeter, built_form, 'Semi-Detached': 3, 'Detached': 4, } - if built_form == "Detached" and property_type == "Flat": - # We don't have 4 exposed walls for a flat - exposed_wall_area = total_wall_area * (3 / 4) - else: - exposed_wall_area = total_wall_area * (number_exposed_walls.get(built_form, 3) / 4) + + exposed_wall_area = total_wall_area * (number_exposed_walls.get(built_form, 3) / 4) return exposed_wall_area