File size: 16,328 Bytes
784ba07
 
 
 
18b7c9d
784ba07
99662df
173cac5
784ba07
18b7c9d
7b3e296
 
 
 
 
173cac5
784ba07
7b3e296
784ba07
 
 
 
 
 
 
 
 
173cac5
784ba07
18b7c9d
7b3e296
6f04a37
784ba07
 
 
38191ba
784ba07
 
 
 
 
 
 
 
 
18b7c9d
784ba07
 
 
 
18b7c9d
784ba07
 
18b7c9d
784ba07
38191ba
7d659c6
784ba07
 
 
 
 
 
438ff43
 
784ba07
 
 
 
 
18b7c9d
04ffbd0
784ba07
 
18b7c9d
784ba07
 
 
 
438ff43
18b7c9d
784ba07
 
 
438ff43
18b7c9d
784ba07
 
18b7c9d
 
 
 
 
 
 
 
 
784ba07
438ff43
18b7c9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438ff43
18b7c9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
438ff43
 
 
18b7c9d
 
 
 
 
 
438ff43
 
 
 
 
18b7c9d
 
 
 
7b3e296
18b7c9d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
import streamlit as st
import requests
import pandas as pd
import pulp
import plotly.graph_objs as go
import plotly.express as px
import numpy as np
import json

# Function to fetch renewable energy data
def get_json():
    """
    open data.json
    """
    with open('data.json') as f:
        data = json.load(f)
    if not data:
        return None, "No data found."

    base_times = data[next(iter(data))]['x']
    result_df = pd.DataFrame({"Time": base_times})

    for energy_type, energy_data in data.items():
        if 'x' in energy_data and 'y' in energy_data:
            values = energy_data['y']
            result_df[f"{energy_type} hourly capacity factor"] = values

    return result_df

# Function to optimize the energy system and create visualizations
def optimize_energy_system(solar_cost, onshore_wind_cost, offshore_wind_cost, river_cost, battery_cost, yearly_demand, solar_range, wind_range, river_range, offshore_wind_range):
    data = get_json()

    for col in data.columns[1:]:
        data[col] = pd.to_numeric(data[col], errors='coerce')
    data = data.fillna(0)

    time_steps = range(len(data['Time']))
    solar_cf = data['solar hourly capacity factor']
    onshore_wind_cf = data['onshore_wind hourly capacity factor']
    offshore_wind_cf = data['offshore_wind hourly capacity factor']
    river_cf = data['river hourly capacity factor']
    demand_cf = data['demand hourly capacity factor']

    regions = ['region1']
    technologies = ['solar', 'onshore_wind', 'offshore_wind', 'river']
    capacity_factor = {
        'solar': solar_cf,
        'onshore_wind': onshore_wind_cf,
        'offshore_wind': offshore_wind_cf,
        'river': river_cf
    }

    renewable_capacity_cost = {'solar': solar_cost, 'onshore_wind': onshore_wind_cost, 'offshore_wind': offshore_wind_cost, 'river': river_cost}
    battery_cost_per_mwh = battery_cost
    battery_efficiency = 0.9

    demand = demand_cf * yearly_demand / 100 * 1000 * 1000

    renewable_capacity = pulp.LpVariable.dicts("renewable_capacity",
                                               [(r, g) for r in regions for g in technologies],
                                               lowBound=0, cat='Continuous')
    curtailment = pulp.LpVariable.dicts("curtailment",
                                        [(r, t) for r in regions for t in time_steps],
                                        lowBound=0, cat='Continuous')
    battery_capacity = pulp.LpVariable("battery_capacity", lowBound=0, cat='Continuous')
    battery_charge = pulp.LpVariable.dicts("battery_charge", time_steps, lowBound=0, cat='Continuous')
    battery_discharge = pulp.LpVariable.dicts("battery_discharge", time_steps, lowBound=0, cat='Continuous')
    SOC = pulp.LpVariable.dicts("SOC", time_steps, lowBound=0, cat='Continuous')

    model = pulp.LpProblem("EnergySystemOptimizationWithBattery", pulp.LpMinimize)

    model += pulp.lpSum([renewable_capacity[(r, g)] * renewable_capacity_cost[g]
                         for r in regions for g in technologies]) + \
             battery_capacity * battery_cost_per_mwh, "TotalCost"

    for r in regions:
        for t in time_steps:
            model += pulp.lpSum([renewable_capacity[(r, g)] * capacity_factor[g][t]
                                 for g in technologies]) + battery_discharge[t] == demand[t] + battery_charge[t] + curtailment[(r, t)], f"DemandConstraint_{r}_{t}"

            if t == 0:
                model += SOC[t] == battery_charge[t] * battery_efficiency - battery_discharge[t] * (1 / battery_efficiency), f"SOCUpdate_{t}"
            else:
                model += SOC[t] == SOC[t - 1] + battery_charge[t] * battery_efficiency - battery_discharge[t] * (1 / battery_efficiency), f"SOCUpdate_{t}"

            model += SOC[t] <= battery_capacity, f"SOCUpperBound_{t}"

    model += renewable_capacity[('region1', 'solar')] >= solar_range[0], "SolarMinConstraint"
    model += renewable_capacity[('region1', 'solar')] <= solar_range[1], "SolarMaxConstraint"
    model += renewable_capacity[('region1', 'onshore_wind')] >= wind_range[0], "WindMinConstraint"
    model += renewable_capacity[('region1', 'onshore_wind')] <= wind_range[1], "WindMaxConstraint"
    model += renewable_capacity[('region1', 'offshore_wind')] >= offshore_wind_range[0], "OffshoreWindMinConstraint"
    model += renewable_capacity[('region1', 'offshore_wind')] <= offshore_wind_range[1], "OffshoreWindMaxConstraint"
    model += renewable_capacity[('region1', 'river')] >= river_range[0], "RiverMinConstraint"
    model += renewable_capacity[('region1', 'river')] <= river_range[1], "RiverMaxConstraint"

    model.solve()

    supply_solar = solar_cf * renewable_capacity[('region1', 'solar')].varValue
    supply_onshore_wind = onshore_wind_cf * renewable_capacity[('region1', 'onshore_wind')].varValue
    supply_offshore_wind = offshore_wind_cf * renewable_capacity[('region1', 'offshore_wind')].varValue
    supply_river = river_cf * renewable_capacity[('region1', 'river')].varValue

    battery_discharge_values = [battery_discharge[t].varValue for t in time_steps]
    battery_charge_values = [-battery_charge[t].varValue for t in time_steps]
    SOC_values = [SOC[t].varValue for t in time_steps]
    curtailment_values = [-curtailment[(r, t)].varValue for r in regions for t in time_steps]

    max_SOC = max(SOC_values)
    SOC_normalized = [(soc / max_SOC) * 100 for soc in SOC_values] if max_SOC > 0 else [0] * len(SOC_values)

    fig_energy = go.Figure()
    fig_energy.add_trace(go.Scatter(x=data['Time'], y=supply_solar, mode='lines', stackgroup='one', name='Solar', line=dict(color='#FFD700', width=0)))
    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)))
    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)))
    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)))
    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)))
    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)))
    fig_energy.add_trace(go.Scatter(x=data['Time'], y=-demand, mode='lines', stackgroup='two', name='Demand', line=dict(color='black', width=0)))
    fig_energy.add_trace(go.Scatter(x=data['Time'], y=curtailment_values, mode='lines', stackgroup='two', name='Curtailment', line=dict(color='#aaaaaa', width=0)))
    
    fig_energy.update_layout(
        title_text='Power Supply and Demand',
        title_x=0.5,
        yaxis_title='Power dispatch (MW)',
        legend_title='Source',
        font=dict(size=12),
        margin=dict(l=40, r=40, t=40, b=40),
        hovermode='x unified',
        plot_bgcolor='white',
        xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
        yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
    )

    # Heatmap generation for each renewable energy source
    heatmaps = []
    for energy_source in ['solar', 'onshore_wind', 'offshore_wind', 'river']:
        df_heatmap = data[['Time', f'{energy_source} hourly capacity factor']].copy()
        df_heatmap['Time'] = pd.to_datetime(df_heatmap['Time'], errors='coerce')
        df_heatmap['day_of_year'] = df_heatmap['Time'].dt.dayofyear
        df_heatmap['hour_of_day'] = df_heatmap['Time'].dt.hour

        pivot_df = df_heatmap.pivot_table(
            index='hour_of_day',
            columns='day_of_year',
            values=f'{energy_source} hourly capacity factor',
            aggfunc='mean'
        )

        fig_heatmap = px.imshow(
            pivot_df.values,
            labels=dict(x="Day of Year", y="Hour of Day", color=f"{energy_source.replace('_', ' ').title()} Capacity Factor"),
            x=pivot_df.columns,
            y=pivot_df.index,
            aspect="auto",
            color_continuous_scale='Plasma'
        )

        fig_heatmap.update_layout(
            title=f'{energy_source.replace("_", " ").title()} Hourly Capacity Factor (24 Hours x 365 Days)',
            xaxis_title='Day of Year',
            yaxis_title='Hour of Day',
            font=dict(size=12),
            plot_bgcolor='white',
            margin=dict(l=40, r=40, t=40, b=40),
        )
        heatmaps.append(fig_heatmap)

    # Create capacity range visualization for each technology
    fig_capacity_ranges = go.Figure()
    technologies = ['solar', 'onshore_wind', 'offshore_wind', 'river']
    capacity_ranges = [solar_range, wind_range, offshore_wind_range, river_range]
    optimized_capacities = [
        renewable_capacity[('region1', 'solar')].varValue,
        renewable_capacity[('region1', 'onshore_wind')].varValue,
        renewable_capacity[('region1', 'offshore_wind')].varValue,
        renewable_capacity[('region1', 'river')].varValue
    ]

    for tech, cap_range, optimized_cap in zip(technologies, capacity_ranges, optimized_capacities):
        fig_capacity_ranges.add_trace(go.Scatter(
            x=[tech, tech],
            y=cap_range,
            mode='lines',
            name=f'{tech} capacity range',
            line=dict(color='blue', width=4)
        ))
        fig_capacity_ranges.add_trace(go.Scatter(
            x=[tech],
            y=[optimized_cap],
            mode='markers',
            name=f'{tech} optimized capacity',
            marker=dict(color='red', symbol='x', size=10)
        ))

    fig_capacity_ranges.update_layout(
        title_text='Optimized Capacity vs. Capacity Ranges',
        title_x=0.5,
        yaxis_title='Capacity (MW)',
        xaxis_title='Technology',
        font=dict(size=12),
        margin=dict(l=40, r=40, t=40, b=40),
        hovermode='x unified',
        plot_bgcolor='white',
        xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
        yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
    )

    return fig_energy, heatmaps, curtailment_values, SOC_normalized, fig_capacity_ranges, renewable_capacity

# 資源コストの感度解析を行う関数
def analyze_cost_sensitivity(renewable_capacity_cost, technologies, renewable_capacity):
    # コストの変動範囲(0.5倍から1.5倍)
    cost_multipliers = np.linspace(0.5, 1.5, 11)

    # 結果を格納する辞書
    sensitivity_results = {}

    for tech in technologies:
        # 各技術ごとのコスト変動に対する総コストの変化を計算
        original_cost = renewable_capacity_cost[tech]
        total_costs = []

        for multiplier in cost_multipliers:
            # コストを変更
            modified_cost = original_cost * multiplier
            # 総コスト = 変更後のコスト * 設備容量
            total_cost = modified_cost * renewable_capacity[('region1', tech)].varValue
            total_costs.append(total_cost)

        # 技術ごとに結果を保存
        sensitivity_results[tech] = total_costs

    # 可視化
    fig = go.Figure()

    for tech, total_costs in sensitivity_results.items():
        fig.add_trace(go.Scatter(
            x=cost_multipliers,
            y=total_costs,
            mode='lines+markers',
            name=f'{tech} Cost Sensitivity'
        ))

    # グラフのレイアウト
    fig.update_layout(
        title='Cost Sensitivity Analysis: Impact of Cost Changes on Total System Cost',
        xaxis_title='Cost Multiplier (0.5x to 1.5x)',
        yaxis_title='Total System Cost (¥)',
        hovermode='x unified',
        plot_bgcolor='white',
        xaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray'),
        yaxis=dict(showgrid=True, gridwidth=0.5, gridcolor='lightgray')
    )

    return fig

# Streamlit UI setup
st.set_page_config(page_title='Renewable Energy System Optimization', layout='wide')
st.title('Renewable Energy System Optimization')

st.markdown("""
### Model Overview
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.
The renewable technologies considered are:
- Solar PV
- Onshore Wind
- Offshore Wind
- Run of River (Hydro)
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.
""")

with st.sidebar:
    st.header('Input Parameters')
    solar_cost = st.number_input("Solar Capacity Cost (¥/MW)", value=80.0)
    onshore_wind_cost = st.number_input("Onshore Wind Capacity Cost (¥/MW)", value=120.0)
    offshore_wind_cost = st.number_input("Offshore Wind Capacity Cost (¥/MW)", value=180.0)
    river_cost = st.number_input("River Capacity Cost (¥/MW)", value=1000.0)
    battery_cost = st.number_input("Battery Cost (¥/MWh)", value=80.0)
    yearly_demand = st.number_input("Yearly Power Demand (TWh/year)", value=15.0)
    solar_range = st.slider("Solar Capacity Range (MW)", 0, 10000, (0, 10000))
    wind_range = st.slider("Onshore Wind Capacity Range (MW)", 0, 10000, (0, 10000))
    offshore_wind_range = st.slider("Offshore Wind Capacity Range (MW)", 0, 10000, (0, 10000))
    river_range = st.slider("River Capacity Range (MW)", 0, 10000, (0, 10000))

calculated_optimal_energy_mix = False

if st.button('Calculate Optimal Energy Mix'):
    fig_energy, heatmaps, curtailment_values, soc_per_hour, fig_capacity_ranges, renewable_capacity = optimize_energy_system(
        solar_cost, onshore_wind_cost, offshore_wind_cost, river_cost, battery_cost, yearly_demand, solar_range, wind_range, river_range, offshore_wind_range
    )
    
    if fig_energy:
        st.plotly_chart(fig_energy, use_container_width=True, height=800)

        # Additional visualizations
        st.markdown("### Hourly Capacity Factor Heatmaps")
        for fig_heatmap in heatmaps:
            st.plotly_chart(fig_heatmap, use_container_width=True, height=800)

        st.markdown("### Additional Analysis")
        st.markdown("The following plots provide additional insights into the renewable energy mix, curtailment, and electricity price variations.")

        # Plot curtailment over time
        curtailment_df = pd.DataFrame({"Time": fig_energy.data[0].x, "Curtailment (MW)": curtailment_values})
        fig_curtailment = px.line(curtailment_df, x='Time', y='Curtailment (MW)', title='Curtailment Over Time', template='plotly_white')
        st.plotly_chart(fig_curtailment, use_container_width=True, height=800)

        # Plot electricity price variation over time
        soc_df = pd.DataFrame({"Time": fig_energy.data[0].x, "State of charge [%]": soc_per_hour})
        fig_battery_operation = px.line(soc_df, x='Time', y='State of charge [%]', title='State of charge in battery', template='plotly_white')
        st.plotly_chart(fig_battery_operation, use_container_width=True, height=800)

        # Plot optimized capacity vs. capacity ranges
        st.plotly_chart(fig_capacity_ranges, use_container_width=True, height=800)

        calculated_optimal_energy_mix = True

# Streamlit UIに感度解析ボタンを追加
if st.button('Analyze Cost Sensitivity'):
    if calculated_optimal_energy_mix:
        fig_sensitivity = analyze_cost_sensitivity({
            'solar': solar_cost,
            'onshore_wind': onshore_wind_cost,
            'offshore_wind': offshore_wind_cost,
            'river': river_cost
        }, ['solar', 'onshore_wind', 'offshore_wind', 'river'], renewable_capacity)
        st.plotly_chart(fig_sensitivity, use_container_width=True, height=800)
    else:
        st.error("Please calculate the optimal energy mix first before running the cost sensitivity analysis.")