diff --git a/Utilities/Processes.py b/Utilities/Processes.py new file mode 100644 index 0000000..80ce0c2 --- /dev/null +++ b/Utilities/Processes.py @@ -0,0 +1,18 @@ +import numpy as np + + +def calculate_no_of_panels(system_size, panel_peak_power): + """ + Calculate the number of panels needed for a given system size and panel peak power. + + Args: + system_size (float): The total system size in kWp. + panel_peak_power (float): The peak power of a single panel in Wp. + + Returns: + int: The number of panels needed. + """ + panel_peak_power_kWp = panel_peak_power / 1000 # Convert Wp to kWp + no_of_panels = np.ceil(system_size / panel_peak_power_kWp) + + return no_of_panels diff --git a/Utilities/Shading.py b/Utilities/Shading.py new file mode 100644 index 0000000..88cc10d --- /dev/null +++ b/Utilities/Shading.py @@ -0,0 +1,113 @@ +import numpy as np +import pandas as pd +import logging + +from ladybug_geometry.geometry3d.pointvector import Point3D, Vector3D +from ladybug_geometry.geometry3d.plane import Plane +from ladybug_geometry.geometry3d.polyface import Polyface3D + +from Utilities.Processes import calculate_no_of_panels + +logger = logging.getLogger(__name__) + + +def define_grid_layout(c): + # get number of panels required + no_of_panels = calculate_no_of_panels( + c["array"]["system_size"], c["panel"]["peak_power"] + ) + + # get maximum number of panels based on spacing and dimensions + max__panels_per_row = np.floor( + ( + c["environment"]["roof"]["dimensions"]["width"] + - 2 * c["array"]["edge_setback"] + ) + / c["panel"]["dimensions"]["width"] + ) + max_number_of_rows = np.floor( + ( + c["environment"]["roof"]["dimensions"]["length"] + - 2 * c["array"]["edge_setback"] + ) + / (c["array"]["spacing"] + c["panel"]["dimensions"]["thickness"]) + ) + max_no_of_panels = max__panels_per_row * max_number_of_rows + logger.info( + f"Number of panels required: {no_of_panels}, Maximum panels possible: {max_no_of_panels}" + ) + if no_of_panels > max_no_of_panels: + no_of_panels = max_no_of_panels + logger.warning( + f"Number of panels required exceeds maximum possible. Setting number of panels to {no_of_panels}." + ) + else: + logger.info( + f"Number of panels required is within the maximum possible. Setting number of panels to {no_of_panels}." + ) + + # coordinate of panel determined by bottom left corner + # x - row wise position, y - column wise position, z - height + # first panel in row 1 is at (0, 0, 0) + # nth panel in row 1 is at ((n-1)*panel_width, 0, 0) + # first panel in nth row is at (0, (n-1)*(panel_thickness + spacing), 0) + + # create matrices for x, y, z coordinates of panels + x = [] + y = [] + z = [] + counter = 0 + for j in range(int(max_number_of_rows)): + for i in range(int(max__panels_per_row)): + if counter < no_of_panels: + x.append(i * c["panel"]["dimensions"]["width"]) + y.append( + j * (c["panel"]["dimensions"]["thickness"] + c["array"]["spacing"]) + ) + z.append(0) + counter += 1 + else: + break + + coordinates = pd.DataFrame( + { + "x": x, + "y": y, + "z": z, + } + ) + return coordinates + + +def create_panels(coordinates, c): + panel_width = c["panel"]["dimensions"]["width"] + panel_length = c["panel"]["dimensions"]["length"] + panel_thickness = c["panel"]["dimensions"]["thickness"] + + # For a vertical panel: + # - The vertical direction (panel height) is along the Z-axis. + y_axis = Vector3D(0, 0, 1) # points upward + + # - The horizontal direction along the panel's width. + # Here, we assume the width runs in the positive X-direction. + x_axis = Vector3D(1, 0, 0) # points east + + panels = [] + for index, row in coordinates.iterrows(): + # Create the bottom-left corner of the panel + panel_origin = Point3D(row["x"], row["y"], row["z"]) + + # Create the plane for the panel + panel_plane = Plane(origin=panel_origin, y_axis=y_axis, x_axis=x_axis) + + # Create the panel geometry + panel = Polyface3D.from_box( + width=panel_width, + depth=panel_length, + height=panel_thickness, + base_plane=panel_plane, + ) + + panels.append(panel) + + return panels diff --git a/Utilities/__init__.py b/Utilities/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..65b5268 --- /dev/null +++ b/config.yml @@ -0,0 +1,26 @@ +array: + system_size: 500 # in kWp + spacing: 1 # spacing between adjacent panel rows in m + edge_setback: 1.8 # distance from the edge of the roof to the array + +environment: + roof: + dimensions: + # dimensions all in m + length: 100 + width: 125 + albedo: 0.8 # % of light reflected from the surface + tilt: 0 # degrees from horizontal + +location: + latitude: 3.1186108758412945 + longitude: 101.57639813680093 + +panel: + peak_power: 710 # in Wp + bifaciality: 0.85 # rear face efficiency relative to front face + # dimensions all in m + dimensions: + length: 2.384 + width: 1.303 + thickness: 0.033 diff --git a/main.py b/main.py new file mode 100644 index 0000000..8ea5c27 --- /dev/null +++ b/main.py @@ -0,0 +1,29 @@ +# %% +import yaml +import logging +from Utilities.Shading import define_grid_layout + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", +) + +stream_handler = logging.StreamHandler() +stream_handler.setLevel(logging.INFO) +stream_formatter = logging.Formatter("%(levelname)s: %(message)s") +stream_handler.setFormatter(stream_formatter) +logging.getLogger().addHandler(stream_handler) + +logger = logging.getLogger(__name__) + +config_path = "config.yml" + +with open(config_path, "r") as file: + c = yaml.safe_load(file) +logger.info("Configuration loaded successfully.") +logger.debug(f"Configuration: {c}") + +coordinates = define_grid_layout(c) + +# %% diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5fa1784 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,44 @@ +asttokens==3.0.0 +certifi==2025.1.31 +charset-normalizer==3.4.1 +colorama==0.4.6 +comm==0.2.2 +debugpy==1.8.13 +decorator==5.2.1 +executing==2.2.0 +h5py==3.13.0 +idna==3.10 +ipykernel==6.29.5 +ipython==9.0.2 +ipython_pygments_lexers==1.1.1 +jedi==0.19.2 +jupyter_client==8.6.3 +jupyter_core==5.7.2 +ladybug-geometry==1.34.1 +matplotlib-inline==0.1.7 +nest-asyncio==1.6.0 +nominatim==0.1 +numpy==2.2.4 +packaging==24.2 +pandas==2.2.3 +parso==0.8.4 +platformdirs==4.3.7 +prompt_toolkit==3.0.50 +psutil==7.0.0 +pure_eval==0.2.3 +pvlib==0.12.0 +Pygments==2.19.1 +python-dateutil==2.9.0.post0 +pytz==2025.2 +pywin32==310 +PyYAML==6.0.2 +pyzmq==26.3.0 +requests==2.32.3 +scipy==1.15.2 +six==1.17.0 +stack-data==0.6.3 +tornado==6.4.2 +traitlets==5.14.3 +tzdata==2025.2 +urllib3==2.3.0 +wcwidth==0.2.13 diff --git a/test_sim.py b/test_sim.py deleted file mode 100644 index 26a7ab5..0000000 --- a/test_sim.py +++ /dev/null @@ -1,17 +0,0 @@ -# %% -import pvlib - -from pvlib.modelchain import ModelChain -from pvlib.pvsystem import PVSystem -from pvlib.location import Location - -# %% -location = Location( - latitude=3.1114844514593343, longitude=101.5785743614895, tz="Asia/Kuala_Lumpur" -) -system = PVSystem( - surface_tilt=90, - surface_azimuth=90, -) -sandia_modules = pvlib.pvsystem.retrieve_sam("SandiaMod") -cec_inverters = pvlib.pvsystem.retrieve_sam("CECInverter")