Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimisation - Calculate the baseline system #3550

Merged
merged 7 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 64 additions & 1 deletion cea/optimization_new/building.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def __init__(self, identifier, demands_file_path):
self._demand_flow = EnergyFlow()
self._footprint = None
self._location = None
self._initial_connectivity_state = 'stand_alone'
self._stand_alone_supply_system_code = None
self._stand_alone_supply_system_composition = {'primary': [], 'secondary': [], 'tertiary': []}

Expand Down Expand Up @@ -77,6 +78,18 @@ def location(self, new_location):
raise ValueError("Please only assign a point to the building location. "
"Try using the load_building_location method for assigning building locations.")

@property
def initial_connectivity_state(self):
return self._initial_connectivity_state

@initial_connectivity_state.setter
def initial_connectivity_state(self, new_base_connectivity):
if new_base_connectivity in ['stand_alone', 'network'] \
or (new_base_connectivity.startswith('N') and new_base_connectivity[1:].isdigit()):
self._initial_connectivity_state = new_base_connectivity
else:
raise ValueError("Please only assign 'stand_alone' or 'network_i' to the base connectivity of the building.")

def load_demand_profile(self, energy_system_type='DH'):
"""
Load the buildings relevant demand profile, i.e. 'QC_sys_kWh' for the district heating optimisation &
Expand Down Expand Up @@ -142,15 +155,65 @@ def load_base_supply_system(self, file_locator, energy_system_type='DH'):
raise ValueError(f"'{energy_system_type}' is not a valid energy system type. No appropriate "
f"'assemblies'-supply system database could therefore be loaded.")

# fetch the system composition (primary, secondary & tertiary components) for the building
# fetch the supply system composition for the building or it's associated district energy system
self.fetch_supply_system_composition()

return

def fetch_supply_system_composition(self):
"""
Identify if the building is connected to a district heating or cooling network or has a stand-alone system.
Depending on the case, fetch the system composition (primary, secondary & tertiary components) for the building
or complete the system composition for the district energy system the building is connected to.

To establish a default stand-alone supply system in the event of a building's disconnection from a district
energy system, it's assumed that the supply system composition of the respective networks is directly applied to
the building's stand-alone supply system.
"""
# register if the building is connected to a district heating or cooling network or has a stand-alone system
system_details = Building._supply_system_database[Building._supply_system_database['code']
== self._stand_alone_supply_system_code]
energy_system_scale = system_details['scale'].values[0].replace(" ", "").lower()

if energy_system_scale in ['', '-', 'building']:
self.initial_connectivity_state = 'stand_alone'
elif energy_system_scale == 'district':
self.initial_connectivity_state = 'network'

# fetch the system composition (primary, secondary & tertiary components)
# ... for the stand-alone supply system
for category in ['primary', 'secondary', 'tertiary']:
category_components = system_details[category + '_components'].values[0].replace(" ", "")

if category_components == '-':
self._stand_alone_supply_system_composition[category] = []
else:
self._stand_alone_supply_system_composition[category] = category_components.split(',')

# ... or for the network supply system
if self.initial_connectivity_state == 'network':

if system_details['code'].values[0] not in SupplySystemStructure.initial_network_supply_systems.keys():
network_id = f'N{len(SupplySystemStructure.initial_network_supply_systems) + 1001}'
SupplySystemStructure.initial_network_supply_systems[system_details['code'].values[0]] = network_id
SupplySystemStructure.initial_network_supply_systems_composition[network_id] = {'primary': [],
'secondary': [],
'tertiary': []}

for category in ['primary', 'secondary', 'tertiary']:
category_components = system_details[category + '_components'].values[0].replace(" ", "")

if category_components == '-':
SupplySystemStructure.initial_network_supply_systems_composition[network_id][category] = []
else:
SupplySystemStructure.initial_network_supply_systems_composition[network_id][category] = \
category_components.split(',')

else:
network_id = SupplySystemStructure.initial_network_supply_systems[system_details['code'].values[0]]

self.initial_connectivity_state = network_id

return

def calculate_supply_system(self, available_potentials):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ class SupplySystemStructure(object):
_releasable_grid_based_energy_carriers = []
_active_component_classes = []
_full_component_activation_order = ()
initial_network_supply_systems = dict()
initial_network_supply_systems_composition = {'N1001': {'primary': [], 'secondary': [], 'tertiary': []}}

def __init__(self, max_supply_flow=EnergyFlow(), available_potentials=None, user_component_selection=None):
self.maximum_supply = max_supply_flow
Expand Down
33 changes: 31 additions & 2 deletions cea/optimization_new/districtEnergySystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ def evaluate_energy_system(connectivity_vector, district_buildings, energy_poten

return non_dominated_systems, process_memory, optimization_tracker

def evaluate(self, optimization_tracker=None, return_full_des=False):
def evaluate(self, optimization_tracker=None, return_full_des=False, component_selection=None):
"""
Evaluate the possible district energy system configurations (based on buildings, potentials and connectivity) by:

Expand All @@ -169,7 +169,12 @@ def evaluate(self, optimization_tracker=None, return_full_des=False):
self.aggregate_demand()
self.distribute_potentials()

# optimise supply systems for each network
# apply user-designated supply systems for each network ...
if component_selection:
self.apply_designated_supply_systems(component_selection)
return

# ... or find the optimal supply systems for each network
self.generate_optimal_supply_systems()

# aggregate objective functions for all subsystems across the entire district energy system
Expand Down Expand Up @@ -314,6 +319,30 @@ def distribute_potentials(self):

return self.distributed_potentials

def apply_designated_supply_systems(self, designated_supply_system_components):
"""
Apply designated supply systems to the district energy system.

:param dict designated_supply_system_components: component selection for the supply systems for each of the
networks in the district energy system.
"""
for network in self.networks:
# use the SupplySystemStructure methods to dimension each of the system's designated components
system_structure = SupplySystemStructure(max_supply_flow=self.subsystem_demands[network.identifier],
available_potentials=self.distributed_potentials[network.identifier],
user_component_selection=
designated_supply_system_components[network.identifier])
system_structure.build()

# create a SupplySystem-instance and operate the system to meet the yearly demand profile.
designated_supply_system = SupplySystem(system_structure,
system_structure.capacity_indicators,
self.subsystem_demands[network.identifier])
designated_supply_system.evaluate()
self.supply_systems[network.identifier] = designated_supply_system

return self.supply_systems

def generate_optimal_supply_systems(self):
"""
Build and calculate operation for supply systems of each of the subsystems of the district energy system:
Expand Down
78 changes: 63 additions & 15 deletions cea/optimization_new/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def __init__(self, config, locator):
self.weather = self._load_weather(locator)
self.buildings = []
self.energy_potentials = []
self.initial_energy_system = None
self.optimal_energy_systems = []

self._initialise_domain_descriptor_classes()
Expand Down Expand Up @@ -139,11 +140,12 @@ def optimize_domain(self):
print("\nInitializing domain:")
self._initialize_energy_system_descriptor_classes()

# Calculate base-case supply systems for all buildings
print("\nCalculating operation of buildings' base-supply systems...")
# Calculate base-case supply systems for all buildings (i.e. initial state as per the input editor)
print("\nCalculating operation of districts' initial supply systems...")
building_energy_potentials = Building.distribute_building_potentials(self.energy_potentials, self.buildings)
[building.calculate_supply_system(building_energy_potentials[building.identifier])
for building in self.buildings]
self.model_initial_energy_system()

# Optimise district energy systems
print("Starting optimisation of district energy systems (i.e. networks + supply systems)...")
Expand Down Expand Up @@ -226,6 +228,25 @@ def optimize_domain(self):

return self.optimal_energy_systems

def model_initial_energy_system(self):
"""
Model the energy system currently installed in the domain, i.e.:
- reconstruct a network linking all buildings that are currently connected to a district energy system
- determine the supply system for each network and each of the stand-alone buildings
"""
# Determine buildings connected to a district energy system
connection_list = [Connection(0, building.identifier) if building.initial_connectivity_state == 'stand_alone'
else Connection(int(building.initial_connectivity_state[1:]) - 1000, building.identifier)
for building in self.buildings]

# Create a network of connected buildings
self.initial_energy_system = DistrictEnergySystem(ConnectivityVector(connection_list), self.buildings,
self.energy_potentials)
self.initial_energy_system.evaluate(component_selection=
SupplySystemStructure.initial_network_supply_systems_composition)

return self.initial_energy_system

def _select_final_optimal_systems(self, last_population, min_final_selection_size):
"""
Each solution in the last population of the 'outer' optimisation algorithm consists of:
Expand Down Expand Up @@ -268,15 +289,14 @@ def generate_result_files(self):
of .xlsx-files to summarise the most important information about the near-pareto-optimal district energy systems
and their corresponding supply systems.
"""
# save the current energy system's network layouts and supply systems
self._write_network_layouts_to_geojson(self.initial_energy_system, 'current_DES')
self._write_supply_systems_to_csv(self.initial_energy_system, 'current_DES')

# save the results for near-pareto-optimal district energy systems one-by-one
for des in self.optimal_energy_systems:
# first save network layouts
for ntw_ind, network in enumerate(des.networks):
network_layout_file = self.locator.get_new_optimization_optimal_network_layout_file(des.identifier,
network.identifier)
network_layout = pd.concat([network.network_nodes, network.network_edges]).drop(['coordinates'], axis=1)
network_layout = network_layout.to_crs(get_geographic_coordinate_system())
network_layout.to_file(network_layout_file, driver='GeoJSON')
self._write_network_layouts_to_geojson(des)

# then save all information about the selected supply systems
self._write_supply_systems_to_csv(des)
Expand All @@ -287,13 +307,41 @@ def generate_result_files(self):

return

def _write_supply_systems_to_csv(self, district_energy_system):
def _write_network_layouts_to_geojson(self, district_energy_system, system_name=None):
"""
Writes the network layout of a given district energy system into a geojson file.

:param network: selected network
:type network: Network-class object
:param system_name: name of the district energy system
:type system_name: str
"""
if not system_name:
system_name = district_energy_system.identifier

for ntw_ind, network in enumerate(district_energy_system.networks):
network_layout_file = self.locator.get_new_optimization_optimal_network_layout_file(system_name,
network.identifier)
network_layout = pd.concat([network.network_nodes, network.network_edges]).drop(['coordinates'], axis=1)
network_layout = network_layout.to_crs(get_geographic_coordinate_system())
network_layout.to_file(network_layout_file, driver='GeoJSON')

return

def _write_supply_systems_to_csv(self, district_energy_system, system_name=None):
"""
Writes information on supply systems of subsystems of a near-pareto-optimal district energy system into
csv files. Information on each of the supply systems is written in a separate file.
Writes information on supply systems of subsystems of a given district energy system into csv files. Information
on each of the supply systems is written in a separate file.

:param district_energy_system: selected district energy system
:type district_energy_system: DistrictEnergySystem-class object
:param system_name: name of the district energy system
:type system_name: str
"""
# Create general values
des_id = district_energy_system.identifier
if not system_name:
system_name = district_energy_system.identifier

supply_system_summary = {'Supply_System': [],
'Heat_Emissions_kWh': [],
'System_Energy_Demand_kWh': [],
Expand All @@ -308,7 +356,7 @@ def _write_supply_systems_to_csv(self, district_energy_system):
supply_system = building.stand_alone_supply_system

# Summarise structure of the supply system & print to file
building_file = self.locator.get_new_optimization_optimal_supply_system_file(des_id, supply_system_id)
building_file = self.locator.get_new_optimization_optimal_supply_system_file(system_name, supply_system_id)
Domain._write_system_structure(building_file, supply_system)

# Calculate supply system fitness-values and add them to the summary of all supply systems
Expand All @@ -318,7 +366,7 @@ def _write_supply_systems_to_csv(self, district_energy_system):
# FOR NETWORKS
for network_id, supply_system in district_energy_system.supply_systems.items():
# Summarise structure of the supply system & print to file
network_file = self.locator.get_new_optimization_optimal_supply_system_file(des_id, network_id)
network_file = self.locator.get_new_optimization_optimal_supply_system_file(system_name, network_id)
Domain._write_system_structure(network_file, supply_system)

# Calculate supply system fitness-values and add them to the summary of all supply systems
Expand All @@ -332,7 +380,7 @@ def _write_supply_systems_to_csv(self, district_energy_system):
supply_system_summary['GHG_Emissions_kgCO2'] += [sum(supply_system_summary['GHG_Emissions_kgCO2'])]
supply_system_summary['Cost_USD'] += [sum(supply_system_summary['Cost_USD'])]

summary_file = self.locator.get_new_optimization_optimal_supply_systems_summary_file(des_id)
summary_file = self.locator.get_new_optimization_optimal_supply_systems_summary_file(system_name)
pd.DataFrame(supply_system_summary).to_csv(summary_file, index=False)

return
Expand Down