import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from prophet import Prophet import gradio as gr import plotly.graph_objects as go from plotly.subplots import make_subplots import warnings warnings.filterwarnings('ignore') from datetime import datetime, timedelta import requests from sklearn.preprocessing import MinMaxScaler from sklearn.metrics import mean_absolute_error, mean_squared_error import tensorflow as tf from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout class SolarPVForecaster: def __init__(self): self.load_data = None self.solar_data = None self.weather_data = None self.prophet_model = None self.lstm_model = None self.scaler = MinMaxScaler() def load_opsd_data(self): """Load and preprocess data from Open Power System Data""" try: # Load time series data url = 'https://data.open-power-system-data.org/time_series/2020-10-06/time_series_60min_singleindex.csv' print("Loading OPSD data...") data = pd.read_csv(url, parse_dates=['utc_timestamp']) # Select relevant columns for Germany (you can change country code) columns_of_interest = [ 'utc_timestamp', 'DE_load_actual_entsoe_transparency', # Actual load 'DE_solar_generation_actual', # Solar generation 'DE_wind_generation_actual', # Wind generation 'DE_price_day_ahead' # Energy price ] # Filter available columns available_cols = [col for col in columns_of_interest if col in data.columns] df = data[available_cols].copy() # Clean and preprocess df.dropna(subset=['utc_timestamp'], inplace=True) df['utc_timestamp'] = pd.to_datetime(df['utc_timestamp']) df.set_index('utc_timestamp', inplace=True) # Fill missing values with interpolation df = df.interpolate(method='time') print(f"Data loaded successfully! Shape: {df.shape}") print(f"Date range: {df.index.min()} to {df.index.max()}") return df except Exception as e: print(f"Error loading OPSD data: {e}") return self.generate_synthetic_data() def generate_synthetic_data(self): """Generate synthetic data for demonstration when real data unavailable""" print("Generating synthetic data for demonstration...") dates = pd.date_range(start='2020-01-01', end='2023-12-31', freq='H') # Base patterns hourly_pattern = np.sin(2 * np.pi * dates.hour / 24) + 0.5 daily_pattern = np.sin(2 * np.pi * dates.dayofyear / 365.25) # Solar generation (MW) - 5 MW system as per research proposal solar_base = 2.5 + 2 * hourly_pattern * (dates.hour >= 6) * (dates.hour <= 18) solar_seasonal = solar_base * (0.7 + 0.3 * np.cos(2 * np.pi * (dates.dayofyear - 172) / 365.25)) # Weather impact factors (smoke, fog, clouds) weather_impact = 1 - 0.3 * np.random.beta(2, 5, len(dates)) # Reduced efficiency due to weather solar_generation = np.maximum(0, solar_seasonal * weather_impact + np.random.normal(0, 0.2, len(dates))) # Auxiliary load (25-40 MW as per research proposal) base_load = 32.5 + 7.5 * hourly_pattern + 5 * daily_pattern load_actual = np.maximum(20, base_load + np.random.normal(0, 2, len(dates))) # Weather conditions temperature = 20 + 15 * np.sin(2 * np.pi * (dates.dayofyear - 80) / 365.25) + np.random.normal(0, 3, len(dates)) humidity = 50 + 30 * np.sin(2 * np.pi * (dates.dayofyear - 200) / 365.25) + np.random.normal(0, 10, len(dates)) wind_speed = 5 + 3 * np.random.exponential(1, len(dates)) # Air quality factors (smoke, fog, dust) smoke_level = np.random.exponential(0.5, len(dates)) # Particulate matter from smoke fog_density = np.maximum(0, np.random.normal(0.1, 0.3, len(dates))) # Visibility reduction dust_concentration = np.random.gamma(2, 0.1, len(dates)) # Dust on panels df = pd.DataFrame({ 'load_actual': load_actual, 'solar_generation': solar_generation, 'temperature': temperature, 'humidity': humidity, 'wind_speed': wind_speed, 'smoke_level': smoke_level, 'fog_density': fog_density, 'dust_concentration': dust_concentration }, index=dates) return df def prepare_lstm_data(self, data, target_col, sequence_length=24): """Prepare data for LSTM model""" # Scale the data scaled_data = self.scaler.fit_transform(data) X, y = [], [] for i in range(sequence_length, len(scaled_data)): X.append(scaled_data[i-sequence_length:i]) y.append(scaled_data[i, data.columns.get_loc(target_col)]) return np.array(X), np.array(y) def build_lstm_model(self, input_shape): """Build LSTM model for forecasting""" model = Sequential([ LSTM(50, return_sequences=True, input_shape=input_shape), Dropout(0.2), LSTM(50, return_sequences=True), Dropout(0.2), LSTM(50), Dropout(0.2), Dense(1) ]) model.compile(optimizer='adam', loss='mse', metrics=['mae']) return model def train_models(self, df): """Train both Prophet and LSTM models""" print("Training forecasting models...") # Prepare data for Prophet (Solar Generation) prophet_data = df.reset_index()[['utc_timestamp', 'solar_generation']].copy() prophet_data.columns = ['ds', 'y'] prophet_data.dropna(inplace=True) # Add weather regressors to Prophet if 'temperature' in df.columns: prophet_data['temperature'] = df['temperature'].values[:len(prophet_data)] if 'humidity' in df.columns: prophet_data['humidity'] = df['humidity'].values[:len(prophet_data)] if 'smoke_level' in df.columns: prophet_data['smoke_level'] = df['smoke_level'].values[:len(prophet_data)] if 'fog_density' in df.columns: prophet_data['fog_density'] = df['fog_density'].values[:len(prophet_data)] # Train Prophet model self.prophet_model = Prophet( daily_seasonality=True, weekly_seasonality=True, yearly_seasonality=True, changepoint_prior_scale=0.05 ) # Add regressors for weather factors for col in ['temperature', 'humidity', 'smoke_level', 'fog_density']: if col in prophet_data.columns: self.prophet_model.add_regressor(col) self.prophet_model.fit(prophet_data) # Prepare and train LSTM model feature_cols = ['solar_generation', 'load_actual', 'temperature', 'humidity', 'smoke_level', 'fog_density', 'dust_concentration'] available_cols = [col for col in feature_cols if col in df.columns] lstm_data = df[available_cols].dropna() X, y = self.prepare_lstm_data(lstm_data, 'solar_generation') # Split data train_size = int(0.8 * len(X)) X_train, X_test = X[:train_size], X[train_size:] y_train, y_test = y[:train_size], y[train_size:] # Build and train LSTM self.lstm_model = self.build_lstm_model((X.shape[1], X.shape[2])) print("Training LSTM model...") history = self.lstm_model.fit( X_train, y_train, epochs=50, batch_size=32, validation_data=(X_test, y_test), verbose=0 ) print("Models trained successfully!") return history def forecast_solar_generation(self, days=7, include_weather=True): """Forecast solar generation using Prophet model""" if self.prophet_model is None: raise ValueError("Model not trained yet!") # Create future dataframe future = self.prophet_model.make_future_dataframe( periods=days * 24, freq='H' ) # Add weather regressor values for future predictions if include_weather: # Simple weather pattern simulation for future future_weather = self.simulate_future_weather(len(future)) for col, values in future_weather.items(): if col in self.prophet_model.extra_regressors: future[col] = values forecast = self.prophet_model.predict(future) return forecast def simulate_future_weather(self, n_periods): """Simulate future weather conditions""" future_weather = {} # Generate synthetic weather data for forecasting base_temp = 25 base_humidity = 60 future_weather['temperature'] = base_temp + 5 * np.sin(np.linspace(0, 4*np.pi, n_periods)) + np.random.normal(0, 2, n_periods) future_weather['humidity'] = base_humidity + 20 * np.sin(np.linspace(0, 4*np.pi, n_periods)) + np.random.normal(0, 5, n_periods) future_weather['smoke_level'] = np.random.exponential(0.5, n_periods) future_weather['fog_density'] = np.maximum(0, np.random.normal(0.1, 0.2, n_periods)) return future_weather def calculate_auxiliary_savings(self, solar_forecast, days=7): """Calculate potential savings in auxiliary power consumption""" # Constants from research proposal AUXILIARY_LOAD_OPERATING = 25 # MW during operation AUXILIARY_LOAD_STANDBY = 40 # MW during standby GRID_TARIFF = 0.12 # $/kWh (example rate) total_solar_generation = solar_forecast['yhat'].sum() # MWh # Calculate savings hours_in_period = days * 24 # Assume 50% operating time, 50% standby time avg_auxiliary_load = (AUXILIARY_LOAD_OPERATING + AUXILIARY_LOAD_STANDBY) / 2 # Solar contribution to auxiliary load solar_contribution = min(total_solar_generation, avg_auxiliary_load * hours_in_period) # Financial savings cost_savings = solar_contribution * 1000 * GRID_TARIFF # Convert MW to kW return { 'total_solar_generation_MWh': round(total_solar_generation, 2), 'solar_contribution_MWh': round(solar_contribution, 2), 'cost_savings_USD': round(cost_savings, 2), 'auxiliary_load_reduction_percent': round((solar_contribution / (avg_auxiliary_load * hours_in_period)) * 100, 2) } # Global instance forecaster = SolarPVForecaster() df_global = None def initialize_system(): """Initialize the forecasting system""" global df_global, forecaster print("Initializing Solar PV Forecasting System...") df_global = forecaster.load_opsd_data() history = forecaster.train_models(df_global) print("System initialized successfully!") return "✅ System Initialized Successfully!" def forecast_and_visualize(days, chart_type, weather_impact): """Main forecasting function with enhanced visualizations""" global df_global, forecaster if df_global is None or forecaster.prophet_model is None: return "❌ Please initialize the system first!", None try: # Generate forecast forecast = forecaster.forecast_solar_generation(days, include_weather=weather_impact) forecast_future = forecast.tail(days * 24) # Calculate savings savings = forecaster.calculate_auxiliary_savings(forecast_future, days) if chart_type == "Solar Generation Forecast": fig = go.Figure() # Historical data recent_data = df_global.tail(7 * 24) # Last 7 days fig.add_trace(go.Scatter( x=recent_data.index, y=recent_data['solar_generation'], name='Historical Solar Generation', line=dict(color='orange') )) # Forecast fig.add_trace(go.Scatter( x=pd.to_datetime(forecast_future['ds']), y=forecast_future['yhat'], name='Forecasted Solar Generation', line=dict(color='green') )) # Confidence interval fig.add_trace(go.Scatter( x=pd.to_datetime(forecast_future['ds']), y=forecast_future['yhat_upper'], fill=None, mode='lines', line_color='rgba(0,100,80,0)', showlegend=False )) fig.add_trace(go.Scatter( x=pd.to_datetime(forecast_future['ds']), y=forecast_future['yhat_lower'], fill='tonexty', mode='lines', line_color='rgba(0,100,80,0)', name='Confidence Interval', fillcolor='rgba(0,100,80,0.2)' )) fig.update_layout( title=f"5 MW Solar PV Generation Forecast ({days} Days)", xaxis_title="Date", yaxis_title="Solar Generation (MW)", template="plotly_white", height=500 ) elif chart_type == "Weather Impact Analysis": fig = make_subplots( rows=2, cols=2, subplot_titles=('Temperature Effect', 'Humidity Effect', 'Smoke/Fog Impact', 'Generation vs Weather'), specs=[[{"secondary_y": True}, {"secondary_y": True}], [{"secondary_y": True}, {"secondary_y": True}]] ) # Temperature effect recent_temp = df_global['temperature'].tail(days * 24) recent_solar = df_global['solar_generation'].tail(days * 24) fig.add_trace( go.Scatter(x=recent_temp.index, y=recent_temp, name="Temperature", line=dict(color='red')), row=1, col=1 ) fig.add_trace( go.Scatter(x=recent_solar.index, y=recent_solar, name="Solar Gen", line=dict(color='orange')), row=1, col=1, secondary_y=True ) # Add more weather correlations... fig.update_layout( title="Weather Factors Impact on Solar Generation", height=600, template="plotly_white" ) elif chart_type == "Economic Analysis": # Economic benefits visualization categories = ['Solar Generation', 'Cost Savings', 'Load Reduction', 'Efficiency'] values = [ savings['total_solar_generation_MWh'], savings['cost_savings_USD'] / 1000, # Convert to thousands savings['auxiliary_load_reduction_percent'], min(100, savings['auxiliary_load_reduction_percent'] * 1.2) ] fig = go.Figure(data=[ go.Bar(x=categories, y=values, marker_color=['green', 'blue', 'orange', 'red']) ]) fig.update_layout( title="Economic Impact Analysis - 5 MW Solar Integration", yaxis_title="Value (MWh / k$ / %)", template="plotly_white", height=500 ) else: # Load vs Generation Comparison fig = go.Figure() # Recent auxiliary load (simulated as 25-40 MW range) recent_load = df_global['load_actual'].tail(days * 24) fig.add_trace(go.Scatter( x=recent_load.index, y=recent_load, name='Auxiliary Load Demand', line=dict(color='red') )) fig.add_trace(go.Scatter( x=pd.to_datetime(forecast_future['ds']), y=forecast_future['yhat'], name='Solar Generation', line=dict(color='green') )) # Add reference lines for auxiliary load limits fig.add_hline(y=25, line_dash="dash", line_color="orange", annotation_text="Operating Load (25 MW)") fig.add_hline(y=40, line_dash="dash", line_color="red", annotation_text="Standby Load (40 MW)") fig.update_layout( title="Solar Generation vs Auxiliary Load Requirements", xaxis_title="Date", yaxis_title="Power (MW)", template="plotly_white", height=500 ) # Generate detailed report report = f""" ## 📊 Solar PV Integration Analysis Report **Forecast Period:** {days} days **Weather Impact Included:** {'Yes' if weather_impact else 'No'} ### 🔋 Generation Summary: - **Total Solar Generation:** {savings['total_solar_generation_MWh']} MWh - **Auxiliary Load Contribution:** {savings['solar_contribution_MWh']} MWh - **Load Reduction:** {savings['auxiliary_load_reduction_percent']}% ### 💰 Economic Benefits: - **Cost Savings:** ${savings['cost_savings_USD']:,} - **Grid Dependency Reduction:** {savings['auxiliary_load_reduction_percent']}% ### 🌱 Environmental Impact: - **CO2 Reduction:** ~{savings['solar_contribution_MWh'] * 0.5:.1f} tons CO2eq - **Renewable Energy Share:** Increased by {savings['auxiliary_load_reduction_percent']}% --- *This analysis supports the research objective of integrating 5 MW solar PV with 1180 MW Combined Cycle Power Plant for efficient auxiliary consumption.* """ return report, fig except Exception as e: return f"❌ Error in forecasting: {str(e)}", None def get_system_status(): """Get current system status""" global df_global, forecaster if df_global is None: return "❌ System not initialized" status = f""" ## 🔧 System Status **Data Loaded:** ✅ {len(df_global):,} records **Date Range:** {df_global.index.min()} to {df_global.index.max()} **Prophet Model:** {'✅ Trained' if forecaster.prophet_model else '❌ Not trained'} **LSTM Model:** {'✅ Trained' if forecaster.lstm_model else '❌ Not trained'} **Available Features:** {chr(10).join([f"• {col}" for col in df_global.columns])} """ return status # Create Gradio Interface def create_interface(): with gr.Blocks( title="Solar PV Integration Forecasting System", theme=gr.themes.Soft(), css=""" .gradio-container { max-width: 1200px !important; } .header { text-align: center; background: linear-gradient(90deg, #1e3c72, #2a5298); color: white; padding: 20px; border-radius: 10px; margin-bottom: 20px; } """ ) as iface: gr.Markdown("""
MSc Thesis Research: Integrating 5 MW Solar Project with 1180 MW Combined Cycle Power Plant
Student: Muhammad Saddan | Supervisor: Dr Muhammad Asghar Saqib