Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -2,20 +2,21 @@ import streamlit as st
|
|
| 2 |
import requests
|
| 3 |
import pandas as pd
|
| 4 |
import pulp
|
| 5 |
-
import plotly.graph_objs as go
|
| 6 |
import plotly.express as px
|
|
|
|
| 7 |
import numpy as np
|
|
|
|
| 8 |
|
| 9 |
-
#
|
| 10 |
def get_renewable_energy_data(city_code):
|
| 11 |
url = f"https://energy-sustainability.jp/_ajax/renewable_energy/get/?code={city_code}"
|
| 12 |
response = requests.get(url)
|
| 13 |
if response.status_code != 200:
|
| 14 |
-
return None, "
|
| 15 |
|
| 16 |
data = response.json()
|
| 17 |
if not data:
|
| 18 |
-
return None, "
|
| 19 |
|
| 20 |
base_times = data[next(iter(data))]['x']
|
| 21 |
result_df = pd.DataFrame({"Time": base_times})
|
|
@@ -27,12 +28,14 @@ def get_renewable_energy_data(city_code):
|
|
| 27 |
|
| 28 |
return result_df, None
|
| 29 |
|
| 30 |
-
#
|
| 31 |
-
def optimize_energy_system(city_code, solar_cost, onshore_wind_cost, offshore_wind_cost, river_cost,
|
|
|
|
|
|
|
| 32 |
data, error = get_renewable_energy_data(city_code)
|
| 33 |
if error:
|
| 34 |
st.error(error)
|
| 35 |
-
return None
|
| 36 |
|
| 37 |
for col in data.columns[1:]:
|
| 38 |
data[col] = pd.to_numeric(data[col], errors='coerce')
|
|
@@ -43,18 +46,40 @@ def optimize_energy_system(city_code, solar_cost, onshore_wind_cost, offshore_wi
|
|
| 43 |
onshore_wind_cf = data['onshore_wind hourly capacity factor']
|
| 44 |
offshore_wind_cf = data['offshore_wind hourly capacity factor']
|
| 45 |
river_cf = data['river hourly capacity factor']
|
|
|
|
|
|
|
| 46 |
demand_cf = data['demand hourly capacity factor']
|
| 47 |
|
| 48 |
regions = ['region1']
|
| 49 |
-
technologies = ['solar', 'onshore_wind', 'offshore_wind', 'river']
|
| 50 |
capacity_factor = {
|
| 51 |
'solar': solar_cf,
|
| 52 |
'onshore_wind': onshore_wind_cf,
|
| 53 |
'offshore_wind': offshore_wind_cf,
|
| 54 |
-
'river': river_cf
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
}
|
| 56 |
|
| 57 |
-
renewable_capacity_cost = {'solar': solar_cost, 'onshore_wind': onshore_wind_cost, 'offshore_wind': offshore_wind_cost, 'river': river_cost}
|
| 58 |
battery_cost_per_mwh = battery_cost
|
| 59 |
battery_efficiency = 0.9
|
| 60 |
|
|
@@ -71,269 +96,142 @@ def optimize_energy_system(city_code, solar_cost, onshore_wind_cost, offshore_wi
|
|
| 71 |
battery_discharge = pulp.LpVariable.dicts("battery_discharge", time_steps, lowBound=0, cat='Continuous')
|
| 72 |
SOC = pulp.LpVariable.dicts("SOC", time_steps, lowBound=0, cat='Continuous')
|
| 73 |
|
| 74 |
-
|
|
|
|
| 75 |
|
|
|
|
|
|
|
|
|
|
| 76 |
model += pulp.lpSum([renewable_capacity[(r, g)] * renewable_capacity_cost[g]
|
| 77 |
for r in regions for g in technologies]) + \
|
| 78 |
-
battery_capacity * battery_cost_per_mwh
|
|
|
|
| 79 |
|
|
|
|
| 80 |
for r in regions:
|
| 81 |
for t in time_steps:
|
| 82 |
model += pulp.lpSum([renewable_capacity[(r, g)] * capacity_factor[g][t]
|
| 83 |
for g in technologies]) + battery_discharge[t] == demand[t] + battery_charge[t] + curtailment[(r, t)], f"DemandConstraint_{r}_{t}"
|
| 84 |
-
|
| 85 |
if t == 0:
|
| 86 |
model += SOC[t] == battery_charge[t] * battery_efficiency - battery_discharge[t] * (1 / battery_efficiency), f"SOCUpdate_{t}"
|
| 87 |
else:
|
| 88 |
model += SOC[t] == SOC[t - 1] + battery_charge[t] * battery_efficiency - battery_discharge[t] * (1 / battery_efficiency), f"SOCUpdate_{t}"
|
| 89 |
-
|
| 90 |
model += SOC[t] <= battery_capacity, f"SOCUpperBound_{t}"
|
| 91 |
|
| 92 |
-
|
| 93 |
-
model += renewable_capacity[(
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
model += renewable_capacity[('region1', '
|
| 97 |
-
model += renewable_capacity[('region1', '
|
| 98 |
-
model += renewable_capacity[('region1', '
|
| 99 |
-
model += renewable_capacity[('region1', '
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
model.solve()
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
margin=dict(l=40, r=40, t=40, b=40),
|
| 133 |
-
hovermode='x unified',
|
| 134 |
-
plot_bgcolor='white',
|
| 135 |
-
xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
|
| 136 |
-
yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
|
| 137 |
-
)
|
| 138 |
-
|
| 139 |
-
# Heatmap generation for each renewable energy source
|
| 140 |
-
heatmaps = []
|
| 141 |
-
for energy_source in ['solar', 'onshore_wind', 'offshore_wind', 'river']:
|
| 142 |
-
df_heatmap = data[['Time', f'{energy_source} hourly capacity factor']].copy()
|
| 143 |
-
df_heatmap['Time'] = pd.to_datetime(df_heatmap['Time'], errors='coerce')
|
| 144 |
-
df_heatmap['day_of_year'] = df_heatmap['Time'].dt.dayofyear
|
| 145 |
-
df_heatmap['hour_of_day'] = df_heatmap['Time'].dt.hour
|
| 146 |
-
|
| 147 |
-
pivot_df = df_heatmap.pivot_table(
|
| 148 |
-
index='hour_of_day',
|
| 149 |
-
columns='day_of_year',
|
| 150 |
-
values=f'{energy_source} hourly capacity factor',
|
| 151 |
-
aggfunc='mean'
|
| 152 |
-
)
|
| 153 |
-
|
| 154 |
-
fig_heatmap = px.imshow(
|
| 155 |
-
pivot_df.values,
|
| 156 |
-
labels=dict(x="Day of Year", y="Hour of Day", color=f"{energy_source.replace('_', ' ').title()} Capacity Factor"),
|
| 157 |
-
x=pivot_df.columns,
|
| 158 |
-
y=pivot_df.index,
|
| 159 |
-
aspect="auto",
|
| 160 |
-
color_continuous_scale='Plasma'
|
| 161 |
-
)
|
| 162 |
-
|
| 163 |
-
fig_heatmap.update_layout(
|
| 164 |
-
title=f'{energy_source.replace("_", " ").title()} Hourly Capacity Factor (24 Hours x 365 Days)',
|
| 165 |
-
xaxis_title='Day of Year',
|
| 166 |
-
yaxis_title='Hour of Day',
|
| 167 |
-
font=dict(size=12),
|
| 168 |
-
plot_bgcolor='white',
|
| 169 |
-
margin=dict(l=40, r=40, t=40, b=40),
|
| 170 |
-
)
|
| 171 |
-
heatmaps.append(fig_heatmap)
|
| 172 |
-
|
| 173 |
-
# Create capacity range visualization for each technology
|
| 174 |
-
fig_capacity_ranges = go.Figure()
|
| 175 |
-
technologies = ['solar', 'onshore_wind', 'offshore_wind', 'river']
|
| 176 |
-
capacity_ranges = [solar_range, wind_range, offshore_wind_range, river_range]
|
| 177 |
-
optimized_capacities = [
|
| 178 |
-
renewable_capacity[('region1', 'solar')].varValue,
|
| 179 |
-
renewable_capacity[('region1', 'onshore_wind')].varValue,
|
| 180 |
-
renewable_capacity[('region1', 'offshore_wind')].varValue,
|
| 181 |
-
renewable_capacity[('region1', 'river')].varValue
|
| 182 |
-
]
|
| 183 |
-
|
| 184 |
-
for tech, cap_range, optimized_cap in zip(technologies, capacity_ranges, optimized_capacities):
|
| 185 |
-
fig_capacity_ranges.add_trace(go.Scatter(
|
| 186 |
-
x=[tech, tech],
|
| 187 |
-
y=cap_range,
|
| 188 |
-
mode='lines',
|
| 189 |
-
name=f'{tech} capacity range',
|
| 190 |
-
line=dict(color='blue', width=4)
|
| 191 |
-
))
|
| 192 |
-
fig_capacity_ranges.add_trace(go.Scatter(
|
| 193 |
-
x=[tech],
|
| 194 |
-
y=[optimized_cap],
|
| 195 |
-
mode='markers',
|
| 196 |
-
name=f'{tech} optimized capacity',
|
| 197 |
-
marker=dict(color='red', symbol='x', size=10)
|
| 198 |
-
))
|
| 199 |
-
|
| 200 |
-
fig_capacity_ranges.update_layout(
|
| 201 |
-
title_text='Optimized Capacity vs. Capacity Ranges',
|
| 202 |
-
title_x=0.5,
|
| 203 |
-
yaxis_title='Capacity (MW)',
|
| 204 |
-
xaxis_title='Technology',
|
| 205 |
-
font=dict(size=12),
|
| 206 |
-
margin=dict(l=40, r=40, t=40, b=40),
|
| 207 |
-
hovermode='x unified',
|
| 208 |
-
plot_bgcolor='white',
|
| 209 |
-
xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
|
| 210 |
-
yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
|
| 211 |
-
)
|
| 212 |
-
|
| 213 |
-
return fig_energy, heatmaps, curtailment_values, SOC_normalized, fig_capacity_ranges, renewable_capacity
|
| 214 |
-
|
| 215 |
-
# 資源コストの感度解析を行う関数
|
| 216 |
-
def analyze_cost_sensitivity(renewable_capacity_cost, technologies, renewable_capacity):
|
| 217 |
-
# コストの変動範囲(0.5倍から1.5倍)
|
| 218 |
-
cost_multipliers = np.linspace(0.5, 1.5, 11)
|
| 219 |
-
|
| 220 |
-
# 結果を格納する辞書
|
| 221 |
-
sensitivity_results = {}
|
| 222 |
-
|
| 223 |
-
for tech in technologies:
|
| 224 |
-
# 各技術ごとのコスト変動に対する総コストの変化を計算
|
| 225 |
-
original_cost = renewable_capacity_cost[tech]
|
| 226 |
-
total_costs = []
|
| 227 |
-
|
| 228 |
-
for multiplier in cost_multipliers:
|
| 229 |
-
# コストを変更
|
| 230 |
-
modified_cost = original_cost * multiplier
|
| 231 |
-
# 総コスト = 変更後のコスト * 設備容量
|
| 232 |
-
total_cost = modified_cost * renewable_capacity[('region1', tech)].varValue
|
| 233 |
-
total_costs.append(total_cost)
|
| 234 |
-
|
| 235 |
-
# 技術ごとに結果を保存
|
| 236 |
-
sensitivity_results[tech] = total_costs
|
| 237 |
-
|
| 238 |
-
# 可視化
|
| 239 |
-
fig = go.Figure()
|
| 240 |
-
|
| 241 |
-
for tech, total_costs in sensitivity_results.items():
|
| 242 |
-
fig.add_trace(go.Scatter(
|
| 243 |
-
x=cost_multipliers,
|
| 244 |
-
y=total_costs,
|
| 245 |
-
mode='lines+markers',
|
| 246 |
-
name=f'{tech} Cost Sensitivity'
|
| 247 |
-
))
|
| 248 |
-
|
| 249 |
-
# グラフのレイアウト
|
| 250 |
-
fig.update_layout(
|
| 251 |
-
title='Cost Sensitivity Analysis: Impact of Cost Changes on Total System Cost',
|
| 252 |
-
xaxis_title='Cost Multiplier (0.5x to 1.5x)',
|
| 253 |
-
yaxis_title='Total System Cost (¥)',
|
| 254 |
-
hovermode='x unified',
|
| 255 |
-
plot_bgcolor='white',
|
| 256 |
-
xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
|
| 257 |
-
yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
|
| 258 |
-
)
|
| 259 |
-
|
| 260 |
-
return fig
|
| 261 |
|
| 262 |
# Streamlit UI setup
|
| 263 |
-
st.set_page_config(page_title='
|
| 264 |
-
st.title('
|
| 265 |
-
|
| 266 |
-
st.markdown("""
|
| 267 |
-
### Model Overview
|
| 268 |
-
|
| 269 |
-
This application is designed to optimize renewable energy systems for a specific region. The model allows the user to set the costs for different renewable energy technologies and battery storage, as well as minimum and maximum capacity limits for each technology. The optimization uses linear programming to minimize the total cost while ensuring demand is met, incorporating energy storage to help manage intermittency.
|
| 270 |
|
| 271 |
-
|
| 272 |
-
- Solar PV
|
| 273 |
-
- Onshore Wind
|
| 274 |
-
- Offshore Wind
|
| 275 |
-
- Run of River (Hydro)
|
| 276 |
-
|
| 277 |
-
The optimization problem aims to balance supply and demand at minimal cost, while also providing flexibility in the form of battery energy storage. Curtailment and battery state of charge are also considered in the model.
|
| 278 |
-
|
| 279 |
-
""")
|
| 280 |
|
| 281 |
with st.sidebar:
|
| 282 |
st.header('Input Parameters')
|
| 283 |
-
city_code = st.text_input("
|
| 284 |
-
solar_cost = st.number_input("Solar
|
| 285 |
-
onshore_wind_cost = st.number_input("Onshore Wind
|
| 286 |
-
offshore_wind_cost = st.number_input("Offshore Wind
|
| 287 |
-
river_cost = st.number_input("River
|
| 288 |
-
|
| 289 |
-
|
|
|
|
|
|
|
| 290 |
solar_range = st.slider("Solar Capacity Range (MW)", 0, 10000, (0, 10000))
|
| 291 |
wind_range = st.slider("Onshore Wind Capacity Range (MW)", 0, 10000, (0, 10000))
|
| 292 |
offshore_wind_range = st.slider("Offshore Wind Capacity Range (MW)", 0, 10000, (0, 10000))
|
| 293 |
river_range = st.slider("River Capacity Range (MW)", 0, 10000, (0, 10000))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
if st.button('Calculate Optimal Energy Mix'):
|
| 298 |
-
fig_energy, heatmaps, curtailment_values, soc_per_hour, fig_capacity_ranges, renewable_capacity = optimize_energy_system(
|
| 299 |
-
city_code, solar_cost, onshore_wind_cost, offshore_wind_cost, river_cost, battery_cost, yearly_demand, solar_range, wind_range, river_range, offshore_wind_range
|
| 300 |
-
)
|
| 301 |
-
|
| 302 |
-
if fig_energy:
|
| 303 |
-
st.plotly_chart(fig_energy, use_container_width=True, height=800)
|
| 304 |
-
|
| 305 |
-
# Additional visualizations
|
| 306 |
-
st.markdown("### Hourly Capacity Factor Heatmaps")
|
| 307 |
-
for fig_heatmap in heatmaps:
|
| 308 |
-
st.plotly_chart(fig_heatmap, use_container_width=True, height=800)
|
| 309 |
-
|
| 310 |
-
st.markdown("### Additional Analysis")
|
| 311 |
-
st.markdown("The following plots provide additional insights into the renewable energy mix, curtailment, and electricity price variations.")
|
| 312 |
-
|
| 313 |
-
# Plot curtailment over time
|
| 314 |
-
curtailment_df = pd.DataFrame({"Time": fig_energy.data[0].x, "Curtailment (MW)": curtailment_values})
|
| 315 |
-
fig_curtailment = px.line(curtailment_df, x='Time', y='Curtailment (MW)', title='Curtailment Over Time', template='plotly_white')
|
| 316 |
-
st.plotly_chart(fig_curtailment, use_container_width=True, height=800)
|
| 317 |
-
|
| 318 |
-
# Plot electricity price variation over time
|
| 319 |
-
soc_df = pd.DataFrame({"Time": fig_energy.data[0].x, "State of charge [%]": soc_per_hour})
|
| 320 |
-
fig_battery_operation = px.line(soc_df, x='Time', y='State of charge [%]', title='State of charge in battery', template='plotly_white')
|
| 321 |
-
st.plotly_chart(fig_battery_operation, use_container_width=True, height=800)
|
| 322 |
-
|
| 323 |
-
# Plot optimized capacity vs. capacity ranges
|
| 324 |
-
st.plotly_chart(fig_capacity_ranges, use_container_width=True, height=800)
|
| 325 |
-
|
| 326 |
-
calculated_optimal_energy_mix = True
|
| 327 |
-
|
| 328 |
-
# Streamlit UIに感度解析ボタンを追加
|
| 329 |
-
if st.button('Analyze Cost Sensitivity'):
|
| 330 |
-
if calculated_optimal_energy_mix:
|
| 331 |
-
fig_sensitivity = analyze_cost_sensitivity({
|
| 332 |
-
'solar': solar_cost,
|
| 333 |
-
'onshore_wind': onshore_wind_cost,
|
| 334 |
-
'offshore_wind': offshore_wind_cost,
|
| 335 |
-
'river': river_cost
|
| 336 |
-
}, ['solar', 'onshore_wind', 'offshore_wind', 'river'], renewable_capacity)
|
| 337 |
-
st.plotly_chart(fig_sensitivity, use_container_width=True, height=800)
|
| 338 |
-
else:
|
| 339 |
-
st.error("Please calculate the optimal energy mix first before running the cost sensitivity analysis.")
|
|
|
|
| 2 |
import requests
|
| 3 |
import pandas as pd
|
| 4 |
import pulp
|
|
|
|
| 5 |
import plotly.express as px
|
| 6 |
+
import plotly.graph_objects as go
|
| 7 |
import numpy as np
|
| 8 |
+
import matplotlib.pyplot as plt
|
| 9 |
|
| 10 |
+
# Renewable energy data fetch function
|
| 11 |
def get_renewable_energy_data(city_code):
|
| 12 |
url = f"https://energy-sustainability.jp/_ajax/renewable_energy/get/?code={city_code}"
|
| 13 |
response = requests.get(url)
|
| 14 |
if response.status_code != 200:
|
| 15 |
+
return None, "Failed to retrieve data."
|
| 16 |
|
| 17 |
data = response.json()
|
| 18 |
if not data:
|
| 19 |
+
return None, "No data found."
|
| 20 |
|
| 21 |
base_times = data[next(iter(data))]['x']
|
| 22 |
result_df = pd.DataFrame({"Time": base_times})
|
|
|
|
| 28 |
|
| 29 |
return result_df, None
|
| 30 |
|
| 31 |
+
# Optimize energy system with MGA
|
| 32 |
+
def optimize_energy_system(city_code, solar_cost, onshore_wind_cost, offshore_wind_cost, river_cost, nuclear_cost, coal_cost,
|
| 33 |
+
battery_cost, yearly_demand, solar_range, wind_range, river_range, offshore_wind_range,
|
| 34 |
+
nuclear_range, coal_range, thresholds, selected_tech):
|
| 35 |
data, error = get_renewable_energy_data(city_code)
|
| 36 |
if error:
|
| 37 |
st.error(error)
|
| 38 |
+
return None
|
| 39 |
|
| 40 |
for col in data.columns[1:]:
|
| 41 |
data[col] = pd.to_numeric(data[col], errors='coerce')
|
|
|
|
| 46 |
onshore_wind_cf = data['onshore_wind hourly capacity factor']
|
| 47 |
offshore_wind_cf = data['offshore_wind hourly capacity factor']
|
| 48 |
river_cf = data['river hourly capacity factor']
|
| 49 |
+
nuclear_cf = 1.0 # Assume nuclear operates at full capacity
|
| 50 |
+
coal_cf = 0.8 # Assume coal operates at 80% capacity
|
| 51 |
demand_cf = data['demand hourly capacity factor']
|
| 52 |
|
| 53 |
regions = ['region1']
|
| 54 |
+
technologies = ['solar', 'onshore_wind', 'offshore_wind', 'river', 'nuclear', 'coal']
|
| 55 |
capacity_factor = {
|
| 56 |
'solar': solar_cf,
|
| 57 |
'onshore_wind': onshore_wind_cf,
|
| 58 |
'offshore_wind': offshore_wind_cf,
|
| 59 |
+
'river': river_cf,
|
| 60 |
+
'nuclear': [nuclear_cf] * len(time_steps),
|
| 61 |
+
'coal': [coal_cf] * len(time_steps)
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
renewable_capacity_cost = {
|
| 65 |
+
'solar': solar_cost,
|
| 66 |
+
'onshore_wind': onshore_wind_cost,
|
| 67 |
+
'offshore_wind': offshore_wind_cost,
|
| 68 |
+
'river': river_cost,
|
| 69 |
+
'nuclear': nuclear_cost,
|
| 70 |
+
'coal': coal_cost
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
# CO2 emissions per MWh
|
| 74 |
+
co2_emissions = {
|
| 75 |
+
'solar': 0,
|
| 76 |
+
'onshore_wind': 0,
|
| 77 |
+
'offshore_wind': 0,
|
| 78 |
+
'river': 0,
|
| 79 |
+
'nuclear': 0,
|
| 80 |
+
'coal': 820 # in kg CO2 per MWh
|
| 81 |
}
|
| 82 |
|
|
|
|
| 83 |
battery_cost_per_mwh = battery_cost
|
| 84 |
battery_efficiency = 0.9
|
| 85 |
|
|
|
|
| 96 |
battery_discharge = pulp.LpVariable.dicts("battery_discharge", time_steps, lowBound=0, cat='Continuous')
|
| 97 |
SOC = pulp.LpVariable.dicts("SOC", time_steps, lowBound=0, cat='Continuous')
|
| 98 |
|
| 99 |
+
# Add a variable for CO2 emissions
|
| 100 |
+
total_co2_emissions = pulp.LpVariable("total_co2_emissions", lowBound=0, cat='Continuous')
|
| 101 |
|
| 102 |
+
model = pulp.LpProblem("EnergySystemOptimizationWithBatteryAndCO2", pulp.LpMinimize)
|
| 103 |
+
|
| 104 |
+
# Objective: minimize total cost and CO2 emissions
|
| 105 |
model += pulp.lpSum([renewable_capacity[(r, g)] * renewable_capacity_cost[g]
|
| 106 |
for r in regions for g in technologies]) + \
|
| 107 |
+
battery_capacity * battery_cost_per_mwh + \
|
| 108 |
+
0.01 * total_co2_emissions, "TotalCostAndCO2"
|
| 109 |
|
| 110 |
+
# Constraints: meet demand, manage battery SOC, calculate CO2 emissions
|
| 111 |
for r in regions:
|
| 112 |
for t in time_steps:
|
| 113 |
model += pulp.lpSum([renewable_capacity[(r, g)] * capacity_factor[g][t]
|
| 114 |
for g in technologies]) + battery_discharge[t] == demand[t] + battery_charge[t] + curtailment[(r, t)], f"DemandConstraint_{r}_{t}"
|
|
|
|
| 115 |
if t == 0:
|
| 116 |
model += SOC[t] == battery_charge[t] * battery_efficiency - battery_discharge[t] * (1 / battery_efficiency), f"SOCUpdate_{t}"
|
| 117 |
else:
|
| 118 |
model += SOC[t] == SOC[t - 1] + battery_charge[t] * battery_efficiency - battery_discharge[t] * (1 / battery_efficiency), f"SOCUpdate_{t}"
|
|
|
|
| 119 |
model += SOC[t] <= battery_capacity, f"SOCUpperBound_{t}"
|
| 120 |
|
| 121 |
+
# Add CO2 emissions constraint
|
| 122 |
+
model += total_co2_emissions == pulp.lpSum([renewable_capacity[(r, 'coal')] * coal_cf * co2_emissions['coal'] for r in regions]), "CO2Emissions"
|
| 123 |
+
|
| 124 |
+
# Capacity range constraints
|
| 125 |
+
model += renewable_capacity[('region1', 'solar')] >= solar_range[0]
|
| 126 |
+
model += renewable_capacity[('region1', 'solar')] <= solar_range[1]
|
| 127 |
+
model += renewable_capacity[('region1', 'onshore_wind')] >= wind_range[0]
|
| 128 |
+
model += renewable_capacity[('region1', 'onshore_wind')] <= wind_range[1]
|
| 129 |
+
model += renewable_capacity[('region1', 'offshore_wind')] >= offshore_wind_range[0]
|
| 130 |
+
model += renewable_capacity[('region1', 'offshore_wind')] <= offshore_wind_range[1]
|
| 131 |
+
model += renewable_capacity[('region1', 'river')] >= river_range[0]
|
| 132 |
+
model += renewable_capacity[('region1', 'river')] <= river_range[1]
|
| 133 |
+
model += renewable_capacity[('region1', 'nuclear')] >= nuclear_range[0]
|
| 134 |
+
model += renewable_capacity[('region1', 'nuclear')] <= nuclear_range[1]
|
| 135 |
+
model += renewable_capacity[('region1', 'coal')] >= coal_range[0]
|
| 136 |
+
model += renewable_capacity[('region1', 'coal')] <= coal_range[1]
|
| 137 |
+
|
| 138 |
+
# Solve the initial model to find the optimal solution
|
| 139 |
model.solve()
|
| 140 |
+
optimal_cost = pulp.value(model.objective)
|
| 141 |
+
|
| 142 |
+
# MGA: Generate alternative solutions
|
| 143 |
+
alternative_solutions = []
|
| 144 |
+
for threshold in thresholds:
|
| 145 |
+
relaxed_cost = optimal_cost * (1 + threshold)
|
| 146 |
+
for tech in selected_tech:
|
| 147 |
+
alt_model = pulp.LpProblem(f"AlternativeModel_{tech}_{threshold}", pulp.LpMinimize)
|
| 148 |
+
alt_model += pulp.lpSum([renewable_capacity[(r, g)] * renewable_capacity_cost[g]
|
| 149 |
+
for r in regions for g in technologies]) + battery_capacity * battery_cost_per_mwh + 0.01 * total_co2_emissions <= relaxed_cost
|
| 150 |
+
|
| 151 |
+
# Avoid overlapping constraint names by appending unique suffixes
|
| 152 |
+
for i, (name, constraint) in enumerate(model.constraints.items()):
|
| 153 |
+
alt_model += constraint.copy(), f"{name}_{tech}_{threshold}_{i}"
|
| 154 |
+
|
| 155 |
+
# Modify the objective to minimize or maximize capacity of selected technology
|
| 156 |
+
alt_model += renewable_capacity[('region1', tech)], f"Minimize_{tech}_Capacity"
|
| 157 |
+
alt_model.solve()
|
| 158 |
+
|
| 159 |
+
if pulp.LpStatus[alt_model.status] == 'Optimal':
|
| 160 |
+
alternative_solutions.append({
|
| 161 |
+
'threshold': threshold,
|
| 162 |
+
'technology': tech,
|
| 163 |
+
'solution': {g: renewable_capacity[('region1', g)].varValue for g in technologies},
|
| 164 |
+
'battery_capacity': battery_capacity.varValue,
|
| 165 |
+
'total_cost': pulp.value(alt_model.objective),
|
| 166 |
+
'total_co2_emissions': total_co2_emissions.varValue
|
| 167 |
+
})
|
| 168 |
+
|
| 169 |
+
return alternative_solutions, data, renewable_capacity, battery_discharge, battery_charge, demand, curtailment
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
|
| 171 |
# Streamlit UI setup
|
| 172 |
+
st.set_page_config(page_title='Energy System Optimization with Nuclear, Coal, and CO2', layout='wide')
|
| 173 |
+
st.title('Energy System Optimization with MGA: Including Nuclear, Coal, and CO2 Considerations')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
|
| 175 |
+
technologies = ['solar', 'onshore_wind', 'offshore_wind', 'river', 'nuclear', 'coal']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
|
| 177 |
with st.sidebar:
|
| 178 |
st.header('Input Parameters')
|
| 179 |
+
city_code = st.text_input("City Code", "999999")
|
| 180 |
+
solar_cost = st.number_input("Solar Cost (¥/MW)", value=80.0)
|
| 181 |
+
onshore_wind_cost = st.number_input("Onshore Wind Cost (¥/MW)", value=120.0)
|
| 182 |
+
offshore_wind_cost = st.number_input("Offshore Wind Cost (¥/MW)", value=180.0)
|
| 183 |
+
river_cost = st.number_input("River Cost (¥/MW)", value=1000.0)
|
| 184 |
+
nuclear_cost = st.number_input("Nuclear Cost (¥/MW)", value=500.0)
|
| 185 |
+
coal_cost = st.number_input("Coal Cost (¥/MW)", value=300.0)
|
| 186 |
+
battery_cost = st.number_input("Battery Cost (¥/MWh)", value=80.0)
|
| 187 |
+
yearly_demand = st.number_input("Yearly Demand (TWh/year)", value=15.0)
|
| 188 |
solar_range = st.slider("Solar Capacity Range (MW)", 0, 10000, (0, 10000))
|
| 189 |
wind_range = st.slider("Onshore Wind Capacity Range (MW)", 0, 10000, (0, 10000))
|
| 190 |
offshore_wind_range = st.slider("Offshore Wind Capacity Range (MW)", 0, 10000, (0, 10000))
|
| 191 |
river_range = st.slider("River Capacity Range (MW)", 0, 10000, (0, 10000))
|
| 192 |
+
nuclear_range = st.slider("Nuclear Capacity Range (MW)", 0, 10000, (0, 10000))
|
| 193 |
+
coal_range = st.slider("Coal Capacity Range (MW)", 0, 10000, (0, 10000))
|
| 194 |
+
thresholds = st.multiselect("Thresholds (%)", [0, 5, 10], default=[0, 5])
|
| 195 |
+
selected_technologies = st.multiselect("Technologies", technologies, default=technologies)
|
| 196 |
+
|
| 197 |
+
if st.button("Run Optimization"):
|
| 198 |
+
solutions, data, renewable_capacity, battery_discharge, battery_charge, demand, curtailment = optimize_energy_system(
|
| 199 |
+
city_code, solar_cost, onshore_wind_cost, offshore_wind_cost, river_cost,
|
| 200 |
+
nuclear_cost, coal_cost, battery_cost, yearly_demand, solar_range, wind_range,
|
| 201 |
+
river_range, offshore_wind_range, nuclear_range, coal_range,
|
| 202 |
+
[t / 100 for t in thresholds], selected_technologies)
|
| 203 |
+
if solutions:
|
| 204 |
+
st.write("Optimization Results", solutions)
|
| 205 |
+
|
| 206 |
+
supply_solar = [renewable_capacity[("region1", "solar")].varValue * cf for cf in data['solar hourly capacity factor']]
|
| 207 |
+
supply_onshore_wind = [renewable_capacity[("region1", "onshore_wind")].varValue * cf for cf in data['onshore_wind hourly capacity factor']]
|
| 208 |
+
supply_offshore_wind = [renewable_capacity[("region1", "offshore_wind")].varValue * cf for cf in data['offshore_wind hourly capacity factor']]
|
| 209 |
+
supply_river = [renewable_capacity[("region1", "river")].varValue * cf for cf in data['river hourly capacity factor']]
|
| 210 |
+
battery_discharge_values = [battery_discharge[t].varValue for t in range(len(data['Time']))]
|
| 211 |
+
battery_charge_values = [battery_charge[t].varValue for t in range(len(data['Time']))]
|
| 212 |
+
curtailment_values = [curtailment[("region1", t)].varValue for t in range(len(data['Time']))]
|
| 213 |
+
|
| 214 |
+
fig_energy = go.Figure()
|
| 215 |
+
fig_energy.add_trace(go.Scatter(x=data['Time'], y=supply_solar, mode='lines', stackgroup='one', name='Solar', line=dict(color='#FFD700', width=0)))
|
| 216 |
+
fig_energy.add_trace(go.Scatter(x=data['Time'], y=supply_onshore_wind, mode='lines', stackgroup='one', name='Onshore Wind', line=dict(color='#1F78B4', width=0)))
|
| 217 |
+
fig_energy.add_trace(go.Scatter(x=data['Time'], y=supply_offshore_wind, mode='lines', stackgroup='one', name='Offshore Wind', line=dict(color='#66C2A5', width=0)))
|
| 218 |
+
fig_energy.add_trace(go.Scatter(x=data['Time'], y=supply_river, mode='lines', stackgroup='one', name='Run of River', line=dict(color='#FF7F00', width=0)))
|
| 219 |
+
fig_energy.add_trace(go.Scatter(x=data['Time'], y=battery_discharge_values, mode='lines', stackgroup='one', name='Battery Discharge', fill='tonexty', line=dict(color='#6A3D9A', width=0)))
|
| 220 |
+
fig_energy.add_trace(go.Scatter(x=data['Time'], y=battery_charge_values, mode='lines', stackgroup='two', name='Battery Charge', fill='tonexty', line=dict(color='#6A3D9A', width=0)))
|
| 221 |
+
fig_energy.add_trace(go.Scatter(x=data['Time'], y=-demand, mode='lines', stackgroup='two', name='Demand', line=dict(color='black', width=0)))
|
| 222 |
+
fig_energy.add_trace(go.Scatter(x=data['Time'], y=curtailment_values, mode='lines', stackgroup='two', name='Curtailment', line=dict(color='#aaaaaa', width=0)))
|
| 223 |
+
|
| 224 |
+
fig_energy.update_layout(
|
| 225 |
+
title_text='Power Supply and Demand',
|
| 226 |
+
title_x=0.5,
|
| 227 |
+
yaxis_title='Power dispatch (MW)',
|
| 228 |
+
legend_title='Source',
|
| 229 |
+
font=dict(size=12),
|
| 230 |
+
margin=dict(l=40, r=40, t=40, b=40),
|
| 231 |
+
hovermode='x unified',
|
| 232 |
+
plot_bgcolor='white',
|
| 233 |
+
xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
|
| 234 |
+
yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
|
| 235 |
+
)
|
| 236 |
|
| 237 |
+
st.plotly_chart(fig_energy)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|