solar-intelligence / tests /test_orientation_simulator.py
ghostieee11's picture
Upload folder using huggingface_hub
15b595e verified
Raw
History Blame Contribute Delete
7.73 kB
"""Tests for orientation_simulator module."""
from __future__ import annotations
import numpy as np
import pandas as pd
import pytest
from solar_intelligence.data_loader import generate_synthetic_solar_data
from solar_intelligence.orientation_simulator import OrientationSimulator
@pytest.fixture
def simulator_delhi():
"""Orientation simulator for New Delhi."""
return OrientationSimulator(
latitude=28.6139, longitude=77.2090,
tilt_angles=[0, 15, 30, 45],
azimuths={"North": 0, "East": 90, "South": 180, "West": 270},
)
@pytest.fixture
def simulator_sydney():
"""Orientation simulator for Sydney (Southern Hemisphere)."""
return OrientationSimulator(
latitude=-33.8688, longitude=151.2093,
tilt_angles=[0, 15, 30, 45],
azimuths={"North": 0, "East": 90, "South": 180, "West": 270},
)
@pytest.fixture
def ghi_daily_delhi():
"""Daily GHI for one year — New Delhi."""
ds = generate_synthetic_solar_data(lat=28.6139, lon=77.2090, start_year=2023, end_year=2023)
return ds["ALLSKY_SFC_SW_DWN"].values
@pytest.fixture
def ghi_daily_sydney():
"""Daily GHI for one year — Sydney."""
ds = generate_synthetic_solar_data(lat=-33.8688, lon=151.2093, start_year=2023, end_year=2023)
return ds["ALLSKY_SFC_SW_DWN"].values
class TestSolarPosition:
def test_solar_position_returns_dataframe(self, simulator_delhi):
solpos = simulator_delhi.solar_position_timeseries(year=2023)
assert isinstance(solpos, pd.DataFrame)
assert "apparent_zenith" in solpos.columns
assert "azimuth" in solpos.columns
assert len(solpos) == 8760 # 365 × 24
class TestOrientationSimulation:
def test_simulate_returns_dataframe(self, simulator_delhi, ghi_daily_delhi):
sim = simulator_delhi.simulate_all_orientations(ghi_daily_delhi, year=2023)
assert isinstance(sim, pd.DataFrame)
assert "direction" in sim.columns
assert "tilt_deg" in sim.columns
assert "monthly_energy_kwh" in sim.columns
assert "annual_energy_kwh" in sim.columns
def test_all_orientations_present(self, simulator_delhi, ghi_daily_delhi):
sim = simulator_delhi.simulate_all_orientations(ghi_daily_delhi)
directions = set(sim["direction"])
assert directions == {"North", "East", "South", "West"}
def test_all_tilts_present(self, simulator_delhi, ghi_daily_delhi):
sim = simulator_delhi.simulate_all_orientations(ghi_daily_delhi)
tilts = set(sim["tilt_deg"])
assert tilts == {0, 15, 30, 45}
def test_energy_positive(self, simulator_delhi, ghi_daily_delhi):
sim = simulator_delhi.simulate_all_orientations(ghi_daily_delhi)
assert all(sim["annual_energy_kwh"] > 0)
def test_south_beats_north_in_northern_hemisphere(self, simulator_delhi, ghi_daily_delhi):
"""In Northern Hemisphere, south-facing should outperform north-facing."""
sim = simulator_delhi.simulate_all_orientations(ghi_daily_delhi)
annual = sim.drop_duplicates(subset=["direction", "tilt_deg"])
south_30 = annual[
(annual["direction"] == "South") & (annual["tilt_deg"] == 30)
]["annual_energy_kwh"].values[0]
north_30 = annual[
(annual["direction"] == "North") & (annual["tilt_deg"] == 30)
]["annual_energy_kwh"].values[0]
assert south_30 > north_30, (
f"South ({south_30}) should beat North ({north_30}) in NH"
)
def test_north_beats_south_in_southern_hemisphere(self, simulator_sydney, ghi_daily_sydney):
"""In Southern Hemisphere, north-facing should outperform south-facing."""
sim = simulator_sydney.simulate_all_orientations(ghi_daily_sydney)
annual = sim.drop_duplicates(subset=["direction", "tilt_deg"])
north_30 = annual[
(annual["direction"] == "North") & (annual["tilt_deg"] == 30)
]["annual_energy_kwh"].values[0]
south_30 = annual[
(annual["direction"] == "South") & (annual["tilt_deg"] == 30)
]["annual_energy_kwh"].values[0]
assert north_30 > south_30, (
f"North ({north_30}) should beat South ({south_30}) in SH"
)
class TestOptimalOrientation:
def test_optimal_returns_dict(self, simulator_delhi, ghi_daily_delhi):
optimal = simulator_delhi.optimal_orientation(ghi_daily_delhi)
assert "best_direction" in optimal
assert "best_tilt" in optimal
assert "annual_energy_kwh" in optimal
assert "energy_gain_vs_horizontal_pct" in optimal
def test_optimal_direction_is_south_for_nh(self, simulator_delhi, ghi_daily_delhi):
"""For Northern Hemisphere, optimal direction should be South."""
optimal = simulator_delhi.optimal_orientation(ghi_daily_delhi)
assert optimal["best_direction"] == "South"
def test_optimal_tilt_near_latitude(self, simulator_delhi, ghi_daily_delhi):
"""Optimal tilt should approximate latitude for annual optimization."""
optimal = simulator_delhi.optimal_orientation(ghi_daily_delhi)
lat = simulator_delhi.latitude
# Should be within ±20° of latitude
assert abs(optimal["best_tilt"] - lat) < 20, (
f"Optimal tilt {optimal['best_tilt']}° far from latitude {lat}°"
)
def test_gain_vs_horizontal_positive(self, simulator_delhi, ghi_daily_delhi):
"""Tilted panels should outperform horizontal."""
optimal = simulator_delhi.optimal_orientation(ghi_daily_delhi)
assert optimal["energy_gain_vs_horizontal_pct"] > 0
class TestDailyProfile:
def test_daily_profile_shape(self, simulator_delhi, ghi_daily_delhi):
profile = simulator_delhi.daily_profile_by_orientation(
ghi_daily_delhi, date="2023-06-21",
)
assert isinstance(profile, pd.DataFrame)
assert "hour" in profile.columns
assert "direction" in profile.columns
assert "energy_kwh" in profile.columns
def test_nighttime_low_energy(self, simulator_delhi, ghi_daily_delhi):
"""Energy in deep night hours (UTC 20-22, local 1:30-3:30 AM IST) should be ~0."""
profile = simulator_delhi.daily_profile_by_orientation(
ghi_daily_delhi, date="2023-06-21",
)
# UTC hour 21 = IST 2:30 AM — definitely nighttime
night = profile[profile["hour"] == 21]
assert all(night["energy_kwh"] < 0.01)
class TestTiltSensitivity:
def test_tilt_sensitivity_shape(self, simulator_delhi, ghi_daily_delhi):
sensitivity = simulator_delhi.tilt_sensitivity_analysis(
ghi_daily_delhi, tilt_range=[0, 15, 30, 45, 60, 90],
)
assert len(sensitivity) == 6
assert "tilt_deg" in sensitivity.columns
assert "annual_energy_kwh" in sensitivity.columns
def test_extreme_tilt_less_energy(self, simulator_delhi, ghi_daily_delhi):
"""90° tilt (vertical) should produce less than 30° tilt."""
sensitivity = simulator_delhi.tilt_sensitivity_analysis(
ghi_daily_delhi, tilt_range=[30, 90],
)
e_30 = sensitivity[sensitivity["tilt_deg"] == 30]["annual_energy_kwh"].values[0]
e_90 = sensitivity[sensitivity["tilt_deg"] == 90]["annual_energy_kwh"].values[0]
assert e_30 > e_90
class TestSeasonalComparison:
def test_seasonal_comparison_shape(self, simulator_delhi, ghi_daily_delhi):
seasonal = simulator_delhi.seasonal_comparison(ghi_daily_delhi)
assert isinstance(seasonal, pd.DataFrame)
assert "season" in seasonal.columns
assert "direction" in seasonal.columns
assert "seasonal_energy_kwh" in seasonal.columns