業界別最適化Example集: 貯水池の放流スケジュール最適化問題 (水資源管理)
nAG数値計算ライブラリ > 業界別最適化Example集 > 貯水池の放流スケジュール最適化問題 (水資源管理)

Keyword: 水資源管理, 最適化, スケジューリング, 線形計画法, LP

水資源管理分野の最適化問題:貯水池の放流スケジュール最適化

問題の概要

貯水池

水力発電用の貯水池では、発電収益を最大化しつつ、ダムの安全性と下流域の水需要を満たすために適正な水位を維持することが求められます。この問題では、直近の2週間という短期的な期間において、適正水位範囲の維持、絶対的な容量制約の遵守、そして水力発電収益の最大化を両立する最適な放流スケジュールを求めることを目的としています。

主要な制約条件としては、貯水池の水位が物理的な最大容量を超えないこと、最小容量を下回らないこと、そして適正水位範囲内に収まることが挙げられます。また、放流量についても、ダムの放流設備の能力による上限と下限の制約を考慮する必要があります。

貯水池の放流スケジュール最適化問題(具体例)

この問題では、水力発電用貯水池の今後14日間の放流スケジュールを最適化します。目的は、貯水池の水位を適正範囲内に維持しつつ、水力発電収益を最大化することです。最適化にあたっては、予測流入量データを基に、放流量を適切に調整します。制約条件として、貯水池の水位が物理的な最大容量と最小容量の範囲内に収まること、放流量がダムの放流設備の能力の上限と下限の範囲内であること、そして貯水池の水位が適正範囲から逸脱した場合にペナルティが発生することを考慮します。最適な放流計画を立てることで、ダムの安全性と水力発電による収益の最大化を両立することを目指します。

以下の表には、次の14日間の予測流入量が記載されており、これを基に放流スケジュールを最適化します。

日付 予測流入量(m³/日)
1日目 3,000
2日目 4,000
3日目 15,000
4日目 50,000
5日目 30,000
6日目 12,000
7日目 8,000
8日目 5,000
9日目 4,000
10日目 3,000
11日目 3,000
12日目 4,000
13日目 3,000
14日目 5,000

問題の定式化

パラメータ

パラメータ 説明
\(T\) 計画期間(日数) 14
\(I_t\) \(t\)日目の予測流入量(m³/日) 上記の表を参照
\(V_{max}\) ダムの物理的な最大容量(m³) 2,000,000
\(V_{min}\) ダムの物理的な最小容量(m³) 200,000
\(V_{max\_opt}\) 適正範囲の上限(m³) 1,820,000
\(V_{min\_opt}\) 適正範囲の下限(m³) 1,780,000
\(V_{start}\) 初期貯水量(m³) 1,800,000
\(R_{max}\) 1日あたりの最大放流量(m³/日) 15,000
\(R_{min}\) 1日あたりの最小放流量(m³/日) 1,000
\(P\) 1m³あたりの発電収益(円/m³) 10
\(C\) 貯水量が適正範囲を外れた場合のペナルティ(円/m³) 12

決定変数

変数 説明 範囲
\(R_t\) \(t\)日目の放流量(m³/日) \(R_{min} \leq R_t \leq R_{max}\)
\(V_t\) \(t\)日目終了時点の貯水量(m³) \(V_{min} \leq V_t \leq V_{max}\)
\(O_t\) \(t\)日目の貯水量が適正範囲を外れた量(m³) \(O_t \geq 0\)

目的関数

目的
発電収益を最大化しつつ、貯水量が適正範囲を外れた際のペナルティを最小化 \(\max \sum_{t=1}^{T} P \cdot R_t - \sum_{t=1}^{T} C \cdot O_t\)

制約条件

制約 説明
貯水量の物理的範囲 \(V_{min} \leq V_t \leq V_{max}, \forall t\) 貯水量はダムの物理的な最小容量以上、最大容量以下でなければならない
放流量の範囲 \(R_{min} \leq R_t \leq R_{max}, \forall t\) 放流量は最小値以上、最大値以下でなければならない
初期貯水量 \(V_0 = V_{start}\) 初日の貯水量は初期貯水量に等しい
貯水量の遷移 \(V_t = V_{t-1} + I_t - R_t, \forall t\) 貯水量は前日の貯水量に流入量を加え、放流量を引いたものになる
貯水量の適正範囲上限逸脱 \(O_t \geq V_t - V_{max\_opt}, \forall t\) 貯水量が適正範囲の上限を超えた分だけ逸脱量が発生する
貯水量の適正範囲下限逸脱 \(O_t \geq V_{min\_opt} - V_t, \forall t\) 貯水量が適正範囲の下限を下回った分だけ逸脱量が発生する
非負制約 \(O_t \geq 0, \forall t\) 逸脱量は非負でなければならない

コード例

以下に、この線形計画問題を nAG Library for Python の スパース線形計画問題に適したソルバー関数 handle_solve_lp_ipm を用いて解くコード例を示します。

import numpy as np
from naginterfaces.library import opt

# パラメータ
I = np.array([3000, 4000, 15000, 50000, 30000, 12000, 8000, 5000, 4000, 3000, 3000, 4000, 3000, 5000])  # 各日の予測流入量(m³/日)
R_max = 15000           # 1日あたりの最大放流量(m³/日)
R_min = 1000            # 1日あたりの最小放流量(m³/日)
V_max = 2000000         # 貯水池の最大容量(m³)
V_min = 200000          # 貯水池の最小容量(m³)
V_max_opt = 1820000     # 貯水池の最適容量上限(m³)
V_min_opt = 1780000     # 貯水池の最適容量下限(m³)
P = 10                  # 1m³あたりの発電収益(円/m³)
C = 12                  # 貯水量が最適範囲を外れた場合のペナルティ(円/m³)

# 初期貯水量
V_start = (V_min_opt+V_max_opt)/2

# オプティマイザーの初期化
T = len(I)           # 計画期間(日数)
nvar = 3 * T
handle = opt.handle_init(nvar)

# 目的関数の係数(発電収益の最大化とペナルティの最小化)
cvec = [P] * T + [0] * T +  [-C] * T  # R, V, O の順番、Vは目的関数には含まれないのでゼロ
opt.handle_set_linobj(handle, cvec)

# 変数の上下限
bl = np.zeros(nvar)     # 下限の初期化
bu = np.zeros(nvar)     # 上限の初期化
bl[:T] = R_min          # R_t の下限
bu[:T] = R_max          # R_t の上限
bl[T:2*T] = V_min       # V_t の下限
bu[T:2*T] = V_max       # V_t の上限
bl[2*T:3*T] = 0         # O_t の下限
bu[2*T:3*T] = np.inf    # O_t の上限
opt.handle_set_simplebounds(handle, bl, bu)

# 初日の貯水量遷移制約
bl_1st_day = [V_start + I[0]]
bu_1st_day = [V_start + I[0]]
irowb_1st_day = [1, 1]
icolb_1st_day = [1, T+1]  # R1, V1
coeff_1st_day = [1, 1]    # R1 + V1
opt.handle_set_linconstr(handle, bl_1st_day, bu_1st_day, irowb_1st_day, icolb_1st_day, coeff_1st_day)

# 2日目以降の貯水量遷移制約
bl_2nd_day_on =I[1:]
bu_2nd_day_on =I[1:]
irowb_2nd_day_on = []
icolb_2nd_day_on = []
coeff_2nd_day_on = [1, -1, 1] * (T-1)
for t in range(1, T):
    bl_2nd_day_on[t-1] = I[t] 
    bu_2nd_day_on[t-1] = I[t] 
    irowb_2nd_day_on.extend([t,t,t])
    icolb_2nd_day_on.extend([t+1,T+t,T+t+1])  # Rt, Vt-1, Vt
opt.handle_set_linconstr(handle, bl_2nd_day_on, bu_2nd_day_on, irowb_2nd_day_on, icolb_2nd_day_on, coeff_2nd_day_on)

# 貯水適正量上限逸脱
bl_excess_upper = np.full(T, -V_max_opt)
bu_excess_upper = np.full(T, np.inf)
irowb_excess_upper = []
icolb_excess_upper = []
coeff_excess_upper = [1, -1] * T  # O_t - V_t
for t in range(T):
    irowb_excess_upper.extend([t+1, t+1])
    icolb_excess_upper.extend([2*T+t+1, T+t+1])  # O_t, V_t
opt.handle_set_linconstr(handle, bl_excess_upper, bu_excess_upper, irowb_excess_upper, icolb_excess_upper, coeff_excess_upper)

# 貯水適正量下限逸脱
bl_excess_lower = np.full(T, V_min_opt)
bu_excess_lower = np.full(T, np.inf)
irowb_excess_lower = []
icolb_excess_lower = []
coeff_excess_lower = [1, 1] * T
for t in range(T):
    irowb_excess_lower.extend([t+1, t+1])
    icolb_excess_lower.extend([2*T+t+1, T+t+1])  # O_t, V_t
opt.handle_set_linconstr(handle, bl_excess_lower, bu_excess_lower, irowb_excess_lower, icolb_excess_lower, coeff_excess_lower)

# ソルバーオプションの設定
opt.handle_opt_set(handle, 'Print Level = 0')
opt.handle_opt_set(handle, 'Task = Maximize')

# 初期値
x_init = [R_min]*T + [V_min]*T + [0]*T

# ソルバーの実行
optimal_solution, _, result_info, _ = opt.handle_solve_lp_ipm(handle,x_init)

#  結果を表示
R_opt = optimal_solution[:T]
V_opt = optimal_solution[T:2*T]
O_opt_upper = [max(0, v - V_max_opt) for v in V_opt]  # 上限超過量
O_opt_lower = [max(0, V_min_opt - v) for v in V_opt]  # 下限未満量

# 目的関数の値を取得
obj_value = result_info[1]

# 表示
print("\n最適解:")
print("日\t流入量\t\t放流量\t\t貯水量\t\t上限超過量\t下限未満量")
print(f"0\t-\t\t-\t\t{V_start:10.0f}\t-\t\t-")
for t in range(T):
    print(f"{t+1}\t{I[t]:<10}\t{R_opt[t]:10.0f}\t{V_opt[t]:10.0f}\t{O_opt_upper[t]:10.0f}\t{O_opt_lower[t]:10.0f}")

# 発電収益とペナルティー表示
revenue = sum(P * R_opt[t] for t in range(T))
penalty_upper = sum(C * O_opt_upper[t] for t in range(T))  # 上限超過ペナルティ
penalty_lower = sum(C * O_opt_lower[t] for t in range(T))  # 下限未満ペナルティ
penalty = penalty_upper + penalty_lower  # 総ペナルティ
print(f"-------------------------------")
print(f"総発電収益: {revenue:.0f}円")
print(f"上限超過ペナルティ: {penalty_upper:.0f}円")
print(f"下限未満ペナルティ: {penalty_lower:.0f}円")
print(f"総利益: {obj_value:.0f}円")

結果例

上記のコードを実行すると、以下のような出力が得られます。

最適解:
日      流入量          放流量          貯水量          上限超過量      下限未満量
0       -               -                  1800000      -               -
1       3000                 14927         1788073               0               0
2       4000                 14874         1777199               0            2801
3       15000                15000         1777199               0            2801
4       50000                15000         1812199               0               0
5       30000                15000         1827199            7199               0
6       12000                15000         1824199            4199               0
7       8000                 13125         1819074               0               0
8       5000                 10301         1813773               0               0
9       4000                  9005         1808768               0               0
10      3000                  8220         1803548               0               0
11      3000                  7910         1798637               0               0
12      4000                  8282         1794355               0               0
13      3000                  9654         1787701               0               0
14      5000                 12701         1780000               0               0
-------------------------------
総発電収益: 1690000円
上限超過ペナルティ: 136766円
下限未満ペナルティ: 67234円
総利益: 1486000円

この結果から、最適放流スケジュールに従うことで、14日間の発電収益が1,690,000円、上限超過ペナルティが136,766円、下限を下回ったことによるペナルティが67,234円で、総利益が1,486,000円となる事がわかります。また、適正水位範囲からの逸脱も少ないことがわかります。具体的には、上限を超えたのは5日目と6日目で、それぞれ7,199m³と4,199m³の逸脱、下限未満の逸脱は2日目と3日目にそれぞれ2,801m³が発生しています。

まとめ

ここでは、水力発電用貯水池の短期的な放流スケジュールを最適化することで、発電収益の最大化と適正水位の維持を両立するアプローチを示しました。線形計画法を用いることで、現実的な制約条件の下で最適解を効率的に求めることができました。これに環境流量の確保といった生態系保全の制約や、発電収益だけでなく下流域の水需要の満足度も考慮した目的関数を含めるなど、より現実的な応用も可能です。


関連情報
Privacy Policy  /  Trademarks