diff --git a/Utilities/Optimisers.py b/Utilities/Optimisers.py
new file mode 100644
index 0000000..e69de29
diff --git a/Utilities/Shading.py b/Utilities/Shading.py
index 1e1db71..30ab8f9 100644
--- a/Utilities/Shading.py
+++ b/Utilities/Shading.py
@@ -12,13 +12,14 @@ from Utilities.Processes import (
 
 logger = logging.getLogger(__name__)
 
+
 def get_location(c):
     location = pvlib.location.Location(
         latitude=c["environment"]["location"]["latitude"],
         longitude=c["environment"]["location"]["longitude"],
         tz=c["simulation_date_time"]["tz"],
     )
-    
+
     return location
 
 
@@ -85,9 +86,7 @@ def define_grid_layout(c):
         for i in range(int(max__panels_per_row)):
             if counter < no_of_panels:
                 x.append(i * c["panel"]["dimensions"]["width"])
-                y.append(
-                    j * pitch
-                )
+                y.append(j * pitch)
                 z.append(0)
                 counter += 1
             else:
@@ -121,10 +120,111 @@ def get_solar_data(c):
 
     # Get solar position data using PVLib
     solar_positions = location.get_solarposition(times)
-    clearsky_data = location.get_clearsky(times)
+    # filter solar positions to only include times when the sun is above the horizon
+    solar_positions = solar_positions[solar_positions["apparent_elevation"] > 0]
+    # get datetime range from solar_positions
+    day_times = solar_positions.index
+    clearsky_data = location.get_clearsky(day_times)
 
     return solar_positions, clearsky_data
 
 
-def calculate_shading(c, coordinates, solar_positions):
-    
\ No newline at end of file
+def calculate_shading(c):
+    panel_coordinates, no_of_panels = define_grid_layout(c)
+    solar_positions, clearsky_data = get_solar_data(c)
+
+    # split the solar positions data into morning and afternoon, using solar azimuth of
+    # 180 degrees as the threshold
+    morning_solar_positions = solar_positions[solar_positions["azimuth"] <= 180]
+    afternoon_solar_positions = solar_positions[solar_positions["azimuth"] > 180]
+
+    # the first row is always not shaded so exclude
+    no_of_rows = np.unique(panel_coordinates["y"]).shape[0]
+    no_of_shaded_rows = no_of_rows - 1
+
+    collector_width = c["panel"]["dimensions"]["length"]
+    # calculate delta between unique y coordinates of panels to get pitch
+    pitch = np.unique(panel_coordinates["y"])[1] - np.unique(panel_coordinates["y"])[0]
+    surface_to_axis_offset = 0
+    shaded_row_rotation = c["array"]["tilt"]
+    shading_row_rotation = c["array"]["tilt"]
+    axis_tilt = 0
+    axis_azimuth = c["array"]["front_face_azimuth"]
+
+    morning_shaded_fraction = pvlib.shading.shaded_fraction1d(
+        solar_zenith=morning_solar_positions["zenith"],
+        solar_azimuth=morning_solar_positions["azimuth"],
+        axis_azimuth=axis_azimuth,
+        shaded_row_rotation=shaded_row_rotation,
+        shading_row_rotation=shading_row_rotation,
+        collector_width=collector_width,
+        pitch=pitch,
+        surface_to_axis_offset=surface_to_axis_offset,
+        axis_tilt=axis_tilt,
+    )
+    morning_shaded_fraction = morning_shaded_fraction * no_of_shaded_rows / no_of_rows
+
+    afternoon_shaded_fraction = pvlib.shading.shaded_fraction1d(
+        solar_zenith=afternoon_solar_positions["zenith"],
+        solar_azimuth=afternoon_solar_positions["azimuth"],
+        axis_azimuth=axis_azimuth + 180,
+        shaded_row_rotation=shaded_row_rotation,
+        shading_row_rotation=shading_row_rotation,
+        collector_width=collector_width,
+        pitch=pitch,
+        surface_to_axis_offset=surface_to_axis_offset,
+        axis_tilt=axis_tilt,
+    )
+    afternoon_shaded_fraction = (
+        afternoon_shaded_fraction * no_of_shaded_rows / no_of_rows
+    )
+    logger.info(
+        f"Shaded fraction calculated for morning and afternoon solar positions."
+    )
+
+    # calculate irradiance on plane of array
+    poa_front = pvlib.irradiance.get_total_irradiance(
+        surface_tilt=c["array"]["tilt"],
+        surface_azimuth=c["array"]["front_face_azimuth"],
+        solar_zenith=morning_solar_positions["zenith"],
+        solar_azimuth=morning_solar_positions["azimuth"],
+        dni=clearsky_data["dni"],
+        ghi=clearsky_data["ghi"],
+        dhi=clearsky_data["dhi"],
+    )
+    # drop rows with poa_global NaN values
+    poa_front = poa_front.dropna(subset=["poa_global"])
+
+    poa_rear = pvlib.irradiance.get_total_irradiance(
+        surface_tilt=180 - c["array"]["tilt"],
+        surface_azimuth=c["array"]["front_face_azimuth"] + 180,
+        solar_zenith=afternoon_solar_positions["zenith"],
+        solar_azimuth=afternoon_solar_positions["azimuth"],
+        dni=clearsky_data["dni"],
+        ghi=clearsky_data["ghi"],
+        dhi=clearsky_data["dhi"],
+    )
+    # drop rows with poa_global NaN values
+    poa_rear = poa_rear.dropna(subset=["poa_global"])
+
+    effective_front = (
+        poa_front["poa_global"]
+        * (1 - morning_shaded_fraction)
+        * c["panel"]["efficiency"]
+    )
+    effective_rear = (
+        poa_rear["poa_global"]
+        * (1 - afternoon_shaded_fraction)
+        * c["panel"]["bifaciality"]
+        * c["panel"]["efficiency"]
+    )
+
+    energy_front = effective_front * 15 / 60 / 1e3
+    energy_rear = effective_rear * 15 / 60 / 1e3
+    energy_total = energy_front.sum() + energy_rear.sum()
+    logger.info(f"Energy yield calculated: {energy_total} kWh/m2")
+
+    panel_area = c["panel"]["dimensions"]["length"] * c["panel"]["dimensions"]["width"]
+    total_area = panel_area * no_of_panels
+    total_energy = energy_total * total_area
+    logger.info(f"Total energy yield calculated: {total_energy} kWh")
diff --git a/config.yml b/config.yml
index 0725ee5..6acde60 100644
--- a/config.yml
+++ b/config.yml
@@ -1,6 +1,6 @@
 array:
-  system_size: 100 # in kWp
-  spacing: 1.5 # spacing between adjacent panel rows in m
+  system_size: 400 # in kWp
+  spacing: 0.5 # spacing between adjacent panel rows in m
   edge_setback: 1.8 # distance from the edge of the roof to the array
   front_face_azimuth: 90 # 90=east, 180=south, 270=west
   tilt: 90 # just 0 and 90 are supported for now
@@ -24,6 +24,7 @@ environment:
 
 panel:
   peak_power: 710 # in Wp
+  efficiency: 0.229 # max efficiency of the front face
   bifaciality: 0.85 # rear face efficiency relative to front face
   # dimensions all in m
   dimensions:
diff --git a/main.py b/main.py
index eb695d1..83f5c5d 100644
--- a/main.py
+++ b/main.py
@@ -1,7 +1,7 @@
 # %%
 import yaml
 import logging
-from Utilities.Shading import define_grid_layout
+from Utilities.Shading import calculate_shading
 
 logging.basicConfig(
     level=logging.INFO,
@@ -24,7 +24,7 @@ with open(config_path, "r") as file:
 logger.info("Configuration loaded successfully.")
 logger.debug(f"Configuration: {c}")
 
-shading = define_grid_layout(c)
+shading = calculate_shading(c)
 logger.info("Shading calculation completed successfully.")
 
 # %%