File size: 18,374 Bytes
8648c7d
 
 
 
90f4000
8648c7d
 
90f4000
2141c25
ee6abdf
2141c25
 
23f9f3c
2141c25
 
a7a421f
 
 
 
 
 
 
 
 
 
 
 
 
6ce3865
90f4000
2141c25
 
90f4000
 
 
 
 
 
 
 
 
 
 
6ce3865
90f4000
 
 
 
 
 
 
 
7d7cde1
 
6ce3865
 
90f4000
6ce3865
 
aa7b34a
 
90f4000
 
 
6ce3865
90f4000
 
 
6ce3865
90f4000
450b5a3
90f4000
 
aa7b34a
90f4000
450b5a3
90f4000
 
450b5a3
90f4000
aa7b34a
450b5a3
90f4000
450b5a3
90f4000
 
0d4f673
90f4000
 
 
 
 
 
 
 
 
7d7cde1
90f4000
6ce3865
90f4000
0d4f673
90f4000
 
 
 
 
 
 
 
 
 
aa7b34a
90f4000
 
 
aa7b34a
90f4000
 
 
 
 
 
 
 
 
 
 
 
aa7b34a
90f4000
 
 
 
aa7b34a
90f4000
 
 
aa7b34a
90f4000
 
 
 
 
 
 
 
 
 
 
 
 
 
1dd074a
2868453
90f4000
 
e62b9af
2868453
 
7d7cde1
 
 
 
 
 
 
 
 
 
 
90f4000
 
aa7b34a
90f4000
 
 
 
 
1dd074a
90f4000
 
2141c25
aa7b34a
90f4000
ade33aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90f4000
 
 
 
 
 
 
 
 
 
 
aa7b34a
90f4000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81489b4
90f4000
 
 
 
 
 
 
 
 
4ae389d
aa7b34a
90f4000
 
 
aa7b34a
 
90f4000
aa7b34a
 
fffbaf1
aa7b34a
 
 
 
 
18cab14
 
aa7b34a
90f4000
 
 
 
 
 
fffbaf1
90f4000
 
 
 
fffbaf1
c9b1361
90f4000
 
 
 
fffbaf1
90f4000
4ae389d
aa7b34a
fffbaf1
4ae389d
90f4000
 
 
 
0e60608
c9b1361
90f4000
 
 
fffbaf1
90f4000
 
 
0e60608
90f4000
0e60608
 
 
 
fffbaf1
0e60608
90f4000
1dd074a
90f4000
 
 
0e60608
fffbaf1
90f4000
0e60608
 
90f4000
fffbaf1
90f4000
a6ff2e4
 
 
4ae389d
e9a48f0
 
a64db3a
90f4000
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
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 matplotlib.pyplot as plt
import json

def get_json():
    """
    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

# Optimize energy system and use MGA
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, thresholds, selected_tech):
    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)

    # Objective: minimize total cost (renewable capacities and battery)
    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"

    # Constraints: meet demand, manage battery SOC
    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}"

    # Capacity range constraints
    model += renewable_capacity[('region1', 'solar')] >= solar_range[0]
    model += renewable_capacity[('region1', 'solar')] <= solar_range[1]
    model += renewable_capacity[('region1', 'onshore_wind')] >= wind_range[0]
    model += renewable_capacity[('region1', 'onshore_wind')] <= wind_range[1]
    model += renewable_capacity[('region1', 'offshore_wind')] >= offshore_wind_range[0]
    model += renewable_capacity[('region1', 'offshore_wind')] <= offshore_wind_range[1]
    model += renewable_capacity[('region1', 'river')] >= river_range[0]
    model += renewable_capacity[('region1', 'river')] <= river_range[1]

    # Solve the initial model to find the optimal solution
    model.solve()
    optimal_cost = pulp.value(model.objective)

    # MGA: Generate alternative solutions for selected technologies only
    mga_models = []
    alternative_solutions = []
    for threshold in thresholds:
        relaxed_cost = optimal_cost * (1 + threshold)
        for tech in selected_tech:  # 選択された技術のみ実行
            # Minimize capacity of each technology
            alt_model_min = pulp.LpProblem(f"AlternativeModel_Min_{tech}_{threshold}", pulp.LpMinimize)
            alt_model_min += pulp.lpSum([renewable_capacity[(r, g)] * renewable_capacity_cost[g]
                                         for r in regions for g in technologies]) + battery_capacity * battery_cost_per_mwh <= relaxed_cost

            # Copy original constraints with unique names
            for name, constraint in model.constraints.items():
                alt_model_min += constraint.copy(), f"{name}_min_{tech}_{threshold}"

            # Minimize the capacity of the selected technology
            alt_model_min += renewable_capacity[('region1', tech)], f"Minimize_{tech}_Capacity"
            alt_model_min.solve()
            if pulp.LpStatus[alt_model_min.status] == 'Optimal':
                alternative_solutions.append({
                    'threshold': threshold,
                    'type': 'min',
                    'technology': tech,
                    'solution': {g: renewable_capacity[('region1', g)].varValue for g in technologies},
                    'battery_capacity': battery_capacity.varValue,
                    'total_cost': pulp.value(alt_model_min.objective)
                })

            # Maximize capacity of each technology
            alt_model_max = pulp.LpProblem(f"AlternativeModel_Max_{tech}_{threshold}", pulp.LpMinimize)
            alt_model_max += pulp.lpSum([renewable_capacity[(r, g)] * renewable_capacity_cost[g]
                                         for r in regions for g in technologies]) + battery_capacity * battery_cost_per_mwh <= relaxed_cost

            # Copy original constraints with unique names
            for name, constraint in model.constraints.items():
                alt_model_max += constraint.copy(), f"{name}_max_{tech}_{threshold}"

            # Maximize the capacity of the selected technology
            alt_model_max += -renewable_capacity[('region1', tech)], f"Maximize_{tech}_Capacity"
            alt_model_max.solve()
            if pulp.LpStatus[alt_model_max.status] == 'Optimal':
                alternative_solutions.append({
                    'threshold': threshold,
                    'type': 'max',
                    'technology': tech,
                    'solution': {g: renewable_capacity[('region1', g)].varValue for g in technologies},
                    'battery_capacity': battery_capacity.varValue,
                    'total_cost': pulp.value(alt_model_max.objective)
                })

    return alternative_solutions

# Streamlit UI setup
st.set_page_config(page_title='Renewable Energy System Optimization with MGA', layout='wide')
st.title('Modeling to Generate Alternatives (MGA) in Renewable Energy System Optimization')

# Sidebar Inputs
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))
    # 0.1の刻みで0から1までの値を生成し、小数点以下3桁に丸める
    thresholds = st.multiselect(
        "Select MGA Cost Deviation Thresholds (%)",
        list(np.arange(0, 11, 0.5)),
        default=[0, 5, 10]
    )
    # 技術の選択オプション
    selected_technologies = st.multiselect("Select Technologies to Optimize", ['solar', 'onshore_wind', 'offshore_wind', 'river'], default=['solar', 'onshore_wind', 'offshore_wind', 'river'])

if st.button("Run MGA Optimization"):
    # 実行して alternative_solutions を取得
    alternative_solutions = 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, [t / 100 for t in thresholds], selected_technologies)

    if alternative_solutions:
        # # コスト積み上げ用データの収集
        # cost_data = []
        # for sol in alternative_solutions:
        #     cost_data.append({
        #         'threshold': sol['threshold'] * 100,
        #         'type': sol['type'],
        #         'technology': sol['technology'],
        #         'total_cost': sol['total_cost']
        #     })

        # # コスト積み上げグラフのプロット
        # cost_df = pd.DataFrame(cost_data)
        # fig_cost = px.bar(cost_df, x='threshold', y='total_cost', color='technology', title="Cost Breakdown by Technology and Threshold")
        # fig_cost.update_layout(xaxis_title='Threshold (%)', yaxis_title='Total Cost (¥)')

        # # Streamlitでコスト積み上げグラフを表示
        # st.plotly_chart(fig_cost, use_container_width=True)

        # 各技術ごとに異なる色を指定
        colors = {
            'solar': 'gold',
            'onshore_wind': 'skyblue',
            'offshore_wind': 'lightgreen',
            'river': 'salmon'
        }

        # 各技術の容量範囲を個別のグラフで表示
        epsilon_values = sorted(list(set(sol['threshold'] * 100 for sol in alternative_solutions)))

        for tech in selected_technologies:
            storage_min = []
            storage_max = []
            for epsilon in epsilon_values:
                capacities = [sol['solution'][tech] for sol in alternative_solutions if sol['technology'] == tech and sol['threshold'] * 100 == epsilon]
                storage_min.append(min(capacities))
                storage_max.append(max(capacities))

            # 各技術ごとにグラフ作成
            fig, ax = plt.subplots()
            ax.fill_between(epsilon_values, storage_min, storage_max, color=colors[tech], alpha=0.3, label=f"{tech} range")
            ax.plot(epsilon_values, storage_min, marker='o', color=colors[tech], linestyle='-', linewidth=1.5, label=f"{tech} Min")
            ax.plot(epsilon_values, storage_max, marker='o', color=colors[tech], linestyle='-', linewidth=1.5, label=f"{tech} Max")

            # ラベルとタイトル
            ax.set_xlabel(r'$\epsilon$ [%]')
            ax.set_ylabel(f'{tech.capitalize()} Capacity [MW]')
            ax.set_title(f'Capacity Range for {tech.capitalize()}')
            ax.legend()
            ax.grid(True, linestyle='--', alpha=0.7)

            # Streamlitに各技術のプロットを表示
            st.pyplot(fig)


st.markdown("""
    This application uses **Modeling to Generate Alternatives (MGA)** to explore near-optimal solutions in a renewable energy system model. MGA helps to identify alternative configurations that are close to the optimal solution but vary in their specific technological composition, providing flexibility for policy makers and stakeholders who might prioritize factors beyond cost minimization, such as social acceptance or regional preferences [1].
    """)

st.write("## Objective Function and Cost Minimization")
st.markdown("""
    In our renewable energy model, the **objective function** is to minimize the total annual cost of the system, which includes the costs of installing renewable generation capacities (such as solar, wind, and hydroelectric) and storage (batteries). The objective function is defined as:
    """)
st.latex(r"""
    \text{Minimize } \quad \sum_{r, g} \text{Cost}_{g} \times \text{Capacity}_{r, g} + \text{Battery Cost} \times \text{Battery Capacity}
    """)
st.markdown("""
    where:
    - $r$ represents the region (in this case, a single region),
    - $g$ represents the generation technology (solar, onshore wind, offshore wind, river),
    - ${Cost}_{g}$ is the per-MW cost of technology $g$,
    - $Capacity_{r, g}$ is the installed capacity of technology $g$ in region $r$,
    - ${Battery\, Cost}$ represents the cost per MWh of battery storage,
    - ${Battery\, Capacity}$ is the total installed battery capacity.
    """)

st.markdown("""
## What is MGA and Why is it Important?
Typically, optimization models produce a **single optimal solution** that minimizes the cost under a given set of constraints. However, in many real-world applications, there are **multiple near-optimal solutions** that achieve similar costs but vary in other characteristics. This diversity is valuable because:
- **Flexibility**: Different solutions might be preferable depending on policy objectives, geographic constraints, or social preferences.
- **Robustness**: Exploring near-optimal solutions reveals which elements (e.g., specific technologies or infrastructure investments) are consistently essential, regardless of slight variations in cost.

MGA addresses this need by generating **alternative solutions** that are close to the optimal cost but differ in technological composition.
""")

st.write("## How MGA Works: Adding a Cost Constraint")
st.markdown("""
To generate alternatives, MGA introduces a **cost tolerance** parameter $\epsilon$, which represents the acceptable increase in total cost relative to the optimal solution. The cost constraint for alternative solutions is expressed as:
""")
st.latex(r"""
\text{Total Cost} \leq (1 + \epsilon) \times \text{Optimal Cost}
""")
st.markdown("""
where:
- $\epsilon$ is the cost deviation percentage (e.g. if $\epsilon = 0.05$), then the solution can be up to 5% more expensive than the optimal cost),
- ${Optimal\, Cost}$ is the minimum cost obtained from the initial optimization.

This constraint allows for flexibility in cost, enabling the exploration of solutions that are **near-optimal** but differ in terms of installed capacities for each technology [2].
""")

st.markdown("""
### MGA Process in This Application
1. **Initial Optimization**: First, we solve for the optimal solution to obtain the minimal total cost, referred to as ${Optimal\, Cost}$.
2. **Setting the Cost Threshold**: We introduce a range of $\epsilon$ values (0%, 5%, 10%, etc.) to explore how alternative solutions differ as we allow for higher costs.
3. **Minimizing and Maximizing Capacities**: For each selected technology (e.g., solar, wind, hydro), we attempt to:
   - **Minimize the installed capacity** within the allowed cost threshold, identifying configurations with the lowest feasible capacity for that technology.
   - **Maximize the installed capacity** under the same conditions, exploring configurations with higher reliance on that technology.

These steps generate a set of **alternative solutions** that are close in cost but vary significantly in their reliance on each technology, revealing **flexibility** and **trade-offs** in the renewable energy system configuration.
""")

st.write("## Interpreting the Cost Threshold ($\epsilon$ )")
st.markdown("""
The cost threshold parameter ($\epsilon$ ) is crucial in MGA, as it determines the range within which we consider solutions to be "near-optimal." For example:
- **$\epsilon = 0% $**: Only the exact optimal solution is considered.
- **$\epsilon = 5% $**: Solutions within 5% of the optimal cost are considered acceptable, allowing for slightly more flexibility in technology choice.
- **$\epsilon = 10% $**: Solutions within 10% of the optimal cost are allowed, providing even greater flexibility.

By exploring a range of $\epsilon$ values, we can see how the system configuration changes as we relax the cost constraint, offering a broader view of feasible solutions.
""")

st.markdown("""
## Visualization of Results
- **Cost Breakdown**: The total cost of each solution, broken down by technology, helps us see the contribution of each technology to the total cost.
- **Capacity Ranges**: For each technology, we plot the minimum and maximum capacities across different $\epsilon$ values, showing the flexibility in system design as cost thresholds change.

This visualization provides insights into:
- Which technologies are essential (appear consistently in solutions across all $\epsilon$ values),
- Which technologies offer flexibility (capacities vary widely as $\epsilon$ increases),
- The cost impact of relying more or less on specific technologies.

Through MGA, we can make more **informed decisions** about the renewable energy mix and identify robust, flexible strategies that align with broader goals beyond cost minimization.
""")

st.markdown("""
[1] E. D. Brill, “The Use of Optimization Models in Public-Sector Planning,” Management Science, vol. 25, no. 5, pp. 413–422, 1979, Publisher: INFORMS, ISSN:
0025-1909. DOI: [https://doi.org/10.1287/mnsc.25.5.413.](https://pubsonline.informs.org/doi/10.1287/mnsc.25.5.413)

[2] Neumann, Fabian, and Tom Brown. "The near-optimal feasible space of a renewable power system model." Electric Power Systems Research, vol. 190, 2021, p. 106690. [https://doi.org/10.1016/j.epsr.2020.106690.](https://doi.org/10.1016/j.epsr.2020.106690)
""")