feat: single objective optimization finished
This commit is contained in:
parent
07cdfc4bf7
commit
f7815dc4c0
|
@ -10,55 +10,178 @@ from energy_system_modelling_package.energy_system_modelling_factories.system_si
|
|||
|
||||
|
||||
class GeneticAlgorithm:
|
||||
def __init__(self, population_size=100, generations=50, crossover_rate=0.8, mutation_rate=0.1,
|
||||
def __init__(self, population_size=50, generations=50, crossover_rate=0.8, mutation_rate=0.1,
|
||||
optimization_scenario='cost', output_path=None):
|
||||
self.population_size = population_size
|
||||
self.population = []
|
||||
self.generations = generations
|
||||
self.crossover_rate = crossover_rate
|
||||
self.mutation_rate = mutation_rate
|
||||
self.optimization_scenario = optimization_scenario
|
||||
self.list_of_solutions = []
|
||||
self.best_solution = None
|
||||
self.best_solution_generation = None
|
||||
self.output_path = output_path
|
||||
|
||||
def energy_system_archetype_optimal_sizing(self):
|
||||
pass
|
||||
|
||||
# Initialize Population
|
||||
def initialize_population(self, building, energy_system, heating_design_load=None, cooling_design_load=None,
|
||||
available_space=None, dt=1800, fuel_price_index=0.05, electricity_tariff_type='fixed',
|
||||
consumer_price_index=0.04, interest_rate=0.04, discount_rate=0.03, percentage_credit=0,
|
||||
credit_years=15):
|
||||
def initialize_population(self, building, energy_system):
|
||||
"""
|
||||
The population to optimize the sizes of generation and storage components of any energy system are optimized.
|
||||
The initial population will be a list of individuals where each of them represent a possible solution to the
|
||||
problem.
|
||||
:param building:
|
||||
:param energy_system:
|
||||
:return: List
|
||||
"""
|
||||
population = []
|
||||
for i in range(self.population_size):
|
||||
population.append(Individual(building, energy_system, self.optimization_scenario,
|
||||
heating_design_load=heating_design_load, cooling_design_load=cooling_design_load,
|
||||
available_space=available_space, dt=dt, fuel_price_index=fuel_price_index,
|
||||
electricity_tariff_type=electricity_tariff_type,
|
||||
consumer_price_index=consumer_price_index, interest_rate=interest_rate,
|
||||
discount_rate=discount_rate, percentage_credit=percentage_credit,
|
||||
credit_years=credit_years))
|
||||
return population
|
||||
self.population.append(Individual(building, energy_system, self.optimization_scenario))
|
||||
|
||||
def order_population(self):
|
||||
"""
|
||||
ordering the population based on the fitness score in ascending order
|
||||
:return:
|
||||
"""
|
||||
self.population = sorted(self.population, key=lambda x: x.individual['fitness_score'])
|
||||
|
||||
def tournament_selection(self):
|
||||
selected = []
|
||||
for _ in range(len(self.population)):
|
||||
i, j = random.sample(range(self.population_size), 2)
|
||||
if self.population[i].individual['fitness_score'] < self.population[j].individual['fitness_score']:
|
||||
selected.append(copy.deepcopy(self.population[i]))
|
||||
else:
|
||||
selected.append(copy.deepcopy(self.population[j]))
|
||||
return selected
|
||||
|
||||
def crossover(self, parent1, parent2):
|
||||
"""
|
||||
Crossover between two parents to produce two children.
|
||||
|
||||
Swaps generation components and storage components between the two parents with a 50% chance.
|
||||
|
||||
:param parent1: First parent individual.
|
||||
:param parent2: Second parent individual.
|
||||
:return: Two child individuals (child1 and child2).
|
||||
"""
|
||||
if random.random() < self.crossover_rate:
|
||||
# Deep copy of the parents to create children
|
||||
child1, child2 = copy.deepcopy(parent1), copy.deepcopy(parent2)
|
||||
|
||||
# Crossover for Generation Components
|
||||
for i in range(len(parent1.individual['Generation Components'])):
|
||||
if random.random() < 0.5:
|
||||
# Swap the entire generation component
|
||||
child1.individual['Generation Components'][i], child2.individual['Generation Components'][i] = (
|
||||
child2.individual['Generation Components'][i],
|
||||
child1.individual['Generation Components'][i]
|
||||
)
|
||||
|
||||
# Crossover for Energy Storage Components
|
||||
for i in range(len(parent1.individual['Energy Storage Components'])):
|
||||
if random.random() < 0.5:
|
||||
# Swap the entire storage component
|
||||
child1.individual['Energy Storage Components'][i], child2.individual['Energy Storage Components'][i] = (
|
||||
child2.individual['Energy Storage Components'][i],
|
||||
child1.individual['Energy Storage Components'][i]
|
||||
)
|
||||
|
||||
return child1, child2
|
||||
else:
|
||||
# If crossover does not happen, return copies of the original parents
|
||||
return copy.deepcopy(parent1), copy.deepcopy(parent2)
|
||||
|
||||
def mutate(self, individual, building):
|
||||
"""
|
||||
Mutates the individual's generation and storage components.
|
||||
|
||||
- `individual`: The individual to mutate (contains generation and storage components).
|
||||
- `building`: Building data that contains constraints such as peak heating load and available space.
|
||||
|
||||
Returns the mutated individual.
|
||||
"""
|
||||
# Mutate Generation Components
|
||||
for generation_component in individual['Generation Components']:
|
||||
if random.random() < self.mutation_rate:
|
||||
if generation_component['nominal_heating_efficiency'] is not None:
|
||||
# Mutate heating capacity
|
||||
generation_component['heating_capacity'] = random.uniform(
|
||||
0, building.heating_peak_load[cte.YEAR][0])
|
||||
if generation_component['nominal_cooling_efficiency'] is not None:
|
||||
# Mutate cooling capacity
|
||||
generation_component['cooling_capacity'] = random.uniform(
|
||||
0, building.cooling_peak_load[cte.YEAR][0])
|
||||
|
||||
# Mutate Storage Components
|
||||
for storage_component in individual['Energy Storage Components']:
|
||||
if random.random() < self.mutation_rate:
|
||||
if storage_component['type'] == f'{cte.THERMAL}_storage':
|
||||
# Mutate the volume of thermal storage
|
||||
max_available_space = 0.01 * building.volume / building.storeys_above_ground
|
||||
storage_component['volume'] = random.uniform(0, max_available_space)
|
||||
if storage_component['heating_coil_capacity'] is not None:
|
||||
storage_component['volume'] = random.uniform(0, building.heating_peak_load[cte.YEAR][0])
|
||||
|
||||
return individual
|
||||
|
||||
def solve_ga(self, building, energy_system):
|
||||
"""
|
||||
Solving GA for a single energy system. Here are the steps:
|
||||
1- Population is initialized using the "initialize_population" method in this class.
|
||||
:param building:
|
||||
:param energy_system:
|
||||
:return:
|
||||
1. Initialize population using the "initialize_population" method in this class.
|
||||
2. Evaluate the initial population using the "score_evaluation" method in the Individual class.
|
||||
3. Sort population based on fitness score.
|
||||
4. Repeat selection, crossover, and mutation for a fixed number of generations.
|
||||
5. Track the best solution found during the optimization process.
|
||||
|
||||
:param building: Building object for the energy system.
|
||||
:param energy_system: Energy system to optimize.
|
||||
:return: Best solution after running the GA.
|
||||
"""
|
||||
energy_system = energy_system
|
||||
best_individuals = []
|
||||
best_solution = None
|
||||
population = self.initialize_population(building, energy_system)
|
||||
for individual in population:
|
||||
# Step 1: Initialize the population
|
||||
self.initialize_population(building, energy_system)
|
||||
# Step 2: Evaluate the initial population
|
||||
for individual in self.population:
|
||||
individual.score_evaluation()
|
||||
print(individual.individual['feasible'])
|
||||
print(individual.individual['fitness_score'])
|
||||
# Step 3: Order population based on fitness scores
|
||||
self.order_population()
|
||||
# Track the best solution
|
||||
self.best_solution = self.population[0]
|
||||
self.list_of_solutions.append(copy.deepcopy(self.best_solution.individual))
|
||||
# Step 4: Run GA for a fixed number of generations
|
||||
for generation in range(self.generations):
|
||||
print(f"Generation {generation}")
|
||||
# Selection (using tournament selection)
|
||||
selected_population = self.tournament_selection()
|
||||
# Create the next generation through crossover and mutation
|
||||
next_population = []
|
||||
for i in range(0, self.population_size, 2):
|
||||
parent1 = selected_population[i]
|
||||
parent2 = selected_population[i + 1] if (i + 1) < len(selected_population) else selected_population[0]
|
||||
# Step 5: Apply crossover
|
||||
child1, child2 = self.crossover(parent1, parent2)
|
||||
# Step 6: Apply mutation
|
||||
self.mutate(child1.individual, building)
|
||||
self.mutate(child2.individual, building)
|
||||
# Step 7: Evaluate the children
|
||||
child1.score_evaluation()
|
||||
child2.score_evaluation()
|
||||
next_population.extend([child1, child2])
|
||||
# Replace old population with the new one
|
||||
self.population = next_population
|
||||
# Step 8: Sort the new population based on fitness
|
||||
self.order_population()
|
||||
# Track the best solution found in this generation
|
||||
if self.population[0].individual['fitness_score'] < self.best_solution.individual['fitness_score']:
|
||||
self.best_solution = self.population[0]
|
||||
self.best_solution_generation = generation
|
||||
# Store the best solution in the list of solutions
|
||||
self.list_of_solutions.append(copy.deepcopy(self.population[0]))
|
||||
print(f"Best solution found in generation {self.best_solution_generation}")
|
||||
print(f"Best solution: {self.best_solution.individual}")
|
||||
|
||||
# Return the best solution after running GA
|
||||
return self.best_solution
|
||||
|
||||
|
||||
|
|
|
@ -11,10 +11,9 @@ import numpy_financial as npf
|
|||
|
||||
|
||||
class Individual:
|
||||
def __init__(self, building, energy_system, optimization_scenario, heating_design_load=None,
|
||||
cooling_design_load=None, available_space=None, dt=None, fuel_price_index=None,
|
||||
electricity_tariff_type='fixed', consumer_price_index=None, interest_rate=None,
|
||||
discount_rate=None, percentage_credit=None, credit_years=None):
|
||||
def __init__(self, building, energy_system, optimization_scenario, heating_design_load=None, cooling_design_load=None,
|
||||
dt=900, fuel_price_index=0.05, electricity_tariff_type='fixed', consumer_price_index=0.04,
|
||||
interest_rate=0.04, discount_rate=0.03, percentage_credit=0, credit_years=15):
|
||||
"""
|
||||
:param building: building object
|
||||
:param energy_system: energy system to be optimized
|
||||
|
@ -22,8 +21,6 @@ class Individual:
|
|||
energy consumption, and both together
|
||||
:param heating_design_load: heating design load in W
|
||||
:param cooling_design_load: cooling design load in W
|
||||
:param available_space: available space for the system. currently it is only used to have a constraint on the
|
||||
maximum size of the storage tank
|
||||
:param dt the time step size used for simulations
|
||||
:param fuel_price_index the price increase index of all fuels. A single value is used for all fuels.
|
||||
:param electricity_tariff_type the electricity tariff type between 'fixed' and 'variable' for economic optimization
|
||||
|
@ -35,7 +32,7 @@ class Individual:
|
|||
self.optimization_scenario = optimization_scenario
|
||||
self.heating_design_load = heating_design_load
|
||||
self.cooling_design_load = cooling_design_load
|
||||
self.available_space = available_space
|
||||
self.available_space = building.volume / building.storeys_above_ground
|
||||
self.dt = dt
|
||||
self.fuel_price_index = fuel_price_index
|
||||
self.electricity_tariff_type = electricity_tariff_type
|
||||
|
@ -145,7 +142,10 @@ class Individual:
|
|||
if storage_component['type'] == f'{cte.THERMAL}_storage':
|
||||
storage_component['volume'] = random.uniform(0, 0.01 * self.available_space)
|
||||
if storage_component['heating_coil_capacity'] is not None:
|
||||
storage_component['heating_coil_capacity'] = random.uniform(0, self.heating_design_load)
|
||||
if self.heating_design_load is not None:
|
||||
storage_component['heating_coil_capacity'] = random.uniform(0, self.heating_design_load)
|
||||
else:
|
||||
storage_component['heating_coil_capacity'] = random.uniform(0, self.building.heating_peak_load[cte.YEAR][0])
|
||||
|
||||
def score_evaluation(self):
|
||||
self.initialization()
|
||||
|
@ -207,7 +207,7 @@ class Individual:
|
|||
tes.volume = self.individual['Energy Storage Components'][0]['volume']
|
||||
tes.height = self.building.average_storey_height - 1
|
||||
tes.heating_coil_capacity = self.individual['Energy Storage Components'][0]['heating_coil_capacity'] \
|
||||
if self.individual['Energy Storage Components'][0]['heating_coil_capacity'] is not None else 0
|
||||
if self.individual['Energy Storage Components'][0]['heating_coil_capacity'] is not None else None
|
||||
heating_demand_joules = self.building.heating_demand[cte.HOUR]
|
||||
heating_peak_load_watts = self.heating_design_load if (self.heating_design_load is not
|
||||
None) else self.building.heating_peak_load[cte.YEAR][0]
|
||||
|
@ -230,7 +230,7 @@ class Individual:
|
|||
tes.volume = self.individual['Energy Storage Components'][0]['volume']
|
||||
tes.height = self.building.average_storey_height - 1
|
||||
tes.heating_coil_capacity = self.individual['Energy Storage Components'][0]['heating_coil_capacity'] \
|
||||
if self.individual['Energy Storage Components'][0]['heating_coil_capacity'] is not None else 0
|
||||
if self.individual['Energy Storage Components'][0]['heating_coil_capacity'] is not None else None
|
||||
dhw_demand_joules = self.building.domestic_hot_water_heat_demand[cte.HOUR]
|
||||
upper_limit_tes = 65
|
||||
outdoor_temperature = self.building.external_temperature[cte.HOUR]
|
||||
|
@ -375,7 +375,7 @@ class Individual:
|
|||
if generation_system.energy_storage_systems is not None:
|
||||
for energy_storage_system in generation_system.energy_storage_systems:
|
||||
if energy_storage_system.type_energy_stored == cte.THERMAL:
|
||||
if energy_storage_system.heating_coil_capacity is None:
|
||||
if energy_storage_system.heating_coil_capacity is not None:
|
||||
investment_cost += float(capital_costs_chapter.item('D306010_storage_tank').initial_investment[0])
|
||||
reposition_cost += float(capital_costs_chapter.item('D306010_storage_tank').reposition[0])
|
||||
lifetime += float(capital_costs_chapter.item('D306010_storage_tank').lifetime)
|
||||
|
|
Loading…
Reference in New Issue
Block a user