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

Change Replacements Treatment #210

Draft
wants to merge 16 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ Classify the change according to the following categories:
### Deprecated
### Removed

## Develop - 2023-05-01
### Changed
- Changed tax treatment of replacement costs for technologies with replacement_year inputs (currently, Generator and ElectricStorage). Previously, replacement costs were treated as tax deductible (similar to O&M costs). Accordingly, neither ITC nor MACRS were applied to these costs. Now, we are treating replacement costs are depreciable and are applying the ITC and MACRS to these costs. Updates are mostly captured in the `effective_cost()` calculation.

### Added
- The following new inputs have been added to `ElectricStorage` and `Generator`:
-- `replace_macrs_option_years`
-- `replace_macrs_bonus_fraction`
-- `replace_total_itc_fraction`
- *Note* For Generators, by default we assume no ITC or MACRS incentives for the upfront cost nor replacements. For ElectricStorage replacements, we assume that the MACRS bonus depreciation has phased out, that storage qualifies for 5-year depreciation (2025 and onward), and that the ITC remains at 30%.
## Develop 12-13-2023
### Fixed
- Fixed issue with running Wind on Windows: add execute permission for ssc.dll
Expand Down
2 changes: 1 addition & 1 deletion docs/src/developer/concept.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The REopt Model is built via the [build_reopt!](@ref) method. However, the [run_
The `max_kw` input value for any technology is considered to be the maximum _additional_ capacity that may be installed beyond the `existing_kw`. Note also that the `Site` space constraints (`roof_squarefeet` and `land_acres`) for `PV` technologies can be less than the provided `max_kw` value.

### Lower size limits
The `min_kw` input value for any technology sets the lower bound on the capacity. If `min_kw` is non-zero then the model will be forced to choose at least that system size. The `min_kw` value is set equal to the `existing_kw` value in the Business As Usual scenario.
The `min_kw` input value for any technology sets the lower bound on the _additional_ capacity that may be installed beyond the `existing_kw`. If `min_kw` is non-zero then the model will be forced to choose at least that system size. The `min_kw` value is set equal to the `existing_kw` value in the Business As Usual scenario.

# Business As Usual Scenario
In order to calculate the Net Present Value of the optimal solution, as well as other baseline metrics, one can optionally run the Business As Usual (BAU) scenario. When an array of `JuMP.Model`s is provided to `run_reopt` the BAU scenario is also run. For example:
Expand Down
47 changes: 26 additions & 21 deletions src/core/cost_curve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -316,43 +316,48 @@ function cost_curve(tech::AbstractTech, financial::Financial)
itc_unit_basis = (cap_cost_slope[s] + rebate_federal) / (1 - itc)
end

macrs_schedule = [0.0]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Added all of this as in-line conditional statements below

macrs_bonus_fraction = 0.0
macrs_itc_reduction = 0.0

if tech.macrs_option_years != 0
macrs_bonus_fraction = tech.macrs_bonus_fraction
macrs_itc_reduction = tech.macrs_itc_reduction
end
if tech.macrs_option_years == 5
macrs_schedule = financial.macrs_five_year
end
if tech.macrs_option_years == 7
macrs_schedule = financial.macrs_seven_year
end

replacement_cost = 0.0
replacement_year = financial.analysis_years
if nameof(T) in [:Generator] # Generator is currently only Tech with replacement year and cost
if tech.replacement_year >= financial.analysis_years # assume no replacement in final year of project
replace_macrs_schedule = [0.0]
replace_macrs_bonus_fraction = 0.0
replace_itc = 0.0

if nameof(T) in [:Generator] # Generator is currently only Tech with replacement year and cost (battery is not a tech)
if tech.replacement_year >= financial.analysis_years # assume no replacement in final year of project or later
replacement_cost = 0.0
else
replacement_cost = tech.replace_cost_per_kw
if tech.replace_macrs_option_years != 0
replace_macrs_bonus_fraction = tech.replace_macrs_bonus_fraction
end
if tech.replace_macrs_option_years == 5
replace_macrs_schedule = financial.macrs_five_year
end
if tech.replace_macrs_option_years == 7
replace_macrs_schedule = financial.macrs_seven_year
end
end
replacement_year = tech.replacement_year
replace_itc = tech.replace_federal_itc_fraction
end

updated_slope = effective_cost(;
itc_basis = itc_unit_basis, # input tech cost with incentives, but no ITC
replacement_cost = replacement_cost,
replacement_year = replacement_year,
discount_rate = financial.owner_discount_rate_fraction,
tax_rate = financial.owner_tax_rate_fraction,
itc = itc,
macrs_schedule = macrs_schedule,
macrs_bonus_fraction = macrs_bonus_fraction,
macrs_itc_reduction = macrs_itc_reduction,
rebate_per_kw = rebate_federal
macrs_schedule = tech.macrs_option_years == 5 ? financial.macrs_five_year : tech.macrs_option_years == 7 ? financial.macrs_seven_year : [0.0],
macrs_bonus_fraction = tech.macrs_option_years != 0 ? tech.macrs_bonus_fraction : 0.0,
macrs_itc_reduction = tech.macrs_option_years != 0 ? tech.macrs_itc_reduction : 0.0,
rebate_per_kw = rebate_federal,
replace_macrs_schedule = replace_macrs_schedule,
replace_macrs_bonus_fraction = replace_macrs_bonus_fraction,
replace_itc = replace_itc
)
print("\n\nTech in cost_curve fn:", T)
print("\nupdated_slope: ", updated_slope, "\n")
# The way REopt incentives currently work, the federal rebate is the only incentive that doesn't reduce ITC basis
push!(updated_cap_cost_slope, updated_slope)
end
Expand Down
6 changes: 3 additions & 3 deletions src/core/electric_utility.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
net_metering_limit_kw::Real = 0,
interconnection_limit_kw::Real = 1.0e9, # Limit on total electric system capacity size that can be interconnected to the grid
outage_start_time_step::Int=0, # for modeling a single outage, with critical load spliced into the baseline load ...
outage_end_time_step::Int=0, # ... utiltity production_factor = 0 during the outage
outage_end_time_step::Int=0, # ... utility production_factor = 0 during the outage
allow_simultaneous_export_import::Bool = true, # if true the site has two meters (in effect)
# next 5 variables below used for minimax the expected outage cost,
# with max taken over outage start time, expectation taken over outage duration
Expand Down Expand Up @@ -74,7 +74,7 @@ struct ElectricUtility
emissions_factor_SO2_decrease_fraction::Real
emissions_factor_PM25_decrease_fraction::Real
outage_start_time_step::Int # for modeling a single outage, with critical load spliced into the baseline load ...
outage_end_time_step::Int # ... utiltity production_factor = 0 during the outage
outage_end_time_step::Int # ... utility production_factor = 0 during the outage
allow_simultaneous_export_import::Bool # if true the site has two meters (in effect)
# next 5 variables below used for minimax the expected outage cost,
# with max taken over outage start time, expectation taken over outage duration
Expand All @@ -95,7 +95,7 @@ struct ElectricUtility
net_metering_limit_kw::Real = 0,
interconnection_limit_kw::Real = 1.0e9,
outage_start_time_step::Int=0, # for modeling a single outage, with critical load spliced into the baseline load ...
outage_end_time_step::Int=0, # ... utiltity production_factor = 0 during the outage
outage_end_time_step::Int=0, # ... utility production_factor = 0 during the outage
allow_simultaneous_export_import::Bool=true, # if true the site has two meters (in effect)
# next 5 variables below used for minimax the expected outage cost,
# with max taken over outage start time, expectation taken over outage duration
Expand Down
68 changes: 61 additions & 7 deletions src/core/energy_storage/electric_storage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -158,13 +158,21 @@ end
total_itc_fraction::Float64 = 0.3
total_rebate_per_kw::Real = 0.0
total_rebate_per_kwh::Real = 0.0
replace_macrs_option_years::Int = 5 # Batteries will qualify for 5-year MACRS starting in 2025
replace_macrs_bonus_fraction::Float64 = 0.0 # MACRS bonus assumed to be 0% in 2027 onward
replace_total_itc_fraction::Float64 = 0.3 # ITC assumed not to phase out
charge_efficiency::Float64 = rectifier_efficiency_fraction * internal_efficiency_fraction^0.5
discharge_efficiency::Float64 = inverter_efficiency_fraction * internal_efficiency_fraction^0.5
grid_charge_efficiency::Float64 = can_grid_charge ? charge_efficiency : 0.0
model_degradation::Bool = false
degradation::Dict = Dict()
minimum_avg_soc_fraction::Float64 = 0.0
```

!!! note "Replacement costs"
The Investment Tax Credit (ITC) is applied to replacement costs at `replace_federal_itc_fraction`.
MACRS is applied to replacement costs with a first year bonus depreciation of `replace_macrs_bonus_fraction` and a schedule of `replace_macrs_option_years`.

"""
Base.@kwdef struct ElectricStorageDefaults
off_grid_flag::Bool = false
Expand All @@ -190,6 +198,9 @@ Base.@kwdef struct ElectricStorageDefaults
total_itc_fraction::Float64 = 0.3
total_rebate_per_kw::Real = 0.0
total_rebate_per_kwh::Real = 0.0
replace_macrs_option_years::Int = 5 # Batteries will qualify for 5-year MACRS starting in 2025
replace_macrs_bonus_fraction::Float64 = 0.0 # MACRS bonus assumed to be 0% in 2027 onward
replace_total_itc_fraction::Float64 = 0.3 # ITC assumed not to phase out
charge_efficiency::Float64 = rectifier_efficiency_fraction * internal_efficiency_fraction^0.5
discharge_efficiency::Float64 = inverter_efficiency_fraction * internal_efficiency_fraction^0.5
grid_charge_efficiency::Float64 = can_grid_charge ? charge_efficiency : 0.0
Expand Down Expand Up @@ -228,11 +239,16 @@ struct ElectricStorage <: AbstractElectricStorage
total_itc_fraction::Float64
total_rebate_per_kw::Real
total_rebate_per_kwh::Real
replace_macrs_option_years::Int
replace_macrs_bonus_fraction::Float64
replace_total_itc_fraction::Float64
charge_efficiency::Float64
discharge_efficiency::Float64
grid_charge_efficiency::Float64
net_present_cost_per_kw::Real
net_present_cost_per_kwh::Real
net_present_replace_cost_per_kw::Real
net_present_replace_cost_per_kwh::Real
model_degradation::Bool
degradation::Degradation
minimum_avg_soc_fraction::Float64
Expand All @@ -247,18 +263,21 @@ struct ElectricStorage <: AbstractElectricStorage
if s.battery_replacement_year >= f.analysis_years
@warn "Battery replacement costs (per_kwh) will not be considered because battery_replacement_year >= analysis_years."
end

net_present_cost_per_kw = effective_cost(;
itc_basis = s.installed_cost_per_kw,
replacement_cost = s.inverter_replacement_year >= f.analysis_years ? 0.0 : s.replace_cost_per_kw,
replacement_year = s.inverter_replacement_year,
discount_rate = f.owner_discount_rate_fraction,
tax_rate = f.owner_tax_rate_fraction,
itc = s.total_itc_fraction,
macrs_schedule = s.macrs_option_years == 7 ? f.macrs_seven_year : f.macrs_five_year,
macrs_schedule = s.macrs_option_years == 7 ? f.macrs_seven_year : s.macrs_option_years == 5 ? f.macrs_five_year : [0.0],
macrs_bonus_fraction = s.macrs_bonus_fraction,
macrs_itc_reduction = s.macrs_itc_reduction,
rebate_per_kw = s.total_rebate_per_kw
rebate_per_kw = s.total_rebate_per_kw,
replace_macrs_schedule = s.replace_macrs_option_years == 7 ? f.macrs_seven_year : s.replace_macrs_option_years == 5 ? f.macrs_five_year : [0.0],
replace_macrs_bonus_fraction = s.replace_macrs_bonus_fraction,
replace_itc = s.replace_total_itc_fraction
)
net_present_cost_per_kwh = effective_cost(;
itc_basis = s.installed_cost_per_kwh,
Expand All @@ -267,13 +286,38 @@ struct ElectricStorage <: AbstractElectricStorage
discount_rate = f.owner_discount_rate_fraction,
tax_rate = f.owner_tax_rate_fraction,
itc = s.total_itc_fraction,
macrs_schedule = s.macrs_option_years == 7 ? f.macrs_seven_year : f.macrs_five_year,
macrs_schedule = s.macrs_option_years == 7 ? f.macrs_seven_year : s.macrs_option_years == 5 ? f.macrs_five_year : [0.0],
macrs_bonus_fraction = s.macrs_bonus_fraction,
macrs_itc_reduction = s.macrs_itc_reduction
macrs_itc_reduction = s.macrs_itc_reduction,
replace_macrs_schedule = s.replace_macrs_option_years == 7 ? f.macrs_seven_year : s.replace_macrs_option_years == 5 ? f.macrs_five_year : [0.0],
replace_macrs_bonus_fraction = s.replace_macrs_bonus_fraction,
replace_itc = s.replace_total_itc_fraction
)

net_present_cost_per_kwh -= s.total_rebate_per_kwh

net_present_replace_cost_per_kw = replacement_effective_cost(; # gets used in results/financial.jl
replacement_cost = s.inverter_replacement_year >= f.analysis_years ? 0.0 : s.replace_cost_per_kw,
replacement_year = s.inverter_replacement_year,
discount_rate = f.owner_discount_rate_fraction,
tax_rate = f.owner_tax_rate_fraction,
macrs_itc_reduction = s.macrs_itc_reduction,
replace_macrs_schedule = s.replace_macrs_option_years == 7 ? f.macrs_seven_year : s.replace_macrs_option_years == 5 ? f.macrs_five_year : [0.0],
replace_macrs_bonus_fraction = s.replace_macrs_bonus_fraction,
replace_itc = s.replace_total_itc_fraction
)

net_present_replace_cost_per_kwh = replacement_effective_cost(; # gets used in results/financial.jl
replacement_cost = s.battery_replacement_year >= f.analysis_years ? 0.0 : s.replace_cost_per_kwh,
replacement_year = s.battery_replacement_year,
discount_rate = f.owner_discount_rate_fraction,
tax_rate = f.owner_tax_rate_fraction,
macrs_itc_reduction = s.macrs_itc_reduction,
replace_macrs_schedule = s.replace_macrs_option_years == 7 ? f.macrs_seven_year : s.replace_macrs_option_years == 5 ? f.macrs_five_year : [0.0],
replace_macrs_bonus_fraction = s.replace_macrs_bonus_fraction,
replace_itc = s.replace_total_itc_fraction
)

if haskey(d, :degradation)
degr = Degradation(;dictkeys_tosymbols(d[:degradation])...)
else
Expand All @@ -288,10 +332,15 @@ struct ElectricStorage <: AbstractElectricStorage
haskey(d, :replace_cost_per_kwh) && d[:replace_cost_per_kwh] != 0.0
@warn "Setting ElectricStorage replacement costs to zero. Using degradation.maintenance_cost_per_kwh instead."
end
replace_cost_per_kw = 0.0
# TODO: These get set to zero but their non-zero value is used in the net_present_cost calculation above? Should this if statement come before the effective_cost fn is called?
replace_cost_per_kw = 0.0
replace_cost_per_kwh = 0.0
net_present_replace_cost_per_kw = 0.0 ## TODO: is this the right thing to do with degredation modeling?
net_present_replace_cost_per_kwh = 0.0
end




return new(
s.min_kw,
s.max_kw,
Expand All @@ -315,11 +364,16 @@ struct ElectricStorage <: AbstractElectricStorage
s.total_itc_fraction,
s.total_rebate_per_kw,
s.total_rebate_per_kwh,
s.replace_macrs_option_years,
s.replace_macrs_bonus_fraction,
s.replace_total_itc_fraction,
s.charge_efficiency,
s.discharge_efficiency,
s.grid_charge_efficiency,
net_present_cost_per_kw,
net_present_cost_per_kwh,
net_present_replace_cost_per_kw,
net_present_replace_cost_per_kwh,
s.model_degradation,
degr,
s.minimum_avg_soc_fraction
Expand Down
7 changes: 5 additions & 2 deletions src/core/energy_storage/thermal_storage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,12 @@ struct ThermalStorage <: AbstractThermalStorage
discount_rate = f.owner_discount_rate_fraction,
tax_rate = f.owner_tax_rate_fraction,
itc = s.total_itc_fraction,
macrs_schedule = s.macrs_option_years == 7 ? f.macrs_seven_year : f.macrs_five_year,
macrs_schedule = s.macrs_option_years == 7 ? f.macrs_seven_year : s.macrs_option_years == 5 ? f.macrs_five_year : [0.0],
macrs_bonus_fraction = s.macrs_bonus_fraction,
macrs_itc_reduction = s.macrs_itc_reduction
macrs_itc_reduction = s.macrs_itc_reduction,
replace_macrs_schedule = [0.0],
replace_macrs_bonus_fraction = 0.0,
replace_itc = 0.0
) - s.total_rebate_per_kwh

return new(
Expand Down
Loading
Loading