-
Notifications
You must be signed in to change notification settings - Fork 5
Open
Description
Motivation
Currently, storage components only support a single cash flow that is applied to the SOC (State of Charge) variable. This creates problems when trying to model more realistic economic behavior of storage systems where:
- There might be different costs for charging vs discharging operations
- There could be a holding cost based on SOC (capacity payments)
- Degradation costs might vary based on operation type
Current Behavior
- Storage components only allow associating cashflows with the SOC variable
- These cashflows are processed differently from non-storage components in the objective function
- To work around this limitation, users need to remove cashflows from storage entirely
Desired Behavior
Storage components should support three distinct types of cashflows:
- SOC Cashflows: Applied to energy stored (capacity payments, opportunity costs)
- Charge Cashflows: Applied to charging operations (electricity purchase costs, wear-and-tear)
- Discharge Cashflows: Applied to discharging operations (electricity sales revenue)
Design
- Extend the
Storageclass to accept three separate cashflow lists:
class Storage(Component):
def __init__(
self,
# ... existing parameters ...
cashflows=None, # Legacy parameter (applied to SOC)
soc_cashflows=None, # Applied to SOC
charge_cashflows=None, # Applied to charging operations
discharge_cashflows=None, # Applied to discharging operations
# ... other parameters ...
):
# Initialize cashflow lists
self.soc_cashflows = soc_cashflows or []
self.charge_cashflows = charge_cashflows or []
self.discharge_cashflows = discharge_cashflows or []
# For backward compatibility
if cashflows:
self.soc_cashflows.extend(cashflows)- Update the objective function to consider all three cashflow types:
def objective_rule(m: pyo.ConcreteModel) -> pyo.Expression:
total = 0
# ... existing code for non-storage components ...
# Handle storage components
for comp in m.system.components:
if comp.name in m.STORAGE:
# SOC cashflows
for cf in comp.soc_cashflows:
for t in m.T:
price = cf.price_profile[t] if t < len(cf.price_profile) else cf.alpha
total += cf.sign * price * ((m.SOC[comp.name, t] / cf.dprime) ** cf.scalex)
# Charge cashflows
for cf in comp.charge_cashflows:
for t in m.T:
price = cf.price_profile[t] if t < len(cf.price_profile) else cf.alpha
total += cf.sign * price * ((m.charge[comp.name, t] / cf.dprime) ** cf.scalex)
# Discharge cashflows
for cf in comp.discharge_cashflows:
for t in m.T:
price = cf.price_profile[t] if t < len(cf.price_profile) else cf.alpha
total += cf.sign * price * ((m.discharge[comp.name, t] / cf.dprime) ** cf.scalex)Impact
Example Usage
battery = Storage(
name="battery",
resource=electricity,
max_capacity=50,
rte=0.9,
max_charge_rate=0.3,
max_discharge_rate=0.3,
initial_stored=0.2,
soc_cashflows=[Cost(name="battery_capacity_cost", alpha=0.5)], # $/MWh-stored
charge_cashflows=[Cost(name="battery_charging_cost", alpha=45)], # $/MWh-charged
discharge_cashflows=[Revenue(name="battery_discharge_rev", alpha=50)], # $/MWh-discharged
)- More realistic modeling of storage economics
- Better representation of market participation for batteries
- Ability to model arbitrage and ancillary services more accurately
- More intuitive model specification for users
Metadata
Metadata
Assignees
Labels
No labels