From b585e6cac2f216f9e9d38898f9ceda1fe7266f0d Mon Sep 17 00:00:00 2001 From: Pilar Date: Thu, 15 Apr 2021 17:28:07 -0400 Subject: [PATCH] this class has been extracted from the monthly_energy_balance project and a complete project to manage the interaction with sra has been created (this one) --- cache/.gitignore | 4 + helper/helper.py | 37 +++++++ simplified_radiosity_algorithm.py | 157 ++++++++++++++++++++++++++++++ tmp/.gitignore | 4 + 4 files changed, 202 insertions(+) create mode 100644 cache/.gitignore create mode 100644 helper/helper.py create mode 100644 simplified_radiosity_algorithm.py create mode 100644 tmp/.gitignore diff --git a/cache/.gitignore b/cache/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/cache/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file diff --git a/helper/helper.py b/helper/helper.py new file mode 100644 index 0000000..9565f84 --- /dev/null +++ b/helper/helper.py @@ -0,0 +1,37 @@ +""" +Helper class for SRA +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import pandas as pd +import numpy as np +import helpers.constants as cte + + +class Helper: + def __init__(self): + self._month_hour = None + + @property + def month_hour(self): + """ + returns a DataFrame that has x values of the month number (January = 1, February = 2...), + being x the number of hours of the corresponding month + :return: DataFrame(int) + """ + array = [] + for i in range(0, 12): + total_hours = cte.days_of_month[i] * 24 + array = np.concatenate((array, np.full(total_hours, i + 1))) + self._month_hour = pd.DataFrame(array, columns=['month']) + return self._month_hour + + def get_mean_values(self, values): + out = None + if values is not None: + if 'month' not in values.columns: + values = pd.concat([self.month_hour, pd.DataFrame(values)], axis=1) + out = values.groupby('month', as_index=False).mean() + del out['month'] + return out diff --git a/simplified_radiosity_algorithm.py b/simplified_radiosity_algorithm.py new file mode 100644 index 0000000..e5d4f5f --- /dev/null +++ b/simplified_radiosity_algorithm.py @@ -0,0 +1,157 @@ +""" +SimplifiedRadiosityAlgorithm manage the interaction with the third party software CitySim_SRA +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar_monsalvete@concordia.ca +""" + +import os +import shutil +from pathlib import Path +import pandas as pd +import subprocess +from subprocess import SubprocessError, TimeoutExpired, CalledProcessError + +from exports.exports_factory import ExportsFactory +from imports.weather_factory import WeatherFactory +from helper.helper import Helper as mv + + +class SimplifiedRadiosityAlgorithm: + """ + SimplifiedRadiosityAlgorithm factory class + """ + # todo: define executable as configurable parameter + # _executable = 'citysim_sra' # for linux + _executable = 'shortwave_integer' # for windows + + def __init__(self, city, sra_working_path, weather_file_name): + self._city = city + self._sra_working_path = sra_working_path + self._weather_file_name = weather_file_name + self._sra_in_file_name = self._city.name + '_sra.xml' + self._sra_out_file_name = self._city.name + '_sra_SW.out' + + self._tmp_path = (sra_working_path / 'tmp').resolve() + Path(self._tmp_path).mkdir(parents=True, exist_ok=True) + # Ensure tmp file is empty + for child in self._tmp_path.glob('*'): + if child.is_file(): + child.unlink() + + self._cache_path = (sra_working_path / 'cache').resolve() + Path(self._cache_path).mkdir(parents=True, exist_ok=True) + + self._results = None + self._radiation = [] + + def call_sra(self, keep_files=False): + """ + creates required input files and calls the software + """ + self._create_cli_file() + ExportsFactory('sra', self._city, self._tmp_path).export() + try: + completed = subprocess.run([self._executable, str(Path(self._tmp_path / self._sra_in_file_name).resolve())]) + except (SubprocessError, TimeoutExpired, CalledProcessError) as error: + raise Exception(error) + file = (self._tmp_path / self._sra_out_file_name).resolve() + new_path = (self._cache_path / self._sra_out_file_name).resolve() + try: + shutil.move(str(file), str(new_path)) + except Exception: + raise Exception('No SRA output file found') + if not keep_files: + os.remove(Path(self._tmp_path / f'{self._city.name}_sra.xml').resolve()) + return completed + + @property + def results(self): + if self._results is None: + try: + path = (self._cache_path / self._sra_out_file_name).resolve() + self._results = pd.read_csv(path, sep='\s+', header=0) + except Exception: + raise Exception('No SRA output file found') + return self._results + + @property + def radiation(self) -> []: + if len(self._radiation) == 0: + id_building = '' + header_building = [] + for column in self.results.columns.values: + if id_building != column.split(':')[1]: + id_building = column.split(':')[1] + if len(header_building) > 0: + self._radiation.append(pd.concat([mv().month_hour, self.results[header_building]], + axis=1)) + header_building = [column] + else: + header_building.append(column) + self._radiation.append(pd.concat([mv().month_hour, self.results[header_building]], axis=1)) + return self._radiation + + def set_irradiance_surfaces(self, city, mode=0, building_name=None): + """ + saves in building surfaces the correspondent irradiance at different time-scales depending on the mode + if building is None, it saves all buildings' surfaces in file, if building is specified, it saves only that + specific building values + mode = 0, set only monthly values + mode = 1, set only hourly values + mode = 2, set both + :parameter city: city + :parameter mode: str (time-scale definition) + :parameter building_name: str + :return: none + """ + for radiation in self.radiation: + city_object_name = radiation.columns.values.tolist()[1].split(':')[1] + if building_name is not None: + if city_object_name != building_name: + # todo: in case there is a specific building name defined, then assign values only to that specific building + continue + + building = city.city_object(city_object_name) + for column in radiation.columns.values: + if column == 'month': + continue + header_id = column + surface_id = header_id.split(':')[2] + surface = building.surface_by_id(surface_id) + new_value = pd.DataFrame(radiation[[header_id]].to_numpy(), columns=['sra']) + if mode == 0 or mode == 2: + month_new_value = mv().get_mean_values(new_value) + if 'month' not in surface.global_irradiance: + surface.global_irradiance['month'] = month_new_value + else: + pd.concat([surface.global_irradiance['month'], month_new_value], axis=1) + if mode == 1 or mode == 2: + if 'hour' not in surface.global_irradiance: + surface.global_irradiance['hour'] = new_value + else: + pd.concat([surface.global_irradiance['hour'], new_value], axis=1) + + def _create_cli_file(self): + file = self._city.climate_file + days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + WeatherFactory('epw', self._city, file_name=self._weather_file_name).enrich() + content = self._city.name + '\n' + content += str(self._city.latitude) + ',' + str(self._city.longitude) + ',0.0,' + str(self._city.time_zone) + '\n' + content += '\ndm m h G_Dh G_Bn\n' + total_days = 0 + for month in range(1, 13): + if month > 1: + total_days += days_in_month[month - 2] + for day in range(1, days_in_month[month-1]+1): + for hour in range(1, 25): + if month == 1: + i = 24 * (day-1) + hour - 1 + else: + i = (total_days+day-1)*24 + hour - 1 + representative_building = self._city.buildings[0] + content += str(day) + ' ' + str(month) + ' ' + str(hour) + ' ' \ + + str(representative_building.global_horizontal['hour'].epw[i]) + ' ' \ + + str(representative_building.beam['hour'].epw[i]) + '\n' + with open(file, "w") as file: + file.write(content) + return diff --git a/tmp/.gitignore b/tmp/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/tmp/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file