Spaces:
Sleeping
Sleeping
| """Tests for visualization module. | |
| Tests that all chart generators produce valid HoloViews/hvPlot objects | |
| without requiring a running Panel server. | |
| """ | |
| from __future__ import annotations | |
| import holoviews as hv | |
| import numpy as np | |
| import pandas as pd | |
| import pytest | |
| from solar_intelligence.data_loader import generate_synthetic_solar_data | |
| from solar_intelligence.energy_estimator import EnergyEstimator | |
| from solar_intelligence.financial import FinancialAnalyzer | |
| from solar_intelligence.orientation_simulator import OrientationSimulator | |
| from solar_intelligence.solar_analysis import SolarAnalyzer | |
| from solar_intelligence.visualization import SolarVisualizer | |
| hv.extension("bokeh") | |
| def visualizer(): | |
| return SolarVisualizer(width=600, height=350) | |
| def dataset(): | |
| return generate_synthetic_solar_data(lat=28.6, lon=77.2, start_year=2023, end_year=2023) | |
| def analyzer(dataset): | |
| return SolarAnalyzer(dataset=dataset, latitude=28.6, longitude=77.2) | |
| def estimator(): | |
| return EnergyEstimator(num_panels=10) | |
| def sim_data(dataset): | |
| sim = OrientationSimulator( | |
| latitude=28.6, longitude=77.2, | |
| tilt_angles=[0, 30], | |
| azimuths={"North": 0, "South": 180, "East": 90, "West": 270}, | |
| ) | |
| ghi = dataset["ALLSKY_SFC_SW_DWN"].values | |
| return sim.simulate_all_orientations(ghi, year=2023) | |
| def sensitivity_data(dataset): | |
| sim = OrientationSimulator(latitude=28.6, longitude=77.2) | |
| ghi = dataset["ALLSKY_SFC_SW_DWN"].values | |
| return sim.tilt_sensitivity_analysis(ghi, tilt_range=[0, 15, 30, 45]) | |
| def profile_data(dataset): | |
| sim = OrientationSimulator( | |
| latitude=28.6, longitude=77.2, | |
| azimuths={"South": 180, "East": 90}, | |
| ) | |
| ghi = dataset["ALLSKY_SFC_SW_DWN"].values | |
| return sim.daily_profile_by_orientation(ghi, date="2023-06-21", directions=["South", "East"]) | |
| def seasonal_data(dataset): | |
| sim = OrientationSimulator( | |
| latitude=28.6, longitude=77.2, | |
| tilt_angles=[0, 30], | |
| azimuths={"South": 180, "North": 0, "East": 90, "West": 270}, | |
| ) | |
| ghi = dataset["ALLSKY_SFC_SW_DWN"].values | |
| return sim.seasonal_comparison(ghi, directions=["South", "North"]) | |
| # --------------------------------------------------------------------------- | |
| # Irradiance Charts | |
| # --------------------------------------------------------------------------- | |
| class TestIrradianceCharts: | |
| def test_monthly_irradiance_bar(self, visualizer, analyzer): | |
| monthly = analyzer.monthly_irradiance() | |
| chart = visualizer.monthly_irradiance_bar(monthly) | |
| assert chart is not None | |
| # hvplot returns an object with a plot method or is an HoloViews element | |
| assert hasattr(chart, 'opts') or hasattr(chart, 'data') | |
| def test_daily_irradiance_timeseries(self, visualizer, analyzer): | |
| rolling = analyzer.rolling_average() | |
| chart = visualizer.daily_irradiance_timeseries(rolling) | |
| assert chart is not None | |
| def test_seasonal_heatmap(self, visualizer, dataset): | |
| chart = visualizer.seasonal_heatmap(dataset) | |
| assert chart is not None | |
| def test_clearsky_vs_actual(self, visualizer, dataset): | |
| chart = visualizer.clearsky_vs_actual(dataset) | |
| assert chart is not None | |
| def test_irradiance_distribution(self, visualizer, dataset): | |
| chart = visualizer.irradiance_distribution(dataset) | |
| assert chart is not None | |
| # --------------------------------------------------------------------------- | |
| # Orientation Charts | |
| # --------------------------------------------------------------------------- | |
| class TestOrientationCharts: | |
| def test_orientation_comparison_bar(self, visualizer, sim_data): | |
| chart = visualizer.orientation_comparison_bar(sim_data, tilt=30) | |
| assert chart is not None | |
| def test_tilt_energy_curve(self, visualizer, sensitivity_data): | |
| chart = visualizer.tilt_energy_curve(sensitivity_data) | |
| assert chart is not None | |
| def test_orientation_heatmap(self, visualizer, sim_data): | |
| chart = visualizer.orientation_heatmap(sim_data) | |
| assert chart is not None | |
| def test_daily_profile_overlay(self, visualizer, profile_data): | |
| chart = visualizer.daily_profile_overlay(profile_data) | |
| assert chart is not None | |
| def test_seasonal_orientation_comparison(self, visualizer, seasonal_data): | |
| chart = visualizer.seasonal_orientation_comparison(seasonal_data) | |
| assert chart is not None | |
| # --------------------------------------------------------------------------- | |
| # Energy Charts | |
| # --------------------------------------------------------------------------- | |
| class TestEnergyCharts: | |
| def test_energy_projection_area(self, visualizer, dataset, estimator): | |
| monthly = estimator.estimate_monthly_energy(dataset) | |
| chart = visualizer.energy_projection_area(monthly) | |
| assert chart is not None | |
| def test_annual_energy_summary_table(self, visualizer, dataset, estimator): | |
| summary = estimator.system_summary(dataset) | |
| table = visualizer.annual_energy_summary_table(summary) | |
| assert isinstance(table, hv.Table) | |
| # --------------------------------------------------------------------------- | |
| # Map Visualizations | |
| # --------------------------------------------------------------------------- | |
| class TestMapVisualization: | |
| def test_global_solar_map(self, visualizer): | |
| lats = np.linspace(-60, 60, 120) | |
| lons = np.linspace(-180, 180, 360) | |
| ghi_grid = 7 - 0.08 * np.abs(np.meshgrid(lons, lats)[1]) | |
| chart = visualizer.global_solar_map(lats, lons, ghi_grid) | |
| assert isinstance(chart, (hv.Image, hv.Overlay)) | |
| def test_location_marker(self, visualizer): | |
| marker = visualizer.location_marker(28.6, 77.2, "Delhi") | |
| assert isinstance(marker, hv.Points) | |
| def test_map_with_marker_overlay(self, visualizer): | |
| lats = np.linspace(10, 40, 30) | |
| lons = np.linspace(60, 100, 40) | |
| ghi = np.random.default_rng(42).uniform(3, 7, (30, 40)) | |
| solar_map = visualizer.global_solar_map(lats, lons, ghi) | |
| marker = visualizer.location_marker(28.6, 77.2) | |
| combined = solar_map * marker | |
| assert combined is not None | |
| # --------------------------------------------------------------------------- | |
| # Financial Charts | |
| # --------------------------------------------------------------------------- | |
| class TestFinancialCharts: | |
| def test_payback_timeline(self, visualizer): | |
| fa = FinancialAnalyzer() | |
| savings = fa.lifetime_savings(5000) | |
| chart = visualizer.payback_timeline(savings) | |
| assert chart is not None | |
| def test_carbon_savings_bar(self, visualizer): | |
| fa = FinancialAnalyzer() | |
| savings = fa.lifetime_savings(5000) | |
| chart = visualizer.carbon_savings_bar(savings) | |
| assert chart is not None | |
| # --------------------------------------------------------------------------- | |
| # Composite Layouts | |
| # --------------------------------------------------------------------------- | |
| class TestCompositeLayouts: | |
| def test_overview_layout(self, visualizer, analyzer, dataset): | |
| monthly = analyzer.monthly_irradiance() | |
| rolling = analyzer.rolling_average() | |
| layout = visualizer.create_overview_layout(monthly, rolling, dataset) | |
| assert isinstance(layout, hv.Layout) | |
| def test_orientation_layout(self, visualizer, sim_data, sensitivity_data, | |
| profile_data, seasonal_data): | |
| layout = visualizer.create_orientation_layout( | |
| sim_data, sensitivity_data, profile_data, seasonal_data, | |
| ) | |
| assert isinstance(layout, hv.Layout) | |
| # --------------------------------------------------------------------------- | |
| # Datashader Integration | |
| # --------------------------------------------------------------------------- | |
| class TestDatashaderIntegration: | |
| def test_large_grid_renders(self, visualizer): | |
| """Test that a 1000x2000 grid (2M points) renders without error.""" | |
| lats = np.linspace(-90, 90, 1000) | |
| lons = np.linspace(-180, 180, 2000) | |
| lat_grid, lon_grid = np.meshgrid(lats, lons, indexing="ij") | |
| ghi = 7 - 0.08 * np.abs(lat_grid) + np.random.default_rng(42).normal(0, 0.2, lat_grid.shape) | |
| ghi = np.clip(ghi, 0.5, 9) | |
| chart = visualizer.global_solar_map(lats, lons, ghi) | |
| assert isinstance(chart, (hv.Image, hv.Overlay)) | |
| def test_datashader_rasterize_points(self): | |
| """Test Datashader rasterization of point cloud data.""" | |
| import datashader as ds | |
| n = 500_000 | |
| rng = np.random.default_rng(42) | |
| df = pd.DataFrame({ | |
| "lon": rng.uniform(-180, 180, n), | |
| "lat": rng.uniform(-90, 90, n), | |
| "ghi": rng.uniform(1, 9, n), | |
| }) | |
| canvas = ds.Canvas(plot_width=400, plot_height=200) | |
| agg = canvas.points(df, "lon", "lat", agg=ds.mean("ghi")) | |
| assert agg.shape == (200, 400) | |
| assert float(agg.mean()) > 0 | |