Skip to content

Commit

Permalink
Merge branch 'develop' into reopt_hpc
Browse files Browse the repository at this point in the history
  • Loading branch information
rathod-b committed Jun 7, 2023
2 parents a4ba506 + 4155a22 commit 7a727ec
Show file tree
Hide file tree
Showing 56 changed files with 19,851 additions and 653 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build_docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@latest
with:
version: '1.7'
version: '1.8'
- name: Install dependencies
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
- name: Build and deploy
Expand Down
77 changes: 76 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,81 @@ Classify the change according to the following categories:
### Deprecated
### Removed

## v0.32.2
### Fixed
- Fixed bug in multiple PVs pv_to_location dictionary creation.
- Fixed bug in reporting of grid purchase results when multiple energy tiers are present.
- Fixed bug in TOU demand charge calculation when multiple demand tiers are present.

## v0.32.1
### Fixed
- In `backup_reliability.jl`:
- Check if generator input is a Vector instead of has length greater than 1
- Correct calculation of battery SOC adjustment in `fuel_use()` function
- Correct outage time step survival condition in `fuel_use()` function
- Add test to ensure `backup_reliability()` gives the same results for equivalent scenarios (1. battery only and 2. battery plus generator with no fuel) and that the survival probability decreases monotonically with outage duration
- Add test to ensure `backup_reliability()` gives the same results as `simulate_outages()` when operational availability inputs are 1, probability of failure to run is 0, and mean time to failure is a very large number.

## v0.32.0
### Fixed
- Fixed calculation of `wind_kw_ac_hourly` in `outagesim/outage_simulator.jl`
- Add a test of multiple outages that includes wind
- Add a timeout to PVWatts API call so that if it does not connect within 10 seconds, it will retry. It seems to always work on the first retry.

## v0.31.0
### Added
- Created and exported easiur_data function (returns health emissions costs and escalations) for the API to be able to call for it's easiur_costs endpoint
- Added docstrings for easiur_data and emissions_profiles

## v0.30.0
### Added
- `Generator` input **fuel_higher_heating_value_kwh_per_gal**, which defaults to the constant KWH_PER_GAL_DIESEL
### Changed
- Added more description to **production_factor_series inputs**
### Fixed
- Fixed spelling of degradation_fraction
- use push! instead of append() for array in core/cost_curve.jl
- Fixed calculation of batt_roundtrip_efficiency in outage_simulator.jl

## v0.29.0
### Added
- Add `CHP` `FuelUsed` and `FuelCost` modeling/tracking for stochastic/multi-outages
- Add `CHP` outputs for stochastic/multi-outages
### Changed
- Made outages output names not dynamic to allow integration into API
- Add missing units to outages results field names: **unserved_load_series_kw**, **unserved_load_per_outage_kwh**, **generator_fuel_used_per_outage_gal**
- Default `Financial` field **microgrid_upgrade_cost_fraction** to 0
- Add conditional logic to make `CHP.min_allowable_kw` 25% of `max_kw` if there is a conflicting relationship
- Iterate on calculating `CHP` heuristic size based on average heating load which is also used to set `max_kw` if not given: once `size_class` is determined, recalculate using the efficiency numbers for that `size_class`.
### Fixed
- Fix non-handling of cost-curve/segmented techs in stochastic outages
- Fix issues with `simulated_load.jl` monthly heating energy input to return the heating load profile

## v0.28.1
### Added
- `emissions_profiles` function, exported for external use as an endpoint in REopt_API for the webtool/UI

## v0.28.0
### Changed
- Changed Financial **breakeven_cost_of_emissions_reduction_per_tonnes_CO2** to **breakeven_cost_of_emissions_reduction_per_tonne_CO2**
- Changed `CHP.size_class` to start at 0 instead of 1, consistent with the API, and 0 represents the average of all `size_class`s
- Change `CHP.max_kw` to be based on either the heuristic sizing from average heating load (if heating) or peak electric load (if no heating, aka Prime Generator in the UI)
- The "big_number" for `max_kw` was causing the model to take forever to solve and some erroneous behavior; this is also consistent with the API to limit max_kw to a reasonable number
### Added
- Added previously missing Financial BAU outputs: **lifecycle_om_costs_before_tax**, **lifecycle_om_costs_after_tax**, **year_one_om_costs_before_tax**
### Fixed
- Fixed if statement to determing ElectricLoad "year" from && to ||, so that defaults to 2017 if any CRB input is used

## v0.27.0
### Added
- Energy Resilience Performance tool: capability to model limited reliability of backup generators and RE, and calculate survival probability metrics during power outages for a DER scenario
- Exported `backup_reliability` function to run the reliability based calculations
### Changed
- Changed `Generator` inputs **fuel_slope_gal_per_kwh** and **fuel_intercept_gal_per_hr** to **electric_efficiency_full_load** and **electric_efficiency_half_load** to represent the same fuel burn curve in a different way consistent with `CHP`

## Develop - 2023-02-22
## v0.26.0
### Added
- Added `has_stacktrace` boolean which is returned with error messages and indicates if error is of type which contains stacktrace
- Constraint on wind sizing based on Site.land_acres
- New Wind input **acres_per_kw**, defaults to 0.03
- Descriptions/help text for many inputs and outputs
Expand All @@ -39,6 +111,9 @@ Classify the change according to the following categories:
- Round Hot and Cold TES size result to 0 digits
- Use CoolProp to get water properties for Hot and Cold TES based on average of temperature inputs
### Fixed
- `Wind` evaluations with BAU - was temporarily broken because of an unconverted **year_one** -> **annual** expected name
- Fixed calculation of **year_one_coincident_peak_cost_before_tax** in `ElectricTariff` results to correctly calculate before-tax value. Previously, the after-tax value was being calculated for this field instead.
- Fixed `outage_simulator` to work with sub-hourly outage simulation scenarios
- Fixed a bug which threw an error when providing time-series thermal load inputs in a scenario inputs .json.
- Fixed calculation of ["Financial"]["lifecycle_om_costs_before_tax_bau"] (was previously showing after tax result)
- Added **bau_annual_emissions_tonnes_SO2** to the bau_outputs dict in results.jl and removed duplicate **bau_annual_emissions_tonnes_NOx** result
Expand Down
4 changes: 2 additions & 2 deletions 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.25.0"
version = "0.32.2"

[deps]
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"
Expand Down Expand Up @@ -36,5 +36,5 @@ LinDistFlow = "0.1, 0.2"
MathOptInterface = "0.9, 0.10, 1"
Requires = "1.3"
Roots = "1.3, 2"
TestEnv = "1.7"
TestEnv = "1.7, 1.8, 1.9"
julia = "1.4, 1.5, 1.6, 1.7, 1.8"
2 changes: 1 addition & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ REopt = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6"

[compat]
Documenter = "0.26"
julia = "1.6, 1.7"
julia = "1.6, 1.7, 1.8"
5 changes: 5 additions & 0 deletions docs/src/reopt/methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ build_reopt!
## simulate_outages
```@docs
simulate_outages
```

## backup_reliability
```@docs
backup_reliability
```
5 changes: 4 additions & 1 deletion src/REopt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ export
get_chp_defaults_prime_mover_size_class,
get_steam_turbine_defaults_size_class,
simulated_load,
get_absorption_chiller_defaults
get_absorption_chiller_defaults,
emissions_profiles,
easiur_data

import HTTP
import JSON
Expand All @@ -71,6 +73,7 @@ global hdl = nothing
using JLD
using Requires
using CoolProp
using LinearAlgebra
using CSV
using DataFrames

Expand Down
2 changes: 1 addition & 1 deletion src/constraints/electric_utility_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ function add_elec_utility_expressions(m, p; _n="")
if !isempty(p.s.electric_tariff.tou_demand_rates)
m[Symbol("DemandTOUCharges"*_n)] = @expression(m,
p.pwf_e * sum( p.s.electric_tariff.tou_demand_rates[r] * m[Symbol("dvPeakDemandTOU"*_n)][r, tier]
for r in p.ratchets, tier in p.s.electric_tariff.n_tou_demand_tiers)
for r in p.ratchets, tier in 1:p.s.electric_tariff.n_tou_demand_tiers)
)
else
m[Symbol("DemandTOUCharges"*_n)] = 0
Expand Down
11 changes: 8 additions & 3 deletions src/constraints/generator_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,19 @@
# OF THE POSSIBILITY OF SUCH DAMAGE.
# *********************************************************************************
function add_fuel_burn_constraints(m,p)
fuel_slope_gal_per_kwhe, fuel_intercept_gal_per_hr = generator_fuel_slope_and_intercept(
electric_efficiency_full_load=p.s.generator.electric_efficiency_full_load,
electric_efficiency_half_load=p.s.generator.electric_efficiency_half_load,
fuel_higher_heating_value_kwh_per_gal=p.s.generator.fuel_higher_heating_value_kwh_per_gal
)
@constraint(m, [t in p.techs.gen, ts in p.time_steps],
m[:dvFuelUsage][t, ts] == (p.s.generator.fuel_slope_gal_per_kwh * KWH_PER_GAL_DIESEL *
m[:dvFuelUsage][t, ts] == (fuel_slope_gal_per_kwhe * p.s.generator.fuel_higher_heating_value_kwh_per_gal *
p.production_factor[t, ts] * p.hours_per_time_step * m[:dvRatedProduction][t, ts]) +
(p.s.generator.fuel_intercept_gal_per_hr * KWH_PER_GAL_DIESEL * p.hours_per_time_step * m[:binGenIsOnInTS][t, ts])
(fuel_intercept_gal_per_hr * p.s.generator.fuel_higher_heating_value_kwh_per_gal * p.hours_per_time_step * m[:binGenIsOnInTS][t, ts])
)
@constraint(m,
sum(m[:dvFuelUsage][t, ts] for t in p.techs.gen, ts in p.time_steps) <=
p.s.generator.fuel_avail_gal * KWH_PER_GAL_DIESEL
p.s.generator.fuel_avail_gal * p.s.generator.fuel_higher_heating_value_kwh_per_gal
)
end

Expand Down
115 changes: 102 additions & 13 deletions src/constraints/outage_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,22 @@ function add_outage_cost_constraints(m,p)
@expression(m, ExpectedOutageCost,
sum(m[:dvMaxOutageCost][s] * p.s.electric_utility.outage_probabilities[s] for s in p.s.electric_utility.scenarios)
)

if !isempty(setdiff(p.techs.elec, p.techs.segmented))
@constraint(m, [t in setdiff(p.techs.elec, p.techs.segmented)],
m[:binMGTechUsed][t] => {m[:dvMGTechUpgradeCost][t] >= p.s.financial.microgrid_upgrade_cost_fraction * p.third_party_factor *
p.cap_cost_slope[t] * m[:dvMGsize][t]}
)
end

@constraint(m, [t in p.techs.elec],
m[:binMGTechUsed][t] => {m[:dvMGTechUpgradeCost][t] >= p.s.financial.microgrid_upgrade_cost_fraction * p.third_party_factor *
p.cap_cost_slope[t] * m[:dvMGsize][t]}
)
if !isempty(p.techs.segmented)
@warn "Adding binary variable(s) to model cost curves in stochastic outages"
@constraint(m, [t in p.techs.segmented], # cannot have this for statement in sum( ... for t in ...) ???
m[:binMGTechUsed][t] => {m[:dvMGTechUpgradeCost][t] >= p.s.financial.microgrid_upgrade_cost_fraction * p.third_party_factor *
sum(p.cap_cost_slope[t][s] * m[Symbol("dvSegmentSystemSize"*t)][s] +
p.seg_yint[t][s] * m[Symbol("binSegment"*t)][s] for s in 1:p.n_segs_by_tech[t])}
)
end

@constraint(m,
m[:binMGStorageUsed] => {m[:dvMGStorageUpgradeCost] >= p.s.financial.microgrid_upgrade_cost_fraction * m[:TotalStorageCapCosts]}
Expand Down Expand Up @@ -111,12 +122,17 @@ function add_MG_production_constraints(m,p)
end


function add_MG_fuel_burn_constraints(m,p)
function add_MG_Gen_fuel_burn_constraints(m,p)
fuel_slope_gal_per_kwhe, fuel_intercept_gal_per_hr = generator_fuel_slope_and_intercept(
electric_efficiency_full_load=p.s.generator.electric_efficiency_full_load,
electric_efficiency_half_load=p.s.generator.electric_efficiency_half_load,
fuel_higher_heating_value_kwh_per_gal=p.s.generator.fuel_higher_heating_value_kwh_per_gal
)
# Define dvMGFuelUsed by summing over outage time_steps.
@constraint(m, [t in p.techs.gen, s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps],
m[:dvMGFuelUsed][t, s, tz] == p.s.generator.fuel_slope_gal_per_kwh * p.hours_per_time_step * p.levelization_factor[t] *
m[:dvMGFuelUsed][t, s, tz] == fuel_slope_gal_per_kwhe * p.hours_per_time_step * p.levelization_factor[t] *
sum( p.production_factor[t, tz+ts-1] * m[:dvMGRatedProduction][t, s, tz, ts] for ts in 1:p.s.electric_utility.outage_durations[s])
+ p.s.generator.fuel_intercept_gal_per_hr * p.hours_per_time_step *
+ fuel_intercept_gal_per_hr * p.hours_per_time_step *
sum( m[:binMGGenIsOnInTS][s, tz, ts] for ts in 1:p.s.electric_utility.outage_durations[s])
)

Expand All @@ -126,11 +142,11 @@ function add_MG_fuel_burn_constraints(m,p)
)

@constraint(m, [s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps],
m[:dvMGMaxFuelUsage][s] >= sum( m[:dvMGFuelUsed][t, s, tz] for t in p.techs.gen )
m[:dvMGGenMaxFuelUsage][s] >= sum( m[:dvMGFuelUsed][t, s, tz] for t in p.techs.gen )
)

@expression(m, ExpectedMGFuelUsed,
sum( m[:dvMGMaxFuelUsage][s] * p.s.electric_utility.outage_probabilities[s] for s in p.s.electric_utility.scenarios )
@expression(m, ExpectedMGGenFuelUsed,
sum( m[:dvMGGenMaxFuelUsage][s] * p.s.electric_utility.outage_probabilities[s] for s in p.s.electric_utility.scenarios )
)

# fuel cost = gallons * $/gal for each tech, outage
Expand All @@ -139,14 +155,71 @@ function add_MG_fuel_burn_constraints(m,p)
)

@constraint(m, [s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps],
m[:dvMGMaxFuelCost][s] >= sum( MGFuelCost[t, s, tz] for t in p.techs.gen )
m[:dvMGGenMaxFuelCost][s] >= sum( MGFuelCost[t, s, tz] for t in p.techs.gen )
)

@expression(m, ExpectedMGFuelCost,
sum( m[:dvMGMaxFuelCost][s] * p.s.electric_utility.outage_probabilities[s] for s in p.s.electric_utility.scenarios )
@expression(m, ExpectedMGGenFuelCost,
sum( m[:dvMGGenMaxFuelCost][s] * p.s.electric_utility.outage_probabilities[s] for s in p.s.electric_utility.scenarios )
)

m[:ExpectedMGFuelCost] += ExpectedMGGenFuelCost
end

function add_MG_CHP_fuel_burn_constraints(m, p; _n="")
# Fuel burn slope and intercept
fuel_burn_full_load = 1.0 / p.s.chp.electric_efficiency_full_load # [kWt/kWe]
fuel_burn_half_load = 0.5 / p.s.chp.electric_efficiency_half_load # [kWt/kWe]
fuel_burn_slope = (fuel_burn_full_load - fuel_burn_half_load) / (1.0 - 0.5) # [kWt/kWe]
fuel_burn_intercept = fuel_burn_full_load - fuel_burn_slope * 1.0 # [kWt/kWe_rated]

# Conditionally add dvFuelBurnYIntercept if coefficient p.FuelBurnYIntRate is greater than ~zero
if fuel_burn_intercept > 1.0E-7
#Constraint (1c1): Total Fuel burn for CHP **with** y-intercept fuel burn and supplementary firing
@constraint(m, MGCHPFuelBurnCon[t in p.techs.chp, s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps],
m[Symbol("dvMGFuelUsed"*_n)][t,s,tz] == p.hours_per_time_step * (
m[Symbol("dvMGCHPFuelBurnYIntercept"*_n)][s,tz] +
sum(p.production_factor[t,tz+ts-1] * fuel_burn_slope * m[Symbol("dvMGRatedProduction"*_n)][t,s,tz,ts]
for ts in 1:p.s.electric_utility.outage_durations[s]))
)

#Constraint (1d): Y-intercept fuel burn for CHP across the scenario outage time steps
@constraint(m, MGCHPFuelBurnYIntCon[t in p.techs.chp, s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps],
m[Symbol("binMGCHPIsOnInTS"*_n)][s,tz,ts] =>
{m[Symbol("dvMGCHPFuelBurnYIntercept"*_n)][s,tz] >= sum(fuel_burn_intercept * m[Symbol("dvMGsize"*_n)][t]
for _ in 1:p.s.electric_utility.outage_durations[s])}
)
else
#Constraint (1c2): Total Fuel burn for CHP **without** y-intercept fuel burn
@constraint(m, MGCHPFuelBurnConLinear[t in p.techs.chp, s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps],
m[Symbol("dvMGFuelUsed"*_n)][t,s,tz] == p.hours_per_time_step *
sum(p.production_factor[t,tz+ts-1] * fuel_burn_slope * m[Symbol("dvMGRatedProduction"*_n)][t,s,tz,ts]
for ts in 1:p.s.electric_utility.outage_durations[s])
)
end

@constraint(m, [s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps],
m[:dvMGCHPMaxFuelUsage][s] >= sum( m[:dvMGFuelUsed][t, s, tz] for t in p.techs.chp )
)

@expression(m, ExpectedMGCHPFuelUsed,
sum( m[:dvMGCHPMaxFuelUsage][s] * p.s.electric_utility.outage_probabilities[s] for s in p.s.electric_utility.scenarios )
)

# fuel cost = kWh * $/kWh
@expression(m, MGCHPFuelCost[t in p.techs.chp, s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps],
m[:dvMGFuelUsed][t, s, tz] * p.fuel_cost_per_kwh[t][tz]
)

@constraint(m, [s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps],
m[:dvMGCHPMaxFuelCost][s] >= sum( MGCHPFuelCost[t, s, tz] for t in p.techs.chp )
)

@expression(m, ExpectedMGCHPFuelCost,
sum( m[:dvMGCHPMaxFuelCost][s] * p.s.electric_utility.outage_probabilities[s] for s in p.s.electric_utility.scenarios )
)

m[:ExpectedMGFuelCost] += ExpectedMGCHPFuelCost
end

function add_binMGGenIsOnInTS_constraints(m,p)
# The following 2 constraints define binMGGenIsOnInTS to be the binary corollary to dvMGRatedProd for generator,
Expand All @@ -165,6 +238,22 @@ function add_binMGGenIsOnInTS_constraints(m,p)
# TODO? make binMGGenIsOnInTS indexed on p.techs.gen
end

function add_binMGCHPIsOnInTS_constraints(m, p; _n="")
# The following 2 constraints define binMGCHPIsOnInTS to be the binary corollary to dvMGRatedProd for CHP,
# i.e. binMGCHPIsOnInTS = 1 for dvMGRatedProd > min_turn_down_fraction * dvMGsize, and binMGCHPIsOnInTS = 0 for dvMGRatedProd = 0
@constraint(m, [t in p.techs.chp, s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps, ts in p.s.electric_utility.outage_time_steps],
!m[:binMGCHPIsOnInTS][s, tz, ts] => { m[:dvMGRatedProduction][t, s, tz, ts] <= 0 }
)
@constraint(m, [t in p.techs.chp, s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps, ts in p.s.electric_utility.outage_time_steps],
m[:binMGCHPIsOnInTS][s, tz, ts] => {
m[:dvMGRatedProduction][t, s, tz, ts] >= p.s.chp.min_turn_down_fraction * m[:dvMGsize][t]
}
)
@constraint(m, [t in p.techs.chp, s in p.s.electric_utility.scenarios, tz in p.s.electric_utility.outage_start_time_steps, ts in p.s.electric_utility.outage_time_steps],
m[:binMGTechUsed][t] >= m[:binMGCHPIsOnInTS][s, tz, ts]
)
# TODO? make binMGCHPIsOnInTS indexed on p.techs.chp
end

function add_MG_storage_dispatch_constraints(m,p)
# initial SOC at start of each outage equals the grid-optimal SOC
Expand Down
Loading

0 comments on commit 7a727ec

Please sign in to comment.