diff --git a/CHANGELOG.md b/CHANGELOG.md index 0154dd1eb..36c037fe3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,13 +26,25 @@ Classify the change according to the following categories: ##### Removed ### Patches +## v2.4.0 +### Minor Updates +##### Added +- In `job/models.py`: + - add **CHPInputs** model + - add **CHPOutputs** model +- In `job/process_results.py` add **CHPOutputs** +- In `job/validators.py` add new input models +- In `job/views.py`: + - add new input/output models to properly save the inputs/outputs + - add `/chp_defaults` endpoint which calls the http.jl chp_defaults endpoint + - add `/simulated_load` endpoint which calls the http.jl simulated_load endpoint + ## v2.3.1 ### Minor Updates ##### Fixed Lookback charge parameters expected from the URDB API call were changed to the non-caplitalized format, so they are now used properly. ## v2.3.0 -### Minor Updates ##### Changed The following name changes were made in the `job/` endpoint and `julia_src/http.jl`: - Change "_pct" to "_rate_fraction" for input and output names containing "discount", "escalation", and "tax_pct" (financial terms) diff --git a/job/migrations/0011_chpoutputs_chpinputs.py b/job/migrations/0011_chpoutputs_chpinputs.py new file mode 100644 index 000000000..bba98b736 --- /dev/null +++ b/job/migrations/0011_chpoutputs_chpinputs.py @@ -0,0 +1,101 @@ +# Generated by Django 4.0.6 on 2022-10-12 01:56 + +import django.contrib.postgres.fields +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion +import job.models +import picklefield.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0010_rename_prod_factor_series_pvinputs_production_factor_series_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='CHPOutputs', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('size_kw', models.FloatField(blank=True, help_text='Power capacity size of the CHP system [kW]', null=True)), + ('size_supplemental_firing_kw', models.FloatField(blank=True, help_text='Power capacity of CHP supplementary firing system [kW]', null=True)), + ('year_one_fuel_used_mmbtu', models.FloatField(blank=True, help_text='Fuel consumed in year one [MMBtu]', null=True)), + ('year_one_electric_energy_produced_kwh', models.FloatField(blank=True, help_text='Electric energy produced in year one [kWh]', null=True)), + ('year_one_thermal_energy_produced_mmbtu', models.FloatField(blank=True, help_text='Thermal energy produced in year one [MMBtu]', null=True)), + ('year_one_electric_production_series_kw', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, help_text='Electric power production time-series array [kW]', size=None)), + ('year_one_to_grid_series_kw', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, help_text='Electric power exported time-series array [kW]', size=None)), + ('year_one_to_battery_series_kw', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, help_text='Electric power to charge the battery storage time-series array [kW]', size=None)), + ('year_one_to_load_series_kw', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, help_text='Electric power to serve the electric load time-series array [kW]', size=None)), + ('year_one_thermal_to_tes_series_mmbtu_per_hour', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, help_text='Thermal power to TES time-series array [MMBtu/hr]', size=None)), + ('year_one_thermal_to_waste_series_mmbtu_per_hour', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, help_text='Thermal power wasted/unused/vented time-series array [MMBtu/hr]', size=None)), + ('year_one_thermal_to_load_series_mmbtu_per_hour', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, help_text='Thermal power to serve the heating load time-series array [MMBtu/hr]', size=None)), + ('year_one_chp_fuel_cost_before_tax', models.FloatField(blank=True, help_text='Cost of fuel consumed by the CHP system in year one [\\$]', null=True)), + ('lifecycle_chp_fuel_cost_after_tax', models.FloatField(blank=True, help_text='Present value of cost of fuel consumed by the CHP system, after tax [\\$]', null=True)), + ('year_one_chp_standby_cost_before_tax', models.FloatField(blank=True, help_text='CHP standby charges in year one [\\$]', null=True)), + ('lifecycle_chp_standby_cost_after_tax', models.FloatField(blank=True, help_text='Present value of all CHP standby charges, after tax.', null=True)), + ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='CHPOutputs', to='job.apimeta')), + ], + bases=(job.models.BaseModel, models.Model), + ), + migrations.CreateModel( + name='CHPInputs', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('fuel_cost_per_mmbtu', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, validators=[django.core.validators.MinValueValidator(0)]), blank=True, default=list, help_text='The CHP system fuel cost is a required input when the CHP system is included as an option.The `fuel_cost_per_mmbtu` can be a scalar, a list of 12 monthly values, or a time series of values for every time step.If a scalar or a vector of 12 values are provided, then the value is scaled up to 8760 values.If a vector of 8760, 17520, or 35040 values is provided, it is adjusted to match timesteps per hour in the optimization.', size=None)), + ('prime_mover', models.TextField(blank=True, choices=[('recip_engine', 'Recip Engine'), ('micro_turbine', 'Micro Turbine'), ('combustion_turbine', 'Combustion Turbine'), ('fuel_cell', 'Fuel Cell')], help_text='CHP prime mover, one of recip_engine, micro_turbine, combustion_turbine, fuel_cell')), + ('installed_cost_per_kw', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, help_text='Installed cost in $/kW', null=True, size=None)), + ('tech_sizes_for_cost_curve', django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, help_text='Capacity intervals correpsonding to cost rates in installed_cost_per_kW, in kW', null=True, size=None)), + ('om_cost_per_kwh', models.FloatField(blank=True, help_text='CHP per unit production (variable) operations and maintenance costs in $/kWh', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1000.0)])), + ('electric_efficiency_half_load', models.FloatField(blank=True, help_text='Electric efficiency of CHP prime-mover at half-load, HHV-basis', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)])), + ('electric_efficiency_full_load', models.FloatField(blank=True, help_text='Electric efficiency of CHP prime-mover at full-load, HHV-basis', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)])), + ('min_turn_down_fraction', models.FloatField(blank=True, help_text='Minimum CHP loading in fraction of capacity (size_kw).', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)])), + ('thermal_efficiency_full_load', models.FloatField(blank=True, help_text='CHP fraction of fuel energy converted to hot-thermal energy at full electric load', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)])), + ('thermal_efficiency_half_load', models.FloatField(blank=True, help_text='CHP fraction of fuel energy converted to hot-thermal energy at half electric load', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)])), + ('min_allowable_kw', models.FloatField(blank=True, help_text='Minimum nonzero CHP size (in kWe) (i.e. it is possible to select no CHP system)', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100000000.0)])), + ('max_kw', models.FloatField(blank=True, help_text='Maximum CHP size (in kWe) constraint for optimization. Set to zero to disable CHP', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100000000.0)])), + ('cooling_thermal_factor', models.FloatField(blank=True, help_text='Knockdown factor on absorption chiller COP based on the CHP prime_mover not being able to produce as high of temp/pressure hot water/steam', null=True, validators=[django.core.validators.MinValueValidator(0.01), django.core.validators.MaxValueValidator(1.0)])), + ('unavailability_periods', django.contrib.postgres.fields.ArrayField(base_field=picklefield.fields.PickledObjectField(editable=False, null=True), blank=True, help_text="CHP unavailability periods for scheduled and unscheduled maintenance, list of dictionaries with keys of ['month', 'start_week_of_month', 'start_day_of_week', 'start_hour', 'duration_hours'] all values are one-indexed and start_day_of_week uses 1 for Monday, 7 for Sunday", null=True, size=None)), + ('size_class', models.IntegerField(blank=True, default=1, help_text='CHP size class. Must be a strictly positive integer value', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)])), + ('min_kw', models.FloatField(blank=True, default=0, help_text='Minimum CHP size constraint for optimization', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('fuel_type', models.TextField(blank=True, choices=[('natural_gas', 'Natural Gas'), ('landfill_bio_gas', 'Landfill Bio Gas'), ('propane', 'Propane'), ('diesel_oil', 'Diesel Oil')], default='natural_gas', help_text='Existing CHP fuel type, one of natural_gas, landfill_bio_gas, propane, diesel_oil')), + ('om_cost_per_kw', models.FloatField(blank=True, help_text='Annual CHP fixed operations and maintenance costs in $/kW', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1000.0)])), + ('om_cost_per_hr_per_kw_rated', models.FloatField(blank=True, default=0.0, help_text='CHP system per-operating-hour (variable) operations and maintenance costs in $/hr-kW', validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1000.0)])), + ('supplementary_firing_capital_cost_per_kw', models.FloatField(blank=True, default=150, help_text='Installed CHP supplementary firing system cost in $/kWe', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100000.0)])), + ('supplementary_firing_max_steam_ratio', models.FloatField(blank=True, default=1.0, help_text='Ratio of max fired steam to un-fired steam production. Relevant only for combustion_turbine prime_mover', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(10.0)])), + ('supplementary_firing_efficiency', models.FloatField(blank=True, default=0.92, help_text='Thermal efficiency of the incremental steam production from supplementary firing. Relevant only for combustion_turbine prime_mover', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1.0)])), + ('standby_rate_per_kw_per_month', models.FloatField(blank=True, default=0, help_text='Standby rate charged to CHP based on CHP electric power size', null=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000)])), + ('reduces_demand_charges', models.BooleanField(blank=True, default=True, help_text='Boolean indicator if CHP reduces demand charges', null=True)), + ('can_supply_steam_turbine', models.BooleanField(blank=True, default=False, help_text='Boolean indicator if CHP can supply steam to the steam turbine for electric production', null=True)), + ('macrs_option_years', models.IntegerField(blank=True, choices=[(0, 'Zero'), (5, 'Five'), (7, 'Seven')], default=0, help_text='Duration over which accelerated depreciation will occur. Set to zero to disable')), + ('macrs_bonus_fraction', models.FloatField(blank=True, default=1.0, help_text='Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('macrs_itc_reduction', models.FloatField(blank=True, default=0.5, help_text='Percent of the ITC value by which depreciable basis is reduced', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('federal_itc_fraction', models.FloatField(blank=True, default=0.1, help_text='Percentage of capital costs that are credited towards federal taxes', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('federal_rebate_per_kw', models.FloatField(blank=True, default=0, help_text='Federal rebates based on installed capacity', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('state_ibi_fraction', models.FloatField(blank=True, default=0, help_text='Percentage of capital costs offset by state incentives', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('state_ibi_max', models.FloatField(blank=True, default=10000000000.0, help_text='Maximum dollar value of state percentage-based capital cost incentive', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000000000.0)])), + ('state_rebate_per_kw', models.FloatField(blank=True, default=0, help_text='State rebate based on installed capacity', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('state_rebate_max', models.FloatField(blank=True, default=10000000000.0, help_text='Maximum state rebate', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000000000.0)])), + ('utility_ibi_fraction', models.FloatField(blank=True, default=0, help_text='Percentage of capital costs offset by utility incentives', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('utility_ibi_max', models.FloatField(blank=True, default=10000000000.0, help_text='Maximum dollar value of utility percentage-based capital cost incentive', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000000000.0)])), + ('utility_rebate_per_kw', models.FloatField(blank=True, default=0, help_text='Utility rebate based on installed capacity', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('utility_rebate_max', models.FloatField(blank=True, default=10000000000.0, help_text='Maximum utility rebate', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000000000.0)])), + ('production_incentive_per_kwh', models.FloatField(blank=True, default=0, help_text='Production-based incentive value', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('production_incentive_max_benefit', models.FloatField(blank=True, default=1000000000.0, help_text='Maximum annual value in present terms of production-based incentives', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('production_incentive_years', models.IntegerField(blank=True, default=0, help_text='Duration of production-based incentives from installation date', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])), + ('production_incentive_max_kw', models.FloatField(blank=True, default=0.0, help_text='Maximum system size eligible for production-based incentive', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1000000000.0)])), + ('can_net_meter', models.BooleanField(blank=True, default=False, help_text='True/False for if technology has option to participate in net metering agreement with utility. Note that a technology can only participate in either net metering or wholesale rates (not both).')), + ('can_wholesale', models.BooleanField(blank=True, default=False, help_text='True/False for if technology has option to export energy that is compensated at the wholesale_rate. Note that a technology can only participate in either net metering or wholesale rates (not both).')), + ('can_export_beyond_nem_limit', models.BooleanField(blank=True, default=False, help_text='True/False for if technology can export energy beyond the annual site load (and be compensated for that energy at the export_rate_beyond_net_metering_limit).')), + ('can_curtail', models.BooleanField(blank=True, default=False, help_text='True/False for if technology has the ability to curtail energy production.')), + ('fuel_renewable_energy_fraction', models.FloatField(blank=True, default=0.0, help_text='Fraction of the CHP fuel considered renewable.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)])), + ('emissions_factor_lb_CO2_per_mmbtu', models.FloatField(blank=True, help_text='Pounds of CO2 emitted per MMBTU of CHP fuel burned.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)])), + ('emissions_factor_lb_NOx_per_mmbtu', models.FloatField(blank=True, help_text='Pounds of CO2 emitted per MMBTU of CHP fuel burned.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)])), + ('emissions_factor_lb_SO2_per_mmbtu', models.FloatField(blank=True, help_text='Pounds of CO2 emitted per MMBTU of CHP fuel burned.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)])), + ('emissions_factor_lb_PM25_per_mmbtu', models.FloatField(blank=True, help_text='Pounds of CO2 emitted per MMBTU of CHP fuel burned.', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(10000.0)])), + ('meta', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='CHPInputs', to='job.apimeta')), + ], + bases=(job.models.BaseModel, models.Model), + ), + ] diff --git a/job/migrations/0012_alter_chpinputs_size_class.py b/job/migrations/0012_alter_chpinputs_size_class.py new file mode 100644 index 000000000..89373fa7d --- /dev/null +++ b/job/migrations/0012_alter_chpinputs_size_class.py @@ -0,0 +1,19 @@ +# Generated by Django 4.0.6 on 2022-11-01 14:22 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0011_chpoutputs_chpinputs'), + ] + + operations = [ + migrations.AlterField( + model_name='chpinputs', + name='size_class', + field=models.IntegerField(blank=True, default=1, help_text='CHP size class. Must be a strictly positive integer value', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(7)]), + ), + ] diff --git a/job/migrations/0013_alter_chpinputs_om_cost_per_kw_and_more.py b/job/migrations/0013_alter_chpinputs_om_cost_per_kw_and_more.py new file mode 100644 index 000000000..1d725e4f9 --- /dev/null +++ b/job/migrations/0013_alter_chpinputs_om_cost_per_kw_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.0.6 on 2022-11-10 22:29 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0012_alter_chpinputs_size_class'), + ] + + operations = [ + migrations.AlterField( + model_name='chpinputs', + name='om_cost_per_kw', + field=models.FloatField(blank=True, default=0.0, help_text='Annual CHP fixed operations and maintenance costs in $/kW', null=True, validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(1000.0)]), + ), + migrations.AlterField( + model_name='chpinputs', + name='prime_mover', + field=models.TextField(blank=True, choices=[('recip_engine', 'Recip Engine'), ('micro_turbine', 'Micro Turbine'), ('combustion_turbine', 'Combustion Turbine'), ('fuel_cell', 'Fuel Cell')], help_text='CHP prime mover, one of recip_engine, micro_turbine, combustion_turbine, fuel_cell', null=True), + ), + migrations.AlterField( + model_name='chpinputs', + name='size_class', + field=models.IntegerField(blank=True, help_text='CHP size class. Must be a strictly positive integer value', null=True, validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(7)]), + ), + ] diff --git a/job/migrations/0014_rename_thermal_to_tes_series_mmbtu_per_hour_existingboileroutputs_year_one_fuel_consumption_series_m.py b/job/migrations/0014_rename_thermal_to_tes_series_mmbtu_per_hour_existingboileroutputs_year_one_fuel_consumption_series_m.py new file mode 100644 index 000000000..6660c1076 --- /dev/null +++ b/job/migrations/0014_rename_thermal_to_tes_series_mmbtu_per_hour_existingboileroutputs_year_one_fuel_consumption_series_m.py @@ -0,0 +1,39 @@ +# Generated by Django 4.0.6 on 2022-11-14 04:47 + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('job', '0013_alter_chpinputs_om_cost_per_kw_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='existingboileroutputs', + old_name='thermal_to_tes_series_mmbtu_per_hour', + new_name='year_one_fuel_consumption_series_mmbtu_per_hour', + ), + migrations.RenameField( + model_name='existingboileroutputs', + old_name='year_one_fuel_consumption_mmbtu_per_hour', + new_name='year_one_thermal_production_series_mmbtu_per_hour', + ), + migrations.RenameField( + model_name='existingboileroutputs', + old_name='year_one_thermal_production_mmbtu_per_hour', + new_name='year_one_thermal_to_steamturbine_series_mmbtu_per_hour', + ), + migrations.AddField( + model_name='chpoutputs', + name='year_one_thermal_to_steamturbine_series_mmbtu_per_hour', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), blank=True, default=list, help_text='Thermal power to TES time-series array [MMBtu/hr]', size=None), + ), + migrations.AddField( + model_name='existingboileroutputs', + name='year_one_thermal_to_tes_series_mmbtu_per_hour', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, null=True), default=list, size=None), + ), + ] diff --git a/job/models.py b/job/models.py index ec9ffdb12..e9dcc7885 100644 --- a/job/models.py +++ b/job/models.py @@ -34,10 +34,13 @@ # TODO rm picklefield from requirements.txt once v1 is retired (replaced with JSONfield) from django.core.exceptions import ValidationError from django.core.validators import MaxValueValidator, MinValueValidator +from picklefield.fields import PickledObjectField import numpy from job.urdb_rate_validator import URDB_RateValidator,URDB_LabelValidator import copy import logging +import os +import json log = logging.getLogger(__name__) @@ -1490,28 +1493,6 @@ class ElectricTariffInputs(BaseModel, models.Model): help_text=("Optional coincident peak demand charge that is applied to the max load during the time_steps " "specified in coincident_peak_load_active_time_steps") ) - # chp_does_not_reduce_demand_charges = models.BooleanField( - # default=False, - # blank=True, - # help_text=("Boolean indicator if CHP does not reduce demand charges") - # ) - # chp_standby_rate_per_kw_per_month = models.FloatField( - # default=0, - # validators=[ - # MinValueValidator(0), - # MaxValueValidator(1000) - # ], - # null=True, blank=True, - # help_text=("Standby rate charged to CHP based on CHP electric power size") - # ) - # emissions_factor_series_lb_CO2_per_kwh = ArrayField( - # models.FloatField(blank=True), - # null=True, blank=True, - # default=list, - # help_text=("Carbon Dioxide emissions factor over all hours in one year. Can be provided as either a single " - # "constant fraction that will be applied across all time_steps, or an annual timeseries array at an " - # "hourly (8,760 samples), 30 minute (17,520 samples), or 15 minute (35,040 samples) resolution.") - # ) def clean(self): error_messages = {} @@ -3350,6 +3331,610 @@ class GeneratorOutputs(BaseModel, models.Model): year_one_emissions_lb_C02 = models.FloatField(null=True, blank=True) year_one_emissions_bau_lb_C02 = models.FloatField(null=True, blank=True) +class CHPInputs(BaseModel, models.Model): + key = "CHP" + meta = models.OneToOneField( + to=APIMeta, + on_delete=models.CASCADE, + related_name="CHPInputs", + unique=True + ) + + # Prime mover + PRIME_MOVER = models.TextChoices('PRIME_MOVER', ( + "recip_engine", + "micro_turbine", + "combustion_turbine", + "fuel_cell" + )) + + FUEL_TYPE_LIST = models.TextChoices('FUEL_TYPE_LIST', ( + "natural_gas", + "landfill_bio_gas", + "propane", + "diesel_oil" + )) + + #Always required + fuel_cost_per_mmbtu = ArrayField( + models.FloatField( + blank=True, + validators=[ + MinValueValidator(0) + ] + ), + default=list, + null=False, + blank=True, + help_text=( + "The CHP system fuel cost is a required input when the CHP system is included as an option." + "The `fuel_cost_per_mmbtu` can be a scalar, a list of 12 monthly values, or a time series of values for every time step." + "If a scalar or a vector of 12 values are provided, then the value is scaled up to 8760 values." + "If a vector of 8760, 17520, or 35040 values is provided, it is adjusted to match timesteps per hour in the optimization." + ) + ) + + # Prime mover - highly suggested, but not required + prime_mover = models.TextField( + null=True, + blank=True, + choices=PRIME_MOVER.choices, + help_text="CHP prime mover, one of recip_engine, micro_turbine, combustion_turbine, fuel_cell" + ) + # These are assigned using chp_defaults() logic by choosing prime_mover and size_class (if not supplied) based on average heating load + installed_cost_per_kw = ArrayField( + models.FloatField(null=True, blank=True), + default=list, + null=True, + blank=True, + help_text="Installed cost in $/kW" + ) + tech_sizes_for_cost_curve = ArrayField( + models.FloatField(null=True, blank=True), + default=list, + null=True, + blank=True, + help_text="Capacity intervals correpsonding to cost rates in installed_cost_per_kW, in kW" + ) + om_cost_per_kwh = models.FloatField( + validators=[ + MinValueValidator(0.0), + MaxValueValidator(1.0e3) + ], + null=True, + blank=True, + help_text="CHP per unit production (variable) operations and maintenance costs in $/kWh" + ) + electric_efficiency_half_load = models.FloatField( + validators=[ + MinValueValidator(0.0), + MaxValueValidator(1.0) + ], + null=True, + blank=True, + help_text="Electric efficiency of CHP prime-mover at half-load, HHV-basis" + ) + electric_efficiency_full_load = models.FloatField( + validators=[ + MinValueValidator(0.0), + MaxValueValidator(1.0) + ], + null=True, + blank=True, + help_text="Electric efficiency of CHP prime-mover at full-load, HHV-basis" + ) + min_turn_down_fraction = models.FloatField( + validators=[ + MinValueValidator(0.0), + MaxValueValidator(1.0) + ], + blank=True, + null=True, + help_text="Minimum CHP loading in fraction of capacity (size_kw)." + ) + thermal_efficiency_full_load = models.FloatField( + validators=[ + MinValueValidator(0.0), + MaxValueValidator(1.0) + ], + null=True, + blank=True, + help_text="CHP fraction of fuel energy converted to hot-thermal energy at full electric load" + ) + thermal_efficiency_half_load = models.FloatField( + validators=[ + MinValueValidator(0.0), + MaxValueValidator(1.0) + ], + null=True, + blank=True, + help_text="CHP fraction of fuel energy converted to hot-thermal energy at half electric load" + ) + min_allowable_kw = models.FloatField( + validators=[ + MinValueValidator(0.0), + MaxValueValidator(MAX_BIG_NUMBER) + ], + null=True, + blank=True, + help_text="Minimum nonzero CHP size (in kWe) (i.e. it is possible to select no CHP system)" + ) + max_kw = models.FloatField( + validators=[ + MinValueValidator(0.0), + MaxValueValidator(MAX_BIG_NUMBER) + ], + null=True, + blank=True, + help_text="Maximum CHP size (in kWe) constraint for optimization. Set to zero to disable CHP" + ) + cooling_thermal_factor = models.FloatField( + validators=[ + MinValueValidator(0.01), + MaxValueValidator(1.0) + ], + null=True, + blank=True, + help_text=( + "Knockdown factor on absorption chiller COP based on the CHP prime_mover not being able to produce " + "as high of temp/pressure hot water/steam" + ) + ) # only needed with cooling load + unavailability_periods = ArrayField( + PickledObjectField(null=True, editable=True), + null=True, + blank=True, + help_text=( + "CHP unavailability periods for scheduled and unscheduled maintenance, list of dictionaries with keys of " + "['month', 'start_week_of_month', 'start_day_of_week', 'start_hour', 'duration_hours'] " + "all values are one-indexed and start_day_of_week uses 1 for Monday, 7 for Sunday" + ) + + ) + # Optional inputs + size_class = models.IntegerField( + validators=[ + MinValueValidator(1), + MaxValueValidator(7) + ], + null=True, + blank=True, + help_text="CHP size class. Must be a strictly positive integer value" + ) + min_kw = models.FloatField( + default=0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + help_text="Minimum CHP size constraint for optimization" + ) + fuel_type = models.TextField( + null=False, + blank=True, + choices=FUEL_TYPE_LIST.choices, + default="natural_gas", + help_text="Existing CHP fuel type, one of natural_gas, landfill_bio_gas, propane, diesel_oil" + ) + om_cost_per_kw = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0.0), + MaxValueValidator(1.0e3) + ], + blank=True, + null=True, + help_text="Annual CHP fixed operations and maintenance costs in $/kW" + ) + om_cost_per_hr_per_kw_rated = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0.0), + MaxValueValidator(1.0e3) + ], + blank=True, + help_text="CHP system per-operating-hour (variable) operations and maintenance costs in $/hr-kW" + ) + supplementary_firing_capital_cost_per_kw = models.FloatField( + default=150, + validators=[ + MinValueValidator(0.0), + MaxValueValidator(1.0e5) + ], + null=True, + blank=True, + help_text="Installed CHP supplementary firing system cost in $/kWe" + ) + supplementary_firing_max_steam_ratio = models.FloatField( + default=1.0, + validators=[ + MinValueValidator(0.0), + MaxValueValidator(10.0) + ], + null=True, + blank=True, + help_text="Ratio of max fired steam to un-fired steam production. Relevant only for combustion_turbine prime_mover" + ) + supplementary_firing_efficiency =models.FloatField( + default=0.92, + validators=[ + MinValueValidator(0.0), + MaxValueValidator(1.0) + ], + null=True, + blank=True, + help_text=( + "Thermal efficiency of the incremental steam production from supplementary firing. Relevant only for " + "combustion_turbine prime_mover" + ) + ) + standby_rate_per_kw_per_month = models.FloatField( + default=0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1000) + ], + null=True, + blank=True, + help_text=("Standby rate charged to CHP based on CHP electric power size") + ) + reduces_demand_charges = models.BooleanField( + default=True, + null=True, + blank=True, + help_text=("Boolean indicator if CHP reduces demand charges") + ) + can_supply_steam_turbine = models.BooleanField( + default=False, + null=True, + blank=True, + help_text="Boolean indicator if CHP can supply steam to the steam turbine for electric production" + ) + + #Financial and emissions + macrs_option_years = models.IntegerField( + default=MACRS_YEARS_CHOICES.ZERO, + choices=MACRS_YEARS_CHOICES.choices, + blank=True, + help_text="Duration over which accelerated depreciation will occur. Set to zero to disable" + ) + macrs_bonus_fraction = models.FloatField( + default=1.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Percent of upfront project costs to depreciate in year one in addition to scheduled depreciation" + ) + macrs_itc_reduction = models.FloatField( + default=0.5, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Percent of the ITC value by which depreciable basis is reduced" + ) + federal_itc_fraction = models.FloatField( + default=0.1, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Percentage of capital costs that are credited towards federal taxes" + ) + federal_rebate_per_kw = models.FloatField( + default=0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + help_text="Federal rebates based on installed capacity" + ) + state_ibi_fraction = models.FloatField( + default=0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Percentage of capital costs offset by state incentives" + ) + state_ibi_max = models.FloatField( + default=MAX_INCENTIVE, + validators=[ + MinValueValidator(0), + MaxValueValidator(MAX_INCENTIVE) + ], + blank=True, + help_text="Maximum dollar value of state percentage-based capital cost incentive" + ) + state_rebate_per_kw = models.FloatField( + default=0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + help_text="State rebate based on installed capacity" + ) + state_rebate_max = models.FloatField( + default=MAX_INCENTIVE, + validators=[ + MinValueValidator(0), + MaxValueValidator(MAX_INCENTIVE) + ], + blank=True, + help_text="Maximum state rebate" + ) + utility_ibi_fraction = models.FloatField( + default=0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Percentage of capital costs offset by utility incentives" + ) + utility_ibi_max = models.FloatField( + default=MAX_INCENTIVE, + validators=[ + MinValueValidator(0), + MaxValueValidator(MAX_INCENTIVE) + ], + blank=True, + help_text="Maximum dollar value of utility percentage-based capital cost incentive" + ) + utility_rebate_per_kw = models.FloatField( + default=0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + help_text="Utility rebate based on installed capacity" + ) + utility_rebate_max = models.FloatField( + default=MAX_INCENTIVE, + validators=[ + MinValueValidator(0), + MaxValueValidator(MAX_INCENTIVE) + ], + blank=True, + help_text="Maximum utility rebate" + ) + production_incentive_per_kwh = models.FloatField( + default=0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + help_text="Production-based incentive value" + ) + production_incentive_max_benefit = models.FloatField( + default=1.0e9, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + help_text="Maximum annual value in present terms of production-based incentives" + ) + production_incentive_years = models.IntegerField( + default=0, + validators=[ + MinValueValidator(0), + MaxValueValidator(100) + ], + blank=True, + help_text="Duration of production-based incentives from installation date" + ) + production_incentive_max_kw = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1.0e9) + ], + blank=True, + help_text="Maximum system size eligible for production-based incentive" + ) + can_net_meter = models.BooleanField( + default=False, + blank=True, + help_text=("True/False for if technology has option to participate in net metering agreement with utility. " + "Note that a technology can only participate in either net metering or wholesale rates (not both).") + ) + can_wholesale = models.BooleanField( + default=False, + blank=True, + help_text=("True/False for if technology has option to export energy that is compensated at the wholesale_rate. " + "Note that a technology can only participate in either net metering or wholesale rates (not both).") + ) + can_export_beyond_nem_limit = models.BooleanField( + default=False, + blank=True, + help_text=("True/False for if technology can export energy beyond the annual site load (and be compensated for " + "that energy at the export_rate_beyond_net_metering_limit).") + ) + can_curtail = models.BooleanField( + default=False, + blank=True, + help_text="True/False for if technology has the ability to curtail energy production." + ) + fuel_renewable_energy_fraction = models.FloatField( + default=0.0, + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True, + help_text="Fraction of the CHP fuel considered renewable." + ) + emissions_factor_lb_CO2_per_mmbtu = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1e4) + ], + blank=True, + help_text="Pounds of CO2 emitted per MMBTU of CHP fuel burned." + ) + emissions_factor_lb_NOx_per_mmbtu = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1e4) + ], + blank=True, + help_text="Pounds of CO2 emitted per MMBTU of CHP fuel burned." + ) + emissions_factor_lb_SO2_per_mmbtu = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1e4) + ], + blank=True, + help_text="Pounds of CO2 emitted per MMBTU of CHP fuel burned." + ) + emissions_factor_lb_PM25_per_mmbtu = models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1e4) + ], + blank=True, + help_text="Pounds of CO2 emitted per MMBTU of CHP fuel burned." + ) + + def clean(self): + error_messages = {} + if not self.dict.get("fuel_cost_per_mmbtu"): + error_messages["required inputs"] = "Must provide fuel_cost_per_mmbtu to model {}".format(self.key) + + if error_messages: + raise ValidationError(error_messages) + + self.fuel_cost_per_mmbtu = scalar_to_vector(self.fuel_cost_per_mmbtu) + + if self.emissions_factor_lb_CO2_per_mmbtu == None: + self.emissions_factor_lb_CO2_per_mmbtu = FUEL_DEFAULTS["emissions_factor_lb_CO2_per_mmbtu"].get(self.fuel_type, 0.0) + + if self.emissions_factor_lb_SO2_per_mmbtu == None: + self.emissions_factor_lb_SO2_per_mmbtu = FUEL_DEFAULTS["emissions_factor_lb_SO2_per_mmbtu"].get(self.fuel_type, 0.0) + + if self.emissions_factor_lb_NOx_per_mmbtu == None: + self.emissions_factor_lb_NOx_per_mmbtu = FUEL_DEFAULTS["emissions_factor_lb_NOx_per_mmbtu"].get(self.fuel_type, 0.0) + + if self.emissions_factor_lb_PM25_per_mmbtu == None: + self.emissions_factor_lb_PM25_per_mmbtu = FUEL_DEFAULTS["emissions_factor_lb_PM25_per_mmbtu"].get(self.fuel_type, 0.0) + + +class CHPOutputs(BaseModel, models.Model): + key = "CHP" + meta = models.OneToOneField( + to=APIMeta, + on_delete=models.CASCADE, + related_name="CHPOutputs", + unique=True + ) + + size_kw = models.FloatField( + null=True, blank=True, + help_text="Power capacity size of the CHP system [kW]" + ) + size_supplemental_firing_kw = models.FloatField( + null=True, blank=True, + help_text="Power capacity of CHP supplementary firing system [kW]" + ) + year_one_fuel_used_mmbtu = models.FloatField( + null=True, blank=True, + help_text="Fuel consumed in year one [MMBtu]" + ) + year_one_electric_energy_produced_kwh = models.FloatField( + null=True, blank=True, + help_text="Electric energy produced in year one [kWh]" + ) + year_one_thermal_energy_produced_mmbtu = models.FloatField( + null=True, blank=True, + help_text="Thermal energy produced in year one [MMBtu]" + ) + year_one_electric_production_series_kw = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, blank=True, + help_text="Electric power production time-series array [kW]" + ) + year_one_to_grid_series_kw = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, blank=True, + help_text="Electric power exported time-series array [kW]" + ) + year_one_to_battery_series_kw = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, blank=True, + help_text="Electric power to charge the battery storage time-series array [kW]" + ) + year_one_to_load_series_kw = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, blank=True, + help_text="Electric power to serve the electric load time-series array [kW]" + ) + year_one_thermal_to_tes_series_mmbtu_per_hour = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, blank=True, + help_text="Thermal power to TES time-series array [MMBtu/hr]" + ) + year_one_thermal_to_waste_series_mmbtu_per_hour = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, blank=True, + help_text="Thermal power wasted/unused/vented time-series array [MMBtu/hr]" + ) + year_one_thermal_to_load_series_mmbtu_per_hour = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, blank=True, + help_text="Thermal power to serve the heating load time-series array [MMBtu/hr]" + ) + year_one_thermal_to_steamturbine_series_mmbtu_per_hour = ArrayField( + models.FloatField( + null=True, blank=True + ), + default=list, blank=True, + help_text="Thermal power to TES time-series array [MMBtu/hr]" + ) + year_one_chp_fuel_cost_before_tax = models.FloatField( + null=True, blank=True, + help_text="Cost of fuel consumed by the CHP system in year one [\$]" + ) + lifecycle_chp_fuel_cost_after_tax = models.FloatField( + null=True, blank=True, + help_text="Present value of cost of fuel consumed by the CHP system, after tax [\$]" + ) + year_one_chp_standby_cost_before_tax = models.FloatField( + null=True, blank=True, + help_text="CHP standby charges in year one [\$]" + ) + lifecycle_chp_standby_cost_after_tax = models.FloatField( + null=True, blank=True, + help_text="Present value of all CHP standby charges, after tax." + ) + + def clean(): + pass class Message(BaseModel, models.Model): """ @@ -3387,13 +3972,6 @@ class ExistingBoilerInputs(BaseModel, models.Model): 'hot_water' )) - CHP_PRIME_MOVER = models.TextChoices('CHP_PRIME_MOVER', ( - "recip_engine", - "micro_turbine", - "combustion_turbine", - "fuel_cell" - )) - FUEL_TYPE_LIST = models.TextChoices('FUEL_TYPE_LIST', ( "natural_gas", "landfill_bio_gas", @@ -3422,18 +4000,6 @@ class ExistingBoilerInputs(BaseModel, models.Model): help_text="Boiler thermal production type, hot water or steam" ) - ''' - We dont need to add CHP prime mover because this field is either set by CHP tech. Adding this here results in infeasible solutions. - chp_prime_mover = models.TextField( - blank=True, - null=False, - choices=CHP_PRIME_MOVER.choices, - default="", - help_text="" - ) - - ''' - max_thermal_factor_on_peak_load = models.FloatField( validators=[ MinValueValidator(1.0), @@ -3527,6 +4093,13 @@ class ExistingBoilerInputs(BaseModel, models.Model): # For custom validations within model. def clean(self): + error_messages = {} + if not self.dict.get("fuel_cost_per_mmbtu"): + error_messages["required inputs"] = "Must provide fuel_cost_per_mmbtu to model {}".format(self.key) + + if error_messages: + raise ValidationError(error_messages) + self.fuel_cost_per_mmbtu = scalar_to_vector(self.fuel_cost_per_mmbtu) if self.emissions_factor_lb_CO2_per_mmbtu == None: @@ -3554,7 +4127,7 @@ class ExistingBoilerOutputs(BaseModel, models.Model): year_one_fuel_consumption_mmbtu = models.FloatField(null=True, blank=True) - year_one_fuel_consumption_mmbtu_per_hour = ArrayField( + year_one_fuel_consumption_series_mmbtu_per_hour = ArrayField( models.FloatField(null=True, blank=True), default=list, ) @@ -3563,13 +4136,17 @@ class ExistingBoilerOutputs(BaseModel, models.Model): lifecycle_fuel_cost_after_tax_bau = models.FloatField(null=True, blank=True) year_one_thermal_production_mmbtu = models.FloatField(null=True, blank=True) year_one_fuel_cost_before_tax = models.FloatField(null=True, blank=True) - thermal_to_tes_series_mmbtu_per_hour = models.FloatField(null=True, blank=True) - thermal_to_tes_series_mmbtu_per_hour = ArrayField( + year_one_thermal_to_tes_series_mmbtu_per_hour = ArrayField( models.FloatField(null=True, blank=True), default = list, ) - year_one_thermal_production_mmbtu_per_hour = ArrayField( + year_one_thermal_to_steamturbine_series_mmbtu_per_hour = ArrayField( + models.FloatField(null=True, blank=True), + default = list, + ) + + year_one_thermal_production_series_mmbtu_per_hour = ArrayField( models.FloatField(null=True, blank=True), default = list, ) @@ -4107,6 +4684,9 @@ def filter_none_and_empty_array(d:dict): try: d["DomesticHotWaterLoad"] = filter_none_and_empty_array(meta.DomesticHotWaterLoadInputs.dict) except: pass + try: d["CHP"] = filter_none_and_empty_array(meta.CHPInputs.dict) + except: pass + return d ''' @@ -4120,4 +4700,4 @@ def scalar_to_vector(vec:list): days_per_month = [31,28,31,30,31,30,31,31,30,31,30,31] return numpy.repeat(vec, [i * 24 for i in days_per_month]).tolist() else: - return vec # the vector len was not 1, handle it elsewhere \ No newline at end of file + return vec # the vector len was not 1 or 12, handle it elsewhere \ No newline at end of file diff --git a/job/src/process_results.py b/job/src/process_results.py index 449583608..e79665469 100644 --- a/job/src/process_results.py +++ b/job/src/process_results.py @@ -28,7 +28,8 @@ # OF THE POSSIBILITY OF SUCH DAMAGE. # ********************************************************************************* from job.models import FinancialOutputs, APIMeta, PVOutputs, ElectricStorageOutputs, ElectricTariffOutputs, SiteOutputs,\ - ElectricUtilityOutputs, GeneratorOutputs, ElectricLoadOutputs, WindOutputs, FinancialInputs, ElectricUtilityInputs, ExistingBoilerOutputs + ElectricUtilityOutputs, GeneratorOutputs, ElectricLoadOutputs, WindOutputs, FinancialInputs, ElectricUtilityInputs,\ + ExistingBoilerOutputs, CHPOutputs, CHPInputs import logging log = logging.getLogger(__name__) @@ -62,6 +63,8 @@ def process_results(results: dict, run_uuid: str) -> None: # BoilerOutputs.create(meta=meta, **results["Boiler"]).save() if "ExistingBoiler" in results.keys(): ExistingBoilerOutputs.create(meta=meta, **results["ExistingBoiler"]).save() + if "CHP" in results.keys(): + CHPOutputs.create(meta=meta, **results["CHP"]).save() # TODO process rest of results @@ -77,6 +80,8 @@ def update_inputs_in_database(inputs_to_update: dict, run_uuid: str) -> None: # get input models that need updating FinancialInputs.objects.filter(meta__run_uuid=run_uuid).update(**inputs_to_update["Financial"]) ElectricUtilityInputs.objects.filter(meta__run_uuid=run_uuid).update(**inputs_to_update["ElectricUtility"]) + if inputs_to_update["CHP"]: # Will be an empty dictionary if CHP is not considered + CHPInputs.objects.filter(meta__run_uuid=run_uuid).update(**inputs_to_update["CHP"]) except Exception as e: exc_type, exc_value, exc_traceback = sys.exc_info() debug_msg = "exc_type: {}; exc_value: {}; exc_traceback: {}".format( diff --git a/job/test/posts/chp_defaults_post.json b/job/test/posts/chp_defaults_post.json new file mode 100644 index 000000000..a5ff78d56 --- /dev/null +++ b/job/test/posts/chp_defaults_post.json @@ -0,0 +1,40 @@ +{ + "Site": { + "latitude": 37.78, + "longitude": -122.45 + }, + "ElectricLoad": { + "doe_reference_name": "Hospital" + }, + "SpaceHeatingLoad": { + "doe_reference_name": "Hospital" + }, + "DomesticHotWaterLoad": { + "doe_reference_name": "Hospital" + }, + "ElectricTariff": { + "blended_annual_energy_rate": 0.1, + "blended_annual_demand_rate": 10 + }, + "ExistingBoiler": { + "fuel_cost_per_mmbtu": 10.0, + "production_type": "steam" + }, + "CHP": { + "min_kw": 200.0, + "min_allowable_kw": 0.0, + "max_kw": 200.0, + "fuel_cost_per_mmbtu": 10.0, + "om_cost_per_kwh": 0.0246 + }, + "PV": { + "min_kw": 0, + "max_kw": 0 + }, + "ElectricStorage": { + "min_kw": 0, + "max_kw": 0, + "min_kwh": 0, + "max_kwh": 0 + } +} diff --git a/job/test/posts/validator_post.json b/job/test/posts/validator_post.json index 432af60c0..706f6feff 100644 --- a/job/test/posts/validator_post.json +++ b/job/test/posts/validator_post.json @@ -19055,5 +19055,33 @@ "macrs_bonus_fraction": 0.1, "total_itc_fraction": 0.3, "total_rebate_us_dollars_per_kw": 1000 - } + }, + "CHP": { + "prime_mover": "recip_engine", + "fuel_cost_per_mmbtu": 10.0, + "min_kw": 100, + "max_kw": 800, + "installed_cost_per_kw": 2416.7, + "om_cost_per_kw": 149.8, + "om_cost_per_kwh": 0.0, + "om_cost_per_hr_per_kw_rated": 0.0, + "electric_efficiency_full_load": 0.3573, + "electric_efficiency_half_load": 0.3216, + "min_turn_down_fraction": 0.5, + "thermal_efficiency_full_load": 0.4418, + "thermal_efficiency_half_load": 0.4664, + "macrs_option_years": 0, + "macrs_bonus_fraction": 0.0, + "macrs_itc_reduction": 0.0, + "federal_itc_fraction": 0.0, + "unavailability_periods": [ + { + "month": 1, + "start_week_of_month": 2, + "start_day_of_week": 6, + "start_hour": 1, + "duration_hours": 0 + } + ] + } } diff --git a/job/test/test_http_endpoints.py b/job/test/test_http_endpoints.py new file mode 100644 index 000000000..69473209c --- /dev/null +++ b/job/test/test_http_endpoints.py @@ -0,0 +1,102 @@ + +import json +from tastypie.test import ResourceTestCaseMixin +from django.test import TestCase # have to use unittest.TestCase to get tests to store to database, django.test.TestCase flushes db +import logging +import os +import requests +logging.disable(logging.CRITICAL) + +class TestHTTPEndpoints(ResourceTestCaseMixin, TestCase): + + def test_chp_defaults(self): + + inputs = {"existing_boiler_production_type": "hot_water", + "avg_boiler_fuel_load_mmbtu_per_hour": 28.0 + } + + # Direct call of the http.jl endpoint /chp_defaults + julia_host = os.environ.get('JULIA_HOST', "julia") + response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) + http_response = response.json() + + # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + resp = self.api_client.get(f'/dev/chp_defaults', data=inputs) + view_response = json.loads(resp.content) + + mismatch = [] + for k, v in http_response["default_inputs"].items(): + if v != view_response["default_inputs"][k]: + mismatch.append(k) + + self.assertEqual(mismatch, []) + + # Check the endpoint logic with the expected selection + self.assertEqual(http_response["prime_mover"], "combustion_turbine") + self.assertEqual(http_response["size_class"], 4) + self.assertGreater(http_response["chp_size_based_on_avg_heating_load_kw"], 3500.0) + + # Check that size_class logic is the same, but we shifted it to 1-indexed instead of 0-indexed + # Modify input names for v2 + inputs_v2 = { + "existing_boiler_production_type_steam_or_hw": inputs["existing_boiler_production_type"], + "avg_boiler_fuel_load_mmbtu_per_hr": inputs["avg_boiler_fuel_load_mmbtu_per_hour"] + } + resp = self.api_client.get(f'/v2/chp_defaults', data=inputs_v2) + v2_response = json.loads(resp.content) + self.assertEqual(http_response["size_class"], v2_response["size_class"]+1) + + def test_simulated_load(self): + + # Test heating load because REopt.jl separates SpaceHeating and DHW, so had to aggregate for this endpoint + inputs = {"load_type": "heating", + "doe_reference_name": "Hospital", + "latitude": 37.78, + "longitude": -122.45 + } + + # Direct call of the http.jl endpoint /chp_defaults + julia_host = os.environ.get('JULIA_HOST', "julia") + response = requests.get("http://" + julia_host + ":8081/simulated_load/", json=inputs) + http_response = response.json() + + # Call to the v2 /simulated_load to check for consistency + resp = self.api_client.get(f'/v2/simulated_load', data=inputs) + v2_response = json.loads(resp.content) + self.assertAlmostEqual(http_response["annual_mmbtu"], v2_response["annual_mmbtu"], delta=1.0) + + # Test blended/hybrid buildings + inputs["load_type"] = "electric" + 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]"] + + # Direct call of the http.jl endpoint /chp_defaults + julia_host = os.environ.get('JULIA_HOST', "julia") + response = requests.get("http://" + julia_host + ":8081/simulated_load/", json=inputs) + http_response = response.json() + + # Call to the v2 /simulated_load to check for consistency + resp = self.api_client.get(f'/v2/simulated_load', data=inputs) + v2_response = json.loads(resp.content) + self.assertAlmostEqual(http_response["annual_kwh"], v2_response["annual_kwh"], delta=1.0) + + # Test bad inputs + inputs["invalid_key"] = "invalid_val" + resp = self.api_client.get(f'/v2/simulated_load', data=inputs) + v2_response = json.loads(resp.content) + assert("Error" in v2_response.keys()) + +# For POSTing to an endpoint which returns a `run_uuid` to later GET the results from the database +# resp = self.api_client.post('/dev/job/', format='json', data=scenario)) +# self.assertHttpCreated(resp) +# r = json.loads(resp.content) +# run_uuid = r.get('run_uuid') + +# This method is used for calling a REopt_API endpoint (/ghpghx) from within the scenario.py celery task +# client = TestApiClient() +# ghpghx_results_url = "/v1/ghpghx/"+ghpghx_uuid_list[i]+"/results/" +# ghpghx_results_resp = client.get(ghpghx_results_url) # same as doing ghpModelManager.make_response(ghp_uuid) +# ghpghx_results_resp_dict = json.loads(ghpghx_results_resp.content) \ No newline at end of file diff --git a/job/test/test_job_endpoint.py b/job/test/test_job_endpoint.py index 2ef05824e..32d7c8fb1 100644 --- a/job/test/test_job_endpoint.py +++ b/job/test/test_job_endpoint.py @@ -32,6 +32,7 @@ from django.test import TestCase # have to use unittest.TestCase to get tests to store to database, django.test.TestCase flushes db import logging logging.disable(logging.CRITICAL) +import os class TestJobEndpoint(ResourceTestCaseMixin, TestCase): @@ -159,3 +160,36 @@ def test_off_grid_defaults(self): self.assertAlmostEqual(results["ElectricLoad"]["offgrid_load_met_fraction"], 0.99999, places=-2) self.assertAlmostEqual(sum(results["ElectricLoad"]["offgrid_load_met_series_kw"]), 8760.0, places=-1) self.assertAlmostEqual(results["Financial"]["lifecycle_offgrid_other_annual_costs_after_tax"], 0.0, places=-2) + + def test_chp_defaults_from_julia(self): + # Test that the inputs_with_defaults_set_in_julia feature worked for CHP, consistent with /chp_defaults + post_file = os.path.join('job', 'test', 'posts', 'chp_defaults_post.json') + post = json.load(open(post_file, 'r')) + # Make average MMBtu/hr thermal steam greater than 7 MMBtu/hr threshold for combustion_turbine to be chosen + # Default ExistingBoiler efficiency for production_type = steam is 0.75 + post["SpaceHeatingLoad"]["annual_mmbtu"] = 8760 * 8 / 0.75 + post["DomesticHotWaterLoad"]["annual_mmbtu"] = 8760 * 8 / 0.75 + resp = self.api_client.post('/dev/job/', format='json', data=post) + self.assertHttpCreated(resp) + r = json.loads(resp.content) + run_uuid = r.get('run_uuid') + + resp = self.api_client.get(f'/dev/job/{run_uuid}/results') + r = json.loads(resp.content) + inputs_chp = r["inputs"]["CHP"] + + avg_fuel_load = (post["SpaceHeatingLoad"]["annual_mmbtu"] + + post["DomesticHotWaterLoad"]["annual_mmbtu"]) / 8760.0 + inputs_chp_defaults = {"existing_boiler_production_type": post["ExistingBoiler"]["production_type"], + "avg_boiler_fuel_load_mmbtu_per_hour": avg_fuel_load + } + + # Call to the django view endpoint /chp_defaults which calls the http.jl endpoint + resp = self.api_client.get(f'/dev/chp_defaults', data=inputs_chp_defaults) + view_response = json.loads(resp.content) + + for key in view_response["default_inputs"].keys(): + if post["CHP"].get(key) is None: # Check that default got assigned consistent with /chp_defaults + self.assertEquals(inputs_chp[key], view_response["default_inputs"][key]) + else: # Make sure we didn't overwrite user-input + self.assertEquals(inputs_chp[key], post["CHP"][key]) diff --git a/job/test/test_validator.py b/job/test/test_validator.py index 7f23df7dd..286e0fd7f 100644 --- a/job/test/test_validator.py +++ b/job/test/test_validator.py @@ -256,3 +256,17 @@ def test_missing_required_keys(self): assert("Missing required inputs." in validator.validation_errors[key]) else: assert(key not in validator.validation_errors.keys()) + + # check for missing CHP inputs + post = copy.deepcopy(self.post) + post["APIMeta"]["run_uuid"] = uuid.uuid4() + post["CHP"].pop("fuel_cost_per_mmbtu") + validator = InputValidator(post) + validator.clean_fields() + validator.clean() + validator.cross_clean() + assert("required inputs" in validator.validation_errors["CHP"].keys()) + + + + diff --git a/job/urls.py b/job/urls.py index 62d62406f..f16f899f2 100644 --- a/job/urls.py +++ b/job/urls.py @@ -35,4 +35,6 @@ re_path(r'^help/?$', views.help), re_path(r'^job/inputs/?$', views.inputs), re_path(r'^job/outputs/?$', views.outputs), + re_path(r'^chp_defaults/?$', views.chp_defaults), + re_path(r'^simulated_load/?$', views.simulated_load), ] diff --git a/job/validators.py b/job/validators.py index cf33f743b..b03182992 100644 --- a/job/validators.py +++ b/job/validators.py @@ -31,7 +31,7 @@ import pandas as pd from job.models import MAX_BIG_NUMBER, APIMeta, ExistingBoilerInputs, UserProvidedMeta, SiteInputs, Settings, ElectricLoadInputs, ElectricTariffInputs, \ FinancialInputs, BaseModel, Message, ElectricUtilityInputs, PVInputs, ElectricStorageInputs, GeneratorInputs, WindInputs, SpaceHeatingLoadInputs, \ - DomesticHotWaterLoadInputs + DomesticHotWaterLoadInputs, CHPInputs from django.core.exceptions import ValidationError from pyproj import Proj from typing import Tuple @@ -96,7 +96,8 @@ def __init__(self, raw_inputs: dict): WindInputs, ExistingBoilerInputs, SpaceHeatingLoadInputs, - DomesticHotWaterLoadInputs + DomesticHotWaterLoadInputs, + CHPInputs ) self.pvnames = [] on_grid_required_object_names = [ @@ -402,19 +403,11 @@ def update_pv_defaults_offgrid(self): """ if "ExistingBoiler" in self.models.keys(): - # Clean fuel_cost_per_mmbtu to align with time series - self.clean_time_series("ExistingBoiler", "fuel_cost_per_mmbtu") - if self.models["ExistingBoiler"].efficiency is None: - if "CHP" in self.models.keys(): - pass - # TODO add CHP based validation on ExistingBoiler efficiency - # Get production type by chp prime mover + if self.models["ExistingBoiler"].production_type == 'hot_water': + self.models["ExistingBoiler"].efficiency = 0.8 else: - if self.models["ExistingBoiler"].production_type == 'hot_water': - self.models["ExistingBoiler"].efficiency = 0.8 - else: - self.models["ExistingBoiler"].efficiency = 0.75 + self.models["ExistingBoiler"].efficiency = 0.75 """ ElectricLoad diff --git a/job/views.py b/job/views.py index 381773650..0565cc753 100644 --- a/job/views.py +++ b/job/views.py @@ -36,8 +36,11 @@ from job.models import Settings, PVInputs, ElectricStorageInputs, WindInputs, GeneratorInputs, ElectricLoadInputs,\ ElectricTariffInputs, ElectricUtilityInputs, SpaceHeatingLoadInputs, PVOutputs, ElectricStorageOutputs, WindOutputs, ExistingBoilerInputs,\ GeneratorOutputs, ElectricTariffOutputs, ElectricUtilityOutputs, ElectricLoadOutputs, ExistingBoilerOutputs, \ - DomesticHotWaterLoadInputs, SiteInputs, SiteOutputs, APIMeta, UserProvidedMeta - + DomesticHotWaterLoadInputs, SiteInputs, SiteOutputs, APIMeta, UserProvidedMeta, CHPInputs, CHPOutputs +import os +import requests +import logging +log = logging.getLogger(__name__) def make_error_resp(msg): resp = dict() @@ -66,6 +69,7 @@ def help(request): d["SpaceHeatingLoad"] = SpaceHeatingLoadInputs.info_dict(SpaceHeatingLoadInputs) d["DomesticHotWaterLoad"] = DomesticHotWaterLoadInputs.info_dict(DomesticHotWaterLoadInputs) d["Site"] = SiteInputs.info_dict(SiteInputs) + d["CHP"] = CHPInputs.info_dict(CHPInputs) return JsonResponse(d) except Exception as e: @@ -101,6 +105,7 @@ def outputs(request): d["Generator"] = GeneratorOutputs.info_dict(GeneratorOutputs) d["ExistingBoiler"] = ExistingBoilerOutputs.info_dict(ExistingBoilerOutputs) # d["Boiler"] = BoilerOutputs.info_dict(BoilerOutputs) + d["CHP"] = CHPOutputs.info_dict(CHPOutputs) return JsonResponse(d) except Exception as e: @@ -198,6 +203,9 @@ def results(request, run_uuid): try: r["inputs"]["DomesticHotWaterLoad"] = meta.DomesticHotWaterLoadInputs.dict except: pass + try: r["inputs"]["CHP"] = meta.CHPInputs.dict + except: pass + try: r["outputs"] = dict() r["messages"] = dict() @@ -234,6 +242,8 @@ def results(request, run_uuid): except: pass # try: r["outputs"]["Boiler"] = meta.BoilerOutputs.dict # except: pass + try: r["outputs"]["CHP"] = meta.CHPOutputs.dict + except: pass for d in r["outputs"].values(): if isinstance(d, dict): @@ -254,3 +264,112 @@ def results(request, run_uuid): return JsonResponse(resp, status=500) return JsonResponse(r) + + +def chp_defaults(request): + inputs = { + "existing_boiler_production_type": request.GET.get("existing_boiler_production_type"), + "avg_boiler_fuel_load_mmbtu_per_hour": request.GET.get("avg_boiler_fuel_load_mmbtu_per_hour"), + "prime_mover": request.GET.get("prime_mover"), + "size_class": request.GET.get("size_class"), + "boiler_efficiency": request.GET.get("boiler_efficiency") + } + try: + julia_host = os.environ.get('JULIA_HOST', "julia") + http_jl_response = requests.get("http://" + julia_host + ":8081/chp_defaults/", json=inputs) + response = JsonResponse( + http_jl_response.json() + ) + return response + + except ValueError as e: + return JsonResponse({"Error": str(e.args[0])}, status=500) + + except KeyError as e: + return JsonResponse({"Error. Missing": str(e.args[0])}, status=500) + + except Exception: + exc_type, exc_value, exc_traceback = sys.exc_info() + debug_msg = "exc_type: {}; exc_value: {}; exc_traceback: {}".format(exc_type, exc_value.args[0], + tb.format_tb(exc_traceback)) + log.debug(debug_msg) + return JsonResponse({"Error": "Unexpected error in chp_defaults endpoint. Check log for more."}, status=500) + +def simulated_load(request): + try: + valid_keys = ["doe_reference_name","latitude","longitude","load_type","percent_share","annual_kwh", + "monthly_totals_kwh","annual_mmbtu","annual_fraction","annual_tonhour","monthly_tonhour", + "monthly_mmbtu","monthly_fraction","max_thermal_factor_on_peak_load","chiller_cop", + "addressable_load_fraction", "cooling_doe_ref_name", "cooling_pct_share"] + for key in request.GET.keys(): + k = key + if "[" in key: + k = key.split('[')[0] + if k not in valid_keys: + raise ValueError("{} is not a valid input parameter".format(key)) + # Build inputs dictionary to send to http.jl /simulated_load endpoint + inputs = {} + inputs["latitude"] = float(request.GET['latitude']) # need float to convert unicode + inputs["longitude"] = float(request.GET['longitude']) + inputs["load_type"] = request.GET.get('load_type') + + # This parses the GET request way of sending a list/array for doe_reference_name, + # i.e. doe_reference_name[0], doe_reference_name[1], etc + if 'doe_reference_name' in request.GET.keys(): + inputs["doe_reference_name"] = request.GET.get('doe_reference_name') + elif 'doe_reference_name[0]' in request.GET.keys(): + idx = 0 + doe_reference_name = [] + percent_share_list = [] + while 'doe_reference_name[{}]'.format(idx) in request.GET.keys(): + doe_reference_name.append(request.GET['doe_reference_name[{}]'.format(idx)]) + if 'percent_share[{}]'.format(idx) in request.GET.keys(): + percent_share_list.append(float(request.GET['percent_share[{}]'.format(idx)])) + idx += 1 + inputs["doe_reference_name"] = doe_reference_name + inputs["percent_share_list"] = percent_share_list + + # When wanting cooling profile based on building type(s) for cooling, need separate cooling building(s) + if 'cooling_doe_ref_name' in request.GET.keys(): + inputs["cooling_doe_ref_name"] = request.GET.get('cooling_doe_ref_name') + elif 'cooling_doe_ref_name[0]' in request.GET.keys(): + idx = 0 + cooling_doe_ref_name = [] + cooling_pct_share_list = [] + while 'cooling_doe_ref_name[{}]'.format(idx) in request.GET.keys(): + cooling_doe_ref_name.append(request.GET['cooling_doe_ref_name[{}]'.format(idx)]) + if 'cooling_pct_share[{}]'.format(idx) in request.GET.keys(): + cooling_pct_share_list.append(float(request.GET['cooling_pct_share[{}]'.format(idx)])) + idx += 1 + inputs["cooling_doe_ref_name"] = cooling_doe_ref_name + inputs["cooling_pct_share"] = cooling_pct_share_list + + # Build the rest of inputs for http.jl /simulated_load endpoint + other_keys_types = ["annual", "monthly", "max_thermal_factor", "chiller_cop", "addressable"] + for key in valid_keys: + for key_type in other_keys_types: + if key_type in key: + value = request.GET.get(key) + if value is not None: + inputs[key] = value + + julia_host = os.environ.get('JULIA_HOST', "julia") + http_jl_response = requests.get("http://" + julia_host + ":8081/simulated_load/", json=inputs) + response = JsonResponse( + http_jl_response.json() + ) + + return response + + except ValueError as e: + return JsonResponse({"Error": str(e.args[0])}, status=500) + + except KeyError as e: + return JsonResponse({"Error. Missing": str(e.args[0])}, status=500) + + except Exception: + exc_type, exc_value, exc_traceback = sys.exc_info() + debug_msg = "exc_type: {}; exc_value: {}; exc_traceback: {}".format(exc_type, exc_value.args[0], + tb.format_tb(exc_traceback)) + log.debug(debug_msg) + return JsonResponse({"Error": "Unexpected error in simulated_load endpoint. Check log for more."}, status=500) diff --git a/julia_src/Manifest.toml b/julia_src/Manifest.toml index 6e2281a4c..591975272 100644 --- a/julia_src/Manifest.toml +++ b/julia_src/Manifest.toml @@ -17,30 +17,36 @@ version = "3.4.0" [[deps.ArchGDAL]] deps = ["CEnum", "ColorTypes", "Dates", "DiskArrays", "Extents", "GDAL", "GeoFormatTypes", "GeoInterface", "GeoInterfaceRecipes", "ImageCore", "Tables"] -git-tree-sha1 = "83b8798c00f556c0a8ac89cad7bde716a28fe9c9" +git-tree-sha1 = "70908bb727c9a0ba863c5145aa48ee838cc29b84" uuid = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3" -version = "0.9.2" +version = "0.9.3" [[deps.ArgTools]] uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +[[deps.Arrow_jll]] +deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Lz4_jll", "Pkg", "Thrift_jll", "Zlib_jll", "boost_jll", "snappy_jll"] +git-tree-sha1 = "d64cb60c0e6a138fbe5ea65bcbeea47813a9a700" +uuid = "8ce61222-c28f-5041-a97a-c2198fb817bf" +version = "10.0.0+1" + [[deps.Artifacts]] uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" [[deps.AxisArrays]] deps = ["Dates", "IntervalSets", "IterTools", "RangeArrays"] -git-tree-sha1 = "cf6875678085aed97f52bfc493baaebeb6d40bcb" +git-tree-sha1 = "1dd4d9f5beebac0c03446918741b1a03dc5e5788" uuid = "39de3d68-74b9-583c-8d2d-e117c070f3a9" -version = "0.4.5" +version = "0.4.6" [[deps.Base64]] uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" [[deps.BenchmarkTools]] deps = ["JSON", "Logging", "Printf", "Profile", "Statistics", "UUIDs"] -git-tree-sha1 = "4c10eee4af024676200bc7752e536f858c6b8f93" +git-tree-sha1 = "d9a9701b899b30332bbcb3e1679c41cce81fb0e8" uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" -version = "1.3.1" +version = "1.3.2" [[deps.Blosc]] deps = ["Blosc_jll"] @@ -65,11 +71,11 @@ git-tree-sha1 = "eb4cb44a499229b3b8426dcfb5dd85333951ff90" uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82" version = "0.4.2" -[[deps.Calculus]] -deps = ["LinearAlgebra"] -git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad" -uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9" -version = "0.5.1" +[[deps.CSV]] +deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings"] +git-tree-sha1 = "c5fd7cd27ac4aed0acf4b73948f0110ff2a854b2" +uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +version = "0.10.7" [[deps.ChainRulesCore]] deps = ["Compat", "LinearAlgebra", "SparseArrays"] @@ -126,9 +132,9 @@ version = "0.3.0" [[deps.Compat]] deps = ["Dates", "LinearAlgebra", "UUIDs"] -git-tree-sha1 = "3ca828fe1b75fa84b021a7860bd039eaea84d2f2" +git-tree-sha1 = "aaabba4ce1b7f8a9b34c015053d3b1edf60fa49c" uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" -version = "4.3.0" +version = "4.4.0" [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] @@ -153,9 +159,9 @@ uuid = "3351c21f-4feb-5f29-afb9-f4fcb0e27549" version = "6.4.2+0" [[deps.DataAPI]] -git-tree-sha1 = "46d2680e618f8abd007bce0c3026cb0c4a8f2032" +git-tree-sha1 = "e08915633fcb3ea83bf9d6126292e5bc5c739922" uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" -version = "1.12.0" +version = "1.13.0" [[deps.DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] @@ -184,9 +190,9 @@ version = "1.1.0" [[deps.DiffRules]] deps = ["IrrationalConstants", "LogExpFunctions", "NaNMath", "Random", "SpecialFunctions"] -git-tree-sha1 = "8b7a4d23e22f5d44883671da70865ca98f2ebf9d" +git-tree-sha1 = "c5b6685d53f933c11404a3ae9822afe30d522494" uuid = "b552c78f-8df3-52c6-915a-8e097449b14b" -version = "1.12.0" +version = "1.12.2" [[deps.DiskArrays]] deps = ["OffsetArrays"] @@ -194,6 +200,10 @@ git-tree-sha1 = "bd0543363fc8ceac0c9fb24d38783ffffd95c20c" uuid = "3c3547ce-8d99-4f5e-a174-61eb10b00ae3" version = "0.3.7" +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + [[deps.DocStringExtensions]] deps = ["LibGit2"] git-tree-sha1 = "c36550cb29cbe373e95b3f40486b9a4148f89ffd" @@ -221,6 +231,18 @@ git-tree-sha1 = "7be5f99f7d15578798f338f5433b6c432ea8037b" uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" version = "1.16.0" +[[deps.FilePaths]] +deps = ["FilePathsBase", "MacroTools", "Reexport", "Requires"] +git-tree-sha1 = "919d9412dbf53a2e6fe74af62a73ceed0bce0629" +uuid = "8fc22ac5-c921-52a6-82fd-178b2807b824" +version = "0.8.3" + +[[deps.FilePathsBase]] +deps = ["Compat", "Dates", "Mmap", "Printf", "Test", "UUIDs"] +git-tree-sha1 = "e27c4ebe80e8699540f2d6c805cc12203b614f12" +uuid = "48062228-2e41-5def-b9a4-89aafe57970f" +version = "0.9.20" + [[deps.FixedPointNumbers]] deps = ["Statistics"] git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc" @@ -229,9 +251,9 @@ version = "0.8.4" [[deps.ForwardDiff]] deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "LogExpFunctions", "NaNMath", "Preferences", "Printf", "Random", "SpecialFunctions", "StaticArrays"] -git-tree-sha1 = "187198a4ed8ccd7b5d99c41b69c679269ea2b2d4" +git-tree-sha1 = "10fa12fe96e4d76acfa738f4df2126589a67374f" uuid = "f6369f11-7733-5829-9624-2563aa707210" -version = "0.10.32" +version = "0.10.33" [[deps.Future]] deps = ["Random"] @@ -239,15 +261,15 @@ uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" [[deps.GDAL]] deps = ["CEnum", "GDAL_jll", "NetworkOptions", "PROJ_jll"] -git-tree-sha1 = "9ce70502472a9f23f8889f0f9e2be8451413fe7b" +git-tree-sha1 = "0027ae257eae94ffe1757c038b6e7b56b65565b3" uuid = "add2ef01-049f-52c4-9ee2-e494f65e021a" -version = "1.4.0" +version = "1.5.0" [[deps.GDAL_jll]] -deps = ["Artifacts", "Expat_jll", "GEOS_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "Libtiff_jll", "OpenJpeg_jll", "PROJ_jll", "Pkg", "SQLite_jll", "Zlib_jll", "Zstd_jll", "libgeotiff_jll"] -git-tree-sha1 = "58d7d54715f6bb37b4dde1dab0781c75dbb6d02d" +deps = ["Arrow_jll", "Artifacts", "Expat_jll", "GEOS_jll", "HDF5_jll", "JLLWrappers", "LibCURL_jll", "LibPQ_jll", "Libdl", "Libtiff_jll", "NetCDF_jll", "OpenJpeg_jll", "PROJ_jll", "Pkg", "SQLite_jll", "Zlib_jll", "Zstd_jll", "libgeotiff_jll"] +git-tree-sha1 = "fbead7c60556297f540ecfa6e06d1bb64919a56f" uuid = "a7073274-a066-55f0-b90d-d619367d196c" -version = "300.500.1+0" +version = "301.600.0+0" [[deps.GEOS_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -272,6 +294,11 @@ git-tree-sha1 = "29e1ec25cfb6762f503a19495aec347acf867a9e" uuid = "0329782f-3d07-4b52-b9f6-d3137cf03c7a" version = "1.0.0" +[[deps.Glob]] +git-tree-sha1 = "4df9f7e06108728ebf00a0a11edee4b29a482bb2" +uuid = "c27321d9-0574-5035-807b-f59d2c89b15c" +version = "1.3.0" + [[deps.Graphics]] deps = ["Colors", "LinearAlgebra", "NaNMath"] git-tree-sha1 = "d61890399bc535850c4bf08e4e0d3a7ad0f21cbd" @@ -308,20 +335,32 @@ git-tree-sha1 = "acf614720ef026d38400b3817614c45882d75500" uuid = "a09fc81d-aa75-5fe9-8630-4744c3626534" version = "0.9.4" +[[deps.InfrastructureModels]] +deps = ["JuMP", "Memento"] +git-tree-sha1 = "cca034ca1ae73e08bf4db63f158474d40d7dad9a" +uuid = "2030c09a-7f63-5d83-885d-db604e0e9cc0" +version = "0.7.5" + [[deps.IniFile]] git-tree-sha1 = "f550e6e32074c939295eb5ea6de31849ac2c9625" uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f" version = "0.5.1" +[[deps.InlineStrings]] +deps = ["Parsers"] +git-tree-sha1 = "0cf92ec945125946352f3d46c96976ab972bde6f" +uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" +version = "1.3.2" + [[deps.InteractiveUtils]] deps = ["Markdown"] uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" [[deps.IntervalSets]] -deps = ["Dates", "Statistics"] -git-tree-sha1 = "ad841eddfb05f6d9be0bff1fa48dcae32f134a2d" +deps = ["Dates", "Random", "Statistics"] +git-tree-sha1 = "3f91cd3f56ea48d4d2a75c2a65455c5fc74fa347" uuid = "8197267c-284f-5f27-9208-e0e47529a953" -version = "0.6.2" +version = "0.7.3" [[deps.InverseFunctions]] deps = ["Test"] @@ -362,12 +401,6 @@ git-tree-sha1 = "3c837543ddb02250ef42f4738347454f95079d4e" uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" version = "0.21.3" -[[deps.JSONSchema]] -deps = ["HTTP", "JSON", "URIs"] -git-tree-sha1 = "2f49f7f86762a0fbbeef84912265a1ae61c4ef80" -uuid = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" -version = "0.3.4" - [[deps.JpegTurbo_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "b53380851c6e6664204efb2e62cd24fa5c47e4ba" @@ -375,10 +408,16 @@ uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" version = "2.1.2+0" [[deps.JuMP]] -deps = ["Calculus", "DataStructures", "ForwardDiff", "JSON", "LinearAlgebra", "MathOptInterface", "MutableArithmetics", "NaNMath", "Printf", "Random", "SparseArrays", "SpecialFunctions", "Statistics"] -git-tree-sha1 = "4358b7cbf2db36596bdbbe3becc6b9d87e4eb8f5" +deps = ["LinearAlgebra", "MathOptInterface", "MutableArithmetics", "OrderedCollections", "Printf", "SparseArrays"] +git-tree-sha1 = "9a57156b97ed7821493c9c0a65f5b72710b38cf7" uuid = "4076af6c-e467-56ae-b986-b466b2749572" -version = "0.21.10" +version = "1.4.0" + +[[deps.Kerberos_krb5_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "60274b4ab38e8d1248216fe6b6ace75ae09b0502" +uuid = "b39eb1a6-c29a-53d7-8c32-632cd16f18da" +version = "1.19.3+0" [[deps.LERC_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] @@ -386,6 +425,12 @@ git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434" uuid = "88015f11-f218-50d7-93a8-a6af411a945d" version = "3.0.0+1" +[[deps.LZO_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "e5b909bcf985c5e2605737d2ce278ed791b89be6" +uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" +version = "2.10.1+0" + [[deps.LibCURL]] deps = ["LibCURL_jll", "MozillaCACerts_jll"] uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" @@ -398,6 +443,12 @@ uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" deps = ["Base64", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +[[deps.LibPQ_jll]] +deps = ["Artifacts", "JLLWrappers", "Kerberos_krb5_jll", "Libdl", "OpenSSL_jll", "Pkg"] +git-tree-sha1 = "a299629703a93d8efcefccfc16b18ad9a073d131" +uuid = "08be9ffa-1c94-5ee5-a977-46a84ec9b350" +version = "14.3.0+1" + [[deps.LibSSH2_jll]] deps = ["Artifacts", "Libdl", "MbedTLS_jll"] uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" @@ -405,6 +456,12 @@ uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "42b62845d70a619f063a7da093d995ec8e15e778" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.16.1+1" + [[deps.Libtiff_jll]] deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"] git-tree-sha1 = "3eb79b0ca5764d4799c06699573fd8f533259713" @@ -412,10 +469,10 @@ uuid = "89763e89-9b03-5906-acba-b20f662cd828" version = "4.4.0+0" [[deps.LinDistFlow]] -deps = ["JuMP", "LinearAlgebra", "Logging", "SparseArrays"] -git-tree-sha1 = "ad15878e716a18b82325cacc02af4533bb9777e7" +deps = ["JuMP", "LinearAlgebra", "PowerModelsDistribution", "SparseArrays"] +git-tree-sha1 = "c6c652ab975386cab61559e4ac861e8f75932e94" uuid = "bf674bac-ffe4-48d3-9f32-72124ffa9ede" -version = "0.1.4" +version = "0.2.0" [[deps.LinearAlgebra]] deps = ["Libdl", "libblastrampoline_jll"] @@ -436,6 +493,12 @@ version = "0.3.18" [[deps.Logging]] uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +[[deps.LoggingExtras]] +deps = ["Dates", "Logging"] +git-tree-sha1 = "5d4d2d9904227b8bd66386c1138cf4d5ffa826bf" +uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" +version = "0.4.9" + [[deps.Lz4_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] git-tree-sha1 = "5d494bc6e85c4c9b626ee0cab05daa4085486ab1" @@ -458,10 +521,10 @@ deps = ["Base64"] uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" [[deps.MathOptInterface]] -deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "JSON", "JSONSchema", "LinearAlgebra", "MutableArithmetics", "OrderedCollections", "SparseArrays", "Test", "Unicode"] -git-tree-sha1 = "575644e3c05b258250bb599e57cf73bbf1062901" +deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "DataStructures", "ForwardDiff", "JSON", "LinearAlgebra", "MutableArithmetics", "NaNMath", "OrderedCollections", "Printf", "SparseArrays", "SpecialFunctions", "Test", "Unicode"] +git-tree-sha1 = "ceed48edffe0325a6e9ea00ecf3607af5089c413" uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" -version = "0.9.22" +version = "1.9.0" [[deps.MbedTLS]] deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "Random", "Sockets"] @@ -473,6 +536,12 @@ version = "1.1.7" deps = ["Artifacts", "Libdl"] uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +[[deps.Memento]] +deps = ["Dates", "Distributed", "Requires", "Serialization", "Sockets", "Test", "UUIDs"] +git-tree-sha1 = "bb2e8f4d9f400f6e90d57b34860f6abdc51398e5" +uuid = "f28f55f0-a522-5efc-85c2-fe41dfb9b2d9" +version = "1.4.1" + [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" @@ -487,14 +556,21 @@ uuid = "14a3606d-f60d-562e-9121-12d972cd8159" [[deps.MutableArithmetics]] deps = ["LinearAlgebra", "SparseArrays", "Test"] -git-tree-sha1 = "8d9496b2339095901106961f44718920732616bb" +git-tree-sha1 = "1d57a7dc42d563ad6b5e95d7a8aebd550e5162c0" uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0" -version = "0.2.22" +version = "1.0.5" [[deps.NaNMath]] -git-tree-sha1 = "b086b7ea07f8e38cf122f5016af580881ac914fe" +deps = ["OpenLibm_jll"] +git-tree-sha1 = "a7c3d1da1189a1c2fe843a3bfa04d18d20eb3211" uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" -version = "0.3.7" +version = "1.0.1" + +[[deps.NetCDF_jll]] +deps = ["Artifacts", "HDF5_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "Pkg", "XML2_jll", "Zlib_jll"] +git-tree-sha1 = "072f8371f74c3b9e1b26679de7fbf059d45ea221" +uuid = "7243133f-43d8-5620-bbf4-c2c921802cf3" +version = "400.902.5+1" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" @@ -521,9 +597,9 @@ uuid = "05823500-19ac-5b8b-9628-191a04bc5112" [[deps.OpenSSL_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] -git-tree-sha1 = "e60321e3f2616584ff98f0a4f18d98ae6f89bbb3" +git-tree-sha1 = "f6e9dba33f9f2c44e08a020b0caf6903be540004" uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" -version = "1.1.17+0" +version = "1.1.19+0" [[deps.OpenSpecFun_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"] @@ -538,9 +614,9 @@ version = "1.4.1" [[deps.PROJ_jll]] deps = ["Artifacts", "JLLWrappers", "LibCURL_jll", "Libdl", "Libtiff_jll", "Pkg", "SQLite_jll"] -git-tree-sha1 = "12bd68665a0c3cb4635c4359d3fa9e2769ed59e5" +git-tree-sha1 = "fcb3f39ae1184a056ecc415863d46d2109aa6947" uuid = "58948b4f-47e0-5654-a9ad-f609743f8632" -version = "900.0.0+0" +version = "900.100.0+0" [[deps.PaddedViews]] deps = ["OffsetArrays"] @@ -549,15 +625,33 @@ uuid = "5432bcbf-9aad-5242-b902-cca2824c8663" version = "0.5.11" [[deps.Parsers]] -deps = ["Dates"] -git-tree-sha1 = "6c01a9b494f6d2a9fc180a08b182fcb06f0958a0" +deps = ["Dates", "SnoopPrecompile"] +git-tree-sha1 = "b64719e8b4504983c7fca6cc9db3ebc8acc2a4d6" uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" -version = "2.4.2" +version = "2.5.1" [[deps.Pkg]] deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +[[deps.PolyhedralRelaxations]] +deps = ["DataStructures", "ForwardDiff", "JuMP", "Logging", "LoggingExtras"] +git-tree-sha1 = "05f2adc696ae9a99be3de99dd8970d00a4dccefe" +uuid = "2e741578-48fa-11ea-2d62-b52c946f73a0" +version = "0.3.5" + +[[deps.PooledArrays]] +deps = ["DataAPI", "Future"] +git-tree-sha1 = "a6062fe4063cdafe78f4a0a81cfffb89721b30e7" +uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" +version = "1.4.2" + +[[deps.PowerModelsDistribution]] +deps = ["CSV", "Dates", "FilePaths", "Glob", "InfrastructureModels", "JSON", "JuMP", "LinearAlgebra", "Logging", "LoggingExtras", "PolyhedralRelaxations", "Statistics"] +git-tree-sha1 = "ae1eb6eed3e17fce8893e77fa02965b1622a0fb4" +uuid = "d7431456-977f-11e9-2de3-97ff7677985e" +version = "0.14.4" + [[deps.Preferences]] deps = ["TOML"] git-tree-sha1 = "47e5f437cc0e7ef2ce8406ce1e7e24d44915f88d" @@ -578,9 +672,9 @@ uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.REopt]] deps = ["ArchGDAL", "CoolProp", "Dates", "DelimitedFiles", "HTTP", "JLD", "JSON", "JuMP", "LinDistFlow", "LinearAlgebra", "Logging", "MathOptInterface", "Requires", "Roots", "Statistics", "TestEnv"] -git-tree-sha1 = "70e34138a437cb1a224646c45e230f90ae2d7283" +git-tree-sha1 = "01951cf0e5b4fbdfd9f3a63fb1076346c3fe3b69" uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6" -version = "0.20.1" +version = "0.22.0" [[deps.Random]] deps = ["SHA", "Serialization"] @@ -619,9 +713,15 @@ uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" [[deps.SQLite_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] -git-tree-sha1 = "9d920c4ee8cd5684e23bf84f43ead45c0af796e7" +git-tree-sha1 = "b89fe49b6a19cde7aefa7e7cf013c1160367f37d" uuid = "76ed43ae-9a5d-5a62-8c75-30186b810ce8" -version = "3.39.4+0" +version = "3.40.0+0" + +[[deps.SentinelArrays]] +deps = ["Dates", "Random"] +git-tree-sha1 = "efd23b378ea5f2db53a55ae53d3133de4e080aa9" +uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" +version = "1.3.16" [[deps.Serialization]] uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" @@ -646,9 +746,9 @@ uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" [[deps.SpecialFunctions]] deps = ["ChainRulesCore", "IrrationalConstants", "LogExpFunctions", "OpenLibm_jll", "OpenSpecFun_jll"] -git-tree-sha1 = "5d65101b2ed17a8862c4c05639c3ddc7f3d791e1" +git-tree-sha1 = "d75bda01f8c31ebb72df80a46c88b25d1c79c56d" uuid = "276daf66-3868-5448-9aa4-cd146d93841b" -version = "1.8.7" +version = "2.1.7" [[deps.StackViews]] deps = ["OffsetArrays"] @@ -658,9 +758,9 @@ version = "0.1.1" [[deps.StaticArrays]] deps = ["LinearAlgebra", "Random", "StaticArraysCore", "Statistics"] -git-tree-sha1 = "f86b3a049e5d05227b10e15dbb315c5b90f14988" +git-tree-sha1 = "4e051b85454b4e4f66e6a6b7bdc452ad9da3dcf6" uuid = "90137ffa-7385-5640-81b9-e52037218182" -version = "1.5.9" +version = "1.5.10" [[deps.StaticArraysCore]] git-tree-sha1 = "6b7ba252635a5eff6a0b0664a41ee140a1c9e72a" @@ -707,6 +807,12 @@ git-tree-sha1 = "43519ae06e007949a0e889f3234cf85875ac7e34" uuid = "1e6cf692-eddd-4d53-88a5-2d735e33781b" version = "1.7.3" +[[deps.Thrift_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "boost_jll"] +git-tree-sha1 = "fd7da49fae680c18aa59f421f0ba468e658a2d7a" +uuid = "e0b8ae26-5307-5830-91fd-398402328850" +version = "0.16.0+0" + [[deps.TranscodingStreams]] deps = ["Random", "Test"] git-tree-sha1 = "8a75929dcd3c38611db2f8d08546decb514fcadf" @@ -727,15 +833,27 @@ uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" [[deps.Unitful]] deps = ["ConstructionBase", "Dates", "LinearAlgebra", "Random"] -git-tree-sha1 = "d57a4ed70b6f9ff1da6719f5f2713706d57e0d66" +git-tree-sha1 = "bdc60e70c8a2f63626deeb5c177baacfc43f8a59" uuid = "1986cc42-f94f-5a68-af5c-568840ba703d" -version = "1.12.0" +version = "1.12.1" + +[[deps.WeakRefStrings]] +deps = ["DataAPI", "InlineStrings", "Parsers"] +git-tree-sha1 = "b1be2855ed9ed8eac54e5caff2afcdb442d52c23" +uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5" +version = "1.4.2" + +[[deps.XML2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "Zlib_jll"] +git-tree-sha1 = "58443b63fb7e465a8a7210828c91c08b92132dff" +uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" +version = "2.9.14+0" [[deps.Xpress]] deps = ["Libdl", "LinearAlgebra", "MathOptInterface", "SparseArrays"] -git-tree-sha1 = "927eb181e7a4c21a4ff9bebdd4a8d746e0fdab2f" +git-tree-sha1 = "fac5fe95ce2f540058195e8b8c2cb9f894ada64c" uuid = "9e70acf3-d6c9-5be6-b5bd-4e2c73e3e054" -version = "0.13.2" +version = "0.15.5" [[deps.Zlib_jll]] deps = ["Libdl"] @@ -747,15 +865,21 @@ git-tree-sha1 = "e45044cd873ded54b6a5bac0eb5c971392cf1927" uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" version = "1.5.2+0" +[[deps.boost_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] +git-tree-sha1 = "7a89efe0137720ca82f99e8daa526d23120d0d37" +uuid = "28df3c45-c428-5900-9ff8-a3135698ca75" +version = "1.76.0+1" + [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl", "OpenBLAS_jll"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" [[deps.libgeotiff_jll]] deps = ["Artifacts", "JLLWrappers", "LibCURL_jll", "Libdl", "Libtiff_jll", "PROJ_jll", "Pkg"] -git-tree-sha1 = "e51bca193c8a4774dc1d2e5d40d5c4491c1b4fd4" +git-tree-sha1 = "13dfba87a1fe301c4b40f991d0ec990bbee59bbe" uuid = "06c338fa-64ff-565b-ac2f-249532af990e" -version = "1.7.1+0" +version = "100.700.100+0" [[deps.libpng_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg", "Zlib_jll"] @@ -770,3 +894,9 @@ uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" [[deps.p7zip_jll]] deps = ["Artifacts", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" + +[[deps.snappy_jll]] +deps = ["Artifacts", "JLLWrappers", "LZO_jll", "Libdl", "Pkg", "Zlib_jll"] +git-tree-sha1 = "985c1da710b0e43f7c52f037441021dfd0e3be14" +uuid = "fe1e1685-f7be-5f59-ac9f-4ca204017dfd" +version = "1.1.9+1" diff --git a/julia_src/http.jl b/julia_src/http.jl index 9f4b3c920..5e4258e16 100644 --- a/julia_src/http.jl +++ b/julia_src/http.jl @@ -80,9 +80,20 @@ function reopt(req::HTTP.Request) :emissions_factor_series_lb_CO2_per_kwh, :emissions_factor_series_lb_NOx_per_kwh, :emissions_factor_series_lb_SO2_per_kwh, :emissions_factor_series_lb_PM25_per_kwh ] + if haskey(d, "CHP") + inputs_with_defaults_from_chp = [ + :installed_cost_per_kw, :tech_sizes_for_cost_curve, :om_cost_per_kwh, + :electric_efficiency_full_load, :thermal_efficiency_full_load, :min_allowable_kw, + :cooling_thermal_factor, :min_turn_down_fraction, :unavailability_periods + ] + chp_dict = Dict(key=>getfield(model_inputs.s.chp, key) for key in inputs_with_defaults_from_chp) + else + chp_dict = Dict() + end inputs_with_defaults_set_in_julia = Dict( "Financial" => Dict(key=>getfield(model_inputs.s.financial, key) for key in inputs_with_defaults_from_easiur), - "ElectricUtility" => Dict(key=>getfield(model_inputs.s.electric_utility, key) for key in inputs_with_defaults_from_avert) + "ElectricUtility" => Dict(key=>getfield(model_inputs.s.electric_utility, key) for key in inputs_with_defaults_from_avert), + "CHP" => chp_dict ) catch e @error "Something went wrong in the Julia code!" exception=(e, catch_backtrace()) @@ -118,6 +129,71 @@ function ghpghx(req::HTTP.Request) return HTTP.Response(200, JSON.json(ghpghx_results)) end +function chp_defaults(req::HTTP.Request) + d = JSON.parse(String(req.body)) + keys = ["existing_boiler_production_type", + "avg_boiler_fuel_load_mmbtu_per_hour", + "prime_mover", + "size_class", + "boiler_efficiency"] + # Process .json inputs and convert to correct type if needed + for k in keys + if !haskey(d, k) + d[k] = nothing + elseif !isnothing(d[k]) + if k in ["avg_boiler_fuel_load_mmbtu_per_hour", "boiler_efficiency"] && typeof(d[k]) == String + d[k] = parse(Float64, d[k]) + elseif k == "size_class" && typeof(d[k]) == String + d[k] = parse(Int64, d[k]) + end + end + end + + @info "Getting CHP defaults..." + data = Dict() + error_response = Dict() + try + data = reoptjl.get_chp_defaults_prime_mover_size_class(;hot_water_or_steam=d["existing_boiler_production_type"], + avg_boiler_fuel_load_mmbtu_per_hour=d["avg_boiler_fuel_load_mmbtu_per_hour"], + prime_mover=d["prime_mover"], + size_class=d["size_class"], + boiler_efficiency=d["boiler_efficiency"]) + catch e + @error "Something went wrong in the chp_defaults" exception=(e, catch_backtrace()) + error_response["error"] = sprint(showerror, e) + end + if isempty(error_response) + @info "CHP defaults determined." + response = data + return HTTP.Response(200, JSON.json(response)) + else + @info "An error occured in the chp_defaults endpoint" + return HTTP.Response(500, JSON.json(error_response)) + end +end + +function simulated_load(req::HTTP.Request) + d = JSON.parse(String(req.body)) + + @info "Getting CRB Loads..." + data = Dict() + error_response = Dict() + try + data = reoptjl.simulated_load(d) + catch e + @error "Something went wrong in the simulated_load" exception=(e, catch_backtrace()) + error_response["error"] = sprint(showerror, e) + end + if isempty(error_response) + @info "CRB Loads determined." + response = data + return HTTP.Response(200, JSON.json(response)) + else + @info "An error occured in the simulated_load endpoint" + return HTTP.Response(500, JSON.json(error_response)) + end +end + function health(req::HTTP.Request) return HTTP.Response(200, JSON.json(Dict("Julia-api"=>"healthy!"))) end @@ -128,5 +204,7 @@ const ROUTER = HTTP.Router() HTTP.@register(ROUTER, "POST", "/job", job) HTTP.@register(ROUTER, "POST", "/reopt", reopt) HTTP.@register(ROUTER, "POST", "/ghpghx", ghpghx) +HTTP.@register(ROUTER, "GET", "/chp_defaults", chp_defaults) +HTTP.@register(ROUTER, "GET", "/simulated_load", simulated_load) HTTP.@register(ROUTER, "GET", "/health", health) HTTP.serve(ROUTER, "0.0.0.0", 8081, reuseaddr=true)