Skip to content

Commit

Permalink
Merge pull request #372 from NREL/add-ASHP
Browse files Browse the repository at this point in the history
Add ASHP
  • Loading branch information
zolanaj authored Sep 26, 2024
2 parents 876c7a6 + a4c7759 commit caff795
Show file tree
Hide file tree
Showing 35 changed files with 1,580 additions and 114 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Classify the change according to the following categories:
### Deprecated
### Removed

## Develop 08-09-2024
## Develop 2024-08-19
### Added
- Added new file `src/core/ASHP.jl` with new technology **ASHP**, which uses electricity as input and provides heating and/or cooling as output; load balancing and technology-specific constraints have been updated and added accordingly
- In `src/core/existing_chiller.jl`, Added new atttribute **retire_in_optimal** to the **ExistingChiller** struct
Expand Down
41 changes: 41 additions & 0 deletions data/ashp/ashp_defaults.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"SpaceHeating":
{
"max_ton": 99999999,
"installed_cost_per_ton": 2250,
"om_cost_per_ton": 40,
"macrs_option_years": 0,
"macrs_bonus_fraction": 0.0,
"can_supply_steam_turbine": false,
"can_serve_process_heat": false,
"can_serve_dhw": false,
"can_serve_space_heating": true,
"can_serve_cooling": true,
"back_up_temp_threshold_degF": 10.0,
"sizing_factor": 1.0,
"heating_cop_reference": [1.5,2.3,3.3,4.5],
"heating_cf_reference": [0.38,0.64,1.0,1.4],
"heating_reference_temps_degF": [-5,17,47,80],
"cooling_cop_reference": [4.0, 3.5, 2.9, 2.2],
"cooling_cf_reference": [1.03, 0.98, 0.93, 0.87],
"cooling_reference_temps_degF": [70, 82, 95, 110]
},
"DomesticHotWater":
{
"max_ton": 99999999,
"installed_cost_per_ton": 2250,
"om_cost_per_ton": 40,
"macrs_option_years": 0,
"macrs_bonus_fraction": 0.0,
"can_supply_steam_turbine": false,
"can_serve_process_heat": false,
"can_serve_dhw": true,
"can_serve_space_heating": false,
"can_serve_cooling": false,
"back_up_temp_threshold_degF": 10.0,
"sizing_factor": 1.0,
"heating_cop_reference": [1.5,2.3,3.3,4.5],
"heating_cf_reference": [0.38,0.64,1.0,1.4],
"heating_reference_temps_degF": [-5,17,47,80]
}
}
5 changes: 5 additions & 0 deletions docs/src/reopt/inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,8 @@ REopt.SteamTurbine
```@docs
REopt.ElectricHeater
```

## ASHP
```@docs
REopt.ASHP
```
8 changes: 6 additions & 2 deletions src/REopt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export
avert_emissions_profiles,
cambium_emissions_profile,
easiur_data,
get_existing_chiller_default_cop
get_existing_chiller_default_cop,
get_electric_heater_defaults,
get_ashp_defaults

import HTTP
import JSON
Expand Down Expand Up @@ -135,6 +137,7 @@ include("core/chp.jl")
include("core/ghp.jl")
include("core/steam_turbine.jl")
include("core/electric_heater.jl")
include("core/ashp.jl")
include("core/scenario.jl")
include("core/bau_scenario.jl")
include("core/reopt_inputs.jl")
Expand Down Expand Up @@ -179,6 +182,7 @@ include("results/thermal_storage.jl")
include("results/outages.jl")
include("results/wind.jl")
include("results/electric_load.jl")
include("results/heating_cooling_load.jl")
include("results/existing_boiler.jl")
include("results/boiler.jl")
include("results/existing_chiller.jl")
Expand All @@ -188,7 +192,7 @@ include("results/flexible_hvac.jl")
include("results/ghp.jl")
include("results/steam_turbine.jl")
include("results/electric_heater.jl")
include("results/heating_cooling_load.jl")
include("results/ashp.jl")

include("core/reopt.jl")
include("core/reopt_multinode.jl")
Expand Down
2 changes: 1 addition & 1 deletion src/constraints/electric_utility_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function add_export_constraints(m, p; _n="")
sum(p.max_sizes[t] for t in NEM_techs),
p.hours_per_time_step * maximum([sum((
p.s.electric_load.loads_kw[ts] +
p.s.cooling_load.loads_kw_thermal[ts]/p.cop["ExistingChiller"] +
p.s.cooling_load.loads_kw_thermal[ts]/p.cooling_cop["ExistingChiller"][ts] +
(p.s.space_heating_load.loads_kw[ts] + p.s.dhw_load.loads_kw[ts] + p.s.process_heat_load.loads_kw[ts])
) for ts in p.s.electric_tariff.time_steps_monthly[m]) for m in p.months
])
Expand Down
12 changes: 6 additions & 6 deletions src/constraints/load_balance.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ function add_elec_load_balance_constraints(m, p; _n="")
sum(sum(m[Symbol("dvProductionToStorage"*_n)][b, t, ts] for b in p.s.storage.types.elec)
+ m[Symbol("dvCurtail"*_n)][t, ts] for t in p.techs.elec)
+ sum(m[Symbol("dvGridToStorage"*_n)][b, ts] for b in p.s.storage.types.elec)
+ sum(m[Symbol("dvCoolingProduction"*_n)][t, ts] / p.cop[t] for t in setdiff(p.techs.cooling,p.techs.ghp))
+ sum(m[Symbol("dvHeatingProduction"*_n)][t, q, ts] / p.heating_cop[t] for q in p.heating_loads, t in p.techs.electric_heater)
+ sum(m[Symbol("dvCoolingProduction"*_n)][t, ts] / p.cooling_cop[t][ts] for t in setdiff(p.techs.cooling,p.techs.ghp))
+ sum(m[Symbol("dvHeatingProduction"*_n)][t, q, ts] / p.heating_cop[t][ts] for q in p.heating_loads, t in p.techs.electric_heater)
+ p.s.electric_load.loads_kw[ts]
- p.s.cooling_load.loads_kw_thermal[ts] / p.cop["ExistingChiller"]
- p.s.cooling_load.loads_kw_thermal[ts] / p.cooling_cop["ExistingChiller"][ts]
+ sum(p.ghp_electric_consumption_kw[g,ts] * m[Symbol("binGHP"*_n)][g] for g in p.ghp_options)
)
else
Expand All @@ -28,10 +28,10 @@ function add_elec_load_balance_constraints(m, p; _n="")
+ sum(m[Symbol("dvProductionToGrid"*_n)][t, u, ts] for u in p.export_bins_by_tech[t])
+ m[Symbol("dvCurtail"*_n)][t, ts] for t in p.techs.elec)
+ sum(m[Symbol("dvGridToStorage"*_n)][b, ts] for b in p.s.storage.types.elec)
+ sum(m[Symbol("dvCoolingProduction"*_n)][t, ts] / p.cop[t] for t in setdiff(p.techs.cooling,p.techs.ghp))
+ sum(m[Symbol("dvHeatingProduction"*_n)][t, q, ts] / p.heating_cop[t] for q in p.heating_loads, t in p.techs.electric_heater)
+ sum(m[Symbol("dvCoolingProduction"*_n)][t, ts] / p.cooling_cop[t][ts] for t in setdiff(p.techs.cooling,p.techs.ghp))
+ sum(m[Symbol("dvHeatingProduction"*_n)][t, q, ts] / p.heating_cop[t][ts] for q in p.heating_loads, t in p.techs.electric_heater)
+ p.s.electric_load.loads_kw[ts]
- p.s.cooling_load.loads_kw_thermal[ts] / p.cop["ExistingChiller"]
- p.s.cooling_load.loads_kw_thermal[ts] / p.cooling_cop["ExistingChiller"][ts]
+ sum(p.ghp_electric_consumption_kw[g,ts] * m[Symbol("binGHP"*_n)][g] for g in p.ghp_options)
)
end
Expand Down
8 changes: 4 additions & 4 deletions src/constraints/outage_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,22 @@ function add_outage_cost_constraints(m,p)
end
end

if !isempty(p.techs.segmented)
if !isempty(intersect(p.techs.segmented, p.techs.elec))
@warn "Adding binary variable(s) to model cost curves in stochastic outages"
if solver_is_compatible_with_indicator_constraints(p.s.settings.solver_name)
@constraint(m, [t in p.techs.segmented], # cannot have this for statement in sum( ... for t in ...) ???
@constraint(m, [t in intersect(p.techs.segmented, p.techs.elec)], # cannot have this for statement in sum( ... for t in ...) ???
m[:binMGTechUsed][t] => {m[:dvMGTechUpgradeCost][t] >= p.s.financial.microgrid_upgrade_cost_fraction * p.third_party_factor *
sum(p.cap_cost_slope[t][s] * m[Symbol("dvSegmentSystemSize"*t)][s] +
p.seg_yint[t][s] * m[Symbol("binSegment"*t)][s] for s in 1:p.n_segs_by_tech[t])}
)
else
@constraint(m, [t in p.techs.segmented],
@constraint(m, [t in intersect(p.techs.segmented, p.techs.elec)],
m[:dvMGTechUpgradeCost][t] >= p.s.financial.microgrid_upgrade_cost_fraction * p.third_party_factor *
sum(p.cap_cost_slope[t][s] * m[Symbol("dvSegmentSystemSize"*t)][s] +
p.seg_yint[t][s] * m[Symbol("binSegment"*t)][s] for s in 1:p.n_segs_by_tech[t]) -
(maximum(p.cap_cost_slope[t][s] for s in 1:p.n_segs_by_tech[t]) * p.max_sizes[t] + maximum(p.seg_yint[t][s] for s in 1:p.n_segs_by_tech[t]))*(1-m[:binMGTechUsed][t])
)
@constraint(m, [t in p.techs.segmented], m[:dvMGTechUpgradeCost][t] >= 0.0)
@constraint(m, [t in intersect(p.techs.segmented, p.techs.elec)], m[:dvMGTechUpgradeCost][t] >= 0.0)
end
end

Expand Down
62 changes: 59 additions & 3 deletions src/constraints/thermal_tech_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ function add_heating_tech_constraints(m, p; _n="")
# Constraint (7_heating_prod_size): Production limit based on size for non-electricity-producing heating techs
if !isempty(setdiff(p.techs.heating, union(p.techs.elec, p.techs.ghp)))
@constraint(m, [t in setdiff(p.techs.heating, union(p.techs.elec, p.techs.ghp)), ts in p.time_steps],
sum(m[Symbol("dvHeatingProduction"*_n)][t,q,ts] for q in p.heating_loads) <= m[Symbol("dvSize"*_n)][t]
sum(m[Symbol("dvHeatingProduction"*_n)][t,q,ts] for q in p.heating_loads) <= m[Symbol("dvSize"*_n)][t] * p.heating_cf[t][ts]
)
end
# Constraint (7_heating_load_compatability): Set production variables for incompatible heat loads to zero
Expand All @@ -88,7 +88,56 @@ function add_heating_tech_constraints(m, p; _n="")
end
end
end
# Enfore

# Enforce no waste heat for any technology that isn't both electricity- and heat-producing
for t in setdiff(p.techs.heating, union(p.techs.elec, p.techs.ghp))
for q in p.heating_loads
for ts in p.time_steps
fix(m[Symbol("dvProductionToWaste"*_n)][t,q,ts], 0.0, force=true)
end
end
end
end

function add_heating_cooling_constraints(m, p; _n="")
@constraint(m, [t in setdiff(intersect(p.techs.cooling, p.techs.heating), p.techs.ghp), ts in p.time_steps],
sum(m[Symbol("dvHeatingProduction"*_n)][t,q,ts] for q in p.heating_loads) / p.heating_cf[t][ts] + m[Symbol("dvCoolingProduction"*_n)][t,ts] / p.cooling_cf[t][ts] <= m[Symbol("dvSize"*_n)][t]
)
end


function add_ashp_force_in_constraints(m, p; _n="")
if "ASHPSpaceHeater" in p.techs.ashp && p.s.ashp.force_into_system
for t in setdiff(p.techs.can_serve_space_heating, ["ASHPSpaceHeater"])
for ts in p.time_steps
fix(m[Symbol("dvHeatingProduction"*_n)][t,"SpaceHeating",ts], 0.0, force=true)
fix(m[Symbol("dvProductionToWaste"*_n)][t,"SpaceHeating",ts], 0.0, force=true)
end
end
end

if "ASHPSpaceHeater" in p.techs.cooling && p.s.ashp.force_into_system
for t in setdiff(p.techs.cooling, ["ASHPSpaceHeater"])
for ts in p.time_steps
fix(m[Symbol("dvCoolingProduction"*_n)][t,ts], 0.0, force=true)
end
end
end

if "ASHPWaterHeater" in p.techs.ashp && p.s.ashp_wh.force_into_system
for t in setdiff(p.techs.can_serve_dhw, ["ASHPWaterHeater"])
for ts in p.time_steps
fix(m[Symbol("dvHeatingProduction"*_n)][t,"DomesticHotWater",ts], 0.0, force=true)
fix(m[Symbol("dvProductionToWaste"*_n)][t,"DomesticHotWater",ts], 0.0, force=true)
end
end
end
end

function avoided_capex_by_ashp(m, p; _n="")
m[:AvoidedCapexByASHP] = @expression(m,
sum(p.avoided_capex_by_ashp_present_value[t] for t in p.techs.ashp)
)
end

function no_existing_boiler_production(m, p; _n="")
Expand All @@ -103,7 +152,7 @@ end
function add_cooling_tech_constraints(m, p; _n="")
# Constraint (7_cooling_prod_size): Production limit based on size for boiler
@constraint(m, [t in setdiff(p.techs.cooling, p.techs.ghp), ts in p.time_steps_with_grid],
m[Symbol("dvCoolingProduction"*_n)][t,ts] <= m[Symbol("dvSize"*_n)][t]
m[Symbol("dvCoolingProduction"*_n)][t,ts] <= m[Symbol("dvSize"*_n)][t] * p.cooling_cf[t][ts]
)
# The load balance for cooling is only applied to time_steps_with_grid, so make sure we don't arbitrarily show cooling production for time_steps_without_grid
for t in setdiff(p.techs.cooling, p.techs.ghp)
Expand All @@ -112,3 +161,10 @@ function add_cooling_tech_constraints(m, p; _n="")
end
end
end

function no_existing_chiller_production(m, p; _n="")
for ts in p.time_steps
fix(m[Symbol("dvCoolingProduction"*_n)]["ExistingChiller",ts], 0.0, force=true)
end
fix(m[Symbol("dvSize"*_n)]["ExistingChiller"], 0.0, force=true)
end
Loading

0 comments on commit caff795

Please sign in to comment.