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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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)]),
),
]
22 changes: 21 additions & 1 deletion reoptjl/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down Expand Up @@ -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
)
Expand Down
4 changes: 3 additions & 1 deletion reoptjl/test/posts/all_inputs_test.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions reoptjl/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")


"""
Expand Down
Loading