mcpField
E
energy-storage-optimization

energy-storage-optimization

github.com/kishorkukrejaその他8463 回インストール3 週間前に更新
リポジトリ

energy-storage-optimization について

蓄電システムの計画を支援するスキルです。容量や運用方法を検討するチーム向けに、電池の運用、系統サービス、再生可能エネルギー連携、劣化、経済性を扱います。

主な機能

  1. 01バッテリー容量と運用の最適化
  2. 02電力価格に基づく裁定取引
  3. 03ピークカットとデマンド料金削減
  4. 04再生可能エネルギーの変動緩和と系統サービス
  5. 05劣化と寿命のモデリング

活用シーン

  1. 01負荷や太陽光発電のデータに基づく容量設計
  2. 02電力価格に応じたバッテリー運用の計画
  3. 03デマンド料金削減効果の評価
  4. 04蓄電プロジェクトの収益源の比較

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/kishorkukreja/awesome-supply-chain --skill energy-storage-optimization

インストール後、Claude Code で/energy-storage-optimizationを入力して読み込まれたか確認します。

詳細

エネルギー貯蔵の最適化

エネルギー貯蔵の最適化とバッテリー管理の専門家として、エネルギー貯蔵システム(バッテリー、揚水発電、圧縮空気など)の導入、容量設計、運用、経済性を最適化し、系統サービス、再生可能エネルギーの統合、コスト削減を支援します。

初期評価

エネルギー貯蔵を最適化する前に、以下を把握してください。

  1. 貯蔵の用途

    • 主な用途は何ですか?(裁定取引、ピークカット、再生可能エネルギー、バックアップ)
    • 系統サービスは何ですか?(周波数調整、電圧サポート、ブラックスタート)
    • 顧客の種類は何ですか?(電力会社、商業施設、工場、住宅)
    • 設置場所と系統への接続状況はどうなっていますか?
  2. 貯蔵技術

    • 技術の種類は何ですか?(リチウムイオン、フローバッテリー、揚水発電、CAES)
    • 出力容量はどのくらいですか(MW または kW)?
    • エネルギー容量はどのくらいですか(MWh または kWh)?
    • 往復効率はどのくらいですか?
    • サイクル寿命と劣化の状況はどうなっていますか?
  3. システムパラメーター

    • 負荷プロファイルとパターンはどうなっていますか?
    • 再生可能エネルギーの発電量はどのくらいですか(該当する場合)?
    • 電力料金体系はどうなっていますか?(時間帯別料金、リアルタイム料金、デマンド料金)
    • 系統の制約や要件はありますか?
  4. 目的と制約

    • 主な目標は何ですか?(コスト削減、信頼性、再生可能エネルギーの統合)
    • 予算と経済性はどうなっていますか?(設備投資額、投資回収期間)
    • 物理的な制約はありますか?(設置スペース、温度、安全性)
    • 規制上の要件やインセンティブはありますか?

エネルギー貯蔵のフレームワーク

貯蔵技術

電気化学(バッテリー):

  • リチウムイオン:高効率(90-95%)、高速応答、コスト低下が進行中
  • フローバッテリー:長時間放電に対応、出力とエネルギー容量を独立して拡張可能
  • 鉛蓄電池:成熟した技術、低コスト、サイクル寿命に制限あり
  • ナトリウム硫黄電池:高エネルギー密度、高温で動作

機械式:

  • 揚水発電:最大級の容量、効率70-85%、設置場所に依存
  • 圧縮空気(CAES):大規模に対応、地質条件を利用した貯蔵が必要
  • フライホイール:高出力、短時間放電、長いサイクル寿命

熱:

  • 溶融塩:CSPとの統合、6-15時間の放電時間
  • 氷蓄熱:冷房用途、負荷シフト

水素:

  • Power-to-Gas:季節間貯蔵、往復効率30-40%
  • 燃料電池:バックアップ電源、長時間放電に対応

エネルギー貯蔵の容量最適化

最適なバッテリー容量の設計
import numpy as np
import pandas as pd
from pulp import *

def optimize_battery_sizing(load_profile, solar_generation, electricity_prices,
                           max_power_mw=10, max_energy_mwh=40):
    """
    Determine optimal battery size for load shifting and solar firming

    Parameters:
    - load_profile: array of load (MW) by hour
    - solar_generation: array of solar output (MW) by hour
    - electricity_prices: array of prices ($/MWh) by hour
    - max_power_mw: maximum power capacity to consider
    - max_energy_mwh: maximum energy capacity to consider
    """

    # Evaluate different battery sizes
    power_sizes = np.linspace(1, max_power_mw, 10)
    energy_sizes = np.linspace(2, max_energy_mwh, 10)

    results = []

    for power_mw in power_sizes:
        for energy_mwh in energy_sizes:
            # Energy/Power ratio (hours)
            duration = energy_mwh / power_mw

            if duration < 0.5 or duration > 10:
                continue  # Skip infeasible ratios

            # Calculate annual value
            annual_value = optimize_battery_dispatch(
                load_profile,
                solar_generation,
                electricity_prices,
                power_mw,
                energy_mwh
            )['annual_value']

            # Calculate cost
            power_cost = power_mw * 150000  # $/MW
            energy_cost = energy_mwh * 300000  # $/MWh
            total_capex = power_cost + energy_cost

            # Calculate NPV over 15 years
            discount_rate = 0.08
            years = 15
            npv = -total_capex + sum([annual_value / (1 + discount_rate)**y
                                     for y in range(1, years + 1)])

            results.append({
                'power_mw': power_mw,
                'energy_mwh': energy_mwh,
                'duration_hours': duration,
                'capex': total_capex,
                'annual_value': annual_value,
                'npv': npv,
                'payback_years': total_capex / annual_value if annual_value > 0 else 999
            })

    # Find optimal size
    results_df = pd.DataFrame(results)
    optimal = results_df.loc[results_df['npv'].idxmax()]

    return {
        'optimal_size': {
            'power_mw': optimal['power_mw'],
            'energy_mwh': optimal['energy_mwh'],
            'duration_hours': optimal['duration_hours']
        },
        'economics': {
            'capex': optimal['capex'],
            'annual_value': optimal['annual_value'],
            'npv': optimal['npv'],
            'payback_years': optimal['payback_years']
        },
        'all_results': results_df
    }

def optimize_battery_dispatch(load, solar, prices, power_mw, energy_mwh,
                              efficiency=0.92):
    """
    Optimize battery dispatch for given size
    """
    T = len(load)
    prob = LpProblem("Battery_Dispatch", LpMaximize)

    # Variables
    charge = [LpVariable(f"Charge_{t}", lowBound=0, upBound=power_mw)
             for t in range(T)]
    discharge = [LpVariable(f"Discharge_{t}", lowBound=0, upBound=power_mw)
                for t in range(T)]
    soc = [LpVariable(f"SOC_{t}", lowBound=0, upBound=energy_mwh)
          for t in range(T)]

    # Objective: maximize arbitrage value
    prob += lpSum([(discharge[t] * prices[t] - charge[t] * prices[t])
                  for t in range(T)])

    # Constraints
    for t in range(T):
        # SOC dynamics
        if t == 0:
            prob += soc[t] == energy_mwh * 0.5 + \
                    charge[t] * efficiency - discharge[t] / efficiency
        else:
            prob += soc[t] == soc[t-1] + \
                    charge[t] * efficiency - discharge[t] / efficiency

    # Cyclic constraint (end at same SOC as start)
    prob += soc[T-1] == energy_mwh * 0.5

    # Solve
    prob.solve(PULP_CBC_CMD(msg=0))

    # Calculate annual value
    daily_value = value(prob.objective)
    annual_value = daily_value * 365

    return {
        'daily_value': daily_value,
        'annual_value': annual_value,
        'charge_schedule': [charge[t].varValue for t in range(T)],
        'discharge_schedule': [discharge[t].varValue for t in range(T)],
        'soc_profile': [soc[t].varValue for t in range(T)]
    }

# Example usage
# Generate sample data
hours = 24
load = [100, 95, 90, 85, 80, 85, 100, 120, 140, 150, 155, 160,
       158, 155, 150, 145, 150, 160, 155, 145, 130, 120, 110, 105]

solar = [0, 0, 0, 0, 0, 5, 30, 60, 90, 110, 120, 125,
        120, 110, 95, 70, 40, 15, 0, 0, 0, 0, 0, 0]

prices = [40, 38, 35, 32, 30, 35, 50, 80, 120, 140, 150, 145,
         140, 135, 130, 140, 160, 180, 170, 150, 120, 90, 60, 50]

result = optimize_battery_sizing(load, solar, prices,
                                max_power_mw=10, max_energy_mwh=40)

print(f"Optimal size: {result['optimal_size']['power_mw']:.1f} MW / "
     f"{result['optimal_size']['energy_mwh']:.1f} MWh")
print(f"NPV: ${result['economics']['npv']:,.0f}")
print(f"Payback: {result['economics']['payback_years']:.1f} years")

エネルギー裁定取引の最適化

価格ベースの裁定取引
def optimize_energy_arbitrage(prices, power_capacity, energy_capacity,
                              efficiency=0.9, cycles_per_day=1):
    """
    Optimize battery operation for energy arbitrage

    Buy low, sell high based on electricity prices
    """
    from pulp import *

    T = len(prices)
    prob = LpProblem("Arbitrage", LpMaximize)

    # Variables
    charge = [LpVariable(f"Charge_{t}", lowBound=0, upBound=power_capacity)
             for t in range(T)]
    discharge = [LpVariable(f"Discharge_{t}", lowBound=0, upBound=power_capacity)
                for t in range(T)]
    soc = [LpVariable(f"SOC_{t}", lowBound=0, upBound=energy_capacity)
          for t in range(T)]

    # Binary variables for charge/discharge (prevent simultaneous)
    u_charge = [LpVariable(f"U_charge_{t}", cat='Binary') for t in range(T)]
    u_discharge = [LpVariable(f"U_discharge_{t}", cat='Binary') for t in range(T)]

    # Objective: maximize profit
    revenue = lpSum([discharge[t] * prices[t] for t in range(T)])
    cost = lpSum([charge[t] * prices[t] for t in range(T)])
    prob += revenue - cost

    # Constraints
    for t in range(T):
        # SOC dynamics
        if t == 0:
            initial_soc = energy_capacity * 0.5
            prob += soc[t] == initial_soc + \
                    charge[t] * efficiency - discharge[t] / efficiency
        else:
            prob += soc[t] == soc[t-1] + \
                    charge[t] * efficiency - discharge[t] / efficiency

        # Cannot charge and discharge simultaneously
        prob += u_charge[t] + u_discharge[t] <= 1

        # Link binary variables to continuous
        prob += charge[t] <= power_capacity * u_charge[t]
        prob += discharge[t] <= power_capacity * u_discharge[t]

    # Cycle limit (battery degradation)
    max_throughput = energy_capacity * cycles_per_day
    prob += lpSum([discharge[t] for t in range(T)]) <= max_throughput

    # Solve
    prob.solve(PULP_CBC_CMD(msg=0))

    return {
        'profit': value(prob.objective),
        'revenue': sum([discharge[t].varValue * prices[t] for t in range(T)]),
        'cost': sum([charge[t].varValue * prices[t] for t in range(T)]),
        'charge_schedule': [charge[t].varValue for t in range(T)],
        'discharge_schedule': [discharge[t].varValue for t in range(T)],
        'soc_profile': [soc[t].varValue for t in range(T)],
        'cycles_used': sum([discharge[t].varValue for t in range(T)]) / energy_capacity
    }

ピークカットとデマンド料金の削減

商業施設・工場のピークカット
def optimize_peak_shaving(load_forecast, demand_charge_rate,
                         energy_rate, power_capacity, energy_capacity):
    """
    Optimize battery to reduce demand charges

    Demand charges based on monthly peak demand
    """
    from pulp import *

    T = len(load_forecast)
    prob = LpProblem("Peak_Shaving", LpMinimize)

    # Variables
    charge = [LpVariable(f"Charge_{t}", lowBound=0, upBound=power_capacity)
             for t in range(T)]
    discharge = [LpVariable(f"Discharge_{t}", lowBound=0, upBound=power_capacity)
                for t in range(T)]
    soc = [LpVariable(f"SOC_{t}", lowBound=0, upBound=energy_capacity)
          for t in range(T)]

    # Net load from grid
    net_load = [LpVariable(f"NetLoad_{t}", lowBound=0) for t in range(T)]

    # Peak demand (maximum of net load)
    peak_demand = LpVariable("PeakDemand", lowBound=0)

    # Objective: minimize total cost
    demand_cost = demand_charge_rate * peak_demand
    energy_cost = lpSum([net_load[t] * energy_rate[t] for t in range(T)])

    prob += demand_cost + energy_cost

    # Constraints
    for t in range(T):
        # Net load = original load - discharge + charge
        prob += net_load[t] == load_forecast[t] - discharge[t] + charge[t]

        # Peak constraint
        prob += peak_demand >= net_load[t]

        # SOC dynamics
        if t == 0:
            prob += soc[t] == energy_capacity * 0.5 + \
                    charge[t] * 0.92 - discharge[t] / 0.92
        else:
            prob += soc[t] == soc[t-1] + \
                    charge[t] * 0.92 - discharge[t] / 0.92

    # Solve
    prob.solve(PULP_CBC_CMD(msg=0))

    original_peak = max(load_forecast)
    new_peak = peak_demand.varValue

    return {
        'total_cost': value(prob.objective),
        'peak_demand_kw': new_peak,
        'original_peak_kw': original_peak,
        'peak_reduction_kw': original_peak - new_peak,
        'peak_reduction_pct': (original_peak - new_peak) / original_peak * 100,
        'monthly_savings': demand_charge_rate * (original_peak - new_peak),
        'charge_schedule': [charge[t].varValue for t in range(T)],
        'discharge_schedule': [discharge[t].varValue for t in range(T)],
        'net_load_profile': [net_load[t].varValue for t in range(T)]
    }

# Example
load_forecast = [800, 750, 700, 680, 700, 750, 850, 920, 980, 1020, 1050, 1080,
                1100, 1090, 1070, 1050, 1080, 1100, 1050, 980, 920, 880, 850, 820]

demand_charge = 15  # $/kW
energy_rate = [0.08] * 24  # $/kWh

result = optimize_peak_shaving(load_forecast, demand_charge, energy_rate,
                               power_capacity=200, energy_capacity=400)

print(f"Peak reduction: {result['peak_reduction_kw']:.0f} kW "
     f"({result['peak_reduction_pct']:.1f}%)")
print(f"Monthly savings: ${result['monthly_savings']:,.0f}")

再生可能エネルギーの出力安定化

太陽光発電+貯蔵の最適化
def optimize_solar_plus_storage(solar_generation, load, power_capacity,
                                energy_capacity, tariff_structure):
    """
    Optimize storage with solar to maximize self-consumption
    and minimize grid imports
    """
    from pulp import *

    T = len(solar_generation)
    prob = LpProblem("Solar_Storage", LpMinimize)

    # Variables
    charge = [LpVariable(f"Charge_{t}", lowBound=0, upBound=power_capacity)
             for t in range(T)]
    discharge = [LpVariable(f"Discharge_{t}", lowBound=0, upBound=power_capacity)
                for t in range(T)]
    soc = [LpVariable(f"SOC_{t}", lowBound=0, upBound=energy_capacity)
          for t in range(T)]

    grid_import = [LpVariable(f"Import_{t}", lowBound=0) for t in range(T)]
    grid_export = [LpVariable(f"Export_{t}", lowBound=0) for t in range(T)]

    # Objective: minimize electricity cost
    import_cost = lpSum([grid_import[t] * tariff_structure['import_rate'][t]
                        for t in range(T)])
    export_revenue = lpSum([grid_export[t] * tariff_structure['export_rate'][t]
                           for t in range(T)])

    prob += import_cost - export_revenue

    # Constraints
    for t in range(T):
        # Energy balance
        # Load = Solar + Discharge - Charge + Import - Export
        prob += load[t] == solar_generation[t] + discharge[t] - charge[t] + \
                grid_import[t] - grid_export[t]

        # Battery can only charge from solar
        prob += charge[t] <= solar_generation[t]

        # SOC dynamics
        if t == 0:
            prob += soc[t] == energy_capacity * 0.2 + \
                    charge[t] * 0.92 - discharge[t] / 0.92
        else:
            prob += soc[t] == soc[t-1] + \
                    charge[t] * 0.92 - discharge[t] / 0.92

    # Solve
    prob.solve(PULP_CBC_CMD(msg=0))

    # Calculate metrics
    total_import = sum([grid_import[t].varValue for t in range(T)])
    total_export = sum([grid_export[t].varValue for t in range(T)])
    total_load = sum(load)
    total_solar = sum(solar_generation)

    self_consumption = total_solar - total_export
    self_consumption_rate = self_consumption / total_solar * 100

    return {
        'total_cost': value(prob.objective),
        'grid_import_kwh': total_import,
        'grid_export_kwh': total_export,
        'self_consumption_kwh': self_consumption,
        'self_consumption_rate_pct': self_consumption_rate,
        'charge_schedule': [charge[t].varValue for t in range(T)],
        'discharge_schedule': [discharge[t].varValue for t in range(T)],
        'soc_profile': [soc[t].varValue for t in range(T)]
    }

系統サービスと周波数調整

周波数調整の入札
def optimize_frequency_regulation(regulation_prices, energy_prices,
                                 power_capacity, energy_capacity,
                                 performance_score=0.95):
    """
    Optimize battery participation in frequency regulation market

    Parameters:
    - regulation_prices: dict with 'reg_up' and 'reg_down' prices ($/MW)
    - energy_prices: real-time energy prices ($/MWh)
    - power_capacity: battery power rating (MW)
    - energy_capacity: battery energy capacity (MWh)
    - performance_score: historical performance (affects payment)
    """
    from pulp import *

    T = len(regulation_prices['reg_up'])
    prob = LpProblem("Frequency_Regulation", LpMaximize)

    # Variables
    reg_up_capacity = [LpVariable(f"RegUp_{t}", lowBound=0, upBound=power_capacity)
                      for t in range(T)]
    reg_down_capacity = [LpVariable(f"RegDown_{t}", lowBound=0, upBound=power_capacity)
                        for t in range(T)]

    energy_position = [LpVariable(f"Energy_{t}") for t in range(T)]

    # Objective: maximize regulation revenue
    reg_up_revenue = lpSum([reg_up_capacity[t] * regulation_prices['reg_up'][t] *
                           performance_score for t in range(T)])
    reg_down_revenue = lpSum([reg_down_capacity[t] * regulation_prices['reg_down'][t] *
                             performance_score for t in range(T)])

    prob += reg_up_revenue + reg_down_revenue

    # Constraints
    for t in range(T):
        # Power capacity constraint
        prob += reg_up_capacity[t] + reg_down_capacity[t] <= power_capacity

        # Energy capacity constraint (must be able to sustain regulation)
        # Assume 50% deployment over hour
        prob += energy_position[t] + 0.5 * reg_up_capacity[t] <= energy_capacity
        prob += energy_position[t] - 0.5 * reg_down_capacity[t] >= 0

        # Energy neutrality (return to same SOC)
        if t > 0:
            prob += energy_position[t] == energy_position[t-1]

    prob += energy_position[0] == energy_capacity * 0.5

    # Solve
    prob.solve(PULP_CBC_CMD(msg=0))

    return {
        'total_revenue': value(prob.objective),
        'reg_up_capacity': [reg_up_capacity[t].varValue for t in range(T)],
        'reg_down_capacity': [reg_down_capacity[t].varValue for t in range(T)],
        'avg_capacity_offered': np.mean([reg_up_capacity[t].varValue + reg_down_capacity[t].varValue
                                        for t in range(T)])
    }

バッテリーの劣化と寿命管理

劣化モデリング
class BatteryDegradationModel:
    """
    Model battery degradation from cycling and calendar aging
    """

    def __init__(self, initial_capacity_mwh, chemistry='li-ion'):
        self.initial_capacity = initial_capacity_mwh
        self.current_capacity = initial_capacity_mwh
        self.chemistry = chemistry
        self.total_cycles = 0
        self.total_throughput = 0

    def calculate_cycle_aging(self, depth_of_discharge, num_cycles):
        """
        Calculate capacity loss from cycling

        Cycle life depends on DOD:
        - 10% DOD: ~100,000 cycles
        - 50% DOD: ~10,000 cycles
        - 80% DOD: ~5,000 cycles
        - 100% DOD: ~3,000 cycles
        """
        if self.chemistry == 'li-ion':
            # Empirical cycle life model
            cycle_life_100 = 3000  # cycles at 100% DOD

            # Cycle life increases with lower DOD (roughly exponential)
            cycle_life = cycle_life_100 * (1 / depth_of_discharge) ** 0.5

            # Capacity loss per cycle
            loss_per_cycle = 0.2 / cycle_life  # 20% loss at EOL

            capacity_loss = loss_per_cycle * num_cycles

        elif self.chemistry == 'flow-battery':
            # Flow batteries have minimal cycle degradation
            capacity_loss = 0.0001 * num_cycles

        else:
            capacity_loss = 0

        self.current_capacity -= capacity_loss
        self.total_cycles += num_cycles

        return capacity_loss

    def calculate_calendar_aging(self, days, avg_soc=0.5, avg_temp_c=25):
        """
        Calculate capacity loss from calendar aging

        Higher SOC and temperature accelerate aging
        """
        if self.chemistry == 'li-ion':
            # Base calendar aging: 2-3% per year at 25°C, 50% SOC
            base_rate = 0.025 / 365  # per day

            # Temperature factor (Arrhenius-like)
            temp_factor = np.exp((avg_temp_c - 25) * 0.05)

            # SOC stress factor
            soc_factor = 1 + (avg_soc - 0.5)

            daily_loss_rate = base_rate * temp_factor * soc_factor
            capacity_loss = daily_loss_rate * days * self.initial_capacity

        else:
            capacity_loss = 0

        self.current_capacity -= capacity_loss

        return capacity_loss

    def calculate_state_of_health(self):
        """Return current state of health (%)"""
        return (self.current_capacity / self.initial_capacity) * 100

    def estimate_remaining_life(self, daily_cycles, avg_dod):
        """
        Estimate remaining useful life

        Returns days until 80% capacity threshold
        """
        target_capacity = self.initial_capacity * 0.8
        remaining_capacity_to_lose = self.current_capacity - target_capacity

        if remaining_capacity_to_lose <= 0:
            return 0  # Already below threshold

        # Daily degradation from cycling
        cycle_loss_per_day = self.calculate_cycle_aging(avg_dod, daily_cycles)

        # Daily calendar aging
        calendar_loss_per_day = self.calculate_calendar_aging(1)

        total_daily_loss = cycle_loss_per_day + calendar_loss_per_day

        if total_daily_loss <= 0:
            return float('inf')

        remaining_days = remaining_capacity_to_lose / total_daily_loss

        return remaining_days

# Example usage
battery = BatteryDegradationModel(initial_capacity_mwh=10, chemistry='li-ion')

# Simulate 1 year of operation
for day in range(365):
    # 1 cycle per day at 80% DOD
    battery.calculate_cycle_aging(depth_of_discharge=0.8, num_cycles=1)
    # Calendar aging
    battery.calculate_calendar_aging(days=1, avg_soc=0.6, avg_temp_c=25)

print(f"State of Health after 1 year: {battery.calculate_state_of_health():.1f}%")
print(f"Estimated remaining life: {battery.estimate_remaining_life(1, 0.8)/365:.1f} years")

複数用途による価値の積み上げ

収益の積み上げの最適化
def optimize_multi_use_storage(use_cases, power_capacity, energy_capacity,
                               time_horizon=24):
    """
    Optimize battery across multiple revenue streams (value stacking)

    Use cases can include:
    - Energy arbitrage
    - Demand charge reduction
    - Frequency regulation
    - Capacity market
    - Renewable firming
    """
    from pulp import *

    prob = LpProblem("Value_Stacking", LpMaximize)

    T = time_horizon

    # Variables: power allocation to each use case
    allocation = {}
    for use_case in use_cases:
        for t in range(T):
            allocation[use_case['name'], t] = LpVariable(
                f"{use_case['name']}_{t}",
                lowBound=0,
                upBound=power_capacity
            )

    # Objective: maximize total revenue
    total_revenue = []

    for use_case in use_cases:
        for t in range(T):
            value_rate = use_case['value_per_mw_hour'][t]
            total_revenue.append(allocation[use_case['name'], t] * value_rate)

    prob += lpSum(total_revenue)

    # Constraints

    # Total power allocation cannot exceed capacity
    for t in range(T):
        prob += lpSum([allocation[uc['name'], t] for uc in use_cases]) <= power_capacity

    # Use case specific constraints
    for use_case in use_cases:
        if 'min_commitment' in use_case:
            # Some services require minimum commitment
            for t in range(T):
                prob += allocation[use_case['name'], t] >= \
                        use_case['min_commitment'] * power_capacity

        if 'max_hours' in use_case:
            # Some services limited to certain hours per day
            prob += lpSum([allocation[use_case['name'], t]
                          for t in range(T)]) <= use_case['max_hours'] * power_capacity

    # Solve
    prob.solve(PULP_CBC_CMD(msg=0))

    # Calculate revenue by use case
    revenue_breakdown = {}
    for use_case in use_cases:
        revenue = sum([allocation[use_case['name'], t].varValue *
                      use_case['value_per_mw_hour'][t]
                      for t in range(T)])
        revenue_breakdown[use_case['name']] = revenue

    return {
        'total_revenue': value(prob.objective),
        'revenue_breakdown': revenue_breakdown,
        'allocation_schedule': {
            use_case['name']: [allocation[use_case['name'], t].varValue
                              for t in range(T)]
            for use_case in use_cases
        }
    }

# Example
use_cases = [
    {
        'name': 'Energy_Arbitrage',
        'value_per_mw_hour': [5, 4, 3, 2, 2, 3, 8, 15, 20, 22, 24, 23,
                             22, 21, 20, 22, 25, 28, 26, 22, 18, 12, 8, 6]
    },
    {
        'name': 'Frequency_Regulation',
        'value_per_mw_hour': [12] * 24,
        'min_commitment': 0.5,  # Must commit at least 50% if participating
    },
    {
        'name': 'Demand_Response',
        'value_per_mw_hour': [0]*14 + [50, 50, 50, 50] + [0]*6,
        'max_hours': 4  # Only 4 hours per day
    }
]

result = optimize_multi_use_storage(use_cases, power_capacity=10,
                                   energy_capacity=40, time_horizon=24)

print(f"Total daily revenue: ${result['total_revenue']:,.0f}")
for uc, rev in result['revenue_breakdown'].items():
    print(f"  {uc}: ${rev:,.0f} ({rev/result['total_revenue']*100:.1f}%)")

ツールとライブラリ

Python ライブラリ

最適化:

  • PuLP:線形計画法
  • Pyomo:高度な最適化モデリング
  • cvxpy:凸最適化
  • gurobipy:Gurobi オプティマイザー

バッテリーモデリング:

  • PyBaMM:物理ベースのバッテリーモデリング
  • scikit-learn:劣化予測のための機械学習

エネルギー分析:

  • pvlib:太陽光発電のモデリング
  • pandapower:電力システム分析
  • SAM(NREL):System Advisor Model

データと可視化:

  • pandas、numpy:データ操作
  • matplotlib、plotly:可視化
商用ソフトウェア

エネルギー貯蔵:

  • Energy Toolbase:バッテリー貯蔵の分析と提案
  • Homer Energy:ハイブリッド再生可能エネルギーシステムの設計
  • CAISO BESS:バッテリーエネルギー貯蔵システム用ツール
  • Stem Athena:AI を活用した貯蔵の最適化

系統統合:

  • AutoGrid Flex:分散型エネルギーリソースの最適化
  • Greensmith GEMS:系統エネルギー管理システム
  • Fluence Mosaic:バッテリーエネルギー貯蔵プラットフォーム
  • Tesla Autobidder:自動エネルギー取引

財務分析:

  • Aurora:エネルギー市場のモデリング
  • Ascend Analytics:貯蔵の価値評価
  • NREL REopt:再生可能エネルギーの統合

よくある課題と解決策

課題:収益の不確実性

問題:

  • 電力価格の変動
  • 市場ルールの変更
  • 技術コストの動向

解決策:

  • 保守的な財務予測
  • 収益源の多様化(バリュースタッキング)
  • 柔軟な契約とPPA
  • リアルオプション分析
  • シナリオに基づく計画
課題:劣化管理

問題:

  • バッテリー容量の経時的な低下
  • 保証条件の順守
  • 最適な交換時期

解決策:

  • 劣化を考慮したディスパッチアルゴリズム
  • 健全度(SOH)のモニタリング
  • 保守的なDODとサイクル上限
  • 予知保全
  • 増設戦略
課題:系統連系

問題:

  • 系統連系の遅延とコスト
  • 系統サービス要件
  • 電力会社との調整

解決策:

  • 早期の系統連系申請
  • 事前承認済み機器リスト
  • 明確な運用手順
  • 電力会社との定期的な連絡
  • 適合性試験と認証
課題:最適な規模の決定

問題:

  • 将来の需要/発電量が不確実
  • 技術コストの変化
  • 用途間の競合

解決策:

  • 主要パラメーターの感度分析
  • モジュール式/拡張可能な設計
  • 待つことによるオプション価値
  • 本格導入前のパイロットプログラム
  • 複数年にわたる最適化

出力形式

エネルギー貯蔵最適化レポート

エグゼクティブサマリー:

  • 推奨する蓄電システムの構成
  • 経済性分析と投資回収
  • 主な価値創出要因
  • 導入ロードマップ

システム仕様:

パラメーター値備考
技術Li-ion NMC高エネルギー密度
出力容量5 MWAC 定格
エネルギー容量20 MWh使用可能容量
放電時間4時間定格出力時
往復効率88%AC-AC
保証10 years / 5,000 cyclesEOL容量80%

経済性分析:

指標値
資本コスト$7.5M
設置・EPC$1.5M
プロジェクト総コスト$9.0M
年間収益$1.2M
年間O&M$75K
年間純便益$1.125M
単純投資回収期間8.0年
NPV(15年、8%)$3.2M
IRR12.5%

収益源:

収益源年間価値全体に占める割合
エネルギー裁定取引$450K37%
デマンドチャージ削減$380K32%
周波数調整$270K22%
容量市場$100K8%
合計$1,200K100%

運用戦略:

時間帯主な用途副次的な用途平均SOC目標
00:00-06:00充電(裁定取引)-80%
06:00-10:00周波数調整-50%
10:00-14:00太陽光発電の出力安定化周波数調整40%
14:00-20:00ピークカット裁定取引20%
20:00-24:00放電(裁定取引)-40%

性能指標:

指標目標モニタリング
1日あたりのサイクル数1.2SCADAロギング
平均DOD70%BMSデータ
稼働率> 95%稼働時間の追跡
応答時間< 1 sec市場要件への適合
SOHの劣化< 2.5%/年四半期ごとの試験

確認事項

追加の情報が必要な場合:

  1. 主な用途は何ですか?(裁定取引、ピークカット、再生可能エネルギー、系統サービス)
  2. 規模はどの程度ですか?(住宅、商業、電力会社)
  3. 系統の料金体系はどのようなものですか?(TOU、デマンドチャージ、リアルタイム料金)
  4. 既存または計画中の再生可能エネルギー発電設備はありますか?
  5. 利用可能な予算、または目標とする投資回収期間はどの程度ですか?
  6. 用地、温度、安全に関する制約はありますか?
  7. 想定されるプロジェクトの寿命はどのくらいですか?

関連スキル

  • power-grid-optimization:系統連系と給電のため
  • renewable-energy-planning:太陽光と風力の組み合わせのため
  • energy-logistics:エネルギーサプライチェーンの管理のため
  • demand-forecasting:需要と発電量の予測のため
  • optimization-modeling:高度な最適化手法のため
  • capacity-planning:長期的な設備容量計画のため
  • risk-mitigation:財務リスクと運用リスクの管理のため

コメント