Skip to content

Commit

Permalink
Merge pull request #3550 from architecture-building-systems/optimisat…
Browse files Browse the repository at this point in the history
…ion-baseline-comparison

Optimisation - Calculate the baseline system
  • Loading branch information
ShiZhongming authored May 7, 2024
2 parents 45e5b8a + 0d4c7db commit 8cfb4aa
Show file tree
Hide file tree
Showing 4 changed files with 160 additions and 18 deletions.
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

0 comments on commit 8cfb4aa

Please sign in to comment.