From 05861e27f6f1b537271f90811961ab2adaf10454 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Fri, 31 May 2024 10:56:43 -0600 Subject: [PATCH 1/4] Update REopt.jl to v0.47.0 --- julia_src/Manifest.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index ef21d5b2a..46852a1c6 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -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 = "d916e15578370ec306da3acdbabd8157a25127e1" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.46.1" +version = "0.47.0" [[deps.Random]] deps = ["SHA"] From d6dc85c7672d7982a74f893b769977f33b887e3f Mon Sep 17 00:00:00 2001 From: bill-becker Date: Sat, 1 Jun 2024 14:16:25 -0600 Subject: [PATCH 2/4] Add new ProcessHeatInputs and fix thermal job test --- ...puts_addressable_load_fraction_and_more.py | 55 ++++++++++ reoptjl/models.py | 102 +++++++++++++++++- .../test/posts/test_thermal_in_results.json | 1 + 3 files changed, 153 insertions(+), 5 deletions(-) create mode 100644 reoptjl/migrations/0060_processheatloadinputs_addressable_load_fraction_and_more.py diff --git a/reoptjl/migrations/0060_processheatloadinputs_addressable_load_fraction_and_more.py b/reoptjl/migrations/0060_processheatloadinputs_addressable_load_fraction_and_more.py new file mode 100644 index 000000000..87d3c7c49 --- /dev/null +++ b/reoptjl/migrations/0060_processheatloadinputs_addressable_load_fraction_and_more.py @@ -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), + ), + ] diff --git a/reoptjl/models.py b/reoptjl/models.py index 8d4e1ae11..e9419054b 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -6384,10 +6384,24 @@ 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), @@ -6395,8 +6409,28 @@ class ProcessHeatLoadInputs(BaseModel, models.Model): ], 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( @@ -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): @@ -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" diff --git a/reoptjl/test/posts/test_thermal_in_results.json b/reoptjl/test/posts/test_thermal_in_results.json index 2be1bc8ce..32231c119 100644 --- a/reoptjl/test/posts/test_thermal_in_results.json +++ b/reoptjl/test/posts/test_thermal_in_results.json @@ -38,6 +38,7 @@ "annual_mmbtu": 500.0 }, "ProcessHeatLoad": { + "industry_reference_name": "FlatLoad", "annual_mmbtu": 100 }, "ExistingBoiler": { From 5de08a27f9c13c816bec67c68acfaf4e5946f3a1 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 4 Jun 2024 11:46:53 -0600 Subject: [PATCH 3/4] Update REopt.jl to v0.47.1 and Changelog --- CHANGELOG.md | 7 +++++++ julia_src/Manifest.toml | 4 ++-- reoptjl/api.py | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d582c6dda..36949fa54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 46852a1c6..d60586baa 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -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 = "d916e15578370ec306da3acdbabd8157a25127e1" +git-tree-sha1 = "b51d56a6398f302100004184b64bbe3d1e137277" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.47.0" +version = "0.47.1" [[deps.Random]] deps = ["SHA"] diff --git a/reoptjl/api.py b/reoptjl/api.py index e0c911c50..bd90ef685 100644 --- a/reoptjl/api.py +++ b/reoptjl/api.py @@ -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}) From 539d481873a0e4d80016e07449cb63994c1a8ea7 Mon Sep 17 00:00:00 2001 From: bill-becker Date: Tue, 4 Jun 2024 14:16:15 -0600 Subject: [PATCH 4/4] Fix percent_share to fraction --- reoptjl/test/test_http_endpoints.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reoptjl/test/test_http_endpoints.py b/reoptjl/test/test_http_endpoints.py index b8dca3fbd..760c10775 100644 --- a/reoptjl/test/test_http_endpoints.py +++ b/reoptjl/test/test_http_endpoints.py @@ -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)