Skip to content

Commit

Permalink
Merge pull request #159 from NREL/develop
Browse files Browse the repository at this point in the history
v0.23.0 Create custom REopt logger, Fix URDB handling
  • Loading branch information
adfarth authored Dec 16, 2022
2 parents 51dfbfb + da2cffd commit 02e3be4
Show file tree
Hide file tree
Showing 40 changed files with 856 additions and 351 deletions.
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,25 @@ Classify the change according to the following categories:
### Deprecated
### Removed

## v0.23.0
### Added
- Add **REoptLogger** type of global logger with a standard out to the console and to a dictionary
- Instantiate `logREopt` as the global logger in `__init()__` function call as a global variable
- Handle Warn or Error logs to save them along with information on where they occurred
- Try-catch `core/reopt.jl -> run_reopt()` functions. Process any errors when catching the error.
- Add Warnings and Errors from `logREopt` to results dictionary. If error is unhandled in REopt, include a stacktrace
- Add a `status` of `error` to results for consistency
- Ensure all error text is returned as strings for proper handling in the API
- Add `handle_errors(e::E, stacktrace::V) where {E <: Exception, V <: Vector}` and `handle_errors()` to `core/reopt.jl` to include info, warn and errors from REopt input data processing, optimization, and results processing in the returned dictionary.
- Tests for user-inputs of `ElectricTariff` `demand_lookback_months` and `demand_lookback_range`
### Changed
- `core/reopt.jl` added try-catch statements to call `handle_errors()` when there is a REopt error (handled or unhandled) and return it to the requestor/user.
### Fixed
- URDB lookback was not incorporated based on the descriptions of how the 3 lookback variables should be entered in the code. Modified `parse_urdb_lookback_charges` function to correct.
- TOU demand for 15-min load was only looking at the first 8760 timesteps.
- Tiered energy rates jsons generated by the webtool errored and could not run.
- Aligned lookback parameter names from URDB with API

# v0.22.0
### Added
- Simulated load function which mimicks the REopt_API /simulated_load endpoint for getting commercial reference building load data from annual or monthly energy data, or blended/hybrid buildings
Expand Down
2 changes: 1 addition & 1 deletion Manifest.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This file is machine-generated - editing it directly is not advised

julia_version = "1.7.2"
julia_version = "1.7.1"
manifest_format = "2.0"

[[deps.AbstractFFTs]]
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "REopt"
uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6"
authors = ["Nick Laws", "Hallie Dunham <[email protected]>", "Bill Becker <[email protected]>", "Bhavesh Rathod <[email protected]>", "Alex Zolan <[email protected]>", "Amanda Farthing <[email protected]>"]
version = "0.22.0"
version = "0.23.0"

[deps]
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
Expand Down
4 changes: 3 additions & 1 deletion src/REopt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ end

const EXISTING_BOILER_EFFICIENCY = 0.8
const GAL_PER_M3 = 264.172 # [gal/m^3]
const KWH_PER_GAL_DIESEL = 40.7 # [kWh/gal_diesel]
const KWH_PER_GAL_DIESEL = 40.7 # [kWh/gal_diesel] higher heating value of diesel
const KWH_PER_MMBTU = 293.07107 # [kWh/mmbtu]
const KWH_THERMAL_PER_TONHOUR = 3.51685
const TONNE_PER_LB = 1/2204.62 # [tonne/lb]
Expand Down Expand Up @@ -119,6 +119,8 @@ const FUEL_DEFAULTS = Dict(
)
)

include("logging.jl")

include("keys.jl")
include("core/types.jl")
include("core/utils.jl")
Expand Down
2 changes: 1 addition & 1 deletion src/constraints/battery_degradation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ function add_degradation(m, p; b="ElectricStorage")
# add augmentation cost to objective
# maintenance_cost_per_kwh must have length == length(days) - 1, i.e. starts on day 2
else
@error "Battery maintenance strategy $strategy is not supported. Choose from augmentation and replacement."
throw(@error("Battery maintenance strategy $strategy is not supported. Choose from augmentation and replacement."))
end

@objective(m, Min, m[:Costs] + m[:degr_cost])
Expand Down
29 changes: 7 additions & 22 deletions src/constraints/electric_utility_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ function add_export_constraints(m, p; _n="")
end
else
if !(isempty(_n))
@error """Binaries decisions for net metering capacity limit is not implemented for multinode models to keep
them linear. Please set the net metering limit to zero or equal to the interconnection limit."""
throw(@error("Binaries decisions for net metering capacity limit is not implemented for multinode models to keep
them linear. Please set the net metering limit to zero or equal to the interconnection limit."))
end

binNEM = @variable(m, binary = true)
Expand Down Expand Up @@ -298,25 +298,10 @@ function add_demand_lookback_constraints(m, p; _n="")
if p.s.electric_tariff.demand_lookback_range != 0 # then the dvPeakDemandLookback varies by month

##Constraint (12e): dvPeakDemandLookback is the highest peak demand in DemandLookbackMonths
for mth in p.months
if mth > p.s.electric_tariff.demand_lookback_range
@constraint(m, [lm in 1:p.s.electric_tariff.demand_lookback_range, ts in p.s.electric_tariff.time_steps_monthly[mth - lm]],
m[Symbol(dv)][mth] sum( m[Symbol("dvGridPurchase"*_n)][ts, tier]
for tier in 1:p.s.electric_tariff.n_energy_tiers )
)
else # need to handle rollover months
for lm in 1:p.s.electric_tariff.demand_lookback_range
lkbkmonth = mth - lm
if lkbkmonth 0
lkbkmonth += 12
end
@constraint(m, [ts in p.s.electric_tariff.time_steps_monthly[lkbkmonth]],
m[Symbol(dv)][mth] sum( m[Symbol("dvGridPurchase"*_n)][ts, tier]
for tier in 1:p.s.electric_tariff.n_energy_tiers )
)
end
end
end
@constraint(m, [mth in p.months, lm in 1:p.s.electric_tariff.demand_lookback_range, ts in p.s.electric_tariff.time_steps_monthly[mod(mth - lm - 1, 12) + 1]],
m[Symbol(dv)][mth] sum( m[Symbol("dvGridPurchase"*_n)][ts, tier]
for tier in 1:p.s.electric_tariff.n_energy_tiers )
)

##Constraint (12f): Ratchet peak demand charge is bounded below by lookback
@constraint(m, [mth in p.months],
Expand All @@ -326,7 +311,7 @@ function add_demand_lookback_constraints(m, p; _n="")

else # dvPeakDemandLookback does not vary by month

##Constraint (12e): dvPeakDemandLookback is the highest peak demand in DemandLookbackMonths
##Constraint (12e): dvPeakDemandLookback is the highest peak demand in demand_lookback_months
@constraint(m, [lm in p.s.electric_tariff.demand_lookback_months],
m[Symbol(dv)][1] >= sum(m[Symbol("dvPeakDemandMonth"*_n)][lm, tier] for tier in 1:p.s.electric_tariff.n_monthly_demand_tiers)
)
Expand Down
2 changes: 1 addition & 1 deletion src/constraints/generator_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function add_binGenIsOnInTS_constraints(m,p)
@constraint(m, [t in p.techs.gen, ts in p.time_steps],
m[:dvRatedProduction][t, ts] <= p.max_sizes[t] * m[:binGenIsOnInTS][t, ts]
)
# Note: min_turn_down_fraction is only enforced when off_grid_flag is true and in p.time_steps_with_grid, but not for grid outages for on-grid analyses
# Note: min_turn_down_fraction is only enforced when `off_grid_flag` is true and in p.time_steps_with_grid, but not for grid outages for on-grid analyses
if p.s.settings.off_grid_flag
@constraint(m, [t in p.techs.gen, ts in p.time_steps_without_grid],
p.s.generator.min_turn_down_fraction * m[:dvSize][t] - m[:dvRatedProduction][t, ts] <=
Expand Down
6 changes: 3 additions & 3 deletions src/core/absorption_chiller.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ function AbsorptionChiller(d::Dict;
if !isnothing(cooling_load)
load_max_tons = maximum(cooling_load.loads_kw_thermal) / KWH_THERMAL_PER_TONHOUR
else
throw(@error "Invalid argument cooling_load=nothing: a CoolingLoad is required for the AbsorptionChiller to be a technology option.")
throw(@error("Invalid argument cooling_load=nothing: a CoolingLoad is required for the AbsorptionChiller to be a technology option."))
end
if !isnothing(existing_boiler)
boiler_type = existing_boiler.production_type
Expand Down Expand Up @@ -190,7 +190,7 @@ function get_absorption_chiller_defaults(;
elseif chp_prime_mover in PRIME_MOVERS #if chp_prime mover is blank or is anything but "combustion_turbine" then assume hot water
thermal_consumption_hot_water_or_steam = "hot_water"
else
throw(@error "Invalid argument for `prime_mover`; must be in $PRIME_MOVERS")
throw(@error("Invalid argument for `prime_mover`; must be in $PRIME_MOVERS"))
end
elseif !isnothing(boiler_type)
thermal_consumption_hot_water_or_steam = boiler_type
Expand All @@ -200,7 +200,7 @@ function get_absorption_chiller_defaults(;
end
else
if !(thermal_consumption_hot_water_or_steam in HOT_WATER_OR_STEAM)
throw(@error "Invalid argument for `thermal_consumption_hot_water_or_steam`; must be `hot_water` or `steam`")
throw(@error("Invalid argument for `thermal_consumption_hot_water_or_steam`; must be `hot_water` or `steam`"))
end
end

Expand Down
20 changes: 10 additions & 10 deletions src/core/chp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ prime_movers = ["recip_engine", "micro_turbine", "combustion_turbine", "fuel_cel
If no information is provided, the default `prime_mover` is `recip_engine` and the `size_class` is 1 which represents
the widest range of sizes available.
`fuel_cost_per_mmbtu` is always required
`fuel_cost_per_mmbtu` is always required and can be a scalar, a list of 12 monthly values, or a time series of values for every time step
"""
Base.@kwdef mutable struct CHP <: AbstractCHP
Expand Down Expand Up @@ -172,7 +172,7 @@ function CHP(d::Dict;

# Check for required fuel cost
if !haskey(d, "fuel_cost_per_mmbtu")
throw(@error "CHP must have the required fuel_cost_per_mmbtu input")
throw(@error("CHP must have the required fuel_cost_per_mmbtu input"))
end
# Create CHP struct from inputs, to be mutated as needed
chp = CHP(; dictkeys_tosymbols(d)...)
Expand Down Expand Up @@ -200,7 +200,7 @@ function CHP(d::Dict;
@warn "Ignoring `chp.tech_sizes_for_cost_curve` input because `chp.installed_cost_per_kw` is a scalar"
end
elseif length(chp.installed_cost_per_kw) > 1 && length(chp.installed_cost_per_kw) != length(chp.tech_sizes_for_cost_curve)
throw(@error "To model CHP cost curve, you must provide `chp.tech_sizes_for_cost_curve` vector of equal length to `chp.installed_cost_per_kw`")
throw(@error("To model CHP cost curve, you must provide `chp.tech_sizes_for_cost_curve` vector of equal length to `chp.installed_cost_per_kw`"))
elseif isempty(chp.tech_sizes_for_cost_curve) && isempty(chp.installed_cost_per_kw)
update_installed_cost_params = true
elseif isempty(chp.prime_mover)
Expand Down Expand Up @@ -333,28 +333,28 @@ function get_chp_defaults_prime_mover_size_class(;hot_water_or_steam::Union{Stri
# Inputs validation
if !isnothing(prime_mover)
if !(prime_mover in prime_movers) # Validate user-entered hot_water_or_steam
throw(@error "Invalid argument for `prime_mover`; must be in $prime_movers")
throw(@error("Invalid argument for `prime_mover`; must be in $prime_movers"))
end
end

if !isnothing(hot_water_or_steam) # Option 1 if prime_mover also not input
if !(hot_water_or_steam in ["hot_water", "steam"]) # Validate user-entered hot_water_or_steam
throw(@error "Invalid argument for `hot_water_or_steam``; must be `hot_water` or `steam`")
throw(@error("Invalid argument for `hot_water_or_steam``; must be `hot_water` or `steam`"))
end
else # Options 2, 3, or 4
hot_water_or_steam = "hot_water"
end

if !isnothing(avg_boiler_fuel_load_mmbtu_per_hour) # Option 1
if avg_boiler_fuel_load_mmbtu_per_hour <= 0
throw(@error "avg_boiler_fuel_load_mmbtu_per_hour must be >= 0.0")
throw(@error("avg_boiler_fuel_load_mmbtu_per_hour must be >= 0.0"))
end
end

if !isnothing(size_class) && !isnothing(prime_mover) # Option 3
n_classes = length(prime_mover_defaults_all[prime_mover]["installed_cost_per_kw"])
if size_class < 1 || size_class >= n_classes
throw(@error "The size class input is outside the valid range of 1-$n_classes for prime_mover $prime_mover")
if size_class < 1 || size_class > n_classes
throw(@error("The size class $size_class input is outside the valid range of 1 to $n_classes for prime_mover $prime_mover"))
end
end

Expand Down Expand Up @@ -397,8 +397,8 @@ function get_chp_defaults_prime_mover_size_class(;hot_water_or_steam::Union{Stri

# If size class is specified use that and ignore heuristic CHP sizing for determining size class
if !isnothing(size_class)
if size_class < 1 || size_class >= n_classes
throw(@error "The size class input is outside the valid range of 1-$n_classes for prime_mover $prime_mover")
if size_class < 1 || size_class > n_classes
throw(@error("The size class $size_class input is outside the valid range of 1 to $n_classes for prime_mover $prime_mover"))
end
# If size class is not specified, heuristic sizing based on avg thermal load and size class 0 efficiencies
elseif isnothing(size_class) && !isnothing(chp_elec_size_heuristic_kw)
Expand Down
2 changes: 1 addition & 1 deletion src/core/cost_curve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ function cost_curve(tech::AbstractTech, financial::Financial)
for s in range(1, stop=n_segments)
if cost_curve_bp_x[s + 1] <= 0
# Not sure how else to handle this case, perhaps there is a better way to handle it?
@error "Invalid cost curve for {$nameof(T)}. Value at index {$s} ({$cost_curve_bp_x[s + 1]}) cannot be less than or equal to 0."
throw(@error("Invalid cost curve for {$nameof(T)}. Value at index {$s} ({$cost_curve_bp_x[s + 1]}) cannot be less than or equal to 0."))
end

# Remove federal incentives for ITC basis and tax benefit calculations
Expand Down
4 changes: 2 additions & 2 deletions src/core/doe_commercial_reference_building_loads.jl
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ function find_ashrae_zone_city(lat, lon; get_zone=false)
end
end
if isnothing(archgdal_city)
@info "Could not find latitude/longitude in U.S. Using geometrically nearest city."
@warn "Could not find latitude/longitude in U.S. Using geometrically nearest city."
elseif !get_zone
return archgdal_city
end
Expand Down Expand Up @@ -314,7 +314,7 @@ function get_monthly_energy(power_profile::AbstractArray{<:Real,1};
if !isempty(power_profile)
monthly_energy_total[month] = sum(power_profile[t0:t0+plus_hours-1])
else
throw(@error "Must provide power_profile")
throw(@error("Must provide power_profile"))
end
t0 += plus_hours
end
Expand Down
19 changes: 9 additions & 10 deletions src/core/electric_load.jl
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ mutable struct ElectricLoad # mutable to adjust (critical_)loads_kw based off o
)

if off_grid_flag && !(critical_load_fraction == 1.0)
@warn "ElectricLoad critical_load_fraction must be 1.0 (100%) for off-grid scenarios. Any other value will be overriden when off_grid_flag is True. If you wish to alter the load profile or load met, adjust the loads_kw or min_load_met_annual_fraction."
@warn "ElectricLoad critical_load_fraction must be 1.0 (100%) for off-grid scenarios. Any other value will be overriden when `off_grid_flag` is true. If you wish to alter the load profile or load met, adjust the loads_kw or min_load_met_annual_fraction."
critical_load_fraction = 1.0
end

Expand All @@ -145,25 +145,24 @@ mutable struct ElectricLoad # mutable to adjust (critical_)loads_kw based off o
if length(loads_kw) > 0

if !(length(loads_kw) / time_steps_per_hour 8760)
throw(@error "Provided electric load does not match the time_steps_per_hour.")
throw(@error("Provided electric load does not match the time_steps_per_hour."))
end

elseif !isempty(path_to_csv)
try
loads_kw = vec(readdlm(path_to_csv, ',', Float64, '\n'))
catch e
@error "Unable to read in electric load profile from $path_to_csv. Please provide a valid path to a csv with no header."
throw(e)
throw(@error("Unable to read in electric load profile from $path_to_csv. Please provide a valid path to a csv with no header."))
end

if !(length(loads_kw) / time_steps_per_hour 8760)
throw(@error "Provided electric load does not match the time_steps_per_hour.")
throw(@error("Provided electric load does not match the time_steps_per_hour."))
end

elseif !isempty(doe_reference_name)
# NOTE: must use year that starts on Sunday with DOE reference doe_ref_profiles
if year != 2017
@debug "Changing load profile year to 2017 because DOE reference profiles start on a Sunday."
@warn "Changing load profile year to 2017 because DOE reference profiles start on a Sunday."
end
year = 2017
loads_kw = BuiltInElectricLoad(city, doe_reference_name, latitude, longitude, year, annual_kwh, monthly_totals_kwh)
Expand All @@ -174,14 +173,14 @@ mutable struct ElectricLoad # mutable to adjust (critical_)loads_kw based off o
blended_doe_reference_names, blended_doe_reference_percents, city,
annual_kwh, monthly_totals_kwh)
else
error("Cannot construct ElectricLoad. You must provide either [loads_kw], [doe_reference_name, city],
throw(@error("Cannot construct ElectricLoad. You must provide either [loads_kw], [doe_reference_name, city],
[doe_reference_name, latitude, longitude],
or [blended_doe_reference_names, blended_doe_reference_percents] with city or latitude and longitude.")
or [blended_doe_reference_names, blended_doe_reference_percents] with city or latitude and longitude."))
end

if length(loads_kw) < 8760*time_steps_per_hour
loads_kw = repeat(loads_kw, inner=Int(time_steps_per_hour / (length(loads_kw)/8760)))
@info "Repeating electric loads in each hour to match the time_steps_per_hour."
@warn "Repeating electric loads in each hour to match the time_steps_per_hour."
end

if isnothing(critical_loads_kw)
Expand Down Expand Up @@ -500,7 +499,7 @@ function BuiltInElectricLoad(
),
)
if !(buildingtype in default_buildings)
error("buildingtype $(buildingtype) not in $(default_buildings).")
throw(@error("buildingtype $(buildingtype) not in $(default_buildings)."))
end

if isempty(city)
Expand Down
Loading

0 comments on commit 02e3be4

Please sign in to comment.