|
| 1 | + |
| 2 | +set TIME ordered; # Set of hours in the simulation (1..8760) |
| 3 | +set MONTHS; # Set of months |
| 4 | + |
| 5 | +# ---------------------------------------------------------------------------- |
| 6 | +# PARAMETERS |
| 7 | +# ---------------------------------------------------------------------------- |
| 8 | + |
| 9 | +# Mapping parameter to link time steps to rate periods |
| 10 | +param time_month_map {TIME} within MONTHS; |
| 11 | + |
| 12 | +# Load and Rates |
| 13 | +param site_electric_load {TIME} >= 0; # Baseline facility load (kW) [Symbol: L] |
| 14 | +param energy_rate {MONTHS} >= 0; # Retail energy rate ($/kWh) [Symbol: RE] |
| 15 | +param demand_rate {MONTHS} >= 0; # Retail demand rate ($/kW) [Symbol: RD] |
| 16 | + |
| 17 | +# Market Prices and Signals |
| 18 | +param tso_energy_price {TIME}; # Electricity market price ($/MWh) [Symbol: ME] |
| 19 | +param tso_flex_up_price {TIME} >= 0; # Freq regulation market price ($/MWh) [Symbol: MR] |
| 20 | +param tso_flex_down_price {TIME} >= 0; # Freq regulation market price for regulation down ($/MWh) [Symbol: MR_DN] |
| 21 | +param reg_d_up {TIME}; # Freq reg signal for increased supply (%) [Symbol: RS_U] |
| 22 | +param reg_d_down {TIME}; # Freq reg signal for decreased supply (%) [Symbol: RS_D] |
| 23 | +param tso_load {TIME} >= 0; # Aggregate demand for TSO (MW) [Symbol: TL] |
| 24 | +param dso_load {TIME} >= 0; # Aggregate demand for DSO (MW) [Symbol: DL] |
| 25 | +param cp_tso_rate_kw >= 0; # Coincident peak capacity rate ($/kW) [Symbol: RC] |
| 26 | +param cp_dso_rate_kw >= 0; # Coincident peak capacity rate for DSO (if applicable) ($/kW) [Symbol: RC_DSO] |
| 27 | + |
| 28 | +# Battery Technical Specs |
| 29 | +param battery_power >= 0; # Inverter capacity/Max rate (kW) [Symbol: Gbattery] |
| 30 | +param battery_energy >= 0; # Storage capacity (kWh) [Symbol: Sbattery] |
| 31 | +param efficiency >= 0, <= 1; # Battery efficiency (%) [Symbol: E] |
| 32 | +param battery_fixed_om >= 0; # Battery O&M costs ($/kWh-year) [Symbol: OMbattery] |
| 33 | +param initial_soc_pct >= 0, <= 1, default 0.5; # Initial state of charge (kWh) [Symbol: Soc0] |
| 34 | +param min_soc_pct >= 0, <= 1, default 0.1; # Minimum state of charge as % of capacity (kWh) [Symbol: MinSoc] |
| 35 | +param max_soc_pct >= 0, <= 1, default 0.9; |
| 36 | + |
| 37 | +# Solar Technical Specs |
| 38 | +param solar_capacity >= 0; # Solar generation capacity (kW) [Symbol: Gsolar] |
| 39 | +param solar_production_profile {TIME} >= 0; # Solar production per unit capacity (%) [Symbol: U] |
| 40 | +param solar_fixed_om >= 0; # Solar O&M costs ($/kW-year) [Symbol: OMsolar] |
| 41 | + |
| 42 | +# Coincident Peak Identification |
| 43 | +# Find the max TSO load to identify t* |
| 44 | +param max_tso_val = max {t in TIME} tso_load[t]; |
| 45 | +param max_tso_time = max {t in TIME: tso_load[t] = max_tso_val} t; # Only count 1 hour for the coincident peak, not every hour where max demand happens |
| 46 | + |
| 47 | +param max_dso_val = max {t in TIME} dso_load[t]; |
| 48 | +param max_dso_time = max {t in TIME: dso_load[t] = max_dso_val} t; # Only count 1 hour for the coincident peak, not every hour where max demand happens |
| 49 | + |
| 50 | +# Baseline peak parameter for each month |
| 51 | +param baseline_peak {m in MONTHS} = max {t in TIME: time_month_map[t] = m} site_electric_load[t]; |
| 52 | + |
| 53 | +# Operating and Maintenance Costs |
| 54 | +# OM1 = Fixed costs (Battery + Solar) |
| 55 | +param OM1 = (battery_fixed_om * battery_energy) + (solar_fixed_om * solar_capacity); |
| 56 | + |
| 57 | +# ---------------------------------------------------------------------------- |
| 58 | +# DECISION VARIABLES |
| 59 | +# ---------------------------------------------------------------------------- |
| 60 | + |
| 61 | +var SolarProduction {TIME} >= 0; # Solar electricity production (kW) [Symbol: P] |
| 62 | +var BatteryCharge {TIME} >= 0; # BESS electric charge (kW) [Symbol: C] |
| 63 | +var BatteryDischarge {TIME} >= 0; # BESS electric discharge (kW) [Symbol: D] |
| 64 | +var Soc {TIME} >= min_soc_pct * battery_energy, <= max_soc_pct * battery_energy; # BESS state of charge (kWh) [Symbol: Soc] |
| 65 | +var NetLoad {TIME} >= 0; # Net facility load after BESS/Solar (kW) [Symbol: N] |
| 66 | +var ExportToGrid {TIME} >= 0; # Surplus solar production exported (kW) [Symbol: X] |
| 67 | +var FlexUpBattery {TIME} >= 0, <= battery_power; # BESS capacity participating in Freq Reg (kW) [Symbol: FR] |
| 68 | +var FlexDownBattery {TIME} >= 0, <= battery_power; # BESS capacity participating in Freq Reg for regulation down (kW) [Symbol: FR_DN] |
| 69 | + |
| 70 | +# Auxiliary variables for peak tracking (Required for Demand Charge Savings) |
| 71 | +var PeakNetLoad {MONTHS} >= 0; # Max net load per month |
| 72 | + |
| 73 | +var BatteryCharging {TIME} binary; # Binary variable to indicate if battery is charging (1) or discharging (0) at time t |
| 74 | +var BatteryDischarging {TIME} binary; # Binary variable to indicate if battery is discharging (1) or charging (0) at time t |
| 75 | + |
| 76 | +# ----- |
| 77 | +# Other variables (savings and revenues) for objective function |
| 78 | +# ----- |
| 79 | + |
| 80 | +# Energy Charge Savings |
| 81 | +# E1 = Sum((Lt - Nt) * RE) |
| 82 | +var EnergyChargesSavings = sum {t in TIME} (site_electric_load[t] - NetLoad[t]) * (tso_energy_price[t]/1000 + energy_rate[time_month_map[t]]); |
| 83 | + |
| 84 | +# Demand Charge Savings |
| 85 | +# D1 = Sum((Max(L) - Max(N)) * RD) |
| 86 | +var DemandChargesSavings = sum {m in MONTHS} (baseline_peak[m] - PeakNetLoad[m]) * demand_rate[m]; |
| 87 | + |
| 88 | +# Coincident Peak Charge Savings |
| 89 | +# CP1 = RC * (Lt* - Nt*) |
| 90 | +var CP_TSO_DemandChargesSavings = |
| 91 | + sum {t in TIME: t = max_tso_time} 12*(cp_tso_rate_kw * (site_electric_load[t] - NetLoad[t])); |
| 92 | + |
| 93 | +# Coincident Peak Charge Savings for DSO |
| 94 | +# CP2 = RC_DSO * (Dt* - Nt*) |
| 95 | +var CP_DSO_DemandChargesSavings = |
| 96 | + sum {t in TIME: t = max_dso_time} 12*(cp_dso_rate_kw * (site_electric_load[t] - NetLoad[t])); |
| 97 | + |
| 98 | +# Wholesale Energy Market Revenues |
| 99 | +# M1 = Sum(Xt * MEt) |
| 100 | +var ExportPowerValue = sum {t in TIME} (ExportToGrid[t] * tso_energy_price[t])/1000; |
| 101 | + |
| 102 | +# Frequency Regulation Market Revenues |
| 103 | +# FR1 = Sum(FRt * MRt) |
| 104 | +var FlexUpRevenue = sum {t in TIME} (FlexUpBattery[t] * tso_flex_up_price[t])/1000; |
| 105 | +# FR2 = Sum(FR_DNt * MR_DNt) |
| 106 | +var FlexDownRevenue = sum {t in TIME} (FlexDownBattery[t] * tso_flex_down_price[t])/1000; |
| 107 | + |
| 108 | + |
| 109 | +# ---------------------------------------------------------------------------- |
| 110 | +# OBJECTIVE FUNCTION |
| 111 | +# ---------------------------------------------------------------------------- |
| 112 | + |
| 113 | +# V1 = E1 + D1 + CP1 + M1 + FR1 - OM1 |
| 114 | +maximize NetValue: |
| 115 | + EnergyChargesSavings + |
| 116 | + DemandChargesSavings + |
| 117 | + CP_TSO_DemandChargesSavings + |
| 118 | + CP_DSO_DemandChargesSavings + |
| 119 | + ExportPowerValue + |
| 120 | + FlexUpRevenue + |
| 121 | + FlexDownRevenue - |
| 122 | + OM1; |
| 123 | + |
| 124 | +# ---------------------------------------------------------------------------- |
| 125 | +# OPERATING CONSTRAINTS |
| 126 | +# ---------------------------------------------------------------------------- |
| 127 | + |
| 128 | +subject to Exclusive_State {t in TIME}: |
| 129 | + BatteryCharging[t] + BatteryDischarging[t] <= 1; |
| 130 | + |
| 131 | +# Solar Production |
| 132 | +subject to SolarGenDef {t in TIME}: |
| 133 | + SolarProduction[t] = solar_production_profile[t] * solar_capacity; |
| 134 | + |
| 135 | +# Battery Power Capacity |
| 136 | +subject to ChargeLimit {t in TIME}: |
| 137 | + BatteryCharge[t] <= battery_power * BatteryCharging[t]; |
| 138 | + |
| 139 | +subject to DischargeLimit {t in TIME}: |
| 140 | + BatteryDischarge[t] <= battery_power * BatteryDischarging[t]; |
| 141 | + |
| 142 | +subject to FlexUpLimit {t in TIME}: |
| 143 | + FlexUpBattery[t] <= ((if ord(t) > 1 then Soc[t-1] else initial_soc_pct * battery_energy) - min_soc_pct * battery_energy) / (efficiency^0.5); |
| 144 | + |
| 145 | +subject to FlexDownLimit {t in TIME}: |
| 146 | + FlexDownBattery[t] <= (max_soc_pct * battery_energy - (if ord(t) > 1 then Soc[t-1] else initial_soc_pct * battery_energy)) * (efficiency^0.5); |
| 147 | + |
| 148 | +# Battery State of Charge limits |
| 149 | +subject to SocCapacity {t in TIME}: |
| 150 | + Soc[t] <= battery_energy; |
| 151 | + |
| 152 | +# Battery Continuity Balance |
| 153 | +subject to SocBalance {t in TIME}: |
| 154 | + Soc[t] = (if ord(t) > 1 then Soc[t-1] else initial_soc_pct * battery_energy) |
| 155 | + + ((efficiency^0.5)* BatteryCharge[t]) |
| 156 | + - (BatteryDischarge[t] / (efficiency^0.5)); |
| 157 | + |
| 158 | +# Net Electric Load |
| 159 | +# Nt = Lt - Pt - Dt + Ct + Xt |
| 160 | +subject to NetLoadDef {t in TIME}: |
| 161 | + NetLoad[t] = site_electric_load[t] |
| 162 | + - SolarProduction[t] |
| 163 | + - BatteryDischarge[t] |
| 164 | + + BatteryCharge[t] |
| 165 | + + ExportToGrid[t]; |
| 166 | + |
| 167 | +# Frequency Regulation Participation |
| 168 | +# Discharge must meet Up signal * Committed Capacity |
| 169 | +subject to RegUpConstraint {t in TIME}: |
| 170 | + BatteryDischarge[t] >= reg_d_up[t] * FlexUpBattery[t]; |
| 171 | + |
| 172 | +# Charge must meet Down signal * Committed Capacity |
| 173 | +subject to RegDownConstraint {t in TIME}: |
| 174 | + BatteryCharge[t] >= -reg_d_down[t] * FlexDownBattery[t]; |
| 175 | + |
| 176 | +# Define Peak Tracking for Demand Charges |
| 177 | +# PeakNetLoad must be at least the NetLoad in that month |
| 178 | +subject to PeakTracking {m in MONTHS, t in TIME: time_month_map[t] = m}: |
| 179 | + PeakNetLoad[m] >= NetLoad[t]; |
| 180 | + |
| 181 | +# Limit exports to onsite generation (Solar + Battery Discharge) |
| 182 | +subject to ExportLimit {t in TIME}: |
| 183 | + ExportToGrid[t] <= SolarProduction[t];#+ BatteryDischarge[t]; |
0 commit comments