Skip to content

Commit d736bb2

Browse files
authored
Merge pull request #587 from NREL/develop
REopt.jl v0.47.1
2 parents 4456bcf + 539d481 commit d736bb2

7 files changed

+165
-10
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@ Classify the change according to the following categories:
2626
##### Removed
2727
### Patches
2828

29+
## v3.9.1
30+
### Minor Updates
31+
#### Added
32+
- Added `ProcessHeatLoadInputs` for new ways to input `ProcessHeatLoad`, similar to other loads
33+
#### Fixed
34+
- See fixes and changes here: https://github.com/NREL/REopt.jl/releases/tag/v0.47.0
35+
2936
## v3.9.0
3037
### Minor Updates
3138
#### Added

julia_src/Manifest.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -917,9 +917,9 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
917917

918918
[[deps.REopt]]
919919
deps = ["ArchGDAL", "CSV", "CoolProp", "DataFrames", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"]
920-
git-tree-sha1 = "3c40f3939f79c3f66df69e9acc503fef614cdd63"
920+
git-tree-sha1 = "b51d56a6398f302100004184b64bbe3d1e137277"
921921
uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6"
922-
version = "0.46.1"
922+
version = "0.47.1"
923923

924924
[[deps.Random]]
925925
deps = ["SHA"]

reoptjl/api.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def obj_create(self, bundle, **kwargs):
9898
meta = {
9999
"run_uuid": run_uuid,
100100
"api_version": 3,
101-
"reopt_version": "0.45.0",
101+
"reopt_version": "0.47.1",
102102
"status": "Validating..."
103103
}
104104
bundle.data.update({"APIMeta": meta})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Generated by Django 4.0.7 on 2024-06-01 20:15
2+
3+
import django.contrib.postgres.fields
4+
import django.core.validators
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('reoptjl', '0059_processheatloadinputs_and_more'),
12+
]
13+
14+
operations = [
15+
migrations.AddField(
16+
model_name='processheatloadinputs',
17+
name='addressable_load_fraction',
18+
field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)]), blank=True, default=list, help_text='Fraction of input fuel load which is addressable by heating technologies (default is 1.0).Can be a scalar or vector with length aligned with use of monthly_mmbtu (12) or fuel_loads_mmbtu_per_hour.', size=None),
19+
),
20+
migrations.AddField(
21+
model_name='processheatloadinputs',
22+
name='blended_industry_reference_names',
23+
field=django.contrib.postgres.fields.ArrayField(base_field=models.TextField(blank=True, choices=[('Chemical', 'Chemical'), ('Warehouse', 'Warehouse'), ('FlatLoad', 'Flatload'), ('FlatLoad_24_5', 'Flatload 24 5'), ('FlatLoad_16_7', 'Flatload 16 7'), ('FlatLoad_16_5', 'Flatload 16 5'), ('FlatLoad_8_7', 'Flatload 8 7'), ('FlatLoad_8_5', 'Flatload 8 5')], null=True), blank=True, default=list, help_text='Used in concert with blended_industry_reference_percents to create a blended load profile from multiple Industrial reference facility/sector types.', size=None),
24+
),
25+
migrations.AddField(
26+
model_name='processheatloadinputs',
27+
name='blended_industry_reference_percents',
28+
field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1.0)]), blank=True, default=list, help_text='Used in concert with blended_industry_reference_names to create a blended load profile from multiple Industrial reference facility/sector types. Must sum to 1.0.', size=None),
29+
),
30+
migrations.AddField(
31+
model_name='processheatloadinputs',
32+
name='industry_reference_name',
33+
field=models.TextField(blank=True, choices=[('Chemical', 'Chemical'), ('Warehouse', 'Warehouse'), ('FlatLoad', 'Flatload'), ('FlatLoad_24_5', 'Flatload 24 5'), ('FlatLoad_16_7', 'Flatload 16 7'), ('FlatLoad_16_5', 'Flatload 16 5'), ('FlatLoad_8_7', 'Flatload 8 7'), ('FlatLoad_8_5', 'Flatload 8 5')], help_text='Industrial process heat load reference facility/sector type', null=True),
34+
),
35+
migrations.AddField(
36+
model_name='processheatloadinputs',
37+
name='monthly_mmbtu',
38+
field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100000000.0)]), blank=True, default=list, help_text="Monthly site process heat fuel consumption in [MMbtu], used to scale simulated default building load profile for the site's climate zone", size=None),
39+
),
40+
migrations.AlterField(
41+
model_name='absorptionchillerinputs',
42+
name='heating_load_input',
43+
field=models.TextField(blank=True, choices=[('DomesticHotWater', 'Domestichotwater'), ('SpaceHeating', 'Spaceheating'), ('ProcessHeat', 'Processheat')], help_text='Absorption chiller heat input - determines what heating load is added to by absorption chiller use', null=True),
44+
),
45+
migrations.AlterField(
46+
model_name='processheatloadinputs',
47+
name='annual_mmbtu',
48+
field=models.FloatField(blank=True, help_text='Annual site process heat fuel consumption, used to scale simulated default industry load profile [MMBtu]', null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(100000000.0)]),
49+
),
50+
migrations.AlterField(
51+
model_name='processheatloadinputs',
52+
name='fuel_loads_mmbtu_per_hour',
53+
field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True), blank=True, default=list, help_text='Vector of process heat fuel loads [mmbtu/hr] over one year. Must be hourly (8,760 samples), 30 minute (17,520 samples), or 15 minute (35,040 samples). All non-net load values must be greater than or equal to zero. ', size=None),
54+
),
55+
]

reoptjl/models.py

+97-5
Original file line numberDiff line numberDiff line change
@@ -6384,19 +6384,53 @@ class ProcessHeatLoadInputs(BaseModel, models.Model):
63846384

63856385
possible_sets = [
63866386
["fuel_loads_mmbtu_per_hour"],
6387-
["annual_mmbtu"],
6388-
[],
6387+
["industry_reference_name", "monthly_mmbtu"],
6388+
["annual_mmbtu", "industry_reference_name"],
6389+
["industry_reference_name"],
6390+
["blended_industry_reference_names", "blended_industry_reference_percents"],
6391+
[]
63896392
]
63906393

6394+
INDUSTRY_REFERENCE_NAME = models.TextChoices('INDUSTRY_REFERENCE_NAME', (
6395+
'Chemical '
6396+
'Warehouse '
6397+
'FlatLoad '
6398+
'FlatLoad_24_5 '
6399+
'FlatLoad_16_7 '
6400+
'FlatLoad_16_5 '
6401+
'FlatLoad_8_7 '
6402+
'FlatLoad_8_5'
6403+
))
6404+
63916405
annual_mmbtu = models.FloatField(
63926406
validators=[
63936407
MinValueValidator(1),
63946408
MaxValueValidator(MAX_BIG_NUMBER)
63956409
],
63966410
null=True,
63976411
blank=True,
6398-
help_text=("Annual site process heat consumption, used "
6399-
"to scale simulated load profile [MMBtu]")
6412+
help_text=("Annual site process heat fuel consumption, used "
6413+
"to scale simulated default industry load profile [MMBtu]")
6414+
)
6415+
6416+
industry_reference_name = models.TextField(
6417+
null=True,
6418+
blank=True,
6419+
choices=INDUSTRY_REFERENCE_NAME.choices,
6420+
help_text=("Industrial process heat load reference facility/sector type")
6421+
)
6422+
6423+
monthly_mmbtu = ArrayField(
6424+
models.FloatField(
6425+
validators=[
6426+
MinValueValidator(0),
6427+
MaxValueValidator(MAX_BIG_NUMBER)
6428+
],
6429+
blank=True
6430+
),
6431+
default=list, blank=True,
6432+
help_text=("Monthly site process heat fuel consumption in [MMbtu], used "
6433+
"to scale simulated default building load profile for the site's climate zone")
64006434
)
64016435

64026436
fuel_loads_mmbtu_per_hour = ArrayField(
@@ -6405,11 +6439,50 @@ class ProcessHeatLoadInputs(BaseModel, models.Model):
64056439
),
64066440
default=list,
64076441
blank=True,
6408-
help_text=("Typical load over all hours in one year. Must be hourly (8,760 samples), 30 minute (17,"
6442+
help_text=("Vector of process heat fuel loads [mmbtu/hr] over one year. Must be hourly (8,760 samples), 30 minute (17,"
64096443
"520 samples), or 15 minute (35,040 samples). All non-net load values must be greater than or "
64106444
"equal to zero. "
64116445
)
6446+
)
6447+
6448+
blended_industry_reference_names = ArrayField(
6449+
models.TextField(
6450+
choices=INDUSTRY_REFERENCE_NAME.choices,
6451+
blank=True,
6452+
null=True
6453+
),
6454+
default=list,
6455+
blank=True,
6456+
help_text=("Used in concert with blended_industry_reference_percents to create a blended load profile from multiple "
6457+
"Industrial reference facility/sector types.")
6458+
)
64126459

6460+
blended_industry_reference_percents = ArrayField(
6461+
models.FloatField(
6462+
null=True, blank=True,
6463+
validators=[
6464+
MinValueValidator(0),
6465+
MaxValueValidator(1.0)
6466+
],
6467+
),
6468+
default=list,
6469+
blank=True,
6470+
help_text=("Used in concert with blended_industry_reference_names to create a blended load profile from multiple "
6471+
"Industrial reference facility/sector types. Must sum to 1.0.")
6472+
)
6473+
6474+
addressable_load_fraction = ArrayField(
6475+
models.FloatField(
6476+
validators=[
6477+
MinValueValidator(0),
6478+
MaxValueValidator(1.0)
6479+
],
6480+
blank=True
6481+
),
6482+
default=list,
6483+
blank=True,
6484+
help_text=( "Fraction of input fuel load which is addressable by heating technologies (default is 1.0)."
6485+
"Can be a scalar or vector with length aligned with use of monthly_mmbtu (12) or fuel_loads_mmbtu_per_hour.")
64136486
)
64146487

64156488
def clean(self):
@@ -6420,6 +6493,25 @@ def clean(self):
64206493
error_messages["required inputs"] = \
64216494
"Must provide at least one set of valid inputs from {}.".format(self.possible_sets)
64226495

6496+
if len(self.blended_industry_reference_names) > 0 and self.industry_reference_name == "":
6497+
if len(self.blended_industry_reference_names) != len(self.blended_industry_reference_percents):
6498+
error_messages["blended_industry_reference_names"] = \
6499+
"The number of blended_industry_reference_names must equal the number of blended_industry_reference_percents."
6500+
if not math.isclose(sum(self.blended_industry_reference_percents), 1.0):
6501+
error_messages["blended_industry_reference_percents"] = "Sum must = 1.0."
6502+
6503+
if self.industry_reference_name != "" or \
6504+
len(self.blended_industry_reference_names) > 0:
6505+
self.year = 2017 # the validator provides an "info" message regarding this)
6506+
6507+
if self.addressable_load_fraction == None:
6508+
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
6509+
6510+
# possible sets for defining load profile
6511+
if not at_least_one_set(self.dict, self.possible_sets):
6512+
error_messages["required inputs"] = \
6513+
"Must provide at least one set of valid inputs from {}.".format(self.possible_sets)
6514+
64236515
class HeatingLoadOutputs(BaseModel, models.Model):
64246516

64256517
key = "HeatingLoadOutputs"

reoptjl/test/posts/test_thermal_in_results.json

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"annual_mmbtu": 500.0
3939
},
4040
"ProcessHeatLoad": {
41+
"industry_reference_name": "FlatLoad",
4142
"annual_mmbtu": 100
4243
},
4344
"ExistingBoiler": {

reoptjl/test/test_http_endpoints.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ def test_simulated_load(self):
163163
inputs["annual_kwh"] = 1.5E7
164164
inputs["doe_reference_name[0]"] = "Hospital"
165165
inputs["doe_reference_name[1]"] = "LargeOffice"
166-
inputs["percent_share[0]"] = 25.0
167-
inputs["percent_share[1]"] = 100.0 - inputs["percent_share[0]"]
166+
inputs["percent_share[0]"] = 0.25
167+
inputs["percent_share[1]"] = 1.0 - inputs["percent_share[0]"]
168168

169169
# The /v3/simulated_load endpoint calls the http.jl /simulated_load endpoint
170170
response = self.api_client.get(f'/v3/simulated_load', data=inputs)

0 commit comments

Comments
 (0)