-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
LDES #406
base: develop
Are you sure you want to change the base?
LDES #406
Changes from all commits
5b04b56
8627a8d
564365f
ae61517
3e1a3d6
5542a5d
c2f3c5d
37e3083
9ee741e
e2a0cac
66e848a
53014bc
14f3ced
b67bdcc
7c339a1
ac253e5
d66297f
cdc4da8
0fbdb98
58b6769
0e739ab
25f310f
4f987ac
1f4ebe9
e6f7cc4
8f6ff4f
515a684
fbd6d46
9954293
019b6bc
371a76b
b84e917
5ce036a
ad46c89
ce5977a
51a7ff9
1d3181f
d6b9b6e
2b07689
c6ff686
0b1618b
eb6ed96
c273972
3a8834a
134adb6
5db75ae
0f13d77
7d2694e
31e912b
0d37b7c
84293bd
3bd6e24
d0b4671
f34f44c
0336a2d
300c95e
9f4df44
5f64284
4cf25f3
6212406
8ad69fd
88cc28e
f04b60d
c069122
1c85716
0f2f246
5c512b5
9bde839
bb90447
81927a2
7edb8a3
a885836
80e7f6c
1428684
e8032d1
d13ff0b
586b4c5
8f42464
f642462
b0a24c5
305075d
dd13655
2960b23
c9115a5
5f2ceb6
710f2f5
1d3bce0
b69edbf
949bcb8
293fcd7
1de9ca6
793e8d7
e3a7656
3ce3e32
dcda12a
970bd2b
febc367
8fb61ee
b1b8dd7
1e06aac
9d0c564
12889a5
63ab4eb
c4f138a
10d3264
34179ac
fc44e06
7161f6e
6139428
ac5a954
29657c0
a8f12a3
7d53be8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,10 +25,21 @@ end | |
|
||
|
||
function add_general_storage_dispatch_constraints(m, p, b; _n="") | ||
# Constraint (4a): initial state of charge | ||
@constraint(m, | ||
m[Symbol("dvStoredEnergy"*_n)][b, 0] == p.s.storage.attr[b].soc_init_fraction * m[Symbol("dvStorageEnergy"*_n)][b] | ||
) | ||
# Constraint (4a): initial and final state of charge | ||
if hasproperty(p.s.storage.attr[b], :optimize_soc_init_fraction) && p.s.storage.attr[b].optimize_soc_init_fraction | ||
# @constraint(m, m[:dvStoredEnergy]["ElectricStorage",maximum(p.time_steps)] == p.s.storage.attr[b].soc_init_fraction * m[Symbol("dvStorageEnergy"*_n)][b] ) | ||
print("\nOptimizing "*b*" inital SOC and constraining initial SOC = final SOC\n") | ||
@constraint(m, | ||
m[Symbol("dvStoredEnergy"*_n)][b, 0] == m[:dvStoredEnergy]["ElectricStorage", maximum(p.time_steps)] | ||
) | ||
else | ||
@constraint(m, | ||
m[Symbol("dvStoredEnergy"*_n)][b, 0] == p.s.storage.attr[b].soc_init_fraction * m[Symbol("dvStorageEnergy"*_n)][b] | ||
) | ||
# @constraint(m, | ||
# m[Symbol("dvStoredEnergy"*_n)][b, maximum(p.time_steps)] == p.s.storage.attr[b].soc_init_fraction * m[Symbol("dvStorageEnergy"*_n)][b] | ||
# ) | ||
end | ||
|
||
#Constraint (4n): State of charge upper bound is storage system size | ||
@constraint(m, [ts in p.time_steps], | ||
|
@@ -52,16 +63,18 @@ function add_elec_storage_dispatch_constraints(m, p, b; _n="") | |
|
||
# Constraint (4g): state-of-charge for electrical storage - with grid | ||
@constraint(m, [ts in p.time_steps_with_grid], | ||
m[Symbol("dvStoredEnergy"*_n)][b, ts] == m[Symbol("dvStoredEnergy"*_n)][b, ts-1] + p.hours_per_time_step * ( | ||
m[Symbol("dvStoredEnergy"*_n)][b, ts] == (1-p.s.storage.attr[b].self_discharge_fraction_per_timestep) * m[Symbol("dvStoredEnergy"*_n)][b, ts-1] + | ||
p.hours_per_time_step * ( | ||
sum(p.s.storage.attr[b].charge_efficiency * m[Symbol("dvProductionToStorage"*_n)][b, t, ts] for t in p.techs.elec) | ||
+ p.s.storage.attr[b].grid_charge_efficiency * m[Symbol("dvGridToStorage"*_n)][b, ts] | ||
- m[Symbol("dvDischargeFromStorage"*_n)][b,ts] / p.s.storage.attr[b].discharge_efficiency | ||
- ((m[Symbol("dvDischargeFromStorage"*_n)][b,ts]+m[Symbol("dvStorageToGrid"*_n)][b, ts]) / p.s.storage.attr[b].discharge_efficiency) | ||
) | ||
) | ||
|
||
# Constraint (4h): state-of-charge for electrical storage - no grid | ||
@constraint(m, [ts in p.time_steps_without_grid], | ||
m[Symbol("dvStoredEnergy"*_n)][b, ts] == m[Symbol("dvStoredEnergy"*_n)][b, ts-1] + p.hours_per_time_step * ( | ||
m[Symbol("dvStoredEnergy"*_n)][b, ts] == (1-p.s.storage.attr[b].self_discharge_fraction_per_timestep) * m[Symbol("dvStoredEnergy"*_n)][b, ts-1] + | ||
p.hours_per_time_step * ( | ||
sum(p.s.storage.attr[b].charge_efficiency * m[Symbol("dvProductionToStorage"*_n)][b,t,ts] for t in p.techs.elec) | ||
- m[Symbol("dvDischargeFromStorage"*_n)][b, ts] / p.s.storage.attr[b].discharge_efficiency | ||
) | ||
|
@@ -77,28 +90,59 @@ function add_elec_storage_dispatch_constraints(m, p, b; _n="") | |
@constraint(m, [ts in p.time_steps_with_grid], | ||
m[Symbol("dvStoragePower"*_n)][b] >= m[Symbol("dvDischargeFromStorage"*_n)][b, ts] + | ||
sum(m[Symbol("dvProductionToStorage"*_n)][b, t, ts] for t in p.techs.elec) + m[Symbol("dvGridToStorage"*_n)][b, ts] | ||
+ m[Symbol("dvStorageToGrid"*_n)][b, ts] | ||
) | ||
|
||
#Dispatch from electrical storage is no greater than power capacity | ||
@constraint(m, [ts in p.time_steps_without_grid], | ||
m[Symbol("dvStoragePower"*_n)][b] >= m[Symbol("dvDischargeFromStorage"*_n)][b,ts] + m[Symbol("dvStorageToGrid"*_n)][b, ts]) | ||
|
||
#Constraint (4l)-alt: Dispatch from electrical storage is no greater than power capacity (no grid connection) | ||
@constraint(m, [ts in p.time_steps_without_grid], | ||
m[Symbol("dvStoragePower"*_n)][b] >= m[Symbol("dvDischargeFromStorage"*_n)][b,ts] + | ||
sum(m[Symbol("dvProductionToStorage"*_n)][b, t, ts] for t in p.techs.elec) | ||
) | ||
# Remove grid-to-storage as an option if option to grid charge is turned off | ||
|
||
#Constraint (4m)-1: Remove grid-to-storage as an option if option to grid charge is turned off | ||
if !(p.s.storage.attr[b].can_grid_charge) | ||
for ts in p.time_steps_with_grid | ||
fix(m[Symbol("dvGridToStorage"*_n)][b, ts], 0.0, force=true) | ||
end | ||
end | ||
|
||
#Constraint (4m)-2: Force storage export to grid to zero if option to grid export is turned off | ||
if !p.s.storage.attr[b].can_export_to_grid | ||
for ts in p.time_steps | ||
fix(m[Symbol("dvStorageToGrid"*_n)][b, ts], 0.0, force=true) | ||
end | ||
end | ||
|
||
if p.s.storage.attr[b].minimum_avg_soc_fraction > 0 | ||
avg_soc = sum(m[Symbol("dvStoredEnergy"*_n)][b, ts] for ts in p.time_steps) / | ||
(8760. / p.hours_per_time_step) | ||
@constraint(m, avg_soc >= p.s.storage.attr[b].minimum_avg_soc_fraction * | ||
sum(m[Symbol("dvStorageEnergy"*_n)][b]) | ||
) | ||
end | ||
|
||
if p.s.storage.attr[b] isa ElectricStorage && !isnothing(p.s.storage.attr[b].fixed_duration) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need these isa ElectricStorage checks because we're already inside the function add_elec_storage_dispatch_constraints(m, p, b; _n="") which is only passed ElectricStorage as b |
||
@constraint(m, m[Symbol("dvStoragePower"*_n)][b] == m[Symbol("dvStorageEnergy"*_n)][b] / p.s.storage.attr[b].fixed_duration) | ||
end | ||
|
||
# Prevent charging and discharging of the battery at the same time | ||
hdunham marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if !(p.s.storage.attr[b].allow_simultaneous_charge_discharge) | ||
#TODO: implement indicator constraint version for solvers that support it | ||
@constraint(m, [ts in p.time_steps], | ||
m[Symbol("dvGridToStorage"*_n)][b, ts] + | ||
sum(m[Symbol("dvProductionToStorage"*_n)][b, t, ts] for t in p.techs.elec) <= | ||
p.s.storage.attr[b].max_kw * m[Symbol("binBattCharging")][ts]) | ||
@constraint(m, [ts in p.time_steps], | ||
m[Symbol("dvStorageToGrid"*_n)][b, ts] + | ||
m[Symbol("dvDischargeFromStorage"*_n)][b, ts] <= | ||
p.s.storage.attr[b].max_kw * (1-m[Symbol("binBattCharging")][ts])) | ||
end | ||
|
||
|
||
end | ||
|
||
function add_hot_thermal_storage_dispatch_constraints(m, p, b; _n="") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -213,6 +213,7 @@ function build_reopt!(m::JuMP.AbstractModel, p::REoptInputs) | |
@constraint(m, [ts in p.time_steps], m[:dvGridToStorage][b, ts] == 0) | ||
@constraint(m, [t in p.techs.elec, ts in p.time_steps_with_grid], | ||
m[:dvProductionToStorage][b, t, ts] == 0) | ||
@constraint(m, [ts in p.time_steps], m[Symbol("dvStorageToGrid"*_n)][b, ts] == 0) # if there isn't a battery, then the battery can't export power to the grid | ||
elseif b in p.s.storage.types.hot | ||
@constraint(m, [q in q in setdiff(p.heating_loads, p.heating_loads_served_by_tes[b]), ts in p.time_steps], m[:dvHeatFromStorage][b,q,ts] == 0) | ||
if "DomesticHotWater" in p.heating_loads_served_by_tes[b] | ||
|
@@ -392,9 +393,11 @@ function build_reopt!(m::JuMP.AbstractModel, p::REoptInputs) | |
sum( p.s.storage.attr[b].net_present_cost_per_kwh * m[:dvStorageEnergy][b] for b in p.s.storage.types.all ) | ||
)) | ||
|
||
@expression(m, TotalPerUnitSizeOMCosts, p.third_party_factor * p.pwf_om * | ||
sum( p.om_cost_per_kw[t] * m[:dvSize][t] for t in p.techs.all ) | ||
) | ||
@expression(m, TotalPerUnitSizeOMCosts, p.third_party_factor * p.pwf_om * ( | ||
sum(p.om_cost_per_kw[t] * m[:dvSize][t] for t in p.techs.all) + | ||
sum(p.s.storage.attr[b].om_cost_per_kw * m[:dvStoragePower][b] for b in p.s.storage.types.elec) + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is O&M only being added for electric storage when the inputs for it were added to all storage models? |
||
sum(p.s.storage.attr[b].om_cost_per_kwh * m[:dvStorageEnergy][b] for b in p.s.storage.types.elec) | ||
)) | ||
|
||
add_elec_utility_expressions(m, p) | ||
|
||
|
@@ -587,13 +590,22 @@ function add_variables!(m::JuMP.AbstractModel, p::REoptInputs) | |
dvProductionToStorage[p.s.storage.types.all, union(p.techs.ghp,p.techs.all), p.time_steps] >= 0 # Power from technology t used to charge storage system b [kW] | ||
dvDischargeFromStorage[p.s.storage.types.all, p.time_steps] >= 0 # Power discharged from storage system b [kW] | ||
dvGridToStorage[p.s.storage.types.elec, p.time_steps] >= 0 # Electrical power delivered to storage by the grid [kW] | ||
dvStorageToGrid[p.s.storage.types.elec, p.time_steps] >= 0 # TODO, add: "p.StorageSalesTiers" as well? export of energy from storage to the grid | ||
dvStoredEnergy[p.s.storage.types.all, 0:p.time_steps[end]] >= 0 # State of charge of storage system b | ||
dvStoragePower[p.s.storage.types.all] >= 0 # Power capacity of storage system b [kW] | ||
dvStorageEnergy[p.s.storage.types.all] >= 0 # Energy capacity of storage system b [kWh] | ||
dvPeakDemandTOU[p.ratchets, 1:p.s.electric_tariff.n_tou_demand_tiers] >= 0 # Peak electrical power demand during ratchet r [kW] | ||
dvPeakDemandMonth[p.months, 1:p.s.electric_tariff.n_monthly_demand_tiers] >= 0 # Peak electrical power demand during month m [kW] | ||
MinChargeAdder >= 0 | ||
binGHP[p.ghp_options], Bin # Can be <= 1 if require_ghp_purchase=0, and is ==1 if require_ghp_purchase=1 | ||
|
||
end | ||
|
||
for b in p.s.storage.types.elec | ||
if !(p.s.storage.attr[b].allow_simultaneous_charge_discharge) | ||
@warn "Adding binary variable to prevent simultaneous battery charge/discharge. Some solvers are very slow with integer variables." | ||
@variable(m, binBattCharging[p.time_steps], Bin) # Binary for battery charging (vs discharging) | ||
end | ||
end | ||
|
||
if !isempty(p.techs.gen) # Problem becomes a MILP | ||
|
@@ -612,7 +624,7 @@ function add_variables!(m::JuMP.AbstractModel, p::REoptInputs) | |
end | ||
|
||
if !(p.s.electric_utility.allow_simultaneous_export_import) & !isempty(p.s.electric_tariff.export_bins) | ||
@warn "Adding binary variable to prevent simultaneous grid import/export. Some solvers are very slow with integer variables" | ||
@warn "Adding binary variable to prevent simultaneous grid import/export. Some solvers are very slow with integer variables." | ||
@variable(m, binNoGridPurchases[p.time_steps], Bin) | ||
end | ||
|
||
|
@@ -644,7 +656,7 @@ function add_variables!(m::JuMP.AbstractModel, p::REoptInputs) | |
end | ||
|
||
if !isempty(p.s.electric_utility.outage_durations) # add dvUnserved Load if there is at least one outage | ||
@warn "Adding binary variable to model outages. Some solvers are very slow with integer variables" | ||
@warn "Adding binary variable to model outages. Some solvers are very slow with integer variables." | ||
max_outage_duration = maximum(p.s.electric_utility.outage_durations) | ||
outage_time_steps = p.s.electric_utility.outage_time_steps | ||
tZeros = p.s.electric_utility.outage_start_time_steps | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why was this constraint added? doesn't seem to do anything beyond what (4l)-alt already accomplishes since it's over time steps without grid