REopt Inputs

Inputs to run_reopt can be provided in one of four formats:

  1. a file path (string) to a JSON file,
  2. a Dict,
  3. using the Scenario struct, or
  4. using the REoptInputs struct

Any one of these types can be passed to the run_reopt method as shown in Examples.

The first option is perhaps the most straightforward. For example, the minimum requirements for a JSON scenario file would look like:

{
    "Site": {
        "longitude": -118.1164613,
        "latitude": 34.5794343
    },
    "ElectricLoad": {
        "doe_reference_name": "MidriseApartment",
        "annual_kwh": 1000000.0
    },
    "ElectricTariff": {
        "urdb_label": "5ed6c1a15457a3367add15ae"
    }
}

The order of the keys does not matter. Note that this scenario does not include any energy generation technologies and therefore the results can be used as a baseline for comparison to scenarios that result in cost-optimal generation technologies (alternatively, a user could include a BAUScenario as shown in Examples).

To add PV to the analysis simply add a PV key with an empty dictionary (to use default values):

{
    "Site": {
        "longitude": -118.1164613,
        "latitude": 34.5794343
    },
    "ElectricLoad": {
        "doe_reference_name": "MidriseApartment",
        "annual_kwh": 1000000.0
    },
    "ElectricTariff": {
        "urdb_label": "5ed6c1a15457a3367add15ae"
    },
    "PV": {}
}

This scenario will consider the option to purchase a solar PV system to reduce energy costs, and if solar PV can reduce the energy costs then REopt will provide the optimal PV capacity (assuming perfect foresight!). See PV for all available input keys and default values for PV. To override a default value, simply specify a value for a given key. For example, the site under consideration might have some existing PV capacity to account for, which can be done by setting the existing_kw key to the appropriate value.

Scenario

The Scenario struct captures all of the possible user input keys (see REopt Inputs for potential input formats). A Scenario struct will be automatically created if a Dict or file path are supplied to the run_reopt method. Alternatively, a user can create a Scenario struct and supply this to run_reopt.

REopt.ScenarioType
Scenario(d::Dict; flex_hvac_from_json=false)

A Scenario struct can contain the following keys:

All values of d are expected to be Dicts except for PV, which can be either a Dict or Dict[] (for multiple PV arrays).

Note

Set flex_hvac_from_json=true if FlexibleHVAC values were loaded in from JSON (necessary to handle conversion of Vector of Vectors from JSON to a Matrix in Julia).

source
Scenario(fp::String)

Consruct Scenario from filepath fp to JSON with keys aligned with the Scenario(d::Dict) method.

source

BAUScenario

The Business-as-usual (BAU) inputs are automatically created based on the BAUScenario struct when a user supplies two JuMP.Models to run_reopt() (as shown in Examples). The outputs of the BAU scenario are used to calculate comparative results such as the Financial net present value (npv).

REopt.BAUInputsFunction
BAUInputs(p::REoptInputs)

TheBAUInputs (REoptInputs for the Business As Usual scenario) are created based on the BAUScenario, which is in turn created based on the optimized-case Scenario.

The following assumptions are made for the BAU Inputs:

  • PV and Generator min_kw and max_kw set to the existing_kw values
  • ExistingBoiler and ExistingChiller # TODO
  • All other generation and storage tech sizes set to zero
  • Capital costs are assumed to be zero for existing PV and Generator
  • O&M costs and all other tech inputs are assumed to be the same for existing PV and Generator as those specified for the optimized case
  • Outage assumptions for deterministic vs stochastic # TODO
source

Settings

REopt.SettingsType

Captures high-level inputs affecting the optimization.

Settings is an optional REopt input with the following keys and default values:

    time_steps_per_hour::Int = 1 # corresponds to the time steps per hour for user-provided time series (e.g., `ElectricLoad.loads_kw` and `DomesticHotWaterLoad.fuel_loads_mmbtu_per_hour`) 
    add_soc_incentive::Bool = true # when true, an incentive is added to the model's objective function to keep the ElectricStorage SOC high
    off_grid_flag::Bool = false # true if modeling an off-grid system, not connected to bulk power system
    include_climate_in_objective::Bool = false # true if climate costs of emissions should be included in the model's objective function
    include_health_in_objective::Bool = false # true if health costs of emissions should be included in the model's objective function
source

Site

REopt.SiteType

Inputs related to the physical location:

Site is a required REopt input with the following keys and default values:

    latitude::Real, 
    longitude::Real, 
    land_acres::Union{Real, Nothing} = nothing, 
    roof_squarefeet::Union{Real, Nothing} = nothing,
    min_resil_time_steps::Int=0,
    mg_tech_sizes_equal_grid_sizes::Bool = true,
    node::Int = 1,
    CO2_emissions_reduction_min_pct::Union{Float64, Nothing} = nothing,
    CO2_emissions_reduction_max_pct::Union{Float64, Nothing} = nothing,
    bau_emissions_lb_CO2_per_year::Union{Float64, Nothing} = nothing,
    bau_grid_emissions_lb_CO2_per_year::Union{Float64, Nothing} = nothing,
    renewable_electricity_min_pct::Real = 0.0,
    renewable_electricity_max_pct::Union{Float64, Nothing} = nothing,
    include_exported_elec_emissions_in_total::Bool = true,
    include_exported_renewable_electricity_in_total::Bool = true,
source

ElectricLoad

REopt.ElectricLoadType

ElectricLoad is a required REopt input with the following keys and default values:

    loads_kw::Array{<:Real,1} = Real[],
    path_to_csv::String = "", # for csv containing loads_kw
    year::Int = 2020, # used in ElectricTariff to align rate schedule with weekdays/weekends
    doe_reference_name::String = "",
    blended_doe_reference_names::Array{String, 1} = String[],
    blended_doe_reference_percents::Array{<:Real,1} = Real[],
    city::String = "",
    annual_kwh::Union{Real, Nothing} = nothing,
    monthly_totals_kwh::Array{<:Real,1} = Real[],
    critical_loads_kw::Union{Nothing, Array{Real,1}} = nothing,
    loads_kw_is_net::Bool = true,
    critical_loads_kw_is_net::Bool = false,
    critical_load_pct::Real = off_grid_flag ? 1.0 : 0.5, # if off grid must be 1.0, else 0.5
    operating_reserve_required_pct::Real = off_grid_flag ? 0.1 : 0.0, # if off grid, 10%, else must be 0%. Applied to each time_step as a % of electric load.
    min_load_met_annual_pct::Real = off_grid_flag ? 0.99999 : 1.0 # if off grid, 99.999%, else must be 100%. Applied to each time_step as a % of electric load.
Required inputs

Must provide either loads_kw or path_to_csv or [doe_reference_name and city] or doe_reference_name or [blended_doe_reference_names and blended_doe_reference_percents].

When only doe_reference_name is provided the Site.latitude and Site.longitude are used to look up the ASHRAE climate zone, which determines the appropriate DoE Commercial Reference Building profile.

When using the [doe_reference_name and city] option, choose city from one of the cities used to represent the ASHRAE climate zones:

  • Albuquerque
  • Atlanta
  • Baltimore
  • Boulder
  • Chicago
  • Duluth
  • Fairbanks
  • Helena
  • Houston
  • LosAngeles
  • Miami
  • Minneapolis
  • Phoenix
  • SanFrancisco
  • Seattle

and doe_reference_name from:

  • FastFoodRest
  • FullServiceRest
  • Hospital
  • LargeHotel
  • LargeOffice
  • MediumOffice
  • MidriseApartment
  • Outpatient
  • PrimarySchool
  • RetailStore
  • SecondarySchool
  • SmallHotel
  • SmallOffice
  • StripMall
  • Supermarket
  • Warehouse
  • FlatLoad

Each city and doe_reference_name combination has a default annual_kwh, or you can provide your own annual_kwh or monthly_totals_kwh and the reference profile will be scaled appropriately.

source

ElectricTariff

REopt.ElectricTariffMethod

ElectricTariff is a required REopt input for on-grid scenarios only (it cannot be supplied when Settings.off_grid_flag is true) with the following keys and default values:

    urdb_label::String="",
    urdb_response::Dict=Dict(),
    urdb_utility_name::String="",
    urdb_rate_name::String="",
    year::Int=2020,
    NEM::Bool=false,
    wholesale_rate::T1=nothing,
    export_rate_beyond_net_metering_limit::T2=nothing,
    monthly_energy_rates::Array=[],
    monthly_demand_rates::Array=[],
    blended_annual_energy_rate::S=nothing,
    blended_annual_demand_rate::R=nothing,
    add_monthly_rates_to_urdb_rate::Bool=false,
    tou_energy_rates_per_kwh::Array=[],
    add_tou_energy_rates_to_urdb_rate::Bool=false,
    remove_tiers::Bool=false,
    demand_lookback_months::AbstractArray{Int64, 1}=Int64[],
    demand_lookback_percent::Real=0.0,
    demand_lookback_range::Int=0,
    coincident_peak_load_active_time_steps::Vector{Vector{Int64}}=[Int64[]],
    coincident_peak_load_charge_per_kw::AbstractVector{<:Real}=Real[]
    ) where {
        T1 <: Union{Nothing, Real, Array{<:Real}}, 
        T2 <: Union{Nothing, Real, Array{<:Real}}, 
        S <: Union{Nothing, Real}, 
        R <: Union{Nothing, Real}
    }
NEM input

The NEM boolean is determined by the ElectricUtility.net_metering_limit_kw. There is no need to pass in a NEM value.

source

Financial

REopt.FinancialType

Financial is an optional REopt input with the following keys and default values:

    om_cost_escalation_pct::Real = 0.025,
    elec_cost_escalation_pct::Real = 0.019,
    boiler_fuel_cost_escalation_pct::Real = 0.034,
    chp_fuel_cost_escalation_pct::Real = 0.034,
    generator_fuel_cost_escalation_pct::Real = 0.027,
    offtaker_tax_pct::Real = 0.26,
    offtaker_discount_pct::Real = 0.0564,
    third_party_ownership::Bool = false,
    owner_tax_pct::Real = 0.26,
    owner_discount_pct::Real = 0.0564,
    analysis_years::Int = 25,
    value_of_lost_load_per_kwh::Union{Array{R,1}, R} where R<:Real = 1.00,
    microgrid_upgrade_cost_pct::Real = off_grid_flag ? 0.0 : 0.3, # not applicable when off_grid_flag is true
    macrs_five_year::Array{Float64,1} = [0.2, 0.32, 0.192, 0.1152, 0.1152, 0.0576],  # IRS pub 946
    macrs_seven_year::Array{Float64,1} = [0.1429, 0.2449, 0.1749, 0.1249, 0.0893, 0.0892, 0.0893, 0.0446],
    offgrid_other_capital_costs::Real = 0.0, # only applicable when off_grid_flag is true. Straight-line depreciation is applied to this capex cost, reducing taxable income.
    offgrid_other_annual_costs::Real = 0.0 # only applicable when off_grid_flag is true. Considered tax deductible for owner. Costs are per year. 
    # Emissions cost inputs
    CO2_cost_per_tonne::Real = 51.0,
    CO2_cost_escalation_pct::Real = 0.042173,
    NOx_grid_cost_per_tonne::Union{Nothing,Real} = nothing,
    SO2_grid_cost_per_tonne::Union{Nothing,Real} = nothing,
    PM25_grid_cost_per_tonne::Union{Nothing,Real} = nothing,
    NOx_onsite_fuelburn_cost_per_tonne::Union{Nothing,Real} = nothing,
    SO2_onsite_fuelburn_cost_per_tonne::Union{Nothing,Real} = nothing,
    PM25_onsite_fuelburn_cost_per_tonne::Union{Nothing,Real} = nothing,
    NOx_cost_escalation_pct::Union{Nothing,Real} = nothing,
    SO2_cost_escalation_pct::Union{Nothing,Real} = nothing,
    PM25_cost_escalation_pct::Union{Nothing,Real} = nothing,
    # fields from other models needed for validation
    latitude::Real, # Passed from Site
    longitude::Real, # Passed from Site
    include_health_in_objective::Bool = false # Passed from Settings
Third party financing

When third_party_ownership is false the offtaker's discount and tax percentages are used throughout the model:

    if !third_party_ownership
        owner_tax_pct = offtaker_tax_pct
        owner_discount_pct = offtaker_discount_pct
    end
source

ElectricUtility

REopt.ElectricUtilityType

ElectricUtility is an optional REopt input with the following keys and default values:

    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
    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
    outage_start_time_steps::Array{Int,1}=Int[],  # we minimize the maximum outage cost over outage start times
    outage_durations::Array{Int,1}=Int[],  # one-to-one with outage_probabilities, outage_durations can be a random variable
    outage_probabilities::Array{R,1} where R<:Real = [1.0],
    outage_time_steps::Union{Nothing, UnitRange} = isempty(outage_durations) ? nothing : 1:maximum(outage_durations),
    scenarios::Union{Nothing, UnitRange} = isempty(outage_durations) ? nothing : 1:length(outage_durations),
    # Emissions and renewable energy inputs:
    emissions_factor_series_lb_CO2_per_kwh::Union{Real,Array{<:Real,1}} = Float64[],
    emissions_factor_series_lb_NOx_per_kwh::Union{Real,Array{<:Real,1}} = Float64[],
    emissions_factor_series_lb_SO2_per_kwh::Union{Real,Array{<:Real,1}} = Float64[],
    emissions_factor_series_lb_PM25_per_kwh::Union{Real,Array{<:Real,1}} = Float64[],
    emissions_factor_CO2_decrease_pct::Real = 0.01174,
    emissions_factor_NOx_decrease_pct::Real = 0.01174,
    emissions_factor_SO2_decrease_pct::Real = 0.01174,
    emissions_factor_PM25_decrease_pct::Real = 0.01174,
    # fields from other models needed for validation
    CO2_emissions_reduction_min_pct::Union{Real, Nothing} = nothing, # passed from Site
    CO2_emissions_reduction_max_pct::Union{Real, Nothing} = nothing, # passed from Site
    include_climate_in_objective::Bool = false, # passed from Settings
    include_health_in_objective::Bool = false # passed from Settings

julia

Outage modeling

Outage indexing begins at 1 (not 0) and the outage is inclusive of the outage end time step. For instance, to model a 3-hour outage from 12AM to 3AM on Jan 1, outagestarttimestep = 1 and outageendtimestep = 3. To model a 1-hour outage from 6AM to 7AM on Jan 1, outagestarttimestep = 7 and outageendtimestep = 7.

Cannot supply singular outagestart(or end)timestep and multiple outagestarttimesteps. Must use one or the other.

Outages, Emissions, and Renewable Energy Calculations

If a single deterministic outage is modeled using outagestarttimestep and outageendtimestep, emissions and renewable energy percentage calculations and constraints will factor in this outage. If stochastic outages are modeled using outagestarttimesteps, outagedurations, and outage_probabilities, emissions and renewable energy percentage calculations and constraints will not consider outages.

MPC vs. Non-MPC

This constructor is intended to be used with latitude/longitude arguments provided for the non-MPC case and without latitude/longitude arguments provided for the MPC case.

source

PV

REopt.PVType

PV is an optional REopt input with the following keys and default values:

    array_type::Int=1, # PV Watts array type (0: Ground Mount Fixed (Open Rack); 1: Rooftop, Fixed; 2: Ground Mount 1-Axis Tracking; 3 : 1-Axis Backtracking; 4: Ground Mount, 2-Axis Tracking)
    tilt::Real= array_type == 1 ? 10 : abs(latitude), # tilt = 10 deg for rooftop systems, abs(lat) for ground-mount
    module_type::Int=0, # PV module type (0: Standard; 1: Premium; 2: Thin Film)
    losses::Real=0.14,
    azimuth::Real = latitude≥0 ? 180 : 0, # set azimuth to zero for southern hemisphere
    gcr::Real=0.4,
    radius::Int=0,
    name::String="PV",
    location::String="both",
    existing_kw::Real=0,
    min_kw::Real=0,
    max_kw::Real=1.0e9,
    installed_cost_per_kw::Real=1592.0,
    om_cost_per_kw::Real=17.0,
    degradation_pct::Real=0.005,
    macrs_option_years::Int = 5,
    macrs_bonus_pct::Real = 1.0,
    macrs_itc_reduction::Real = 0.5,
    kw_per_square_foot::Real=0.01,
    acres_per_kw::Real=6e-3,
    inv_eff::Real=0.96,
    dc_ac_ratio::Real=1.2,
    prod_factor_series::Union{Nothing, Array{<:Real,1}} = nothing,
    federal_itc_pct::Real = 0.26,
    federal_rebate_per_kw::Real = 0.0,
    state_ibi_pct::Real = 0.0,
    state_ibi_max::Real = 1.0e10,
    state_rebate_per_kw::Real = 0.0,
    state_rebate_max::Real = 1.0e10,
    utility_ibi_pct::Real = 0.0,
    utility_ibi_max::Real = 1.0e10,
    utility_rebate_per_kw::Real = 0.0,
    utility_rebate_max::Real = 1.0e10,
    production_incentive_per_kwh::Real = 0.0,
    production_incentive_max_benefit::Real = 1.0e9,
    production_incentive_years::Int = 1,
    production_incentive_max_kw::Real = 1.0e9,
    can_net_meter::Bool = off_grid_flag ? false : true,
    can_wholesale::Bool = off_grid_flag ? false : true,
    can_export_beyond_nem_limit::Bool = off_grid_flag ? false : true,
    can_curtail::Bool = true,
    operating_reserve_required_pct::Real = off_grid_flag ? 0.25 : 0.0, # if off grid, 25%, else 0%. Applied to each time_step as a % of PV generation.
Multiple PV types

Multiple PV types can be considered by providing an array of PV inputs. See example in src/test/scenarios/multiple_pvs.json

PV tilt and aziumth

If tilt is not provided, then it is set to the absolute value of Site.latitude for ground-mount systems and is set to 10 degrees for rooftop systems. If azimuth is not provided, then it is set to 180 if the site is in the northern hemisphere and 0 if in the southern hemisphere.

source

Wind

REopt.WindType

Wind is an optional REopt input with the following keys and default values:

    min_kw = 0.0,
    max_kw = 1.0e9,
    installed_cost_per_kw = nothing,
    om_cost_per_kw = 35.0,
    prod_factor_series = nothing,
    size_class = "",
    wind_meters_per_sec = [],
    wind_direction_degrees = [],
    temperature_celsius = [],
    pressure_atmospheres = [],
    macrs_option_years = 5,
    macrs_bonus_pct = 0.0,
    macrs_itc_reduction = 0.5,
    federal_itc_pct = nothing,
    federal_rebate_per_kw = 0.0,
    state_ibi_pct = 0.0,
    state_ibi_max = 1.0e10,
    state_rebate_per_kw = 0.0,
    state_rebate_max = 1.0e10,
    utility_ibi_pct = 0.0,
    utility_ibi_max = 1.0e10,
    utility_rebate_per_kw = 0.0,
    utility_rebate_max = 1.0e10,
    production_incentive_per_kwh = 0.0,
    production_incentive_max_benefit = 1.0e9,
    production_incentive_years = 1,
    production_incentive_max_kw = 1.0e9,
    can_net_meter = true,
    can_wholesale = true,
    can_export_beyond_nem_limit = true

size_class must be one of ["residential", "commercial", "medium", "large"]. If size_class is not provided then it is determined based on the average electric load.

If no installed_cost_per_kw is provided then it is determined from:

size_class_to_installed_cost = Dict(
    "residential"=> 11950.0,
    "commercial"=> 7390.0,
    "medium"=> 4440.0,
    "large"=> 3450.0
)

The Federal Investment Tax Credit is adjusted based on the size_class as follows (if the default of 0.3 is not changed):

size_class_to_itc_incentives = Dict(
    "residential"=> 0.3,
    "commercial"=> 0.3,
    "medium"=> 0.12,
    "large"=> 0.12
)

If the prod_factor_series is not provided then NREL's System Advisor Model (SAM) is used to get the wind turbine production factor.

Wind resource values are optional, i.e. (wind_meters_per_sec, wind_direction_degrees, temperature_celsius, and pressure_atmospheres). If not provided then the resource values are downloaded from NREL's Wind Toolkit. These values are passed to SAM to get the turbine production factor.

source

ElectricStorage

REopt.ElectricStorageDefaultsType

ElectricStorage is an optional optional REopt input with the following keys and default values:

    off_grid_flag::Bool = false  
    min_kw::Real = 0.0
    max_kw::Real = 1.0e4
    min_kwh::Real = 0.0
    max_kwh::Real = 1.0e6
    internal_efficiency_pct::Float64 = 0.975
    inverter_efficiency_pct::Float64 = 0.96
    rectifier_efficiency_pct::Float64 = 0.96
    soc_min_pct::Float64 = 0.2
    soc_init_pct::Float64 = off_grid_flag ? 1.0 : 0.5
    can_grid_charge::Bool = off_grid_flag ? false : true
    installed_cost_per_kw::Real = 775.0
    installed_cost_per_kwh::Real = 388.0
    replace_cost_per_kw::Real = 440.0
    replace_cost_per_kwh::Real = 220.0
    inverter_replacement_year::Int = 10
    battery_replacement_year::Int = 10
    macrs_option_years::Int = 7
    macrs_bonus_pct::Float64 = 1.0
    macrs_itc_reduction::Float64 = 0.5
    total_itc_pct::Float64 = 0.0
    total_rebate_per_kw::Real = 0.0
    total_rebate_per_kwh::Real = 0.0
    charge_efficiency::Float64 = rectifier_efficiency_pct * internal_efficiency_pct^0.5
    discharge_efficiency::Float64 = inverter_efficiency_pct * internal_efficiency_pct^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
source
REopt.DegradationType
Degradation

Inputs used when ElectricStorage.model_degradation is true:

Base.@kwdef mutable struct Degradation
    calendar_fade_coefficient::Real = 2.46E-03
    cycle_fade_coefficient::Real = 7.82E-05
    time_exponent::Real = 0.5
    installed_cost_per_kwh_declination_rate::Float64 = 0.05
    maintenance_strategy::String = "augmentation"  # one of ["augmentation", "replacement"]
    maintenance_cost_per_kwh::Vector{<:Real} = Real[]
end

None of the above values are required. If ElectricStorage.model_degradation is true then the defaults above are used. If the maintenance_cost_per_kwh is not provided then it is determined using the ElectricStorage.installed_cost_per_kwh and the installed_cost_per_kwh_declination_rate along with a present worth factor $f$ to account for the present cost of buying a battery in the future. The present worth factor for each day is:

$f(day) = \frac{ (1-r_g)^\frac{day}{365} } { (1+r_d)^\frac{day}{365} }$

where $r_g$ = installed_cost_per_kwh_declination_rate and $r_d$ = p.s.financial.owner_discount_pct.

Note this day-specific calculation of the present-worth factor accumulates differently from the annually updated discount rate for other net-present value calculations in REopt, and has a higher effective discount rate as a result. The present worth factor is used in two different ways, depending on the maintenance_strategy, which is described below.

Warn

When modeling degradation the following ElectricStorage inputs are not used:

  • replace_cost_per_kw
  • replace_cost_per_kwh
  • inverter_replacement_year
  • battery_replacement_year

The are replaced by the maintenance_cost_per_kwh vector.

Note

When providing the maintenance_cost_per_kwh it must have a length equal to Financial.analysis_years*365.

Battery State Of Health

The state of health [SOH] is a linear function of the daily average state of charge [Eavg] and the daily equivalent full cycles [EFC]. The initial SOH is set to the optimal battery energy capacity (in kWh). The evolution of the SOH beyond the first day is:

$SOH[d] = SOH[d-1] - h\left( \frac{1}{2} k_{cal} Eavg[d-1] / \sqrt{d} + k_{cyc} EFC[d-1] \quad \forall d \in \{2\dots D\} \right)$

where:

  • $k_{cal}$ is the calendar_fade_coefficient
  • $k_{cyc}$ is the cycle_fade_coefficient
  • $h$ is the hours per time step
  • $D$ is the total number of days, 365 * analysis_years

The SOH is used to determine the maintence cost of the storage system, which depends on the maintenance_strategy.

Augmentation Maintenance Strategy

The augmentation maintenance strategy assumes that the battery energy capacity is maintained by replacing degraded cells daily in terms of cost. Using the definition of the SOH above the maintenance cost is:

$C_{\text{aug}} = \sum_{d \in \{2\dots D\}} 0.8 C_{\text{install}} f(day) \left( SOH[d-1] - SOH[d] \right)$

where

  • the $0.8$ factor accounts for sunk costs that do not need to be paid;
  • $C_{\text{install}}$ is the ElectricStorage.installed_cost_per_kwh; and
  • $SOH[d-1] - SOH[d]$ is the incremental amount of battery capacity lost in a day.

The $C_{\text{aug}}$ is added to the objective function to be minimized with all other costs.

Replacement Maintenance Strategy

Modeling the replacement maintenance strategy is more complex than the augmentation strategy. Effectively the replacement strategy says that the battery has to be replaced once the SOH hits 80% of the optimal, purchased capacity. It is possible that multiple replacements could be required under this strategy.

Warn

The "replacement" maintenance strategy requires integer variables and indicator constraints. Not all solvers support indicator constraints and some solvers are slow with integer variables.

The replacement strategy cost is:

$C_{\text{repl}} = B_{\text{kWh}} N_{\text{repl}} f(d_{80}) C_{\text{install}}$

where:

  • $B_{\text{kWh}}$ is the optimal battery capacity (ElectricStorage.size_kwh in the results dictionary);
  • $N_{\text{repl}}$ is the number of battery replacments required (a function of the month in which the SOH reaches 80% of original capacity);
  • $f(d_{80})$ is the present worth factor at approximately the 15th day of the month that the SOH reaches 80% of original capacity.

The $C_{\text{repl}}$ is added to the objective function to be minimized with all other costs.

Example of inputs

The following shows how one would use the degradation model in REopt via the Scenario inputs:

{
    ...
    "ElectricStorage": {
        "installed_cost_per_kwh": 390,
        ...
        "model_degradation": true,
        "degradation": {
            "calendar_fade_coefficient": 2.86E-03,
            "cycle_fade_coefficient": 6.22E-05,
            "installed_cost_per_kwh_declination_rate": 0.06,
            "maintenance_strategy": "replacement",
            ...
        }
    },
    ...
}

Note that not all of the above inputs are necessary. When not providing calendar_fade_coefficient for example the default value will be used.

source

Generator

REopt.GeneratorType

Generator is an optional REopt input with the following keys and default values:

    existing_kw::Real = 0,
    min_kw::Real = 0,
    max_kw::Real = 1.0e6,
    installed_cost_per_kw::Real = 500.0,
    om_cost_per_kw::Real = off_grid_flag ? 20.0 : 10.0,
    om_cost_per_kwh::Real = 0.0,
    fuel_cost_per_gallon::Real = 3.0,
    fuel_slope_gal_per_kwh::Real = 0.076,
    fuel_intercept_gal_per_hr::Real = 0.0,
    fuel_avail_gal::Real = off_grid_flag ? 1.0e9 : 660.0,
    min_turn_down_pct::Real = off_grid_flag ? 0.15 : 0.0,
    only_runs_during_grid_outage::Bool = true,
    sells_energy_back_to_grid::Bool = false,
    can_net_meter::Bool = false,
    can_wholesale::Bool = false,
    can_export_beyond_nem_limit = false,
    can_curtail::Bool = false,
    macrs_option_years::Int = 0,
    macrs_bonus_pct::Real = 1.0,
    macrs_itc_reduction::Real = 0.0,
    federal_itc_pct::Real = 0.0,
    federal_rebate_per_kw::Real = 0.0,
    state_ibi_pct::Real = 0.0,
    state_ibi_max::Real = 1.0e10,
    state_rebate_per_kw::Real = 0.0,
    state_rebate_max::Real = 1.0e10,
    utility_ibi_pct::Real = 0.0,
    utility_ibi_max::Real = 1.0e10,
    utility_rebate_per_kw::Real = 0.0,
    utility_rebate_max::Real = 1.0e10,
    production_incentive_per_kwh::Real = 0.0,
    production_incentive_max_benefit::Real = 1.0e9,
    production_incentive_years::Int = 0,
    production_incentive_max_kw::Real = 1.0e9,
    fuel_renewable_energy_pct::Real = 0.0,
    emissions_factor_lb_CO2_per_gal::Real = 22.51,
    emissions_factor_lb_NOx_per_gal::Real = 0.0775544,
    emissions_factor_lb_SO2_per_gal::Real = 0.040020476,
    emissions_factor_lb_PM25_per_gal::Real = 0.0,
    replacement_year::Int = off_grid_flag ? 10 : analysis_years, 
    replace_cost_per_kw::Real = off_grid_flag ? installed_cost_per_kw : 0.0
Replacement costs

Generator replacement costs will not be considered if Generator.replacement_year >= Financial.analysis_years.

source

ExistingBoiler

REopt.ExistingBoilerType

ExistingBoiler is an optional REopt input with the following keys and default values:

    max_heat_demand_kw::Real=0,
    production_type::String = "hot_water",
    chp_prime_mover::String = "",
    max_thermal_factor_on_peak_load::Real = 1.25,
    efficiency::Real = NaN,
    fuel_cost_per_mmbtu::Union{<:Real, AbstractVector{<:Real}} = [],
    fuel_type::String = "natural_gas", # "restrict_to": ["natural_gas", "landfill_bio_gas", "propane", "diesel_oil"]
    fuel_renewable_energy_pct::Real = get(FUEL_DEFAULTS["fuel_renewable_energy_pct"],fuel_type,0),
    emissions_factor_lb_CO2_per_mmbtu::Real = get(FUEL_DEFAULTS["emissions_factor_lb_CO2_per_mmbtu"],fuel_type,0),
    emissions_factor_lb_NOx_per_mmbtu::Real = get(FUEL_DEFAULTS["emissions_factor_lb_NOx_per_mmbtu"],fuel_type,0),
    emissions_factor_lb_SO2_per_mmbtu::Real = get(FUEL_DEFAULTS["emissions_factor_lb_SO2_per_mmbtu"],fuel_type,0),
    emissions_factor_lb_PM25_per_mmbtu::Real = get(FUEL_DEFAULTS["emissions_factor_lb_PM25_per_mmbtu"],fuel_type,0)
Max ExistingBoiler size

The maximum size [kW] of the ExistingBoiler will be set based on the peak heat demand as follows:

max_kw = max_heat_demand_kw * max_thermal_factor_on_peak_load
ExistingBoiler operating costs

The ExistingBoiler's fuel_cost_per_mmbtu field is a required input. The fuel_cost_per_mmbtu can be a scalar, a list of 12 monthly values, or a time series of values for every time step.

Determining `efficiency`

Must supply either: efficiency, chp_prime_mover, or production_type.

If efficiency is not supplied, the efficiency will be determined based on the production_type. If production_type is not supplied, the production_type will be determined based on the chp_prime_mover (one of ["recipengine", "microturbine", "combustionturbine", "fuelcell"]). The following defaults are used:

production_type_by_chp_prime_mover = Dict(
        "recip_engine" => "hot_water",
        "micro_turbine" => "hot_water",
        "combustion_turbine" => "steam",
        "fuel_cell" => "hot_water"
)
efficiency_defaults = Dict(
    "hot_water" => 0.8,
    "steam" => 0.75
)
source

AbsorptionChiller

REopt.AbsorptionChillerType

AbsorptionChiller is an optional REopt input with the following keys and default values and default values and default values:

    min_ton::Real = 0.0,
    max_ton::Real = 0.0,
    chiller_cop::Real,
    chiller_elec_cop::Real = 14.1,
    installed_cost_per_ton::Real,
    om_cost_per_ton::Real,
    macrs_option_years::Real = 0,
    macrs_bonus_pct::Real = 0
source

CHP

REopt.CHPType

CHP is an optional REopt input with the following keys and default values:

    prime_mover::String = ""
    fuel_cost_per_mmbtu::Union{<:Real, AbstractVector{<:Real}} = []  # REQUIRED

    # Required "custom inputs" if not providing prime_mover:
    installed_cost_per_kw::Union{Float64, AbstractVector{Float64}} = NaN
    tech_sizes_for_cost_curve::Union{Float64, AbstractVector{Float64}} = NaN
    om_cost_per_kwh::Float64 = NaN
    elec_effic_half_load = NaN
    elec_effic_full_load::Float64 = NaN
    min_turn_down_pct::Float64 = NaN
    thermal_effic_full_load::Float64 = NaN
    thermal_effic_half_load::Float64 = NaN
    min_allowable_kw::Float64 = NaN
    max_kw::Float64 = NaN
    cooling_thermal_factor::Float64 = NaN  # only needed with cooling load
    unavailability_periods::AbstractVector{Dict} = Dict[]

    # Optional inputs:
    size_class::Int = 1
    min_kw::Float64 = 0.0
    fuel_type::String = "natural_gas" # "restrict_to": ["natural_gas", "landfill_bio_gas", "propane", "diesel_oil"]
    om_cost_per_kw::Float64 = 0.0
    om_cost_per_hr_per_kw_rated::Float64 = 0.0
    supplementary_firing_capital_cost_per_kw::Float64 = 150.0
    supplementary_firing_max_steam_ratio::Float64 = 1.0
    supplementary_firing_efficiency::Float64 = 0.92
    standby_rate_per_kw_per_month::Float64 = 0.0
    reduces_demand_charges::Bool = true
    use_default_derate::Bool = true
    max_derate_factor::Float64 = 1.0
    derate_start_temp_degF::Float64 = 0.0
    derate_slope_pct_per_degF::Float64 = 0.0
    can_supply_steam_turbine::Bool=false

    macrs_option_years::Int = 5
    macrs_bonus_pct::Float64 = 1.0
    macrs_itc_reduction::Float64 = 0.5
    federal_itc_pct::Float64 = 0.1
    federal_rebate_per_kw::Float64 = 0.0
    state_ibi_pct::Float64 = 0.0
    state_ibi_max::Float64 = 1.0e10
    state_rebate_per_kw::Float64 = 0.0
    state_rebate_max::Float64 = 1.0e10
    utility_ibi_pct::Float64 = 0.0
    utility_ibi_max::Float64 = 1.0e10
    utility_rebate_per_kw::Float64 = 0.0
    utility_rebate_max::Float64 = 1.0e10
    production_incentive_per_kwh::Float64 = 0.0
    production_incentive_max_benefit::Float64 = 1.0e9
    production_incentive_years::Int = 0
    production_incentive_max_kw::Float64 = 1.0e9
    can_net_meter::Bool = false
    can_wholesale::Bool = false
    can_export_beyond_nem_limit::Bool = false
    can_curtail::Bool = false
    fuel_renewable_energy_pct::Float64 = FUEL_DEFAULTS["fuel_renewable_energy_pct"][fuel_type]
    emissions_factor_lb_CO2_per_mmbtu::Float64 = FUEL_DEFAULTS["emissions_factor_lb_CO2_per_mmbtu"][fuel_type]
    emissions_factor_lb_NOx_per_mmbtu::Float64 = FUEL_DEFAULTS["emissions_factor_lb_NOx_per_mmbtu"][fuel_type]
    emissions_factor_lb_SO2_per_mmbtu::Float64 = FUEL_DEFAULTS["emissions_factor_lb_SO2_per_mmbtu"][fuel_type]
    emissions_factor_lb_PM25_per_mmbtu::Float64 = FUEL_DEFAULTS["emissions_factor_lb_PM25_per_mmbtu"][fuel_type]
Required inputs

To model CHP, you must provide at least prime_mover from ["recipengine", "microturbine", "combustionturbine", "fuelcell"] or all of the "custom inputs" defined below. If primemover is provided, any missing value from the "custom inputs" will be populated from data/chp/chpdefaultdata.json, based on the primemover, boilertype, and sizeclass. boilertype is "steam" if `primemoveris "combustion_turbine" and is "hot_water" for all otherprime_mover` types.

fuel_cost_per_mmbtu is always required

source

HotThermalStorage

REopt.HotThermalStorageDefaultsType

HotThermalStorage is an optional REopt input with the following keys and default values:

    min_gal::Float64 = 0.0
    max_gal::Float64 = 0.0
    hot_water_temp_degF::Float64 = 180.0
    cool_water_temp_degF::Float64 = 160.0
    internal_efficiency_pct::Float64 = 0.999999
    soc_min_pct::Float64 = 0.1
    soc_init_pct::Float64 = 0.5
    installed_cost_per_gal::Float64 = 1.50
    thermal_decay_rate_fraction::Float64 = 0.0004
    om_cost_per_gal::Float64 = 0.0
    macrs_option_years::Int = 0
    macrs_bonus_pct::Float64 = 0.0
    macrs_itc_reduction::Float64 = 0.0
    total_itc_pct::Float64 = 0.0
    total_rebate_per_kwh::Float64 = 0.0
source

ColdThermalStorage

REopt.ColdThermalStorageDefaultsType

Cold thermal energy storage sytem; specifically, a chilled water system used to meet thermal cooling loads.

ColdThermalStorage is an optional REopt input with the following keys and default values:

    min_gal::Float64 = 0.0
    max_gal::Float64 = 0.0
    hot_water_temp_degF::Float64 = 56.0
    cool_water_temp_degF::Float64 = 44.0
    internal_efficiency_pct::Float64 = 0.999999
    soc_min_pct::Float64 = 0.1
    soc_init_pct::Float64 = 0.5
    installed_cost_per_gal::Float64 = 1.50
    thermal_decay_rate_fraction::Float64 = 0.0004
    om_cost_per_gal::Float64 = 0.0
    macrs_option_years::Int = 0
    macrs_bonus_pct::Float64 = 0.0
    macrs_itc_reduction::Float64 = 0.0
    total_itc_pct::Float64 = 0.0
    total_rebate_per_kwh::Float64 = 0.0
source

DomesticHotWaterLoad

REopt.DomesticHotWaterLoadType

DomesticHotWaterLoad is an optional REopt input with the following keys and default values:

    doe_reference_name::String = "",
    blended_doe_reference_names::Array{String, 1} = String[],
    blended_doe_reference_percents::Array{<:Real,1} = Real[],
    annual_mmbtu::Union{Real, Nothing} = nothing,
    monthly_mmbtu::Array{<:Real,1} = Real[],
    fuel_loads_mmbtu_per_hour::Array{<:Real,1} = Real[] # Vector of hot water fuel loads [mmbtu/hour]. Length must equal 8760 * `Settings.time_steps_per_hour`

There are many ways in which a DomesticHotWaterLoad can be defined:

  1. When using either doe_reference_name or blended_doe_reference_names in an ElectricLoad one only needs to provide the input key "DomesticHotWaterLoad" in the Scenario (JSON or Dict). In this case the values from DoE reference names from the ElectricLoad will be used to define the DomesticHotWaterLoad.
  2. One can provide the doe_reference_name or blended_doe_reference_names directly in the DomesticHotWaterLoad key within the Scenario. These values can be combined with the annual_mmbtu or monthly_mmbtu inputs to scale the DoE reference profile(s).
  3. One can provide the fuel_loads_mmbtu_per_hour value in the DomesticHotWaterLoad key within the Scenario.
source

SpaceHeatingLoad

REopt.SpaceHeatingLoadType

SpaceHeatingLoad is an optional REopt input with the following keys and default values:

    doe_reference_name::String = "",
    blended_doe_reference_names::Array{String, 1} = String[],
    blended_doe_reference_percents::Array{<:Real,1} = Real[],
    annual_mmbtu::Union{Real, Nothing} = nothing,
    monthly_mmbtu::Array{<:Real,1} = Real[],
    fuel_loads_mmbtu_per_hour::Array{<:Real,1} = Real[] # Vector of space heating fuel loads [mmbtu/hr]. Length must equal 8760 * `Settings.time_steps_per_hour`

There are many ways to define a SpaceHeatingLoad:

  1. a time-series via the fuel_loads_mmbtu_per_hour,
  2. scaling a DoE Commercial Reference Building (CRB) profile or a blend of CRB profiles to either the annual_mmbtu or monthly_mmbtu values;
  3. or using the doe_reference_name or blended_doe_reference_names from the ElectricLoad.

When using an ElectricLoad defined from a doe_reference_name or blended_doe_reference_names one only needs to provide an empty Dict in the scenario JSON to add a SpaceHeatingLoad to a Scenario, i.e.:

...
"ElectricLoad": {"doe_reference_name": "MidriseApartment"},
"SpaceHeatingLoad" : {},
...

In this case the values provided for doe_reference_name, or blended_doe_reference_names and blended_doe_reference_percents are copied from the ElectricLoad to the SpaceHeatingLoad.

source

FlexibleHVAC

REopt.FlexibleHVACType

FlexibleHVAC is an optional REopt input with the following keys and default values:

    system_matrix::AbstractMatrix{Float64}  # N x N, with N states (temperatures in RC network)
    input_matrix::AbstractMatrix{Float64}  # N x M, with M inputs
    exogenous_inputs::AbstractMatrix{Float64}  # M x T, with T time steps
    control_node::Int64
    initial_temperatures::AbstractVector{Float64}
    temperature_upper_bound_degC::Union{Real, Nothing}
    temperature_lower_bound_degC::Union{Real, Nothing}
    installed_cost::Float64

julia

Every model with FlexibleHVAC includes a preprocessing step to calculate the business-as-usual (BAU) cost of meeting the thermal loads using a dead-band controller. The BAU cost is then used in the binary decision for purchasing the FlexibleHVAC system: if the FlexibleHVAC system is purchased then the heating and cooling costs are determined by the HVAC dispatch that minimizes the lifecycle cost of energy. If the FlexibleHVAC system is not purchased then the BAU heating and cooling costs must be paid.

There are two construction methods for FlexibleHVAC, which depend on whether or not the data was loaded in from a JSON file. The issue with data from JSON is that the vector-of-vectors from the JSON file must be appropriately converted to Julia Matrices. When loading in a Scenario from JSON that includes a FlexibleHVAC model, if you include the flex_hvac_from_json argument to the Scenario constructor then the conversion to Matrices will be done appropriately.

Note

At least one of the inputs for temperature_upper_bound_degC or temperature_lower_bound_degC must be provided to evaluate the FlexibleHVAC option. For example, if only temperature_lower_bound_degC is provided then only a heating system will be evaluated. Also, the heating system will only be used (or purchased) if the exogenous_inputs lead to the temperature at the control_node going below the temperature_lower_bound_degC.

Note

The ExistingChiller is electric and so its operating cost is determined by the ElectricTariff.

Note

The ExistingBoiler default operating cost is zero. Please provide the fuel_cost_per_mmbtu field for the ExistingBoiler if you want non-zero BAU heating costs. The fuel_cost_per_mmbtu can be a scalar, a list of 12 monthly values, or a time series of values for every time step.

source

ExistingChiller

REopt.ExistingChillerType

ExistingChiller is an optional REopt input with the following keys and default values:

    loads_kw_thermal::Vector{<:Real},
    cop::Union{Real, Nothing} = nothing,
    max_thermal_factor_on_peak_load::Real=1.25
Max ExistingChiller size

The maximum size [kW] of the ExistingChiller will be set based on the peak thermal load as follows:

max_kw = maximum(loads_kw_thermal) * max_thermal_factor_on_peak_load
source