diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c48440d2..3eac8d298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,14 +23,18 @@ Classify the change according to the following categories: ### Deprecated ### Removed +## v0.43.0 +### Fixed +- `simple_payback_years` calculation when there is export credit +- Issue with `SteamTurbine` heuristic size and default calculation when `size_class` was input +- BAU emissions calculation with heating load which was using thermal instead of fuel + ## v0.42.0 ### Changed - In `core/pv.jl` a change was made to make sure we are using the same assumptions as PVWatts guidelines, the default `tilt` angle for a fixed array should be 20 degrees, irrespective of it being a rooftop `(1)` or ground-mounted (open-rack)`(2)` system. By default the `tilt` will be set to 20 degrees for ground-mount and rooftop, and 0 degrees for axis-tracking (`array_type = (3) or (4)`) > "The PVWatts® default value for the tilt angle depends on the array type: For a fixed array, the default value is 20 degrees, and for one-axis tracking the default value is zero. A common rule of thumb for fixed arrays is to set the tilt angle to the latitude of the system's location to maximize the system's total electrical output over the year. Use a lower tilt angle favor peak production in the summer months when the sun is high in the sky, or a higher tilt angle to increase output during winter months. Higher tilt angles tend to cost more for racking and mounting hardware, and may increase the risk of wind damage to the array." - - ## v0.41.0 ### Changed - Changed default source for CO2 grid emissions values to NREL's Cambium 2022 Database (by default: CO2e, long-run marginal emissions rates levelized (averaged) over the analysis period, assuming start year 2024). Added new emissions inputs and call to Cambium API in `src/core/electric_utility.jl`. Included option for user to use AVERT data for CO2 using **co2_from_avert** boolean. diff --git a/Project.toml b/Project.toml index 567972048..1b1ec48bf 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "REopt" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" authors = ["Nick Laws", "Hallie Dunham ", "Bill Becker ", "Bhavesh Rathod ", "Alex Zolan ", "Amanda Farthing "] -version = "0.42.0" +version = "0.43.0" [deps] ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3" diff --git a/src/core/bau_inputs.jl b/src/core/bau_inputs.jl index 79c40ddeb..d78d328b8 100644 --- a/src/core/bau_inputs.jl +++ b/src/core/bau_inputs.jl @@ -255,7 +255,10 @@ function setup_bau_emissions_inputs(p::REoptInputs, s_bau::BAUScenario, generato ## Boiler emissions if "ExistingBoiler" in p.techs.all for heat_type in ["space_heating", "dhw"] - bau_emissions_lb_CO2_per_year += getproperty(p.s,Symbol("$(heat_type)_load")).annual_mmbtu * p.s.existing_boiler.emissions_factor_lb_CO2_per_mmbtu + # Divide by existing_boiler.efficiency because annual_mmbtu is thermal, so convert to fuel + bau_emissions_lb_CO2_per_year += getproperty(p.s,Symbol("$(heat_type)_load")).annual_mmbtu / + p.s.existing_boiler.efficiency * + p.s.existing_boiler.emissions_factor_lb_CO2_per_mmbtu end end diff --git a/src/core/chp.jl b/src/core/chp.jl index d7859645a..b341be6c9 100644 --- a/src/core/chp.jl +++ b/src/core/chp.jl @@ -333,7 +333,7 @@ end is_electric_only::Bool=false) Depending on the set of inputs, different sets of outputs are determine in addition to all CHP cost and performance parameter defaults: - 1. Inputs: existing_boiler_production_type_steam_or_hw and avg_boiler_fuel_load_mmbtu_per_hour + 1. Inputs: hot_water_or_steam and avg_boiler_fuel_load_mmbtu_per_hour Outputs: prime_mover, size_class, chp_elec_size_heuristic_kw, chp_max_size_kw 2. Inputs: prime_mover and avg_boiler_fuel_load_mmbtu_per_hour Outputs: size_class, chp_elec_size_heuristic_kw, chp_max_size_kw @@ -345,7 +345,7 @@ Depending on the set of inputs, different sets of outputs are determine in addit Outputs: chp_elec_size_heuristic_kw, chp_max_size_kw The main purpose of this function is to communicate the following mapping of dependency of CHP defaults versus - existing_boiler_production_type_steam_or_hot_water and avg_boiler_fuel_load_mmbtu_per_hour: + hot_water_or_steam and avg_boiler_fuel_load_mmbtu_per_hour: If hot_water and <= 27 MMBtu/hr avg_boiler_fuel_load_mmbtu_per_hour --> prime_mover = recip_engine of size_class X If hot_water and > 27 MMBtu/hr avg_boiler_fuel_load_mmbtu_per_hour --> prime_mover = combustion_turbine of size_class X If steam and <= 7 MMBtu/hr avg_boiler_fuel_load_mmbtu_per_hour --> prime_mover = recip_engine of size_class X diff --git a/src/core/scenario.jl b/src/core/scenario.jl index 3b509b3ce..02689a0d6 100644 --- a/src/core/scenario.jl +++ b/src/core/scenario.jl @@ -615,7 +615,7 @@ function Scenario(d::Dict; flex_hvac_from_json=false) end steam_turbine = nothing - if haskey(d, "SteamTurbine") && d["SteamTurbine"]["max_kw"] > 0.0 + if haskey(d, "SteamTurbine") if !isnothing(existing_boiler) total_fuel_heating_load_mmbtu_per_hour = (space_heating_load.loads_kw + dhw_load.loads_kw) / existing_boiler.efficiency / KWH_PER_MMBTU avg_boiler_fuel_load_mmbtu_per_hour = sum(total_fuel_heating_load_mmbtu_per_hour) / length(total_fuel_heating_load_mmbtu_per_hour) diff --git a/src/core/steam_turbine.jl b/src/core/steam_turbine.jl index 22e159f53..6c9d58c45 100644 --- a/src/core/steam_turbine.jl +++ b/src/core/steam_turbine.jl @@ -5,7 +5,7 @@ ```julia size_class::Union{Int64, Nothing} = nothing min_kw::Float64 = 0.0 - max_kw::Float64 = 0.0 + max_kw::Float64 = 1.0e9 electric_produced_to_thermal_consumed_ratio::Float64 = NaN thermal_produced_to_thermal_consumed_ratio::Float64 = NaN is_condensing::Bool = false @@ -34,7 +34,7 @@ Base.@kwdef mutable struct SteamTurbine <: AbstractSteamTurbine size_class::Union{Int64, Nothing} = nothing min_kw::Float64 = 0.0 - max_kw::Float64 = 0.0 + max_kw::Float64 = 1.0e9 electric_produced_to_thermal_consumed_ratio::Float64 = NaN thermal_produced_to_thermal_consumed_ratio::Float64 = NaN is_condensing::Bool = false @@ -204,15 +204,20 @@ function get_steam_turbine_defaults_size_class(;avg_boiler_fuel_load_mmbtu_per_h defaults = JSON.parsefile(joinpath(dirname(@__FILE__), "..", "..", "data", "steam_turbine", "steam_turbine_default_data.json")) class_bounds = [(0.0, 25000.0), (0, 1000.0), (1000.0, 5000.0), (5000.0, 25000.0)] n_classes = length(class_bounds) + steam_turbine_electric_efficiency = 0.07 # Typical, steam_turbine_kwe / boiler_fuel_kwt + st_elec_size_heuristic_kw = nothing if !isnothing(size_class) if size_class < 0 || size_class > (n_classes-1) throw(@error("Invalid size_class $size_class given for steam_turbine, must be in [0,1,2,3]")) end + if !isnothing(avg_boiler_fuel_load_mmbtu_per_hour) + thermal_power_in_kw = avg_boiler_fuel_load_mmbtu_per_hour * KWH_PER_MMBTU + st_elec_size_heuristic_kw = thermal_power_in_kw * steam_turbine_electric_efficiency + end elseif !isnothing(avg_boiler_fuel_load_mmbtu_per_hour) if avg_boiler_fuel_load_mmbtu_per_hour <= 0 throw(@error("avg_boiler_fuel_load_mmbtu_per_hour must be > 0.0 MMBtu/hr")) end - steam_turbine_electric_efficiency = 0.07 # Typical, steam_turbine_kwe / boiler_fuel_kwt thermal_power_in_kw = avg_boiler_fuel_load_mmbtu_per_hour * KWH_PER_MMBTU st_elec_size_heuristic_kw = thermal_power_in_kw * steam_turbine_electric_efficiency # With heuristic size, find the suggested size class @@ -234,7 +239,6 @@ function get_steam_turbine_defaults_size_class(;avg_boiler_fuel_load_mmbtu_per_h end else size_class = 0 - st_elec_size_heuristic_kw = nothing end steam_turbine_defaults = get_steam_turbine_defaults(size_class, defaults) @@ -243,7 +247,7 @@ function get_steam_turbine_defaults_size_class(;avg_boiler_fuel_load_mmbtu_per_h ("prime_mover", "steam_turbine"), ("size_class", size_class), ("default_inputs", steam_turbine_defaults), - ("chp_size_based_on_avg_heating_load_kw", st_elec_size_heuristic_kw), + ("chp_elec_size_heuristic_kw", st_elec_size_heuristic_kw), ("size_class_bounds", class_bounds) ]) diff --git a/src/results/proforma.jl b/src/results/proforma.jl index 0f93f345f..9e403862e 100644 --- a/src/results/proforma.jl +++ b/src/results/proforma.jl @@ -140,7 +140,7 @@ function proforma_results(p::REoptInputs, d::Dict) # Optimal Case calculations electricity_bill_series = escalate_elec(d["ElectricTariff"]["year_one_bill_before_tax"]) - export_credit_series = escalate_elec(d["ElectricTariff"]["year_one_export_benefit_before_tax"]) + export_credit_series = escalate_elec(-d["ElectricTariff"]["year_one_export_benefit_before_tax"]) # In the two party case the electricity and export credits are incurred by the offtaker not the developer if third_party @@ -190,8 +190,8 @@ function proforma_results(p::REoptInputs, d::Dict) electricity_bill_series = escalate_elec(d["ElectricTariff"]["year_one_bill_before_tax"]) electricity_bill_series_bau = escalate_elec(d["ElectricTariff"]["year_one_bill_before_tax_bau"]) - export_credit_series = escalate_elec(-d["ElectricTariff"]["lifecycle_export_benefit_after_tax"]) - export_credit_series_bau = escalate_elec(-d["ElectricTariff"]["lifecycle_export_benefit_after_tax_bau"]) + export_credit_series = escalate_elec(-d["ElectricTariff"]["year_one_export_benefit_before_tax"]) + export_credit_series_bau = escalate_elec(-d["ElectricTariff"]["year_one_export_benefit_before_tax_bau"]) annual_income_from_host_series = repeat([-1 * r["annualized_payment_to_third_party"]], years) @@ -225,7 +225,7 @@ function proforma_results(p::REoptInputs, d::Dict) else # get cumulative cashflow for offtaker electricity_bill_series_bau = escalate_elec(d["ElectricTariff"]["year_one_bill_before_tax_bau"]) - export_credit_series_bau = escalate_elec(-d["ElectricTariff"]["lifecycle_export_benefit_after_tax_bau"]) + export_credit_series_bau = escalate_elec(-d["ElectricTariff"]["year_one_export_benefit_before_tax_bau"]) total_operating_expenses_bau = electricity_bill_series_bau + export_credit_series_bau + m.om_series_bau total_cash_incentives_bau = m.total_pbi_bau * (1 - p.s.financial.offtaker_tax_rate_fraction)