Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,243 +1,55 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
#
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
Returns:
|
| 57 |
-
- fig (Figure): A plotly figure showing the optimized energy dispatch and state of charge (SOC) over time.
|
| 58 |
-
- curtailment_values (list): List of curtailment values for visualization.
|
| 59 |
-
- price_per_hour (list): List of estimated electricity price per hour for visualization.
|
| 60 |
-
"""
|
| 61 |
-
data, error = get_renewable_energy_data(city_code)
|
| 62 |
-
if error:
|
| 63 |
-
st.error(error)
|
| 64 |
-
return None, None, None
|
| 65 |
-
|
| 66 |
-
# Convert capacity factor columns to numeric values, handling any errors and filling missing data.
|
| 67 |
-
for col in data.columns[1:]:
|
| 68 |
-
data[col] = pd.to_numeric(data[col], errors='coerce')
|
| 69 |
-
|
| 70 |
-
data = data.fillna(0) # Fill missing values with zero.
|
| 71 |
-
|
| 72 |
-
# Extract necessary data for optimization.
|
| 73 |
-
time_steps = range(len(data['Time']))
|
| 74 |
-
if 'solar hourly capacity factor' not in data.columns:
|
| 75 |
-
st.error("Solar data is missing in the retrieved dataset.")
|
| 76 |
-
return None, None, None
|
| 77 |
-
|
| 78 |
-
solar_cf = data['solar hourly capacity factor']
|
| 79 |
-
onshore_wind_cf = data.get('onshore_wind hourly capacity factor', pd.Series([0]*len(data)))
|
| 80 |
-
offshore_wind_cf = data.get('offshore_wind hourly capacity factor', pd.Series([0]*len(data)))
|
| 81 |
-
river_cf = data.get('river hourly capacity factor', pd.Series([0]*len(data)))
|
| 82 |
-
demand_cf = data.get('demand hourly capacity factor', pd.Series([0]*len(data)))
|
| 83 |
-
|
| 84 |
-
# Define regions and technologies.
|
| 85 |
-
regions = ['region1']
|
| 86 |
-
technologies = ['solar', 'onshore_wind', 'offshore_wind', 'river']
|
| 87 |
-
capacity_factor = {
|
| 88 |
-
'solar': solar_cf,
|
| 89 |
-
'onshore_wind': onshore_wind_cf,
|
| 90 |
-
'offshore_wind': offshore_wind_cf,
|
| 91 |
-
'river': river_cf
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
# Cost definitions for renewable capacity and battery.
|
| 95 |
-
renewable_capacity_cost = {'solar': solar_cost, 'onshore_wind': onshore_wind_cost, 'offshore_wind': offshore_wind_cost, 'river': river_cost}
|
| 96 |
-
battery_cost_per_mwh = battery_cost
|
| 97 |
-
battery_efficiency = 0.9 # Battery efficiency.
|
| 98 |
-
|
| 99 |
-
# Scale demand from capacity factor and yearly demand in TWh/year to hourly demand in MW.
|
| 100 |
-
demand = demand_cf * yearly_demand / 100 * 1000 * 1000
|
| 101 |
-
|
| 102 |
-
# Define LP variables for renewable capacities, curtailment, battery capacity, charge, discharge, and state of charge (SOC).
|
| 103 |
-
renewable_capacity = pulp.LpVariable.dicts("renewable_capacity",
|
| 104 |
-
[(r, g) for r in regions for g in technologies],
|
| 105 |
-
lowBound=0, cat='Continuous')
|
| 106 |
-
curtailment = pulp.LpVariable.dicts("curtailment",
|
| 107 |
-
[(r, t) for r in regions for t in time_steps],
|
| 108 |
-
lowBound=0, cat='Continuous')
|
| 109 |
-
battery_capacity = pulp.LpVariable("battery_capacity", lowBound=0, cat='Continuous')
|
| 110 |
-
battery_charge = pulp.LpVariable.dicts("battery_charge", time_steps, lowBound=0, cat='Continuous')
|
| 111 |
-
battery_discharge = pulp.LpVariable.dicts("battery_discharge", time_steps, lowBound=0, cat='Continuous')
|
| 112 |
-
SOC = pulp.LpVariable.dicts("SOC", time_steps, lowBound=0, cat='Continuous')
|
| 113 |
-
|
| 114 |
-
# Define the optimization problem: minimize the total system cost.
|
| 115 |
-
model = pulp.LpProblem("EnergySystemOptimizationWithBattery", pulp.LpMinimize)
|
| 116 |
-
|
| 117 |
-
model += pulp.lpSum([renewable_capacity[(r, g)] * renewable_capacity_cost[g]
|
| 118 |
-
for r in regions for g in technologies]) + \
|
| 119 |
-
battery_capacity * battery_cost_per_mwh, "TotalCost"
|
| 120 |
-
|
| 121 |
-
# Constraints to meet demand, manage curtailment, and update state of charge for the battery.
|
| 122 |
-
for r in regions:
|
| 123 |
-
for t in time_steps:
|
| 124 |
-
model += pulp.lpSum([renewable_capacity[(r, g)] * capacity_factor[g][t]
|
| 125 |
-
for g in technologies]) + battery_discharge[t] == demand[t] + battery_charge[t] + curtailment[(r,t)], f"DemandConstraint_{r}_{t}"
|
| 126 |
-
|
| 127 |
-
if t == 0:
|
| 128 |
-
model += SOC[t] == battery_charge[t] * battery_efficiency - battery_discharge[t] * (1 / battery_efficiency), f"SOCUpdate_{t}"
|
| 129 |
-
else:
|
| 130 |
-
model += SOC[t] == SOC[t-1] + battery_charge[t] * battery_efficiency - battery_discharge[t] * (1 / battery_efficiency), f"SOCUpdate_{t}"
|
| 131 |
-
|
| 132 |
-
model += SOC[t] <= battery_capacity, f"SOCUpperBound_{t}"
|
| 133 |
-
|
| 134 |
-
# Solve the optimization model.
|
| 135 |
-
model.solve()
|
| 136 |
-
|
| 137 |
-
# Extract optimized supply and battery values.
|
| 138 |
-
supply_solar = solar_cf * renewable_capacity[('region1', 'solar')].varValue
|
| 139 |
-
supply_onshore_wind = onshore_wind_cf * renewable_capacity[('region1', 'onshore_wind')].varValue
|
| 140 |
-
supply_offshore_wind = offshore_wind_cf * renewable_capacity[('region1', 'offshore_wind')].varValue
|
| 141 |
-
supply_river = river_cf * renewable_capacity[('region1', 'river')].varValue
|
| 142 |
-
|
| 143 |
-
battery_discharge_values = [battery_discharge[t].varValue for t in time_steps]
|
| 144 |
-
battery_charge_values = [-battery_charge[t].varValue for t in time_steps]
|
| 145 |
-
SOC_values = [SOC[t].varValue for t in time_steps]
|
| 146 |
-
curtailment_values = [curtailment[(r, t)].varValue for r in regions for t in time_steps]
|
| 147 |
-
|
| 148 |
-
# Calculate price per hour based on demand and generation (simplified example).
|
| 149 |
-
price_per_hour = [100 + 0.05 * demand[t] for t in time_steps] # Base price + factor for demand.
|
| 150 |
-
|
| 151 |
-
# Normalize state of charge (SOC) to a percentage.
|
| 152 |
-
max_SOC = max(SOC_values)
|
| 153 |
-
SOC_normalized = [(soc / max_SOC) * 100 for soc in SOC_values] if max_SOC > 0 else [0] * len(SOC_values)
|
| 154 |
-
|
| 155 |
-
# Create figure for power supply and demand
|
| 156 |
-
fig_supply_demand = go.Figure()
|
| 157 |
-
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=supply_solar, mode='lines', stackgroup='one', name='Solar'))
|
| 158 |
-
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=supply_onshore_wind, mode='lines', stackgroup='one', name='Onshore Wind'))
|
| 159 |
-
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=supply_offshore_wind, mode='lines', stackgroup='one', name='Offshore Wind'))
|
| 160 |
-
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=supply_river, mode='lines', stackgroup='one', name='River'))
|
| 161 |
-
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=battery_discharge_values, mode='lines', name='Battery Discharge', line=dict(color='red')))
|
| 162 |
-
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=battery_charge_values, mode='lines', name='Battery Charge', line=dict(color='blue')))
|
| 163 |
-
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=-demand, mode='lines', name='Demand', line=dict(color='black')))
|
| 164 |
-
|
| 165 |
-
# Create figure for state of charge (SOC)
|
| 166 |
-
fig_soc = go.Figure()
|
| 167 |
-
fig_soc.add_trace(go.Scatter(x=data['Time'], y=SOC_normalized, mode='lines', name='State of Charge (SOC)'))
|
| 168 |
-
|
| 169 |
-
# Create figure for electricity price over time
|
| 170 |
-
fig_price = go.Figure()
|
| 171 |
-
fig_price.add_trace(go.Scatter(x=data['Time'], y=price_per_hour, mode='lines', name='Electricity Price'))
|
| 172 |
-
|
| 173 |
-
# Layout settings for the figures
|
| 174 |
-
fig_supply_demand.update_layout(
|
| 175 |
-
title_text='Power Supply and Demand',
|
| 176 |
-
yaxis_title='Power dispatch (MW)',
|
| 177 |
-
legend_title='Source',
|
| 178 |
-
font=dict(size=12),
|
| 179 |
-
margin=dict(l=40, r=40, t=40, b=40),
|
| 180 |
-
hovermode='x unified',
|
| 181 |
-
plot_bgcolor='white',
|
| 182 |
-
xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
|
| 183 |
-
yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
|
| 184 |
-
)
|
| 185 |
-
|
| 186 |
-
fig_soc.update_layout(
|
| 187 |
-
title_text='State of Charge (Battery)',
|
| 188 |
-
yaxis_title='State of Charge (%)',
|
| 189 |
-
font=dict(size=12),
|
| 190 |
-
margin=dict(l=40, r=40, t=40, b=40),
|
| 191 |
-
hovermode='x unified',
|
| 192 |
-
plot_bgcolor='white',
|
| 193 |
-
xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
|
| 194 |
-
yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
|
| 195 |
-
)
|
| 196 |
-
|
| 197 |
-
fig_price.update_layout(
|
| 198 |
-
title_text='Electricity Price Over Time',
|
| 199 |
-
yaxis_title='Electricity Price (\u00a5/MWh)',
|
| 200 |
-
font=dict(size=12),
|
| 201 |
-
margin=dict(l=40, r=40, t=40, b=40),
|
| 202 |
-
hovermode='x unified',
|
| 203 |
-
plot_bgcolor='white',
|
| 204 |
-
xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
|
| 205 |
-
yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
|
| 206 |
-
)
|
| 207 |
-
|
| 208 |
-
return fig_supply_demand, fig_soc, fig_price, curtailment_values, price_per_hour
|
| 209 |
-
|
| 210 |
-
# Streamlit UI for the application
|
| 211 |
-
st.set_page_config(page_title='Renewable Energy System Optimization', layout='wide')
|
| 212 |
-
st.title('Renewable Energy System Optimization')
|
| 213 |
-
|
| 214 |
-
# Explanation of the project
|
| 215 |
-
st.markdown("""
|
| 216 |
-
### Overview
|
| 217 |
-
This application is designed to help researchers and policymakers explore and optimize renewable energy systems for a specified region. By inputting cost parameters for different renewable energy sources and energy storage systems, the application determines the optimal mix of resources to meet energy demand while minimizing cost.
|
| 218 |
-
|
| 219 |
-
The optimization problem is solved using linear programming, ensuring a balance between supply and demand, and incorporating battery energy storage to manage intermittency issues inherent in renewable energy.
|
| 220 |
-
|
| 221 |
-
The visualizations provided help to better understand how different energy sources contribute to the overall power supply, how energy storage systems are utilized, and the impact of cost variations on energy prices.
|
| 222 |
-
""")
|
| 223 |
-
|
| 224 |
-
# Input fields for the user
|
| 225 |
-
with st.sidebar:
|
| 226 |
-
st.header('Input Parameters')
|
| 227 |
-
city_code = st.text_input("Enter City Code", value="")
|
| 228 |
-
solar_cost = st.number_input("Solar Capacity Cost (\u00a5/MW)", value=80.0, help="Estimated average cost of solar capacity per MW")
|
| 229 |
-
onshore_wind_cost = st.number_input("Onshore Wind Capacity Cost (\u00a5/MW)", value=120.0, help="Estimated average cost of onshore wind capacity per MW")
|
| 230 |
-
offshore_wind_cost = st.number_input("Offshore Wind Capacity Cost (\u00a5/MW)", value=180.0, help="Estimated average cost of offshore wind capacity per MW")
|
| 231 |
-
river_cost = st.number_input("River Capacity Cost (\u00a5/MW)", value=100.0, help="Estimated average cost of river (hydro) capacity per MW")
|
| 232 |
-
battery_cost = st.number_input("Battery Cost (\u00a5/MWh)", value=80.0, help="Estimated average cost of battery storage per MWh")
|
| 233 |
-
yearly_demand = st.number_input("Yearly Power Demand (TWh/year)", value=15.0, help="Total yearly power demand in TWh")
|
| 234 |
-
|
| 235 |
-
# Button to trigger optimization
|
| 236 |
-
if st.button('Calculate Optimal Energy Mix'):
|
| 237 |
-
fig_supply_demand, fig_soc, fig_price, curtailment_values, price_per_hour = optimize_energy_system(city_code, solar_cost, onshore_wind_cost, offshore_wind_cost, river_cost, battery_cost, yearly_demand)
|
| 238 |
-
if fig_supply_demand:
|
| 239 |
-
st.plotly_chart(fig_supply_demand, use_container_width=True, height=800)
|
| 240 |
-
if fig_soc:
|
| 241 |
-
st.plotly_chart(fig_soc, use_container_width=True, height=800)
|
| 242 |
-
if fig_price:
|
| 243 |
-
st.plotly_chart(fig_price, use_container_width=True, height=800)
|
|
|
|
| 1 |
+
# Create a figure for power supply and demand
|
| 2 |
+
fig_supply_demand = go.Figure()
|
| 3 |
+
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=supply_solar, mode='lines', stackgroup='one', name='Solar'))
|
| 4 |
+
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=supply_onshore_wind, mode='lines', stackgroup='one', name='Onshore Wind'))
|
| 5 |
+
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=supply_offshore_wind, mode='lines', stackgroup='one', name='Offshore Wind'))
|
| 6 |
+
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=supply_river, mode='lines', stackgroup='one', name='River'))
|
| 7 |
+
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=battery_discharge_values, mode='lines', name='Battery Discharge', line=dict(color='red')))
|
| 8 |
+
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=battery_charge_values, mode='lines', name='Battery Charge', line=dict(color='blue')))
|
| 9 |
+
fig_supply_demand.add_trace(go.Scatter(x=data['Time'], y=-demand, mode='lines', name='Demand', line=dict(color='black')))
|
| 10 |
+
|
| 11 |
+
# Create a figure for state of charge (SOC)
|
| 12 |
+
fig_soc = go.Figure()
|
| 13 |
+
fig_soc.add_trace(go.Scatter(x=data['Time'], y=SOC_normalized, mode='lines', name='State of Charge (SOC)'))
|
| 14 |
+
|
| 15 |
+
# Create a figure for electricity price over time
|
| 16 |
+
fig_price = go.Figure()
|
| 17 |
+
fig_price.add_trace(go.Scatter(x=data['Time'], y=price_per_hour, mode='lines', name='Electricity Price'))
|
| 18 |
+
|
| 19 |
+
# Update layouts for each figure
|
| 20 |
+
fig_supply_demand.update_layout(
|
| 21 |
+
title_text='Power Supply and Demand',
|
| 22 |
+
yaxis_title='Power dispatch (MW)',
|
| 23 |
+
legend_title='Source',
|
| 24 |
+
font=dict(size=12),
|
| 25 |
+
margin=dict(l=40, r=40, t=40, b=40),
|
| 26 |
+
hovermode='x unified',
|
| 27 |
+
plot_bgcolor='white',
|
| 28 |
+
xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
|
| 29 |
+
yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
fig_soc.update_layout(
|
| 33 |
+
title_text='State of Charge (Battery)',
|
| 34 |
+
yaxis_title='State of Charge (%)',
|
| 35 |
+
font=dict(size=12),
|
| 36 |
+
margin=dict(l=40, r=40, t=40, b=40),
|
| 37 |
+
hovermode='x unified',
|
| 38 |
+
plot_bgcolor='white',
|
| 39 |
+
xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
|
| 40 |
+
yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
|
| 41 |
+
)
|
| 42 |
+
|
| 43 |
+
fig_price.update_layout(
|
| 44 |
+
title_text='Electricity Price Over Time',
|
| 45 |
+
yaxis_title='Electricity Price (\u00a5/MWh)',
|
| 46 |
+
font=dict(size=12),
|
| 47 |
+
margin=dict(l=40, r=40, t=40, b=40),
|
| 48 |
+
hovermode='x unified',
|
| 49 |
+
plot_bgcolor='white',
|
| 50 |
+
xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
|
| 51 |
+
yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
# Return the figures
|
| 55 |
+
return fig_supply_demand, fig_soc, fig_price, curtailment_values, price_per_hour
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|