feat: code documentation added
This commit is contained in:
parent
a32bea0246
commit
8ccf819ed2
32
README.md
Normal file
32
README.md
Normal 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`**:
|
||||
|
||||
|
@ -77,7 +77,8 @@ for building in city.buildings:
|
||||
system_catalogue_handler=None,
|
||||
roof_percentage_coverage=0.75,
|
||||
facade_coverage_percentage=0,
|
||||
csv_output=False,
|
||||
csv_output=True,
|
||||
output_path=pv_assessment_path).enrich()
|
||||
|
||||
|
||||
|
||||
|
@ -7,20 +7,51 @@ from hub.helpers.monthly_values import MonthlyValues
|
||||
|
||||
|
||||
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,
|
||||
pv_installation_type=None, simulation_model_type=None, module_model_name=None,
|
||||
inverter_efficiency=None, system_catalogue_handler=None, roof_percentage_coverage=None,
|
||||
facade_coverage_percentage=None, csv_output=False, output_path=None):
|
||||
"""
|
||||
:param building:
|
||||
:param tilt_angle:
|
||||
:param solar_angles:
|
||||
:param simulation_model_type:
|
||||
:param module_model_name:
|
||||
:param inverter_efficiency:
|
||||
:param system_catalogue_handler:
|
||||
:param roof_percentage_coverage:
|
||||
:param facade_coverage_percentage:
|
||||
Initializes the PvSystemAssessment instance with the provided parameters or defaults.
|
||||
|
||||
:param building: The building object for which the PV system is assessed.
|
||||
:param pv_system: The photovoltaic system (optional, defaults to building's existing PV system).
|
||||
:param battery: The battery storage system (optional, defaults to building's associated storage).
|
||||
:param tilt_angle: Tilt angle of the PV system.
|
||||
:param solar_angles: DataFrame containing solar angles (e.g., solar altitude, azimuth).
|
||||
:param pv_installation_type: Type of installation ('rooftop', 'facade', etc.).
|
||||
: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.tilt_angle = tilt_angle
|
||||
@ -56,6 +87,16 @@ class PvSystemAssessment:
|
||||
|
||||
@staticmethod
|
||||
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
|
||||
stc_power = float(pv_system.standard_test_condition_maximum_power)
|
||||
stc_irradiance = float(pv_system.standard_test_condition_radiation)
|
||||
@ -78,6 +119,11 @@ class PvSystemAssessment:
|
||||
return pv_output
|
||||
|
||||
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
|
||||
if self.module_model_name is not None:
|
||||
self.system_assignation()
|
||||
@ -109,6 +155,11 @@ class PvSystemAssessment:
|
||||
return panels_per_row, number_of_rows
|
||||
|
||||
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
|
||||
catalog_pv_generation_equipments = [component for component in
|
||||
generation_units_catalogue.entries('generation_equipments') if
|
||||
@ -133,6 +184,13 @@ class PvSystemAssessment:
|
||||
energy_system.generation_systems[idx] = new_system
|
||||
|
||||
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
|
||||
HourlyElectricityDemand(self.building).calculate()]
|
||||
rooftop_pv_output = [0] * 8760
|
||||
|
@ -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 pandas as pd
|
||||
from datetime import datetime
|
||||
@ -6,20 +12,21 @@ from hub.helpers.monthly_values import MonthlyValues
|
||||
|
||||
|
||||
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,
|
||||
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
|
||||
:param city: An object from the City class -> City
|
||||
:param tilt_angle: tilt angle of surface -> float
|
||||
:param surface_azimuth_angle: The orientation of the surface. 0 is North -> float
|
||||
:param standard_meridian: A standard meridian is the meridian whose mean solar time is the basis of the time of day
|
||||
observed in a time zone -> float
|
||||
:param solar_constant: The amount of energy received by a given area one astronomical unit away from the Sun. It is
|
||||
constant and must not be changed
|
||||
:param maximum_clearness_index: This is used to calculate the diffuse fraction of the solar irradiance -> float
|
||||
: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
|
||||
Initialize SolarCalculator with city and solar panel configurations.
|
||||
:param city: City object containing latitude and longitude
|
||||
:param tilt_angle: Tilt angle of the solar panel in degrees
|
||||
:param surface_azimuth_angle: Azimuth angle of the solar panel in degrees
|
||||
:param standard_meridian: Standard meridian for time zone correction
|
||||
:param solar_constant: Extraterrestrial radiation constant in W/m2
|
||||
:param maximum_clearness_index: Maximum clearness index for calculation
|
||||
:param min_cos_zenith: Minimum cosine zenith value
|
||||
:param maximum_zenith_angle: Maximum allowable zenith angle in degrees
|
||||
"""
|
||||
self.city = city
|
||||
self.location_latitude = city.latitude
|
||||
@ -52,6 +59,12 @@ class SolarCalculator:
|
||||
self.day_of_year = self.solar_angles.index.dayofyear
|
||||
|
||||
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
|
||||
eot = 9.87 * math.sin(2 * b) - 7.53 * math.cos(b) - 1.5 * math.sin(b)
|
||||
self.eot.append(eot)
|
||||
@ -79,18 +92,42 @@ class SolarCalculator:
|
||||
return ast_time
|
||||
|
||||
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_radian = math.radians(declination)
|
||||
self.declinations.append(declination)
|
||||
return declination_radian
|
||||
|
||||
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_radian = math.radians(hour_angle)
|
||||
self.hour_angles.append(hour_angle)
|
||||
return 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) *
|
||||
math.cos(hour_angle_radian) + math.sin(self.location_latitude_rad) *
|
||||
math.sin(declination_radian))
|
||||
@ -99,6 +136,16 @@ class SolarCalculator:
|
||||
return 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)
|
||||
zenith_degree = 90 - solar_altitude
|
||||
zenith_radian = math.radians(zenith_degree)
|
||||
@ -106,6 +153,21 @@ class SolarCalculator:
|
||||
return zenith_radian
|
||||
|
||||
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))
|
||||
denom = (math.sin(zenith) * math.cos(self.location_latitude_rad))
|
||||
if math.isclose(denom, 0.0, abs_tol=1e-8):
|
||||
@ -122,6 +184,19 @@ class SolarCalculator:
|
||||
return 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) *
|
||||
math.cos(abs(solar_azimuth_radians - self.surface_azimuth_rad)) *
|
||||
math.sin(self.tilt_angle_rad) + math.sin(solar_altitude_radians) *
|
||||
@ -131,6 +206,15 @@ class SolarCalculator:
|
||||
return incident_radian
|
||||
|
||||
def dni_extra(self, day_of_year, zenith_radian):
|
||||
"""
|
||||
Calculate extraterrestrial DNI and horizontal irradiance.
|
||||
|
||||
:param day_of_year: Day of the year (1–365/366).
|
||||
:param zenith_radian: Solar zenith angle in radians.
|
||||
:return: Tuple (i_on, i_oh) where:
|
||||
- i_on: Extraterrestrial normal irradiance [W/m²].
|
||||
- i_oh: Extraterrestrial horizontal irradiance [W/m²].
|
||||
"""
|
||||
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)
|
||||
self.i_on.append(i_on)
|
||||
@ -138,12 +222,26 @@ class SolarCalculator:
|
||||
return i_on, i_oh
|
||||
|
||||
def clearness_index(self, ghi, i_oh):
|
||||
"""
|
||||
Calculate the clearness index (Kt).
|
||||
|
||||
:param ghi: Global horizontal irradiance [W/m²].
|
||||
:param i_oh: Extraterrestrial horizontal irradiance [W/m²].
|
||||
:return: Clearness index (Kt).
|
||||
"""
|
||||
k_t = ghi / i_oh
|
||||
k_t = max(0, k_t)
|
||||
k_t = min(self.maximum_clearness_index, k_t)
|
||||
return k_t
|
||||
|
||||
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:
|
||||
fraction_diffuse = 1 - 0.09 * k_t
|
||||
elif k_t <= 0.8:
|
||||
@ -155,6 +253,14 @@ class SolarCalculator:
|
||||
return fraction_diffuse
|
||||
|
||||
def radiation_components_horizontal(self, ghi, fraction_diffuse, zenith):
|
||||
"""
|
||||
Compute diffuse and beam components of horizontal radiation.
|
||||
|
||||
:param ghi: Global horizontal irradiance [W/m²].
|
||||
:param fraction_diffuse: Diffuse fraction.
|
||||
:param zenith: Solar zenith angle in degrees.
|
||||
:return: Tuple (diffuse_horizontal, dni).
|
||||
"""
|
||||
diffuse_horizontal = ghi * fraction_diffuse
|
||||
dni = (ghi - diffuse_horizontal) / math.cos(math.radians(zenith))
|
||||
if zenith > self.maximum_zenith_angle or dni < 0:
|
||||
@ -162,6 +268,14 @@ class SolarCalculator:
|
||||
return diffuse_horizontal, dni
|
||||
|
||||
def radiation_components_tilted(self, diffuse_horizontal, dni, incident_angle):
|
||||
"""
|
||||
Compute total radiation on a tilted surface.
|
||||
|
||||
:param diffuse_horizontal: Diffuse horizontal irradiance [W/m²].
|
||||
:param dni: Direct normal irradiance [W/m²].
|
||||
:param incident_angle: Solar incident angle in degrees.
|
||||
:return: Total radiation on tilted surface [W/m²].
|
||||
"""
|
||||
beam_tilted = dni * math.cos(math.radians(incident_angle))
|
||||
beam_tilted = max(beam_tilted, 0)
|
||||
diffuse_tilted = diffuse_horizontal * ((1 + math.cos(math.radians(self.tilt_angle))) / 2)
|
||||
|
BIN
requirements.txt
Normal file
BIN
requirements.txt
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user