Files
entrix_case_challange/optimizer/optimization/battery.py
2025-05-03 20:46:14 +02:00

75 lines
2.7 KiB
Python

import cvxpy as cp
import numpy as np
def solve_battery_optimization_hourly(
hourly_prices, # Array of prices for each hour [0, 1, ..., n-1]
initial_B, # Current state of charge (MWh)
max_capacity=1.0, # MWh
max_rate=1.0 # MW (+ve discharge / -ve charge)
):
"""
Solves the battery scheduling optimization problem assuming hourly steps. We want to decide at the start of each hour t=0..n-1
how much power to buy/sell (P_net_t) and therefore the state of charge at the start of each next hour (B_t+1).
Args:
hourly_prices: Prices (€/MWh) for each hour t=0..n-1.
initial_B: The state of charge at the beginning (time t=0).
max_capacity: Maximum battery energy capacity (MWh).
max_rate: Maximum charge/discharge power rate (MW).
Returns:
Tuple: (status, optimal_profit, power_schedule, B_schedule)
Returns (status, None, None, None) if optimization fails.
"""
n_hours = len(hourly_prices)
# --- CVXPY Variables ---
# Power flow for each hour t=0..n-1 (-discharge, +charge)
P = cp.Variable(n_hours, name="Power_Flow_MW")
# State of charge at the START of each hour t=0..n (B[t] is B at hour t)
B = cp.Variable(n_hours + 1, name="State_of_Charge_MWh")
# --- Objective Function ---
# Profit = sum(price[t] * Power[t])
prices = np.array(hourly_prices)
profit = prices @ P # Equivalent to cp.sum(cp.multiply(prices, P)) / prices.dot(P)
objective = cp.Maximize(profit)
# --- Constraints ---
constraints = []
# 1. Initial B
constraints.append(B[0] == initial_B)
# 2. B Dynamics: B[t+1] = B[t] - P[t] * 1 hour
constraints.append(B[1:] == B[:-1] + P)
# 3. Power Rate Limits: -max_rate <= P[t] <= max_rate
constraints.append(cp.abs(P) <= max_rate)
# 4. B Limits: 0 <= B[t] <= max_capacity (applies to B[0]...B[n])
constraints.append(B >= 0)
constraints.append(B <= max_capacity)
# --- Problem Definition and Solving ---
problem = cp.Problem(objective, constraints)
try:
# Alternative solvers are ECOS, MOSEK, and SCS
optimal_profit = problem.solve(solver=cp.CLARABEL, verbose=False)
if problem.status in [cp.OPTIMAL, cp.OPTIMAL_INACCURATE]:
return (
problem.status,
optimal_profit,
P.value, # NumPy array of optimal power flows per hour
B.value # NumPy array of optimal B at start of each hour
)
else:
print(f"Optimization failed. Solver status: {problem.status}")
return problem.status, None, None, None
except cp.error.SolverError as e:
print(f"Solver Error: {e}")
return "Solver Error", None, None, None