Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ Classify the change according to the following categories:
##### Removed
### Patches

## v3.9.1
### Minor Updates
#### Added
- Added `ProcessHeatLoadInputs` for new ways to input `ProcessHeatLoad`, similar to other loads
#### Fixed
- See fixes and changes here: https://github.com/NREL/REopt.jl/releases/tag/v0.47.0

## v3.9.0
### Minor Updates
#### Added
Expand Down
4 changes: 2 additions & 2 deletions julia_src/Manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -917,9 +917,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 = "3c40f3939f79c3f66df69e9acc503fef614cdd63"
git-tree-sha1 = "b51d56a6398f302100004184b64bbe3d1e137277"
uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6"
version = "0.46.1"
version = "0.47.1"

[[deps.Random]]
deps = ["SHA"]
Expand Down
2 changes: 1 addition & 1 deletion reoptjl/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ def obj_create(self, bundle, **kwargs):
meta = {
"run_uuid": run_uuid,
"api_version": 3,
"reopt_version": "0.45.0",
"reopt_version": "0.47.1",
"status": "Validating..."
}
bundle.data.update({"APIMeta": meta})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Generated by Django 4.0.7 on 2024-06-01 20:15

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


class Migration(migrations.Migration):

dependencies = [
('reoptjl', '0059_processheatloadinputs_and_more'),
]

operations = [
migrations.AddField(
model_name='processheatloadinputs',
name='addressable_load_fraction',
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),
),
migrations.AddField(
model_name='processheatloadinputs',
name='blended_industry_reference_names',
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),
),
migrations.AddField(
model_name='processheatloadinputs',
name='blended_industry_reference_percents',
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),
),
migrations.AddField(
model_name='processheatloadinputs',
name='industry_reference_name',
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),
),
migrations.AddField(
model_name='processheatloadinputs',
name='monthly_mmbtu',
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),
),
migrations.AlterField(
model_name='absorptionchillerinputs',
name='heating_load_input',
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),
),
migrations.AlterField(
model_name='processheatloadinputs',
name='annual_mmbtu',
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)]),
),
migrations.AlterField(
model_name='processheatloadinputs',
name='fuel_loads_mmbtu_per_hour',
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),
),
]
102 changes: 97 additions & 5 deletions reoptjl/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6384,19 +6384,53 @@ class ProcessHeatLoadInputs(BaseModel, models.Model):

possible_sets = [
["fuel_loads_mmbtu_per_hour"],
["annual_mmbtu"],
[],
["industry_reference_name", "monthly_mmbtu"],
["annual_mmbtu", "industry_reference_name"],
["industry_reference_name"],
["blended_industry_reference_names", "blended_industry_reference_percents"],
[]
]

INDUSTRY_REFERENCE_NAME = models.TextChoices('INDUSTRY_REFERENCE_NAME', (
'Chemical '
'Warehouse '
'FlatLoad '
'FlatLoad_24_5 '
'FlatLoad_16_7 '
'FlatLoad_16_5 '
'FlatLoad_8_7 '
'FlatLoad_8_5'
))

annual_mmbtu = models.FloatField(
validators=[
MinValueValidator(1),
MaxValueValidator(MAX_BIG_NUMBER)
],
null=True,
blank=True,
help_text=("Annual site process heat consumption, used "
"to scale simulated load profile [MMBtu]")
help_text=("Annual site process heat fuel consumption, used "
"to scale simulated default industry load profile [MMBtu]")
)

industry_reference_name = models.TextField(
null=True,
blank=True,
choices=INDUSTRY_REFERENCE_NAME.choices,
help_text=("Industrial process heat load reference facility/sector type")
)

monthly_mmbtu = ArrayField(
models.FloatField(
validators=[
MinValueValidator(0),
MaxValueValidator(MAX_BIG_NUMBER)
],
blank=True
),
default=list, blank=True,
help_text=("Monthly site process heat fuel consumption in [MMbtu], used "
"to scale simulated default building load profile for the site's climate zone")
)

fuel_loads_mmbtu_per_hour = ArrayField(
Expand All @@ -6405,11 +6439,50 @@ class ProcessHeatLoadInputs(BaseModel, models.Model):
),
default=list,
blank=True,
help_text=("Typical load over all hours in one year. Must be hourly (8,760 samples), 30 minute (17,"
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. "
)
)

blended_industry_reference_names = ArrayField(
models.TextField(
choices=INDUSTRY_REFERENCE_NAME.choices,
blank=True,
null=True
),
default=list,
blank=True,
help_text=("Used in concert with blended_industry_reference_percents to create a blended load profile from multiple "
"Industrial reference facility/sector types.")
)

blended_industry_reference_percents = ArrayField(
models.FloatField(
null=True, blank=True,
validators=[
MinValueValidator(0),
MaxValueValidator(1.0)
],
),
default=list,
blank=True,
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.")
)

addressable_load_fraction = ArrayField(
models.FloatField(
validators=[
MinValueValidator(0),
MaxValueValidator(1.0)
],
blank=True
),
default=list,
blank=True,
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.")
)

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

if len(self.blended_industry_reference_names) > 0 and self.industry_reference_name == "":
if len(self.blended_industry_reference_names) != len(self.blended_industry_reference_percents):
error_messages["blended_industry_reference_names"] = \
"The number of blended_industry_reference_names must equal the number of blended_industry_reference_percents."
if not math.isclose(sum(self.blended_industry_reference_percents), 1.0):
error_messages["blended_industry_reference_percents"] = "Sum must = 1.0."

if self.industry_reference_name != "" or \
len(self.blended_industry_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

# possible sets for defining load profile
if not at_least_one_set(self.dict, self.possible_sets):
error_messages["required inputs"] = \
"Must provide at least one set of valid inputs from {}.".format(self.possible_sets)

class HeatingLoadOutputs(BaseModel, models.Model):

key = "HeatingLoadOutputs"
Expand Down
1 change: 1 addition & 0 deletions reoptjl/test/posts/test_thermal_in_results.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"annual_mmbtu": 500.0
},
"ProcessHeatLoad": {
"industry_reference_name": "FlatLoad",
"annual_mmbtu": 100
},
"ExistingBoiler": {
Expand Down
4 changes: 2 additions & 2 deletions reoptjl/test/test_http_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ def test_simulated_load(self):
inputs["annual_kwh"] = 1.5E7
inputs["doe_reference_name[0]"] = "Hospital"
inputs["doe_reference_name[1]"] = "LargeOffice"
inputs["percent_share[0]"] = 25.0
inputs["percent_share[1]"] = 100.0 - inputs["percent_share[0]"]
inputs["percent_share[0]"] = 0.25
inputs["percent_share[1]"] = 1.0 - inputs["percent_share[0]"]

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