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

Adding resistive heating and non-fusion dispatch and UC constraints #151

Open
wants to merge 16 commits into
base: v0.3.3fusion
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ Method: choose #HiGHS-specific solver settings # Solver option: "sim

# Run the crossover routine for IPX
# [type: bool, advanced: true, range: {false, true}, default: true]
run_crossover: false
run_crossover: off
emiliocanor marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Resource,Zone,THERM,MUST_RUN,STOR,FLEX,HYDRO,VRE,LDS,Num_VRE_Bins,New_Build,Existing_Cap_MW,Existing_Cap_MWh,Existing_Charge_Cap_MW,Max_Cap_MW,Max_Cap_MWh,Max_Charge_Cap_MW,Min_Cap_MW,Min_Cap_MWh,Min_Charge_Cap_MW,Inv_Cost_per_MWyr,Inv_Cost_per_MWhyr,Inv_Cost_Charge_per_MWyr,Fixed_OM_Cost_per_MWyr,Fixed_OM_Cost_per_MWhyr,Fixed_OM_Cost_Charge_per_MWyr,Var_OM_Cost_per_MWh,Var_OM_Cost_per_MWh_In,Heat_Rate_MMBTU_per_MWh,Fuel,Cap_Size,Start_Cost_per_MW,Start_Fuel_MMBTU_per_MW,Up_Time,Down_Time,Ramp_Up_Percentage,Ramp_Dn_Percentage,Hydro_Energy_to_Power_Ratio,Min_Power,Self_Disch,Eff_Up,Eff_Down,Min_Duration,Max_Duration,Max_Flexible_Demand_Advance,Max_Flexible_Demand_Delay,Flexible_Demand_Energy_Eff,Reg_Max,Rsv_Max,Reg_Cost,Rsv_Cost,MinCapTag,MinCapTag_1,MinCapTag_2,MinCapTag_3,MGA,Resource_Type,CapRes_1,ESR_1,ESR_2,region,cluster,LDS
natural_gas_combined_cycle,1,1,0,0,0,0,0,0,0,1,0,0,0,-1,-1,-1,0,0,0,65400,0,0,10287,0,0,3.55,0,7.43,NG,250,91,2,6,6,0.64,0.64,0,0.468,0,1,1,0,0,0,0,1,0.25,0.5,0,0,2,0,0,0,1,natural_gas_fired_combined_cycle,0.93,0,0,NE,1,0
solar_pv,1,0,0,0,0,0,1,0,1,1,0,0,0,-1,-1,-1,0,0,0,85300,0,0,18760,0,0,0,0,9.13,None,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,1,0,0,0,0,7,1,0,0,1,solar_photovoltaic,0.8,1,1,NE,1,0
onshore_wind,1,0,0,0,0,0,1,0,1,1,0,0,0,-1,-1,-1,0,0,0,97200,0,0,43205,0,0,0.1,0,9.12,None,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,1,0,0,0,0,6,0,1,0,1,onshore_wind_turbine,0.8,1,1,NE,1,0
battery,1,0,0,1,0,0,0,0,0,1,0,0,0,-1,-1,-1,0,0,0,19584,22494,0,4895,5622,0,0.15,0.15,0,None,0,0,0,0,0,1,1,0,0,0,0.92,0.92,1,10,0,0,1,0,0,0,0,12,0,0,1,0,battery_mid,0.95,0,0,NE,0,0
Resource,Zone,THERM,MUST_RUN,STOR,TS,FLEX,HYDRO,VRE,LDS,Num_VRE_Bins,New_Build,Existing_Cap_MW,Existing_Cap_MWh,Existing_Charge_Cap_MW,Max_Cap_MW,Max_Cap_MWh,Max_Charge_Cap_MW,Min_Cap_MW,Min_Cap_MWh,Min_Charge_Cap_MW,Inv_Cost_per_MWyr,Inv_Cost_per_MWhyr,Inv_Cost_Charge_per_MWyr,Fixed_OM_Cost_per_MWyr,Fixed_OM_Cost_per_MWhyr,Fixed_OM_Cost_Charge_per_MWyr,Var_OM_Cost_per_MWh,Var_OM_Cost_per_MWh_In,Heat_Rate_MMBTU_per_MWh,Fuel,Cap_Size,Start_Cost_per_MW,Start_Fuel_MMBTU_per_MW,Up_Time,Down_Time,Ramp_Up_Percentage,Ramp_Dn_Percentage,Hydro_Energy_to_Power_Ratio,Min_Power,Self_Disch,Eff_Up,Eff_Down,Min_Duration,Max_Duration,Max_Flexible_Demand_Advance,Max_Flexible_Demand_Delay,Flexible_Demand_Energy_Eff,Reg_Max,Rsv_Max,Reg_Cost,Rsv_Cost,MinCapTag,MinCapTag_1,MinCapTag_2,MinCapTag_3,MGA,Resource_Type,CapRes_1,ESR_1,ESR_2,region,cluster,LDS
emiliocanor marked this conversation as resolved.
Show resolved Hide resolved
natural_gas_combined_cycle,1,1,0,0,1,0,0,0,0,0,1,0,0,0,-1,-1,-1,0,0,0,65400,0,0,10287,0,0,3.55,0,7.43,NG,250,91,2,6,6,0.64,0.64,0,0.468,0,1,1,0,0,0,0,1,0.25,0.5,0,0,2,0,0,0,1,natural_gas_fired_combined_cycle,0.93,0,0,NE,1,0
solar_pv,1,0,0,0,0,0,0,1,0,1,1,0,0,0,-1,-1,-1,0,0,0,85300,0,0,18760,0,0,0,0,9.13,None,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,1,0,0,0,0,7,1,0,0,1,solar_photovoltaic,0.8,1,1,NE,1,0
onshore_wind,1,0,0,0,0,0,0,1,0,1,1,0,0,0,-1,-1,-1,0,0,0,97200,0,0,43205,0,0,0.1,0,9.12,None,0,0,0,0,0,1,1,0,0,0,1,1,0,0,0,0,1,0,0,0,0,6,0,1,0,1,onshore_wind_turbine,0.8,1,1,NE,1,0
battery,1,0,0,1,0,0,0,0,0,0,1,0,0,0,-1,-1,-1,0,0,0,19584,22494,0,4895,5622,0,0.15,0.15,0,None,0,0,0,0,0,1,1,0,0,0,0.92,0.92,1,10,0,0,1,0,0,0,0,12,0,0,1,0,battery_mid,0.95,0,0,NE,0,0
1 change: 0 additions & 1 deletion src/load_inputs/load_generators_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ function load_generators_data!(setup::Dict, path::AbstractString, inputs_gen::Di

# Add Resource IDs after reading to prevent user errors
gen_in[!,:R_ID] = 1:length(collect(skipmissing(gen_in[!,1])))

# Store DataFrame of generators/resources input data for use in model
inputs_gen["dfGen"] = gen_in

Expand Down
60 changes: 34 additions & 26 deletions src/model/resources/thermal_storage/thermal_storage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ end
function get_resistive_heating(inputs::Dict)::Vector{Int}
dfTS = inputs["dfTS"]
dfTS[dfTS.RH.==1,:R_ID]
end

function get_maintenance(inputs::Dict)::Vector{Int}
dfTS = inputs["dfTS"]
Expand Down Expand Up @@ -121,8 +122,10 @@ function thermal_storage(EP::Model, inputs::Dict, setup::Dict)
end)

# resistive heating variables
vRH[y in RH, t = 1:T] >= 0 #electrical energy from grid
vRHCAP[y in RH] >= 0 #RH power capacity for resource
@variables(EP, begin
vRH[y in RH, t = 1:T] >= 0 #electrical energy from grid
vRHCAP[y in RH] >= 0 #RH power capacity for resource
end)

### THERMAL CORE CONSTRAINTS ###
# Core power output must be <= installed capacity, including hourly capacity factors
Expand Down Expand Up @@ -165,13 +168,13 @@ function thermal_storage(EP::Model, inputs::Dict, setup::Dict)
)

# then for resources with RH
@constraint(EP, cTSocBalInterior[t in INTERIOR_SUBPERIODS, y in intersect(TS, RH)], (
@constraint(EP, cTSocBalInteriorRH[t in INTERIOR_SUBPERIODS, y in intersect(TS, RH)], (
vTS[y,t] == vTS[y,t-1]
- (1 / dfGen[y, :Eff_Down] * EP[:vP][y,t])
- (1 / dfGen[y, :Eff_Down] * dfGen[y, :Start_Fuel_MMBTU_per_MW] * dfGen[y,:Cap_Size] * EP[:vSTART][y,t])
+ (dfGen[y,:Eff_Up] * vCP[y,t])
- (dfGen[y,:Self_Disch] * vTS[y,t-1]))
+ (vRH[y, t]) #100% resistive heating efficiency
- (dfGen[y,:Self_Disch] * vTS[y,t-1])
+ (vRH[y, t])) #100% resistive heating efficiency
emiliocanor marked this conversation as resolved.
Show resolved Hide resolved
)

# add resistive heating to power balance
Expand Down Expand Up @@ -272,8 +275,8 @@ function thermal_storage(EP::Model, inputs::Dict, setup::Dict)

# use thermal core constraints for thermal cores not tagged 'FUS'
if !isempty(NONFUS)
thermal_core_constraints(EP, inputs, setup)
thermal_core_max_cap_constraint(EP, inputs, setup)
thermal_core_constraints!(EP, inputs, setup)
thermal_core_max_cap_constraint!(EP, inputs, setup)
end

# Capacity Reserves Margin policy
Expand All @@ -292,7 +295,7 @@ function thermal_storage(EP::Model, inputs::Dict, setup::Dict)
EP[:eCapResMarBalance] += eCapResMarBalanceFusionAdjustment
end

return EP
return EP
end

function fusion_max_cap_constraint!(EP::Model, inputs::Dict, setup::Dict)
Expand Down Expand Up @@ -338,7 +341,7 @@ function thermal_core_max_cap_constraint!(EP::Model, inputs::Dict, setup::Dict)

#System-wide installed capacity is less than a specified maximum limit
HAS_MAX_LIMIT = dfTS[by_rid(NONFUS, :Max_Core_Power_Capacity) .> 0, :R_ID]
@constraint(EP, cCoreMaxCapacity[y in HAS_MAX_LIMIT], vCCAP[y] <= by_rid(y, :Max_Core_Power_Capacity) / by_rid(y, :Eff_Down))
@constraint(EP, cCoreMaxCapacity[y in HAS_MAX_LIMIT], EP[:vCCAP][y] <= by_rid(y, :Max_Core_Power_Capacity) / by_rid(y, :Eff_Down))

end

Expand Down Expand Up @@ -470,6 +473,7 @@ function thermal_core_constraints!(EP::Model, inputs::Dict, setup::Dict)

dfGen = inputs["dfGen"]
dfTS = inputs["dfTS"]
by_rid(rid, sym) = by_rid_df(rid, sym, dfTS)

T = inputs["T"] # Number of time steps (hours)
Z = inputs["Z"] # Number of zones
Expand All @@ -490,20 +494,20 @@ function thermal_core_constraints!(EP::Model, inputs::Dict, setup::Dict)
@constraints(EP, begin

# ramp up, start
[y in NON_COMMIT, t in START_SUBPERIODS], vCP[y, t] - vCP[y, t+hours_per_subperiod-1] <= by_rid(y, :Ramp_Up_Percentage) * vCCAP[y]
[y in NON_COMMIT, t in START_SUBPERIODS], EP[:vCP][y, t] - EP[:vCP][y, t+hours_per_subperiod-1] <= by_rid(y, :Ramp_Up_Percentage) * EP[:vCCAP][y]

# ramp up, interior
[y in NON_COMMIT, t in INTERIOR_SUBPERIODS], vCP[y, t] - vCP[y, t-1] <= by_rid(y, :Ramp_Up_Percentage) * vCCAP[y]
[y in NON_COMMIT, t in INTERIOR_SUBPERIODS], EP[:vCP][y, t] - EP[:vCP][y, t-1] <= by_rid(y, :Ramp_Up_Percentage) * EP[:vCCAP][y]

# ramp dn, start
[y in NON_COMMIT, t in START_SUBPERIODS], vCP[y,t+hours_per_subperiod-1] - vCP[y,t] <= by_rid(y, :Ramp_Dn_Percentage) * vCCAP[y]
[y in NON_COMMIT, t in START_SUBPERIODS], EP[:vCP][y,t+hours_per_subperiod-1] - EP[:vCP][y,t] <= by_rid(y, :Ramp_Dn_Percentage) * EP[:vCCAP][y]

# ramp dn, interior
[y in NON_COMMIT, t in INTERIOR_SUBPERIODS], vCP[y,t-1] - vCP[y,t] <= by_rid(y, :Ramp_Dn_Percentage) * vCCAP[y]
[y in NON_COMMIT, t in INTERIOR_SUBPERIODS], EP[:vCP][y,t-1] - EP[:vCP][y,t] <= by_rid(y, :Ramp_Dn_Percentage) * EP[:vCCAP][y]
end)

# minimum stable power (assumes capacity factor of 1, so max power already implemented)
@constraints(EP, [y in NON_COMMIT, t=1:T], vCP[y,t] >= by_rid(y, :Min_Power)* vCCAP[y])
@constraint(EP, [y in NON_COMMIT, t=1:T], EP[:vCP][y,t] >= by_rid(y, :Min_Power)* EP[:vCCAP][y])
end

# constraints for generatiors subject to UC
Expand All @@ -525,61 +529,65 @@ function thermal_core_constraints!(EP::Model, inputs::Dict, setup::Dict)
set_integer.(vCCOMMIT[y,:])
set_integer.(vCSTART[y,:])
set_integer.(vCSHUT[y,:])
set_integer(vCCAP[y])
set_integer(EP[:vCCAP][y])
end
end

### Capacitated limits on unit commitment decision variables
@constraints(EP, begin
[y in COMMIT, t=1:T], vCCOMMIT[y,t] <= vCCAP[y] / by_rid(y,:Cap_Size)
[y in COMMIT, t=1:T], vCSTART[y,t] <= vCCAP[y] / by_rid(y,:Cap_Size)
[y in COMMIT, t=1:T], vCSHUT[y,t] <= vCCAP[y] / by_rid(y,:Cap_Size)
[y in COMMIT, t=1:T], vCCOMMIT[y,t] <= EP[:vCCAP][y] / by_rid(y,:Cap_Size)
[y in COMMIT, t=1:T], vCSTART[y,t] <= EP[:vCCAP][y] / by_rid(y,:Cap_Size)
[y in COMMIT, t=1:T], vCSHUT[y,t] <= EP[:vCCAP][y] / by_rid(y,:Cap_Size)
end)

# Commitment state constraint linking startup and shutdown decisions (Constraint #4)
@constraints(EP, begin
# For Start Hours, links first time step with last time step in subperiod
[y in COMMIT, t in START_SUBPERIODS], vCCOMMIT[y,t] == vCCOMMIT[y,(t+hours_per_subperiod-1)] + vCSTART[y,t] - vCSHUT[y,t]
# For all other hours, links commitment state in hour t with commitment state in prior hour + sum of start up and shut down in current hour
[y in COMMIT, t in INTERIOR_SUBPERIODS], vCCOMMIT[y,t] == vCOMMIT[y,t-1] + vCSTART[y,t] - vCSHUT[y,t]
[y in COMMIT, t in INTERIOR_SUBPERIODS], vCCOMMIT[y,t] == vCCOMMIT[y,t-1] + vCSTART[y,t] - vCSHUT[y,t]
end)

#ramp up, start
@constraint(EP,[y in COMMIT, t in START_SUBPERIODS],
vCP[y,t]-vCP[y,(t+hours_per_subperiod-1)] <= by_rid(y,:Ramp_Up_Percentage)*by_rid(y,:Cap_Size)*(vCCOMMIT[y,t]-vCSTART[y,t])
EP[:vCP][y,t]-EP[:vCP][y,(t+hours_per_subperiod-1)] <= by_rid(y,:Ramp_Up_Percentage)*by_rid(y,:Cap_Size)*(vCCOMMIT[y,t]-vCSTART[y,t])
+ min(1, max(by_rid(y,:Min_Power), by_rid(y,:Ramp_Up_Percentage)))*by_rid(y,:Cap_Size)*vCSTART[y,t]
- by_rid(y,:Min_Power)*by_rid(y,:Cap_Size)*vCSHUT[y,t])

#ramp up, interior
@constraint(EP,[y in COMMIT, t in INTERIOR_SUBPERIODS],
vCP[y,t]-vCP[y,(t-1)] <= by_rid(y,:Ramp_Up_Percentage)*by_rid(y,:Cap_Size)*(vCCOMMIT[y,t]-vCSTART[y,t])
EP[:vCP][y,t]-EP[:vCP][y,(t-1)] <= by_rid(y,:Ramp_Up_Percentage)*by_rid(y,:Cap_Size)*(vCCOMMIT[y,t]-vCSTART[y,t])
+ min(1,max(by_rid(y,:Min_Power), by_rid(y,:Ramp_Up_Percentage)))*by_rid(y,:Cap_Size)*vCSTART[y,t]
- by_rid(y,:Min_Power)*by_rid(y,:Cap_Size)*vCSHUT[y,t])

#ramp down, start
@constraint(EP,[y in COMMIT, t in START_SUBPERIODS],
vCP[y,(t+hours_per_subperiod-1)]-vCP[y,t] <= by_rid(y,:Ramp_Dn_Percentage)*by_rid(y,:Cap_Size)*(vCCOMMIT[y,t]-vCSTART[y,t])
EP[:vCP][y,(t+hours_per_subperiod-1)]-EP[:vCP][y,t] <= by_rid(y,:Ramp_Dn_Percentage)*by_rid(y,:Cap_Size)*(vCCOMMIT[y,t]-vCSTART[y,t])
- by_rid(y,:Min_Power)*by_rid(y,:Cap_Size)*vCSTART[y,t]
+ min(1,max(by_rid(y,:Min_Power), by_rid(y,:Ramp_Dn_Percentage)))*by_rid(y,:Cap_Size)*vCSHUT[y,t])

#ramp down, interior
@constraint(EP,[y in COMMIT, t in INTERIOR_SUBPERIODS],
vCP[y,(t-1)]-vCP[y,t] <= by_rid(y,:Ramp_Dn_Percentage)*by_rid(y,:Cap_Size)*(vCCOMMIT[y,t]-vCSTART[y,t])
EP[:vCP][y,(t-1)]-EP[:vCP][y,t] <= by_rid(y,:Ramp_Dn_Percentage)*by_rid(y,:Cap_Size)*(vCCOMMIT[y,t]-vCSTART[y,t])
- by_rid(y,:Min_Power)*by_rid(y,:Cap_Size)*vCSTART[y,t]
+ min(1,max(by_rid(y,:Min_Power), by_rid(y,:Ramp_Dn_Percentage)))*by_rid(y,:Cap_Size)*vCSHUT[y,t])

# minimum and maximum stable power (assumes capacity factor of 1, so max power already implemented)
@constraints(EP, begin
[y in COMMIT, t=1:T], vCP[y,t] >= by_rid(y,:Min_Power)*by_rid(y,:Cap_Size)*vCCOMMIT[y,t]
[y in COMMIT, t=1:T], EP[:vCP][y,t] >= by_rid(y,:Min_Power)*by_rid(y,:Cap_Size)*vCCOMMIT[y,t]
end)

### Minimum up and down times (Constraints #9-10)
p = hours_per_subperiod
Up_Time = zeros(Int, nrow(dfGen))
Up_Time[COMMIT] .= Int.(floor.(dfTS[COMMIT,:Up_Time]))
@constraint(EP, [y in COMMIT, t in 1:T],
vCCOMMIT[y,t] >= sum(vCSTART[y, hoursbefore(p, t, 0:(by_rid(y, :Up_Time) - 1))])
vCCOMMIT[y,t] >= sum(vCSTART[y, hoursbefore(p, t, 0:(Up_Time[y] - 1))])
)
Down_Time = zeros(Int, nrow(dfGen))
Down_Time[COMMIT] .= Int.(floor.(dfTS[COMMIT,:Down_Time]))
@constraint(EP, [y in COMMIT, t in 1:T],
vCCAP[y]/by_rid(y,:Cap_Size)-vCCOMMIT[y,t] >= sum(vCSHUT[y, hoursbefore(p, t, 0:(by_rid(y, :Down_Time) - 1))])
EP[:vCCAP][y]/by_rid(y,:Cap_Size)-vCCOMMIT[y,t] >= sum(vCSHUT[y, hoursbefore(p, t, 0:(Down_Time[y] - 1))])
)

end
Expand Down