diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e4d05b91..11ca41509 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ Classify the change according to the following categories: ### Deprecated ### Removed +## Develop 2024-01-16 +### Fixed +- In `reopt.jl`, group objective function incentives (into **ObjectivePenalties**) and avoid directly modifying m[:Costs]. Previously, some of these were incorrectly included in the reported **Financial.lcc**. + ## v0.39.1 ### Changed - Changed testing suite from using Xpress to using HiGHS, an open-source solver. This has led to a reduction in the number of tests due to incompatibility with indicator constraints. diff --git a/src/core/reopt.jl b/src/core/reopt.jl index 17d2b1082..8292fcb61 100644 --- a/src/core/reopt.jl +++ b/src/core/reopt.jl @@ -247,6 +247,7 @@ function build_reopt!(m::JuMP.AbstractModel, p::REoptInputs) m[:GHPOMCosts] = 0.0 m[:AvoidedCapexByGHP] = 0.0 m[:ResidualGHXCapCost] = 0.0 + m[:ObjectivePenalties] = 0.0 if !isempty(p.techs.all) add_tech_size_constraints(m, p) @@ -441,12 +442,7 @@ function build_reopt!(m::JuMP.AbstractModel, p::REoptInputs) m[:TotalElecBill] * (1 - p.s.financial.offtaker_tax_rate_fraction) - # Subtract Incentives, which are taxable - m[:TotalProductionIncentive] * (1 - p.s.financial.owner_tax_rate_fraction) + - - # Comfort limit violation costs - #TODO: add this to objective like SOC incentive below and - #don't then subtract out when setting lcc in results/financial.jl - m[:dvComfortLimitViolationCost] + + m[:TotalProductionIncentive] * (1 - p.s.financial.owner_tax_rate_fraction) + # Additional annual costs, tax deductible for owner (only applies when `off_grid_flag` is true) p.s.financial.offgrid_other_annual_costs * p.pwf_om * (1 - p.s.financial.owner_tax_rate_fraction) + @@ -470,36 +466,23 @@ function build_reopt!(m::JuMP.AbstractModel, p::REoptInputs) add_to_expression!(Costs, m[:Lifecycle_Emissions_Cost_Health]) end - @expression(m, Objective, - m[:Costs] - ) - + ## Modify objective with incentives that are not part of the LCC + # 1. Comfort limit violation costs + m[:ObjectivePenalties] += m[:dvComfortLimitViolationCost] + # 2. Incentive to keep SOC high if !(isempty(p.s.storage.types.elec)) && p.s.settings.add_soc_incentive - # Incentive to keep SOC high - add_to_expression!( - Objective, - - sum( + m[:ObjectivePenalties] += -1 * sum( m[:dvStoredEnergy][b, ts] for b in p.s.storage.types.elec, ts in p.time_steps ) / (8760. / p.hours_per_time_step) - ) end + # 3. Incentive to minimize unserved load in each outage, not just the max over outage start times if !isempty(p.s.electric_utility.outage_durations) - # Incentive to minimize unserved load in each outage, not just the max over outage start times - add_to_expression!( - Objective, - sum(sum(0.0001 * m[:dvUnservedLoad][s, tz, ts] for ts in 1:p.s.electric_utility.outage_durations[s]) for s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps) - ) + m[:ObjectivePenalties] += sum(sum(0.0001 * m[:dvUnservedLoad][s, tz, ts] for ts in 1:p.s.electric_utility.outage_durations[s]) + for s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps) end - @objective(m, Min, m[:Objective]) - - # if !(isempty(p.s.storage.types.elec)) && p.s.settings.add_soc_incentive # Keep SOC high - # @objective(m, Min, m[:Costs] - - # sum(m[:dvStoredEnergy][b, ts] for b in p.s.storage.types.elec, ts in p.time_steps) / - # (8760. / p.hours_per_time_step) - # ) - - # end + # Set model objective + @objective(m, Min, m[:Costs] + m[:ObjectivePenalties] ) for b in p.s.storage.types.elec if p.s.storage.attr[b].model_degradation diff --git a/src/results/financial.jl b/src/results/financial.jl index ef5f5e7c5..6d17db100 100644 --- a/src/results/financial.jl +++ b/src/results/financial.jl @@ -38,9 +38,6 @@ calculated in combine_results function if BAU scenario is run: """ function add_financial_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _n="") r = Dict{String, Float64}() - if !(Symbol("dvComfortLimitViolationCost"*_n) in keys(m.obj_dict)) - m[Symbol("dvComfortLimitViolationCost"*_n)] = 0.0 - end if !(Symbol("TotalProductionIncentive"*_n) in keys(m.obj_dict)) # not currently included in multi-node modeling b/c these constraints require binary vars. m[Symbol("TotalProductionIncentive"*_n)] = 0.0 end @@ -54,7 +51,8 @@ function add_financial_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _ m[Symbol("GHPCapCosts"*_n)] = 0.0 end - r["lcc"] = value(m[Symbol("Costs"*_n)]) + 0.0001 * value(m[Symbol("MinChargeAdder"*_n)]) - value(m[Symbol("dvComfortLimitViolationCost"*_n)]) + r["lcc"] = value(m[Symbol("Costs"*_n)]) + 0.0001 * value(m[Symbol("MinChargeAdder"*_n)]) + r["lifecycle_om_costs_before_tax"] = value(m[Symbol("TotalPerUnitSizeOMCosts"*_n)] + m[Symbol("TotalPerUnitProdOMCosts"*_n)] + m[Symbol("TotalPerUnitHourOMCosts"*_n)] + m[Symbol("GHPOMCosts"*_n)]) diff --git a/test/test_with_xpress.jl b/test/test_with_xpress.jl index 64d93a19c..09e2ee533 100644 --- a/test/test_with_xpress.jl +++ b/test/test_with_xpress.jl @@ -1479,7 +1479,7 @@ end @test results["Site"]["total_renewable_energy_fraction_bau"] ≈ 0.132118 atol=1e-3 # 0.1354 atol=1e-3 # CO2 emissions - totals ≈ from grid, from fuelburn, ER, $/tCO2 breakeven @test results["Site"]["lifecycle_emissions_reduction_CO2_fraction"] ≈ 0.8 atol=1e-3 # 0.8 - @test results["Financial"]["breakeven_cost_of_emissions_reduction_per_tonne_CO2"] ≈ 373.9 atol=1e-1 + @test results["Financial"]["breakeven_cost_of_emissions_reduction_per_tonne_CO2"] ≈ 374.0 atol=1e-1 @test results["Site"]["annual_emissions_tonnes_CO2"] ≈ 14.2 atol=1 @test results["Site"]["annual_emissions_tonnes_CO2_bau"] ≈ 70.99 atol=1 @test results["Site"]["annual_emissions_from_fuelburn_tonnes_CO2"] ≈ 0.0 atol=1 # 0.0