optimisation bugs fixed and reviewed with kieran

This commit is contained in:
Khalim Conn-Kowlessar 2024-02-20 13:52:28 +00:00
parent 7ee9476dc1
commit 3dc5405b74
6 changed files with 44 additions and 21 deletions

View file

@ -623,7 +623,6 @@ class Property:
floor_height=self.floor_height, floor_height=self.floor_height,
perimeter=self.perimeter, perimeter=self.perimeter,
built_form=self.data["built-form"], built_form=self.data["built-form"],
property_type=self.data["property-type"],
) )
self.insulation_floor_area = self.floor_area / self.number_of_floors self.insulation_floor_area = self.floor_area / self.number_of_floors

View file

@ -134,7 +134,6 @@ async def trigger_plan(body: PlanTriggerRequest):
p.get_spatial_data(uprn_filenames) p.get_spatial_data(uprn_filenames)
logger.info("Getting components and epc recommendations") logger.info("Getting components and epc recommendations")
recommendations = {} recommendations = {}
recommendations_scoring_data = [] recommendations_scoring_data = []
representative_recommendations = {} representative_recommendations = {}

View file

@ -18,8 +18,9 @@ class HeatingRecommender:
# This first iteration of the recommender will provide very basic recommendation # This first iteration of the recommender will provide very basic recommendation
# We recommend heating controls based on the main heating system # We recommend heating controls based on the main heating system
if self.property.main_heating["clean_description"] == "Room heaters, electric": if self.property.main_heating["clean_description"] == "Room heaters, electric":
self.recommend_room_heaters_electric(phase=phase, system_change=False, heating_controls_only=True) # Recommend high heat retention storage heaters
self.recommend_electric_storage_heaters(phase=phase, system_change=True, heating_controls_only=False) # 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 return
if self.property.main_heating["clean_description"] == "Electric storage heaters, radiators": if self.property.main_heating["clean_description"] == "Electric storage heaters, radiators":
@ -164,7 +165,8 @@ class HeatingRecommender:
costs={}, costs={},
description="", description="",
phase=phase, phase=phase,
heating_controls_only=heating_controls_only heating_controls_only=heating_controls_only,
system_change=system_change
) )
) )
return return
@ -221,7 +223,8 @@ class HeatingRecommender:
costs={}, costs={},
description="", description="",
phase=phase, phase=phase,
heating_controls_only=heating_controls_only heating_controls_only=heating_controls_only,
system_change=system_change
) )
) )
return return

View file

@ -30,7 +30,9 @@ class CostOptimiser:
:param min_gain: Numerical value for the minimum gain :param min_gain: Numerical value for the minimum gain
:return: :return:
""" """
if min_gain <= 5: if min_gain == 0:
return min_gain
elif min_gain <= 5:
return min_gain + 0.5 return min_gain + 0.5
elif min_gain <= 20: elif min_gain <= 20:
return min_gain + 1.5 return min_gain + 1.5

View file

@ -9,11 +9,15 @@ class GainOptimiser:
This class is used to maximise gain, given a constrained cost 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 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 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 components: List of components, where each component is a dictionary with keys "id", "cost" and "gain"
:param max_cost: Maximum cost constraint :param max_cost: Maximum cost constraint
:param max_gain: Maximum gain constraint :param max_gain: Maximum gain constraint
@ -78,6 +82,10 @@ class GainOptimiser:
# Remove the original cost constraint # Remove the original cost constraint
self.m.remove(self.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 # Add slack variable
s = self.m.add_var(lb=0) s = self.m.add_var(lb=0)
@ -99,18 +107,34 @@ class GainOptimiser:
def solve(self): def solve(self):
# Solve the problem # 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() self.m.optimize()
if self.m.status == OptimizationStatus.INFEASIBLE: solution = [
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) item for group, group_vars in zip(self.components, self.variables) for item, var in zip(group, group_vars)
if if
var.x >= 0.99 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_gain = self.m.objective.x
self.solution_cost = sum([component['cost'] for component in self.solution]) self.solution_cost = sum([component['cost'] for component in self.solution])

View file

@ -544,7 +544,7 @@ def get_wall_type(
return None 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 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 floor_height: Height of one floor in meters.
:param perimeter: Total perimeter of the building on 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 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: :return:
""" """
wall_area_one_floor = perimeter * floor_height 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, 'Semi-Detached': 3,
'Detached': 4, '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 * (number_exposed_walls.get(built_form, 3) / 4)
exposed_wall_area = total_wall_area * (3 / 4)
else:
exposed_wall_area = total_wall_area * (number_exposed_walls.get(built_form, 3) / 4)
return exposed_wall_area return exposed_wall_area