From e8c2f70ba6d6676cbdcebd679493b16af5227bdd Mon Sep 17 00:00:00 2001 From: adfarth Date: Fri, 15 May 2026 15:05:07 -0700 Subject: [PATCH 1/3] add fixed soc inputs --- CHANGELOG.md | 10 +++++++--- reoptjl/models.py | 22 +++++++++++++++++++++- reoptjl/validators.py | 3 +++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67d6f3c78..2fc954f2e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,10 +26,16 @@ Classify the change according to the following categories: ##### Removed ### Patches +## fixed-soc +### Minor udpates +#### Added +- Add **ElectricStorage** inputs field **fixed_soc_series_fraction** and **fixed_soc_series_fraction_tolerance** to allow users to fix the SOC timeseries within a chosen absolute tolerance +### Changed +- **ElectricStorage** result key **state_of_health** to **state_of_health_series_fraction** ## Develop - 2026-05-14 ### Minor updates -##### Added +#### Added - New `CHP` fields **serve_absorption_chiller_only**, **months_serving_absorption_chiller_only**, **follow_electrical_load**, and **include_cooling_in_chp_size** - New output `thermal_to_absorption_chiller_series_mmbtu_per_hour` added to heating technologies `CHPOutputs`, `ElectricHeaterOutputs`, `CSTOutputs`, `BoilerOutputs`, `SteamTurbineOutputs`, and `ExistingBoilerOutputs`, and new output `storage_to_absorption_chiller_series_mmbtu_per_hour` for `HotThermalStorageOutputs` and `HighTempThermalStorageOutputs`. ### Changed @@ -40,8 +46,6 @@ Classify the change according to the following categories: - Fixed a bug in which the CHP system requires a **DomesticHotWater** load. - Fixed a bug in which the storage to steam turbine flow was included in the thermal heating load served. - - ## v3.18.0 ### Minor Updates ##### Changed diff --git a/reoptjl/models.py b/reoptjl/models.py index 56960d3b6..f2d20b5d9 100644 --- a/reoptjl/models.py +++ b/reoptjl/models.py @@ -3753,6 +3753,26 @@ class ElectricStorageInputs(BaseModel, models.Model): blank=True, help_text="Battery state of charge at first hour of optimization as fraction of energy capacity." ) + fixed_soc_series_fraction = ArrayField( + models.FloatField( + validators=[ + MinValueValidator(0), + MaxValueValidator(1) + ], + blank=True + ), + default=list, blank=True, + help_text=("If provided, SOC (as fraction of total energy capacity) will not be optimized and will instead be fixed to the values provided" + "here +- the absolute fixed_soc_series_fraction_tolerance. Must be an array of values 0-1 with length equal to 8760*time_steps_per_hour.") + ) + fixed_soc_series_fraction_tolerance = models.FloatField( + validators=[ + MinValueValidator(-1), + MaxValueValidator(1) + ], + null=True, blank=True, + help_text="Absolute tolerance on fixed_soc_series_fraction to avoid infeasible solutions when fixed_soc_series_fraction is provided." + ) can_grid_charge = models.BooleanField( blank=True, help_text="Flag to set whether the battery can be charged from the grid, or just onsite generation." @@ -3944,7 +3964,7 @@ class ElectricStorageOutputs(BaseModel, models.Model): ) initial_capital_cost = models.FloatField(null=True, blank=True) maintenance_cost = models.FloatField(null=True, blank=True) - state_of_health = ArrayField( + state_of_health_series_fraction = ArrayField( models.FloatField(null=True, blank=True), blank=True, default=list ) diff --git a/reoptjl/validators.py b/reoptjl/validators.py index a55301486..8e4f0621d 100644 --- a/reoptjl/validators.py +++ b/reoptjl/validators.py @@ -368,6 +368,9 @@ def update_pv_defaults_offgrid(self, pvmodel): self.models["ElectricStorage"].can_grid_charge = True else: self.models["ElectricStorage"].can_grid_charge = False + + if len(self.models["ElectricStorage"].__getattribute__("fixed_soc_series_fraction")) > 1: + self.clean_time_series("ElectricStorage", "fixed_soc_series_fraction") """ From ac53adfa7f634df177d38b938dd279f55f6fc94b Mon Sep 17 00:00:00 2001 From: adfarth Date: Fri, 15 May 2026 15:25:29 -0700 Subject: [PATCH 2/3] Create 0116_rename_state_of_health_electricstorageoutputs_state_of_health_series_fraction_and_more.py --- ...tate_of_health_series_fraction_and_more.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 reoptjl/migrations/0116_rename_state_of_health_electricstorageoutputs_state_of_health_series_fraction_and_more.py diff --git a/reoptjl/migrations/0116_rename_state_of_health_electricstorageoutputs_state_of_health_series_fraction_and_more.py b/reoptjl/migrations/0116_rename_state_of_health_electricstorageoutputs_state_of_health_series_fraction_and_more.py new file mode 100644 index 000000000..4945d667e --- /dev/null +++ b/reoptjl/migrations/0116_rename_state_of_health_electricstorageoutputs_state_of_health_series_fraction_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.26 on 2026-05-15 22:25 + +import django.contrib.postgres.fields +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('reoptjl', '0115_alter_coldthermalstorageinputs_installed_cost_per_gal_and_more'), + ] + + operations = [ + migrations.RenameField( + model_name='electricstorageoutputs', + old_name='state_of_health', + new_name='state_of_health_series_fraction', + ), + migrations.AddField( + model_name='electricstorageinputs', + name='fixed_soc_series_fraction', + field=django.contrib.postgres.fields.ArrayField(base_field=models.FloatField(blank=True, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(1)]), blank=True, default=list, help_text='If provided, SOC (as fraction of total energy capacity) will not be optimized and will instead be fixed to the values providedhere +- the absolute fixed_soc_series_fraction_tolerance. Must be an array of values 0-1 with length equal to 8760*time_steps_per_hour.', size=None), + ), + migrations.AddField( + model_name='electricstorageinputs', + name='fixed_soc_series_fraction_tolerance', + field=models.FloatField(blank=True, help_text='Absolute tolerance on fixed_soc_series_fraction to avoid infeasible solutions when fixed_soc_series_fraction is provided.', null=True, validators=[django.core.validators.MinValueValidator(-1), django.core.validators.MaxValueValidator(1)]), + ), + ] From 0cf8a0599dc11a322f3789ba8e0daddb09c4f6cb Mon Sep 17 00:00:00 2001 From: adfarth Date: Fri, 15 May 2026 15:30:27 -0700 Subject: [PATCH 3/3] Update all_inputs_test.json --- reoptjl/test/posts/all_inputs_test.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/reoptjl/test/posts/all_inputs_test.json b/reoptjl/test/posts/all_inputs_test.json index 36926c78e..d53def1ad 100644 --- a/reoptjl/test/posts/all_inputs_test.json +++ b/reoptjl/test/posts/all_inputs_test.json @@ -187,7 +187,9 @@ "total_itc_fraction": 0.0, "total_rebate_per_kw": 0.0, "total_rebate_per_kwh": 0.0, - "optimize_soc_init_fraction": false + "optimize_soc_init_fraction": false, + "fixed_soc_series_fraction": [], + "fixed_soc_series_fraction_tolerance": 0.05 }, "Generator": { "existing_kw": 0.0,