feat: code documentation added

This commit is contained in:
Saeed Ranjbar 2024-12-05 16:13:03 +01:00
parent a32bea0246
commit 8ccf819ed2
5 changed files with 226 additions and 21 deletions

32
README.md Normal file
View File

@ -0,0 +1,32 @@
# CERC PV Workflow
## Introduction
The CERC PV Workflow is designed to size and model different types of PV systems
## System Requirements
- **`Software Requirements`**:
Operating System: Windows, macOS, or Linux
Python Version: 3.8 or higher
- **`Python Dependencies`**:
The application requires several Python libraries. These can be installed using the following command:
```bash
pip install -r requirements.txt
```
Ensure you have the required database drivers installed (e.g., psycopg2 for PostgreSQL, mysql-connector-python for MySQL).
## Application Setup
- **`Clone or Download the Project`**:
Start by downloading or cloning the project repository into your local system:
```bash
git clone https://ngci.encs.concordia.ca/gitea/s_ranjbar/pv_workflow.git
```
## Workflow Applications
- **`Solar Angles Calculation`**:
- **`Solar Radiation on Tilted Surfaces`**:
- **`PV system assessment`**:

View File

@ -77,7 +77,8 @@ for building in city.buildings:
system_catalogue_handler=None, system_catalogue_handler=None,
roof_percentage_coverage=0.75, roof_percentage_coverage=0.75,
facade_coverage_percentage=0, facade_coverage_percentage=0,
csv_output=False, csv_output=True,
output_path=pv_assessment_path).enrich() output_path=pv_assessment_path).enrich()

View File

@ -7,20 +7,51 @@ from hub.helpers.monthly_values import MonthlyValues
class PvSystemAssessment: class PvSystemAssessment:
"""
A class to assess and size photovoltaic (PV) systems for buildings.
This class provides methods for sizing PV systems on rooftops, calculating their energy output,
assigning system components from a catalog, and evaluating grid-tied systems. It also allows for
CSV output of system results.
Attributes:
building (Building): The building for which the PV system is being assessed.
pv_system (PvSystem): The photovoltaic system used in the assessment.
battery (EnergyStorageSystem): The energy storage system associated with the PV system, if any.
tilt_angle (float): The tilt angle of the PV system.
solar_angles (DataFrame): Solar angles used in simulation (e.g., solar altitude, azimuth).
pv_installation_type (str): Type of installation (e.g., rooftop, facade).
simulation_model_type (str): Type of simulation model (e.g., 'explicit').
module_model_name (str): Model name of the PV module being used.
inverter_efficiency (float): Efficiency of the inverter.
system_catalogue_handler (str): Handler for the PV system catalog.
roof_percentage_coverage (float): Percentage of the roof to be covered by PV panels.
facade_coverage_percentage (float): Percentage of the facade to be covered by PV panels.
csv_output (bool): Whether or not to generate CSV output of the results.
output_path (str): Path to store the output CSV file.
results (dict): Dictionary to store the results of the system assessment.
"""
def __init__(self, building=None, pv_system=None, battery=None, tilt_angle=None, solar_angles=None, def __init__(self, building=None, pv_system=None, battery=None, tilt_angle=None, solar_angles=None,
pv_installation_type=None, simulation_model_type=None, module_model_name=None, pv_installation_type=None, simulation_model_type=None, module_model_name=None,
inverter_efficiency=None, system_catalogue_handler=None, roof_percentage_coverage=None, inverter_efficiency=None, system_catalogue_handler=None, roof_percentage_coverage=None,
facade_coverage_percentage=None, csv_output=False, output_path=None): facade_coverage_percentage=None, csv_output=False, output_path=None):
""" """
:param building: Initializes the PvSystemAssessment instance with the provided parameters or defaults.
:param tilt_angle:
:param solar_angles: :param building: The building object for which the PV system is assessed.
:param simulation_model_type: :param pv_system: The photovoltaic system (optional, defaults to building's existing PV system).
:param module_model_name: :param battery: The battery storage system (optional, defaults to building's associated storage).
:param inverter_efficiency: :param tilt_angle: Tilt angle of the PV system.
:param system_catalogue_handler: :param solar_angles: DataFrame containing solar angles (e.g., solar altitude, azimuth).
:param roof_percentage_coverage: :param pv_installation_type: Type of installation ('rooftop', 'facade', etc.).
:param facade_coverage_percentage: :param simulation_model_type: Type of simulation model ('explicit').
:param module_model_name: Model name of the PV module.
:param inverter_efficiency: Efficiency of the inverter.
:param system_catalogue_handler: Catalog handler to select PV module from a catalog.
:param roof_percentage_coverage: Percentage of roof to cover with PV modules.
:param facade_coverage_percentage: Percentage of facade to cover with PV modules.
:param csv_output: Boolean indicating whether to generate CSV output.
:param output_path: Path for saving the CSV output.
""" """
self.building = building self.building = building
self.tilt_angle = tilt_angle self.tilt_angle = tilt_angle
@ -56,6 +87,16 @@ class PvSystemAssessment:
@staticmethod @staticmethod
def explicit_model(pv_system, inverter_efficiency, number_of_panels, irradiance, outdoor_temperature): def explicit_model(pv_system, inverter_efficiency, number_of_panels, irradiance, outdoor_temperature):
"""
Calculates the PV system's energy output using the explicit model.
:param pv_system: The photovoltaic system.
:param inverter_efficiency: Efficiency of the inverter.
:param number_of_panels: Number of PV panels in the system.
:param irradiance: Hourly irradiance values.
:param outdoor_temperature: Hourly outdoor temperature values.
:return: List of energy outputs for each hour.
"""
inverter_efficiency = inverter_efficiency inverter_efficiency = inverter_efficiency
stc_power = float(pv_system.standard_test_condition_maximum_power) stc_power = float(pv_system.standard_test_condition_maximum_power)
stc_irradiance = float(pv_system.standard_test_condition_radiation) stc_irradiance = float(pv_system.standard_test_condition_radiation)
@ -78,6 +119,11 @@ class PvSystemAssessment:
return pv_output return pv_output
def rooftop_sizing(self): def rooftop_sizing(self):
"""
Sizing of the rooftop PV system.
:return: Number of panels per row and number of rows to be installed.
"""
pv_system = self.pv_system pv_system = self.pv_system
if self.module_model_name is not None: if self.module_model_name is not None:
self.system_assignation() self.system_assignation()
@ -109,6 +155,11 @@ class PvSystemAssessment:
return panels_per_row, number_of_rows return panels_per_row, number_of_rows
def system_assignation(self): def system_assignation(self):
"""
Assigns a PV system from the energy systems catalog based on the module model name.
:raises ValueError: If no PV module with the provided model name exists in the catalog.
"""
generation_units_catalogue = EnergySystemsCatalogFactory(self.system_catalogue_handler).catalog generation_units_catalogue = EnergySystemsCatalogFactory(self.system_catalogue_handler).catalog
catalog_pv_generation_equipments = [component for component in catalog_pv_generation_equipments = [component for component in
generation_units_catalogue.entries('generation_equipments') if generation_units_catalogue.entries('generation_equipments') if
@ -133,6 +184,13 @@ class PvSystemAssessment:
energy_system.generation_systems[idx] = new_system energy_system.generation_systems[idx] = new_system
def grid_tied_system(self): def grid_tied_system(self):
"""
Evaluates the performance and sizing of a grid-tied PV system.
This method assesses energy production, storage, and demand matching for a grid-tied PV system.
:return: The evaluation results as a dictionary.
"""
building_hourly_electricity_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in building_hourly_electricity_demand = [demand / cte.WATTS_HOUR_TO_JULES for demand in
HourlyElectricityDemand(self.building).calculate()] HourlyElectricityDemand(self.building).calculate()]
rooftop_pv_output = [0] * 8760 rooftop_pv_output = [0] * 8760

View File

@ -1,3 +1,9 @@
"""
solar_calculator module
SPDX-License-Identifier: LGPL-3.0-or-later
Copyright © 2022 Concordia CERC group
Project Coder: Saeed Ranjbar saeed.ranjbar@cerc.com
"""
import math import math
import pandas as pd import pandas as pd
from datetime import datetime from datetime import datetime
@ -6,20 +12,21 @@ from hub.helpers.monthly_values import MonthlyValues
class SolarCalculator: class SolarCalculator:
"""
SolarCalculator class performs solar angle and irradiance calculations for a given city and tilt angles
"""
def __init__(self, city, tilt_angle, surface_azimuth_angle, standard_meridian=-75, def __init__(self, city, tilt_angle, surface_azimuth_angle, standard_meridian=-75,
solar_constant=1366.1, maximum_clearness_index=1, min_cos_zenith=0.065, maximum_zenith_angle=87): solar_constant=1366.1, maximum_clearness_index=1, min_cos_zenith=0.065, maximum_zenith_angle=87):
""" """
A class to calculate the solar angles and solar irradiance on a tilted surface in the City Initialize SolarCalculator with city and solar panel configurations.
:param city: An object from the City class -> City :param city: City object containing latitude and longitude
:param tilt_angle: tilt angle of surface -> float :param tilt_angle: Tilt angle of the solar panel in degrees
:param surface_azimuth_angle: The orientation of the surface. 0 is North -> float :param surface_azimuth_angle: Azimuth angle of the solar panel in degrees
:param standard_meridian: A standard meridian is the meridian whose mean solar time is the basis of the time of day :param standard_meridian: Standard meridian for time zone correction
observed in a time zone -> float :param solar_constant: Extraterrestrial radiation constant in W/m2
:param solar_constant: The amount of energy received by a given area one astronomical unit away from the Sun. It is :param maximum_clearness_index: Maximum clearness index for calculation
constant and must not be changed :param min_cos_zenith: Minimum cosine zenith value
:param maximum_clearness_index: This is used to calculate the diffuse fraction of the solar irradiance -> float :param maximum_zenith_angle: Maximum allowable zenith angle in degrees
:param min_cos_zenith: This is needed to avoid unrealistic values in tilted irradiance calculations -> float
:param maximum_zenith_angle: This is needed to avoid negative values in tilted irradiance calculations -> float
""" """
self.city = city self.city = city
self.location_latitude = city.latitude self.location_latitude = city.latitude
@ -52,6 +59,12 @@ class SolarCalculator:
self.day_of_year = self.solar_angles.index.dayofyear self.day_of_year = self.solar_angles.index.dayofyear
def solar_time(self, datetime_val, day_of_year): def solar_time(self, datetime_val, day_of_year):
"""
Calculate apparent solar time for a given datetime and day of the year.
:param datetime_val: Input datetime value
:param day_of_year: Day of the year (1-365)
:return: Apparent solar time (datetime)
"""
b = (day_of_year - 81) * 2 * math.pi / 364 b = (day_of_year - 81) * 2 * math.pi / 364
eot = 9.87 * math.sin(2 * b) - 7.53 * math.cos(b) - 1.5 * math.sin(b) eot = 9.87 * math.sin(2 * b) - 7.53 * math.cos(b) - 1.5 * math.sin(b)
self.eot.append(eot) self.eot.append(eot)
@ -79,18 +92,42 @@ class SolarCalculator:
return ast_time return ast_time
def declination_angle(self, day_of_year): def declination_angle(self, day_of_year):
"""
Calculate the solar declination angle for a given day of the year.
:param day_of_year: Day of the year (1-365)
:return: Declination angle in radians
"""
declination = 23.45 * math.sin(math.radians(360 / 365 * (284 + day_of_year))) declination = 23.45 * math.sin(math.radians(360 / 365 * (284 + day_of_year)))
declination_radian = math.radians(declination) declination_radian = math.radians(declination)
self.declinations.append(declination) self.declinations.append(declination)
return declination_radian return declination_radian
def hour_angle(self, ast_time): def hour_angle(self, ast_time):
"""
Calculate the hour angle for a given apparent solar time (AST).
The hour angle is a measure of time since solar noon in degrees or radians,
where solar noon corresponds to 0°. It is negative in the morning and positive in the afternoon.
:param ast_time: Apparent solar time as a datetime object.
:return: Hour angle in radians.
"""
hour_angle = ((ast_time.hour * 60 + ast_time.minute) - 720) / 4 hour_angle = ((ast_time.hour * 60 + ast_time.minute) - 720) / 4
hour_angle_radian = math.radians(hour_angle) hour_angle_radian = math.radians(hour_angle)
self.hour_angles.append(hour_angle) self.hour_angles.append(hour_angle)
return hour_angle_radian return hour_angle_radian
def solar_altitude(self, declination_radian, hour_angle_radian): def solar_altitude(self, declination_radian, hour_angle_radian):
"""
Calculate the solar altitude angle in radians for a given declination and hour angle.
The solar altitude angle is the angle between the sun's rays and the horizontal plane
at a given location and time. It indicates the sun's height in the sky.
:param declination_radian: Solar declination angle in radians.
:param hour_angle_radian: Hour angle in radians.
:return: Solar altitude angle in radians.
"""
solar_altitude_radians = math.asin(math.cos(self.location_latitude_rad) * math.cos(declination_radian) * solar_altitude_radians = math.asin(math.cos(self.location_latitude_rad) * math.cos(declination_radian) *
math.cos(hour_angle_radian) + math.sin(self.location_latitude_rad) * math.cos(hour_angle_radian) + math.sin(self.location_latitude_rad) *
math.sin(declination_radian)) math.sin(declination_radian))
@ -99,6 +136,16 @@ class SolarCalculator:
return solar_altitude_radians return solar_altitude_radians
def zenith(self, solar_altitude_radians): def zenith(self, solar_altitude_radians):
"""
Calculate the solar zenith angle in radians from the solar altitude angle.
The solar zenith angle is the angle between the vertical direction
(directly overhead) and the line to the sun. It is complementary to
the solar altitude angle.
:param solar_altitude_radians: Solar altitude angle in radians.
:return: Solar zenith angle in radians.
"""
solar_altitude = math.degrees(solar_altitude_radians) solar_altitude = math.degrees(solar_altitude_radians)
zenith_degree = 90 - solar_altitude zenith_degree = 90 - solar_altitude
zenith_radian = math.radians(zenith_degree) zenith_radian = math.radians(zenith_degree)
@ -106,6 +153,21 @@ class SolarCalculator:
return zenith_radian return zenith_radian
def solar_azimuth_analytical(self, hourangle, declination, zenith): def solar_azimuth_analytical(self, hourangle, declination, zenith):
"""
Calculate the solar azimuth angle analytically in radians.
The solar azimuth angle represents the sun's position relative to true north,
measured clockwise. The method uses the hour angle, solar declination, and
solar zenith to compute the azimuth.
:param hourangle: Hour angle of the sun in radians, indicating its position
relative to the solar noon.
:param declination: Solar declination angle in radians, which is the angle
between the sun's rays and the plane of Earth's equator.
:param zenith: Solar zenith angle in radians, the angle between the vertical
direction and the sun's position.
:return: Solar azimuth angle in radians.
"""
numer = (math.cos(zenith) * math.sin(self.location_latitude_rad) - math.sin(declination)) numer = (math.cos(zenith) * math.sin(self.location_latitude_rad) - math.sin(declination))
denom = (math.sin(zenith) * math.cos(self.location_latitude_rad)) denom = (math.sin(zenith) * math.cos(self.location_latitude_rad))
if math.isclose(denom, 0.0, abs_tol=1e-8): if math.isclose(denom, 0.0, abs_tol=1e-8):
@ -122,6 +184,19 @@ class SolarCalculator:
return solar_azimuth_radians return solar_azimuth_radians
def incident_angle(self, solar_altitude_radians, solar_azimuth_radians): def incident_angle(self, solar_altitude_radians, solar_azimuth_radians):
"""
Calculate the solar incident angle in radians.
The incident angle represents the angle between the solar rays and the
normal to a tilted surface. It is a critical parameter for evaluating
the performance of solar panels.
:param solar_altitude_radians: Solar altitude angle in radians, indicating
the sun's position above the horizon.
:param solar_azimuth_radians: Solar azimuth angle in radians, specifying
the sun's position relative to true north.
:return: Solar incident angle in radians.
"""
incident_radian = math.acos(math.cos(solar_altitude_radians) * incident_radian = math.acos(math.cos(solar_altitude_radians) *
math.cos(abs(solar_azimuth_radians - self.surface_azimuth_rad)) * math.cos(abs(solar_azimuth_radians - self.surface_azimuth_rad)) *
math.sin(self.tilt_angle_rad) + math.sin(solar_altitude_radians) * math.sin(self.tilt_angle_rad) + math.sin(solar_altitude_radians) *
@ -131,6 +206,15 @@ class SolarCalculator:
return incident_radian return incident_radian
def dni_extra(self, day_of_year, zenith_radian): def dni_extra(self, day_of_year, zenith_radian):
"""
Calculate extraterrestrial DNI and horizontal irradiance.
:param day_of_year: Day of the year (1365/366).
:param zenith_radian: Solar zenith angle in radians.
:return: Tuple (i_on, i_oh) where:
- i_on: Extraterrestrial normal irradiance [W/].
- i_oh: Extraterrestrial horizontal irradiance [W/].
"""
i_on = self.solar_constant * (1 + 0.033 * math.cos(math.radians(360 * day_of_year / 365))) i_on = self.solar_constant * (1 + 0.033 * math.cos(math.radians(360 * day_of_year / 365)))
i_oh = i_on * max(math.cos(zenith_radian), self.min_cos_zenith) i_oh = i_on * max(math.cos(zenith_radian), self.min_cos_zenith)
self.i_on.append(i_on) self.i_on.append(i_on)
@ -138,12 +222,26 @@ class SolarCalculator:
return i_on, i_oh return i_on, i_oh
def clearness_index(self, ghi, i_oh): def clearness_index(self, ghi, i_oh):
"""
Calculate the clearness index (Kt).
:param ghi: Global horizontal irradiance [W/].
:param i_oh: Extraterrestrial horizontal irradiance [W/].
:return: Clearness index (Kt).
"""
k_t = ghi / i_oh k_t = ghi / i_oh
k_t = max(0, k_t) k_t = max(0, k_t)
k_t = min(self.maximum_clearness_index, k_t) k_t = min(self.maximum_clearness_index, k_t)
return k_t return k_t
def diffuse_fraction(self, k_t, zenith): def diffuse_fraction(self, k_t, zenith):
"""
Estimate the diffuse fraction of irradiance.
:param k_t: Clearness index (Kt).
:param zenith: Solar zenith angle in degrees.
:return: Diffuse fraction.
"""
if k_t <= 0.22: if k_t <= 0.22:
fraction_diffuse = 1 - 0.09 * k_t fraction_diffuse = 1 - 0.09 * k_t
elif k_t <= 0.8: elif k_t <= 0.8:
@ -155,6 +253,14 @@ class SolarCalculator:
return fraction_diffuse return fraction_diffuse
def radiation_components_horizontal(self, ghi, fraction_diffuse, zenith): def radiation_components_horizontal(self, ghi, fraction_diffuse, zenith):
"""
Compute diffuse and beam components of horizontal radiation.
:param ghi: Global horizontal irradiance [W/].
:param fraction_diffuse: Diffuse fraction.
:param zenith: Solar zenith angle in degrees.
:return: Tuple (diffuse_horizontal, dni).
"""
diffuse_horizontal = ghi * fraction_diffuse diffuse_horizontal = ghi * fraction_diffuse
dni = (ghi - diffuse_horizontal) / math.cos(math.radians(zenith)) dni = (ghi - diffuse_horizontal) / math.cos(math.radians(zenith))
if zenith > self.maximum_zenith_angle or dni < 0: if zenith > self.maximum_zenith_angle or dni < 0:
@ -162,6 +268,14 @@ class SolarCalculator:
return diffuse_horizontal, dni return diffuse_horizontal, dni
def radiation_components_tilted(self, diffuse_horizontal, dni, incident_angle): def radiation_components_tilted(self, diffuse_horizontal, dni, incident_angle):
"""
Compute total radiation on a tilted surface.
:param diffuse_horizontal: Diffuse horizontal irradiance [W/].
:param dni: Direct normal irradiance [W/].
:param incident_angle: Solar incident angle in degrees.
:return: Total radiation on tilted surface [W/].
"""
beam_tilted = dni * math.cos(math.radians(incident_angle)) beam_tilted = dni * math.cos(math.radians(incident_angle))
beam_tilted = max(beam_tilted, 0) beam_tilted = max(beam_tilted, 0)
diffuse_tilted = diffuse_horizontal * ((1 + math.cos(math.radians(self.tilt_angle))) / 2) diffuse_tilted = diffuse_horizontal * ((1 + math.cos(math.radians(self.tilt_angle))) / 2)

BIN
requirements.txt Normal file

Binary file not shown.