Skip to content

Commit

Permalink
Merge pull request #626 from NREL/develop
Browse files Browse the repository at this point in the history
REopt.jl v0.50.0: Load year alignment fixes and ASHP max dispatch
  • Loading branch information
Bill-Becker authored Jan 22, 2025
2 parents 9f9db70 + 259ce15 commit 21caef2
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 45 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ Classify the change according to the following categories:
##### Removed
### Patches

## v3.11.0
### Minor Updates
##### Changed
- Require `year` for all custom 8760/35040 load profile inputs
- Truncate the last day of the year instead of the leap day for leap years
##### Added
- Option for ASHP to `force_dispatch` (default = true) which maximizes ASHP thermal output

## v3.10.2
### Minor Updates
##### Changed
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Generated by Django 4.0.7 on 2025-01-14 21:50

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('ghpghx', '0018_ghpghxinputs_wwhp_cooling_pump_fluid_flow_rate_gpm_per_ton_and_more'),
]

operations = [
migrations.AlterField(
model_name='ghpghxinputs',
name='borehole_depth_ft',
field=models.FloatField(blank=True, default=443.0, help_text='Vertical depth of each borehole [ft]', validators=[django.core.validators.MinValueValidator(10.0), django.core.validators.MaxValueValidator(600.0)]),
),
migrations.AlterField(
model_name='ghpghxinputs',
name='borehole_diameter_inch',
field=models.FloatField(blank=True, default=6.0, help_text='Diameter of the borehole/well drilled in the ground [in]', validators=[django.core.validators.MinValueValidator(0.25), django.core.validators.MaxValueValidator(24.0)]),
),
migrations.AlterField(
model_name='ghpghxinputs',
name='ghx_header_depth_ft',
field=models.FloatField(blank=True, default=6.6, help_text='Depth under the ground of the GHX header pipe [ft]', validators=[django.core.validators.MinValueValidator(0.1), django.core.validators.MaxValueValidator(50.0)]),
),
migrations.AlterField(
model_name='ghpghxinputs',
name='ghx_pipe_thermal_conductivity_btu_per_hr_ft_f',
field=models.FloatField(blank=True, default=0.23, help_text='Thermal conductivity of the GHX pipe [Btu/(hr-ft-degF)]', validators=[django.core.validators.MinValueValidator(0.01), django.core.validators.MaxValueValidator(10.0)]),
),
migrations.AlterField(
model_name='ghpghxinputs',
name='ghx_shank_space_inch',
field=models.FloatField(blank=True, default=1.27, help_text='Distance between the centerline of the upwards and downwards u-tube legs [in]', validators=[django.core.validators.MinValueValidator(0.5), django.core.validators.MaxValueValidator(100.0)]),
),
migrations.AlterField(
model_name='ghpghxinputs',
name='grout_thermal_conductivity_btu_per_hr_ft_f',
field=models.FloatField(blank=True, default=0.75, help_text='Thermal conductivity of the grout material in a borehole [Btu/(hr-ft-degF)]', validators=[django.core.validators.MinValueValidator(0.01), django.core.validators.MaxValueValidator(10.0)]),
),
]
4 changes: 2 additions & 2 deletions julia_src/Manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -929,9 +929,9 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"

[[deps.REopt]]
deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"]
git-tree-sha1 = "67eade881254923d75cf80aac40ca8da2f17351e"
git-tree-sha1 = "324394f21cb7e2db3d9e7ebde19c4e83c5a64e0f"
uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6"
version = "0.49.1"
version = "0.50.0"

[[deps.Random]]
deps = ["SHA"]
Expand Down
39 changes: 39 additions & 0 deletions reoptjl/migrations/0075_coolingloadinputs_year_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Generated by Django 4.0.7 on 2025-01-14 19:46

import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('reoptjl', '0074_alter_domestichotwaterloadinputs_normalize_and_scale_load_profile_input_and_more'),
]

operations = [
migrations.AddField(
model_name='coolingloadinputs',
name='year',
field=models.IntegerField(blank=True, help_text="Year of Custom Load Profile. If a custom load profile is uploaded via the thermal_loads_ton parameter, it is important that this year correlates with the electric load profile so that weekdays/weekends are determined correctly for the utility rate tariff. If a DOE Reference Building profile (aka 'simulated' profile) is used, the year is set to 2017 since the DOE profiles start on a Sunday.", null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(9999)]),
),
migrations.AlterField(
model_name='domestichotwaterloadinputs',
name='year',
field=models.IntegerField(blank=True, help_text="Year of Custom Load Profile. If a custom load profile is uploaded via the fuel_loads_mmbtu_per_hour parameter, it is important that this year correlates with the electric load profile so that weekdays/weekends are determined correctly for the utility rate tariff. If a DOE Reference Building profile (aka 'simulated' profile) is used, the year is set to 2017 since the DOE profiles start on a Sunday.", null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(9999)]),
),
migrations.AlterField(
model_name='electricloadinputs',
name='year',
field=models.IntegerField(blank=True, help_text="Year of Custom Load Profile. If a custom load profile is uploaded via the loads_kw parameter, it is important that this year correlates with the load profile so that weekdays/weekends are determined correctly for the utility rate tariff. If a DOE Reference Building profile (aka 'simulated' profile) is used, the year is set to 2017 since the DOE profiles start on a Sunday.", null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(9999)]),
),
migrations.AlterField(
model_name='processheatloadinputs',
name='year',
field=models.IntegerField(blank=True, help_text="Year of Custom Load Profile. If a custom load profile is uploaded via the fuel_loads_mmbtu_per_hour parameter, it is important that this year correlates with the electric load profile so that weekdays/weekends are determined correctly for the utility rate tariff. If a Industrial Reference Building profile (aka 'simulated' profile) is used, the year is set to 2017 to be consistent with the DOE reference building year which starts on a Sunday.", null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(9999)]),
),
migrations.AlterField(
model_name='spaceheatingloadinputs',
name='year',
field=models.IntegerField(blank=True, help_text="Year of Custom Load Profile. If a custom load profile is uploaded via the fuel_loads_mmbtu_per_hour parameter, it is important that this year correlates with the electric load profile so that weekdays/weekends are determined correctly for the utility rate tariff. If a DOE Reference Building profile (aka 'simulated' profile) is used, the year is set to 2017 since the DOE profiles start on a Sunday.", null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(9999)]),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Generated by Django 4.0.7 on 2025-01-18 04:04

import django.contrib.postgres.fields
import django.core.validators
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('reoptjl', '0075_coolingloadinputs_year_and_more'),
]

operations = [
migrations.AddField(
model_name='ashpspaceheaterinputs',
name='force_dispatch',
field=models.BooleanField(default=True, help_text='Boolean indicator that ASHP space heater outputs either maximum capacity or site load if true', null=True),
),
migrations.AddField(
model_name='ashpwaterheaterinputs',
name='force_dispatch',
field=models.BooleanField(default=True, help_text='Boolean indicator that ASHP water heater outputs either maximum capacity or site load if true', null=True),
),
migrations.AlterField(
model_name='ashpwaterheaterinputs',
name='force_into_system',
field=models.BooleanField(blank=True, help_text='Boolean indicator if ASHP water heater serves compatible thermal loads exclusively in optimized scenario', null=True),
),
migrations.AlterField(
model_name='ashpwaterheaterinputs',
name='heating_cf_reference',
field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(20.0)]), blank=True, default=list, help_text='Reference points for ASHP water heating system heating capacity factor(ratio of heating thermal power to rated capacity)', size=None),
),
migrations.AlterField(
model_name='ashpwaterheaterinputs',
name='heating_cop_reference',
field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(20.0)]), blank=True, default=list, help_text='Reference points for ASHP water heating system heating coefficient of performance (COP) (ratio of usable heating thermal energy produced per unit electric energy consumed)', size=None),
),
migrations.AlterField(
model_name='ashpwaterheaterinputs',
name='heating_reference_temps_degF',
field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(-275.0), django.core.validators.MaxValueValidator(200.0)]), blank=True, default=list, help_text="Reference temperatures for ASHP water heating system's heating COP and CF [Fahrenheit]", size=None),
),
]
73 changes: 32 additions & 41 deletions reoptjl/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1213,7 +1213,6 @@ class ElectricLoadInputs(BaseModel, models.Model):
"https://energy.gov/eere/buildings/commercial-reference-buildings")
)
year = models.IntegerField(
default=2022,
validators=[
MinValueValidator(1),
MaxValueValidator(9999)
Expand Down Expand Up @@ -4664,6 +4663,18 @@ class CoolingLoadInputs(BaseModel, models.Model):
)
)

year = models.IntegerField(
validators=[
MinValueValidator(1),
MaxValueValidator(9999)
],
null=True, blank=True,
help_text=("Year of Custom Load Profile. If a custom load profile is uploaded via the thermal_loads_ton parameter, it "
"is important that this year correlates with the electric load profile so that weekdays/weekends are "
"determined correctly for the utility rate tariff. If a DOE Reference Building profile (aka "
"'simulated' profile) is used, the year is set to 2017 since the DOE profiles start on a Sunday.")
)

annual_fraction_of_electric_load = models.FloatField(
validators=[
MinValueValidator(0),
Expand Down Expand Up @@ -4714,10 +4725,6 @@ def clean(self):
"The number of blended_doe_reference_names must equal the number of blended_doe_reference_percents."
if not math.isclose(sum(self.blended_doe_reference_percents), 1.0):
error_messages["blended_doe_reference_percents"] = "Sum must = 1.0."

if self.doe_reference_name != "" or \
len(self.blended_doe_reference_names) > 0:
self.year = 2017 # the validator provides an "info" message regarding this)

if len(self.monthly_fractions_of_electric_load) > 0:
if len(self.monthly_fractions_of_electric_load) != 12:
Expand Down Expand Up @@ -5470,6 +5477,12 @@ class ASHPSpaceHeaterInputs(BaseModel, models.Model):
help_text="Boolean indicator if ASHP space heater serves compatible thermal loads exclusively in optimized scenario"
)

force_dispatch = models.BooleanField(
null=True,
default=True,
help_text="Boolean indicator that ASHP space heater outputs either maximum capacity or site load if true"
)

avoided_capex_by_ashp_present_value = models.FloatField(
validators=[
MinValueValidator(0),
Expand Down Expand Up @@ -5701,7 +5714,7 @@ class ASHPWaterHeaterInputs(BaseModel, models.Model):
),
default=list,
blank=True,
help_text=(("Reference points for ASHP space heating system heating coefficient of performance (COP) "
help_text=(("Reference points for ASHP water heating system heating coefficient of performance (COP) "
"(ratio of usable heating thermal energy produced per unit electric energy consumed)"))
)

Expand All @@ -5715,7 +5728,7 @@ class ASHPWaterHeaterInputs(BaseModel, models.Model):
),
default=list,
blank=True,
help_text=(("Reference points for ASHP space heating system heating capac)ity factor"
help_text=(("Reference points for ASHP water heating system heating capacity factor"
"(ratio of heating thermal power to rated capacity)"))
)

Expand All @@ -5729,7 +5742,7 @@ class ASHPWaterHeaterInputs(BaseModel, models.Model):
),
default=list,
blank=True,
help_text=(("Reference temperatures for ASHP space heating system's heating COP and CF [Fahrenheit]"))
help_text=(("Reference temperatures for ASHP water heating system's heating COP and CF [Fahrenheit]"))
)

avoided_capex_by_ashp_present_value = models.FloatField(
Expand All @@ -5756,13 +5769,19 @@ class ASHPWaterHeaterInputs(BaseModel, models.Model):
force_into_system = models.BooleanField(
null=True,
blank=True,
help_text="Boolean indicator if ASHP space heater serves compatible thermal loads exclusively in optimized scenario"
help_text="Boolean indicator if ASHP water heater serves compatible thermal loads exclusively in optimized scenario"
)

force_dispatch = models.BooleanField(
null=True,
default=True,
help_text="Boolean indicator that ASHP water heater outputs either maximum capacity or site load if true"
)

def clean(self):
error_messages = {}
if self.dict.get("min_allowable_ton") in [None, "", []] and self.dict.get("min_allowable_peak_capacity_fraction") in [None, "", []]:
self.min_allowable_peak_capacity_fraction = 0.5
self.min_allowable_peak_capacity_fraction = 0.25

if self.dict.get("min_allowable_ton") not in [None, "", []] and self.dict.get("min_allowable_peak_capacity_fraction") not in [None, "", []]:
error_messages["bad inputs"] = "At most one of min_allowable_ton and min_allowable_peak_capacity_fraction may be input to model {}".format(self.key)
Expand Down Expand Up @@ -6908,7 +6927,6 @@ class SpaceHeatingLoadInputs(BaseModel, models.Model):
)

year = models.IntegerField(
default=2022,
validators=[
MinValueValidator(1),
MaxValueValidator(9999)
Expand Down Expand Up @@ -6979,10 +6997,6 @@ def clean(self):
"The number of blended_doe_reference_names must equal the number of blended_doe_reference_percents."
if not math.isclose(sum(self.blended_doe_reference_percents), 1.0):
error_messages["blended_doe_reference_percents"] = "Sum must = 1.0."

if self.doe_reference_name != "" or \
len(self.blended_doe_reference_names) > 0:
self.year = 2017 # the validator provides an "info" message regarding this)

if self.addressable_load_fraction == None:
self.addressable_load_fraction = list([1.0]) # should not convert to timeseries, in case it is to be used with monthly_mmbtu or annual_mmbtu
Expand Down Expand Up @@ -7088,7 +7102,6 @@ class DomesticHotWaterLoadInputs(BaseModel, models.Model):
)

year = models.IntegerField(
default=2022,
validators=[
MinValueValidator(1),
MaxValueValidator(9999)
Expand Down Expand Up @@ -7159,10 +7172,6 @@ def clean(self):
"The number of blended_doe_reference_names must equal the number of blended_doe_reference_percents."
if not math.isclose(sum(self.blended_doe_reference_percents), 1.0):
error_messages["blended_doe_reference_percents"] = "Sum must = 1.0."

if self.doe_reference_name != "" or \
len(self.blended_doe_reference_names) > 0:
self.year = 2017 # the validator provides an "info" message regarding this)

if self.addressable_load_fraction == None:
self.addressable_load_fraction = list([1.0]) # should not convert to timeseries, in case it is to be used with monthly_mmbtu or annual_mmbtu
Expand Down Expand Up @@ -7242,36 +7251,22 @@ class ProcessHeatLoadInputs(BaseModel, models.Model):
)

year = models.IntegerField(
default=2022,
validators=[
MinValueValidator(1),
MaxValueValidator(9999)
],
null=True, blank=True,
help_text=("Year of Custom Load Profile. If a custom load profile is uploaded via the fuel_loads_mmbtu_per_hour parameter, it "
"is important that this year correlates with the electric load profile so that weekdays/weekends are "
"determined correctly for the utility rate tariff. If a DOE Reference Building profile (aka "
"'simulated' profile) is used, the year is set to 2017 since the DOE profiles start on a Sunday.")
"determined correctly for the utility rate tariff. If a Industrial Reference Building profile (aka "
"'simulated' profile) is used, the year is set to 2017 to be consistent with the DOE reference building year which starts on a Sunday.")
)

normalize_and_scale_load_profile_input = models.BooleanField(
blank=True,
default=False,
help_text=("Takes the input fuel_loads_mmbtu_per_hour and normalizes and scales it to annual or monthly energy inputs.")
)

year = models.IntegerField(
default=2022,
validators=[
MinValueValidator(1),
MaxValueValidator(9999)
],
null=True, blank=True,
help_text=("Year of Custom Load Profile. If a custom load profile is uploaded via the fuel_loads_mmbtu_per_hour parameter, it "
"is important that this year correlates with the electric load profile so that weekdays/weekends are "
"determined correctly for the utility rate tariff. If a DOE Reference Building profile (aka "
"'simulated' profile) is used, the year is set to 2017 since the DOE profiles start on a Sunday.")
)
)

blended_industrial_reference_names = ArrayField(
models.TextField(
Expand Down Expand Up @@ -7327,10 +7322,6 @@ def clean(self):
"The number of blended_industrial_reference_names must equal the number of blended_industrial_reference_percents."
if not math.isclose(sum(self.blended_industrial_reference_percents), 1.0):
error_messages["blended_industrial_reference_percents"] = "Sum must = 1.0."

if self.industrial_reference_name != "" or \
len(self.blended_industrial_reference_names) > 0:
self.year = 2017 # the validator provides an "info" message regarding this)

if self.addressable_load_fraction == None:
self.addressable_load_fraction = list([1.0]) # should not convert to timeseries, in case it is to be used with monthly_mmbtu or annual_mmbtu
Expand Down
Loading

0 comments on commit 21caef2

Please sign in to comment.