Skip to content
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

Refactor reserves #562

Merged
merged 31 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5ab8d8a
Refector reserves
cfe316 Oct 11, 2023
25a0659
Revert changes to storage
cfe316 Oct 12, 2023
4330877
First set of constr
cfe316 Oct 12, 2023
f363eaa
Second move
cfe316 Oct 12, 2023
fced3dd
Thirdmove
cfe316 Oct 12, 2023
5834b79
Fifth move
cfe316 Oct 12, 2023
f59cec4
Fifth move
cfe316 Oct 12, 2023
11c683a
Sixth move
cfe316 Oct 12, 2023
723578e
Final changes
cfe316 Oct 13, 2023
b1ff0d4
Minor cleanup
cfe316 Oct 13, 2023
6d5bcc7
Typo in previously-unused branch
cfe316 Oct 13, 2023
92e1394
Refactor storage_symmetric
cfe316 Oct 13, 2023
3615524
Remove unneeded lines
cfe316 Oct 13, 2023
158d9c5
Refactor storage_asymmetric
cfe316 Oct 13, 2023
68c617e
Remove extra constraint
cfe316 Oct 13, 2023
8c08712
Refactor thermal_commit reserves
cfe316 Oct 24, 2023
47dec42
Remove note about duplication
cfe316 Oct 24, 2023
29b7322
refactor therm_no_commit
cfe316 Oct 25, 2023
048df4d
Fix storage_all capres
cfe316 Oct 26, 2023
d150f50
Fix capres access
cfe316 Oct 26, 2023
74bb719
Make T an integer again, not UnitRange
cfe316 Nov 9, 2023
0957306
add expression creation function
cfe316 Nov 9, 2023
3f9c6a8
Use new expression manipulation function
cfe316 Nov 9, 2023
579abcd
Update src/model/resources/storage/storage_all.jl
cfe316 Nov 11, 2023
a2a7946
Fix typo
cfe316 Nov 11, 2023
07b96bc
Update note string
cfe316 Nov 11, 2023
3d2946c
Abstract reg_max, rsv_max
cfe316 Nov 11, 2023
021c08c
add tests
cfe316 Nov 11, 2023
5ee48ff
Add additional test
cfe316 Nov 11, 2023
ef16c18
Fix test
cfe316 Nov 11, 2023
13be2dd
Refactor VRE reserves and fix bug
cfe316 Nov 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion src/model/expression_manipulation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,33 @@ function fill_with_const!(arr::Array{GenericAffExpr{C,T}, dims}, con::Real) wher
return nothing
end

###### ###### ###### ###### ###### ######
# Create an expression from some first-dimension indices of a 2D variable array,
# where all of the 2nd-dimension indices are kept
###### ###### ###### ###### ###### ######
#
function extract_time_series_to_expression(var::Matrix{VariableRef},
set::AbstractVector{Int})
TIME_DIM = 2
time_range = 1:size(var)[TIME_DIM]

aff_exprs_data = AffExpr.(0, var[set, :] .=> 1)
new_axes = (set, time_range)
expr = JuMP.Containers.DenseAxisArray(aff_exprs_data, new_axes...)
return expr
end

function extract_time_series_to_expression(var::JuMP.Containers.DenseAxisArray{VariableRef, 2, Tuple{X, Base.OneTo{Int64}}, Y},
set::AbstractVector{Int}) where {X, Y}
TIME_DIM = 2
time_range = var.axes[TIME_DIM]

aff_exprs = AffExpr.(0, var[set, :] .=> 1)
new_axes = (set, time_range)
expr = JuMP.Containers.DenseAxisArray(aff_exprs.data, new_axes...)
return expr
end
Copy link
Collaborator

@lbonaldo lbonaldo Nov 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should include a test to this functions and add it to expression_manipulation_test.jl

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added tests now.


###### ###### ###### ###### ###### ######
# Element-wise addition of one expression into another
# Both arrays must have the same dimensions
Expand Down Expand Up @@ -141,4 +168,4 @@ end

function sum_expression(expr::AbstractArray{AbstractJuMPScalar, dims}) where {dims}
return _sum_expression(expr)
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,24 @@
add_similar_to_expression!(EP[:eCapResMarBalance], eCapResMarBalanceVRE)
end

### Constratints ###
# For resource for which we are modeling hourly power output
for y in VRE_POWER_OUT
# Define the set of generator indices corresponding to the different sites (or bins) of a particular VRE technology (E.g. wind or solar) in a particular zone.
# For example the wind resource in a particular region could be include three types of bins corresponding to different sites with unique interconnection, hourly capacity factor and maximim available capacity limits.
VRE_BINS = intersect(dfGen[dfGen[!,:R_ID].>=y,:R_ID], dfGen[dfGen[!,:R_ID].<=y+dfGen[y,:Num_VRE_Bins]-1,:R_ID])

# Constraints on contribution to regulation and reserves
if Reserves == 1
curtailable_variable_renewable_reserves!(EP, inputs)
else
# Maximum power generated per hour by renewable generators must be less than
# sum of product of hourly capacity factor for each bin times its the bin installed capacity
# Note: inequality constraint allows curtailment of output below maximum level.
@constraint(EP, [t=1:T], EP[:vP][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS))
end

end
### Constraints ###
if Reserves == 1
# Constraints on power output and contribution to regulation and reserves
curtailable_variable_renewable_reserves!(EP, inputs)
remove_reserves_for_binned_vre_resources!(EP, inputs)
else
# For resource for which we are modeling hourly power output
for y in VRE_POWER_OUT
# Define the set of generator indices corresponding to the different sites (or bins) of a particular VRE technology (E.g. wind or solar) in a particular zone.
# For example the wind resource in a particular region could be include three types of bins corresponding to different sites with unique interconnection, hourly capacity factor and maximim available capacity limits.
VRE_BINS = intersect(dfGen[dfGen[!,:R_ID].>=y,:R_ID], dfGen[dfGen[!,:R_ID].<=y+dfGen[y,:Num_VRE_Bins]-1,:R_ID])

# Maximum power generated per hour by renewable generators must be less than
# sum of product of hourly capacity factor for each bin times its the bin installed capacity
# Note: inequality constraint allows curtailment of output below maximum level.
@constraint(EP, [t=1:T], EP[:vP][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS))
end
end

# Set power variables for all bins that are not being modeled for hourly output to be zero
for y in VRE_NO_POWER_OUT
Expand Down Expand Up @@ -102,60 +102,53 @@
```
"""
function curtailable_variable_renewable_reserves!(EP::Model, inputs::Dict)

dfGen = inputs["dfGen"]
T = inputs["T"]

VRE_POWER_OUT = intersect(dfGen[dfGen.Num_VRE_Bins.>=1,:R_ID], inputs["VRE"])

for y in VRE_POWER_OUT
# Define the set of generator indices corresponding to the different sites (or bins) of a particular VRE technology (E.g. wind or solar) in a particular zone.
# For example the wind resource in a particular region could be include three types of bins corresponding to different sites with unique interconnection, hourly capacity factor and maximim available capacity limits.
VRE_BINS = intersect(dfGen[dfGen[!,:R_ID].>=y,:R_ID], dfGen[dfGen[!,:R_ID].<=y+dfGen[y,:Num_VRE_Bins]-1,:R_ID])

if y in inputs["REG"] && y in inputs["RSV"] # Resource eligible for regulation and spinning reserves
@constraints(EP, begin
# For VRE, reserve contributions must be less than the specified percentage of the capacity factor for the hour
[t=1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS)
[t=1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS)

# Power generated and regulation reserve contributions down per hour must be greater than zero
[t=1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= 0

# Power generated and reserve contributions up per hour by renewable generators must be less than
# hourly capacity factor. Note: inequality constraint allows curtailment of output below maximum level.
[t=1:T], EP[:vP][y,t]+EP[:vREG][y,t]+EP[:vRSV][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS)
end)
elseif y in inputs["REG"] # Resource only eligible for regulation reserves
@constraints(EP, begin
# For VRE, reserve contributions must be less than the specified percentage of the capacity factor for the hour
[t=1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS)

# Power generated and regulation reserve contributions down per hour must be greater than zero
[t=1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= 0

# Power generated and reserve contributions up per hour by renewable generators must be less than
# hourly capacity factor. Note: inequality constraint allows curtailment of output below maximum level.
[t=1:T], EP[:vP][y,t]+EP[:vREG][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS)
end)

elseif y in inputs["RSV"] # Resource only eligible for spinning reserves - only available in up, no down spinning reserves

@constraints(EP, begin
# For VRE, reserve contributions must be less than the specified percentage of the capacity factor for the hour
[t=1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS)

# Power generated and reserve contributions up per hour by renewable generators must be less than
# hourly capacity factor. Note: inequality constraint allows curtailment of output below maximum level.
[t=1:T], EP[:vP][y,t]+EP[:vRSV][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS)
end)
else # Resource not eligible for reserves
# Maximum power generated per hour by renewable generators must be less than
# sum of product of hourly capacity factor for each bin times its the bin installed capacity
# Note: inequality constraint allows curtailment of output below maximum level.
@constraint(EP, [t=1:T], EP[:vP][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS))
end
end
VRE = inputs["VRE"]
VRE_POWER_OUT = intersect(dfGen[dfGen.Num_VRE_Bins.>=1,:R_ID], VRE)
REG = intersect(VRE_POWER_OUT, inputs["REG"])
RSV = intersect(VRE_POWER_OUT, inputs["RSV"])

eTotalCap = EP[:eTotalCap]
vP = EP[:vP]
vREG = EP[:vREG]
vRSV = EP[:vRSV]
hourly_capacity_factor(y, t) = inputs["pP_Max"][y, t]
reg_max(y) = dfGen[y, :Reg_Max]
rsv_max(y) = dfGen[y, :Rsv_Max]

hourly_capacity(y, t) = hourly_capacity_factor(y, t) * eTotalCap[y]
resources_in_bin(y) = UnitRange(y, y + dfGen[y, :Num_VRE_Bins] - 1)
hourly_bin_capacity(y, t) = sum(hourly_capacity(yy, t) for yy in resources_in_bin(y))

@constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= reg_max(y) * hourly_bin_capacity(y, t))
@constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= rsv_max(y) * hourly_bin_capacity(y, t))

expr = extract_time_series_to_expression(vP, VRE_POWER_OUT)
add_similar_to_expression!(expr[REG, :], -vREG[REG, :])
@constraint(EP, [y in VRE_POWER_OUT, t in 1:T], expr[y, t] >= 0)

expr = extract_time_series_to_expression(vP, VRE_POWER_OUT)
add_similar_to_expression!(expr[REG, :], +vREG[REG, :])
add_similar_to_expression!(expr[RSV, :], +vRSV[RSV, :])
@constraint(EP, [y in VRE_POWER_OUT, t in 1:T], expr[y, t] <= hourly_bin_capacity(y, t))
end

function remove_reserves_for_binned_vre_resources!(EP::Model, inputs::Dict)
dfGen = inputs["dfGen"]

VRE = inputs["VRE"]
VRE_POWER_OUT = intersect(dfGen[dfGen.Num_VRE_Bins.>=1,:R_ID], VRE)
REG = inputs["REG"]
RSV = inputs["RSV"]

VRE_NO_POWER_OUT = setdiff(VRE, VRE_POWER_OUT)

for y in intersect(VRE_NO_POWER_OUT, REG)
fix.(EP[:vREG][y,:], 0.0, force=true)
end

Check warning on line 150 in src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl

View check run for this annotation

Codecov / codecov/patch

src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl#L149-L150

Added lines #L149 - L150 were not covered by tests
for y in intersect(VRE_NO_POWER_OUT, RSV)
fix.(EP[:vRSV][y,:], 0.0, force=true)
end

Check warning on line 153 in src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl

View check run for this annotation

Codecov / codecov/patch

src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl#L152-L153

Added lines #L152 - L153 were not covered by tests
end
55 changes: 21 additions & 34 deletions src/model/resources/hydro/hydro_res.jl
Original file line number Diff line number Diff line change
Expand Up @@ -185,45 +185,32 @@
T = inputs["T"] # Number of time steps (hours)

HYDRO_RES = inputs["HYDRO_RES"]
REG = inputs["REG"]
RSV = inputs["RSV"]

Check warning on line 189 in src/model/resources/hydro/hydro_res.jl

View check run for this annotation

Codecov / codecov/patch

src/model/resources/hydro/hydro_res.jl#L188-L189

Added lines #L188 - L189 were not covered by tests

HYDRO_RES_REG_RSV = intersect(HYDRO_RES, inputs["REG"], inputs["RSV"]) # Set of reservoir hydro resources with both regulation and spinning reserves
HYDRO_RES_REG = intersect(HYDRO_RES, REG) # Set of reservoir hydro resources with regulation reserves
HYDRO_RES_RSV = intersect(HYDRO_RES, RSV) # Set of reservoir hydro resources with spinning reserves

Check warning on line 192 in src/model/resources/hydro/hydro_res.jl

View check run for this annotation

Codecov / codecov/patch

src/model/resources/hydro/hydro_res.jl#L191-L192

Added lines #L191 - L192 were not covered by tests

HYDRO_RES_REG = intersect(HYDRO_RES, inputs["REG"]) # Set of reservoir hydro resources with regulation reserves
HYDRO_RES_RSV = intersect(HYDRO_RES, inputs["RSV"]) # Set of reservoir hydro resources with spinning reserves
vP = EP[:vP]
vREG = EP[:vREG]
vRSV = EP[:vRSV]
eTotalCap = EP[:eTotalCap]
reg_max(y) = dfGen[y, :Reg_Max]
rsv_max(y) = dfGen[y, :Rsv_Max]

Check warning on line 199 in src/model/resources/hydro/hydro_res.jl

View check run for this annotation

Codecov / codecov/patch

src/model/resources/hydro/hydro_res.jl#L194-L199

Added lines #L194 - L199 were not covered by tests

HYDRO_RES_REG_ONLY = setdiff(HYDRO_RES_REG, HYDRO_RES_RSV) # Set of reservoir hydro resources only with regulation reserves
HYDRO_RES_RSV_ONLY = setdiff(HYDRO_RES_RSV, HYDRO_RES_REG) # Set of reservoir hydro resources only with spinning reserves
max_up_reserves_lhs = extract_time_series_to_expression(vP, HYDRO_RES)
max_dn_reserves_lhs = extract_time_series_to_expression(vP, HYDRO_RES)

Check warning on line 202 in src/model/resources/hydro/hydro_res.jl

View check run for this annotation

Codecov / codecov/patch

src/model/resources/hydro/hydro_res.jl#L201-L202

Added lines #L201 - L202 were not covered by tests

if !isempty(HYDRO_RES_REG_RSV)
@constraints(EP, begin
# Maximum storage contribution to reserves is a specified fraction of installed capacity
cRegulation[y in HYDRO_RES_REG_RSV, t in 1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*EP[:eTotalCap][y]
cReserve[y in HYDRO_RES_REG_RSV, t in 1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*EP[:eTotalCap][y]
# Maximum discharging rate and contribution to reserves up must be less than power rating
cMaxReservesUp[y in HYDRO_RES_REG_RSV, t in 1:T], EP[:vP][y,t]+EP[:vREG][y,t]+EP[:vRSV][y,t] <= EP[:eTotalCap][y]
# Maximum discharging rate and contribution to regulation down must be greater than zero
cMaxReservesDown[y in HYDRO_RES_REG_RSV, t in 1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= 0
end)
end
S = HYDRO_RES_REG
add_similar_to_expression!(max_up_reserves_lhs[S, :], vREG[S, :])
add_similar_to_expression!(max_dn_reserves_lhs[S, :], -vREG[S, :])

Check warning on line 206 in src/model/resources/hydro/hydro_res.jl

View check run for this annotation

Codecov / codecov/patch

src/model/resources/hydro/hydro_res.jl#L204-L206

Added lines #L204 - L206 were not covered by tests

if !isempty(HYDRO_RES_REG_ONLY)
@constraints(EP, begin
# Maximum storage contribution to reserves is a specified fraction of installed capacity
cRegulation[y in HYDRO_RES_REG_ONLY, t in 1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*EP[:eTotalCap][y]
# Maximum discharging rate and contribution to reserves up must be less than power rating
cMaxReservesUp[y in HYDRO_RES_REG_ONLY, t in 1:T], EP[:vP][y,t]+EP[:vREG][y,t] <= EP[:eTotalCap][y]
# Maximum discharging rate and contribution to regulation down must be greater than zero
cMaxReservesDown[y in HYDRO_RES_REG_ONLY, t in 1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= 0
end)
end
S = HYDRO_RES_RSV
add_similar_to_expression!(max_up_reserves_lhs[S, :], vRSV[S, :])

Check warning on line 209 in src/model/resources/hydro/hydro_res.jl

View check run for this annotation

Codecov / codecov/patch

src/model/resources/hydro/hydro_res.jl#L208-L209

Added lines #L208 - L209 were not covered by tests

if !isempty(HYDRO_RES_RSV_ONLY)
@constraints(EP, begin
# Maximum storage contribution to reserves is a specified fraction of installed capacity
cReserve[y in HYDRO_RES_RSV_ONLY, t in 1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*EP[:eTotalCap][y]
# Maximum discharging rate and contribution to reserves up must be less than power rating
cMaxReservesUp[y in HYDRO_RES_RSV_ONLY, t in 1:T], EP[:vP][y,t]+EP[:vRSV][y,t] <= EP[:eTotalCap][y]
end)
end
@constraint(EP, [y in HYDRO_RES, t in 1:T], max_up_reserves_lhs[y, t] <= eTotalCap[y])
@constraint(EP, [y in HYDRO_RES, t in 1:T], max_dn_reserves_lhs[y, t] >= 0)

Check warning on line 212 in src/model/resources/hydro/hydro_res.jl

View check run for this annotation

Codecov / codecov/patch

src/model/resources/hydro/hydro_res.jl#L211-L212

Added lines #L211 - L212 were not covered by tests

@constraint(EP, [y in HYDRO_RES_REG, t in 1:T], vREG[y, t] <= reg_max(y) * eTotalCap[y])
@constraint(EP, [y in HYDRO_RES_RSV, t in 1:T], vRSV[y, t] <= rsv_max(y) * eTotalCap[y])

Check warning on line 215 in src/model/resources/hydro/hydro_res.jl

View check run for this annotation

Codecov / codecov/patch

src/model/resources/hydro/hydro_res.jl#L214-L215

Added lines #L214 - L215 were not covered by tests
end
Loading