Skip to content

CHP load-following and absorption-chiller-only dispatch strategies#577

Open
zolanaj wants to merge 72 commits intodevelopfrom
chp-load-following
Open

CHP load-following and absorption-chiller-only dispatch strategies#577
zolanaj wants to merge 72 commits intodevelopfrom
chp-load-following

Conversation

@zolanaj
Copy link
Collaborator

@zolanaj zolanaj commented Feb 11, 2026

Please check if the PR fulfills these requirements

  • CHANGELOG.md is updated
  • Docs have been added / updated if applicable
  • Any new packages have been added to the [compat] section of Project.toml
  • REopt version number is updated in Project.toml and CHANGELOG.md if merging into master (do this right before merging)
  • Tests for the changes have been added. These tests should compare against expected values that are calculated independently of running REopt. Tests should also include garbage collection (see other tests for examples).
  • [] Any new REopt outputs and required inputs have been added to the corresponding Django model in the REopt API

Added

  • New optional attributes for CHP object CHP.serve_absorption_chiller_only, CHP.months_serving_absorption_chiller_only, and CHP.follow_electrical_load, which impose constraints on CHP operations if selected. The default is set to false for both attributes.
  • New result thermal_to_absorption_chiller_series_mmbtu_per_hour added to heating technologies and new result storage_to_absorption_chiller_series_mmbtu_per_hour for hot thermal storage technologies. This result is included as a part of the thermal site loads served, i.e., the adding this result does not change the existing results.
  • Added results fields to HighTempThermalStorage to match those of HotThermalStorage.

Changed

  • Updated heating dispatch results by separating heat flows to absorption chiller from heating load served (formerly, these were aggregated).

Fixed

  • 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.

@zolanaj zolanaj requested a review from Bill-Becker February 11, 2026 19:35
@zolanaj
Copy link
Collaborator Author

zolanaj commented Feb 11, 2026

@Bill-Becker this is ready for your review! I'm still crafting a test case for the false CHP DomesticHotWater load requirement in #576 but playtesting showed it worked... not sure if the specific instance requiring your workaround is available but I can try that if you have it. The failing tests in the most recent push are currently the PVWatts tests on actions and not related to this PR.

Only other note is that the storage doesn't have a variable sending to the absorption chiller at the moment - I think that may make this incomplete as a result. I'm working on that right now. (API dev will be done on this shortly, too.)

@Bill-Becker
Copy link
Collaborator

@Bill-Becker while getting the CHP sizing heuristic updated to include the cooling load (optionally), I added some tests and found a somewhat peculiar result - if the sizing heuristic includes both an average+max electric load and an average thermal load, the electric load is not considered at all and only the thermal load is used to size the system. was this intentional? I would think the max sizing of the two would be used (unless the CHP is electric_only, in which case only the electric load is used) but that's not the case - I'm guessing maybe the heating load is perhaps more important in CHP sizing when the heuristic is used?

I added a new test to verify this here and, if we want to include electric load, an update to this is straightforward (I implemented, then reverted it).

My understanding was that only if the is_electric_only argument is true, then it would use the electric load params for sizing. Otherwise, it should use the avg heating load. Is that not what you're seeing?

@zolanaj
Copy link
Collaborator Author

zolanaj commented Feb 25, 2026

@Bill-Becker while getting the CHP sizing heuristic updated to include the cooling load (optionally), I added some tests and found a somewhat peculiar result - if the sizing heuristic includes both an average+max electric load and an average thermal load, the electric load is not considered at all and only the thermal load is used to size the system. was this intentional? I would think the max sizing of the two would be used (unless the CHP is electric_only, in which case only the electric load is used) but that's not the case - I'm guessing maybe the heating load is perhaps more important in CHP sizing when the heuristic is used?
I added a new test to verify this here and, if we want to include electric load, an update to this is straightforward (I implemented, then reverted it).

My understanding was that only if the is_electric_only argument is true, then it would use the electric load params for sizing. Otherwise, it should use the avg heating load. Is that not what you're seeing?

That's my understanding. I think the question is this: the heuristic currently sizes according to (1) only the electric load, or (2) only the thermal load. Is there a case in which you would want to look at both the electric and thermal loads for heuristic sizing, whether it's the lower or higher of the two that informs it?

If the answer is "no" then no further adjustments are required and this is ready for your re-review.

@Bill-Becker
Copy link
Collaborator

@Bill-Becker while getting the CHP sizing heuristic updated to include the cooling load (optionally), I added some tests and found a somewhat peculiar result - if the sizing heuristic includes both an average+max electric load and an average thermal load, the electric load is not considered at all and only the thermal load is used to size the system. was this intentional? I would think the max sizing of the two would be used (unless the CHP is electric_only, in which case only the electric load is used) but that's not the case - I'm guessing maybe the heating load is perhaps more important in CHP sizing when the heuristic is used?
I added a new test to verify this here and, if we want to include electric load, an update to this is straightforward (I implemented, then reverted it).

My understanding was that only if the is_electric_only argument is true, then it would use the electric load params for sizing. Otherwise, it should use the avg heating load. Is that not what you're seeing?

That's my understanding. I think the question is this: the heuristic currently sizes according to (1) only the electric load, or (2) only the thermal load. Is there a case in which you would want to look at both the electric and thermal loads for heuristic sizing, whether it's the lower or higher of the two that informs it?

If the answer is "no" then no further adjustments are required and this is ready for your re-review.

I'm sure there's a way to smartly consider both, but for now let's just leave it as one or the other. Thanks!

@zolanaj zolanaj requested a review from Bill-Becker February 25, 2026 21:32
@Bill-Becker
Copy link
Collaborator

Bill-Becker commented Mar 3, 2026

@zolanaj I re-reviewed by testing, and I made some updates based on that testing, described below (thanks co-pilot for summarizing!). I added more tests. Please review the updates. In particular, let me know if the 3rd section down on "CHP Results Double-Counting Absorption Chiller Heat" was intentional and related to preserving backward compatibility for heating load results. I'll check on CI tests and help debug any failing ones, and I'll approve after you review my updates.

min_turn_down_fraction Conflicts with Load-Following

Issue: When follow_electrical_load = true, the constraint CHP_output = min(CHP_size, load) conflicts with min_turn_down_fraction when load < min_turn_down_fraction * CHP_size, causing infeasibility.

Fix Implemented: Added preprocessing validation in CHP constructor that throws error when min_load < min_turn_down_fraction * min_kw (guaranteed infeasibility when min_kw is specified). Provides specific guidance: set min_turn_down_fraction = 0.0 or reduce min_kw below the calculated threshold.

Location: src/core/chp.jl lines 290-306

Tests: Added a test that confirms this error is thrown.


Absorption Chiller Monthly Constraint Bug

Issue: In add_chp_to_absorption_chiller_only_constraints(), heating load variables were fixed to zero for ALL timesteps instead of only restricted months, preventing CHP from serving heating loads year-round.

Fix Implemented: Modified constraint to only fix heating variables during months_serving_absorption_chiller_only timesteps.

Location: src/constraints/chp_constraints.jl lines 183-189

Tests: Added a test for specific monthly restrictions


CHP Results Double-Counting Absorption Chiller Heat

Issue: thermal_to_dhw_load_series (and space heating/process heat) didn't subtract heat going to absorption chiller, causing double-counting in results when heating_load_input = "DomesticHotWater".

Evidence: Test violations showed to_dhw > 0 during restricted months when it should be zero.

Fix Implemented: Added - CHPtoAbsorptionChillerByQualityKW[q,ts] to all load-specific thermal output calculations (DHW, SpaceHeating, ProcessHeat).

Location: src/results/chp.jl lines 117-143

Tests:: The tests I added were failing until this update was made.

Cooling Load Integration for CHP Heuristic Sizing

Enhancement: Extended CHP sizing heuristic to account for cooling loads served by absorption chillers, enabling more accurate CHP sizing when thermal output serves both heating and cooling demands.

Implementation:

  • Added cooling parameters (avg_cooling_load_kw, chiller_cop, include_cooling_in_size) to get_chp_defaults_prime_mover_size_class() function
  • Updated CHP() constructor to accept and pass cooling parameters from processed CoolingLoad object
  • Modified Scenario() constructor to position CHP construction after CoolingLoad processing but before AbsorptionChiller construction, allowing cooling data to flow into CHP sizing
  • Automatic detection: include_cooling_in_size defaults to true when AbsorptionChiller is present in scenario inputs
  • Cooling thermal load is converted to equivalent heating load using absorption chiller COP and CHP thermal efficiency for sizing calculations

Location:

  • src/core/chp.jl lines 153-158 (constructor), 228-236 (call to sizing function), 420-426 (sizing function parameters), 515-527 (cooling load calculations)
  • src/core/scenario.jl lines ~441-564 (CoolingLoad → CHP → AbsorptionChiller ordering)

Tests: Added Case 4 test in "CHP Sizing Heuristic" testset verifying that full Scenario construction with CHP + AbsorptionChiller produces same sizing as direct function call with equivalent parameters.

@zolanaj
Copy link
Collaborator Author

zolanaj commented Mar 4, 2026

@zolanaj I re-reviewed by testing, and I made some updates based on that testing, described below (thanks co-pilot for summarizing!). I added more tests. Please review the updates. In particular, let me know if the 3rd section down on "CHP Results Double-Counting Absorption Chiller Heat" was intentional and related to preserving backward compatibility for heating load results. I'll check on CI tests and help debug any failing ones, and I'll approve after you review my updates.

min_turn_down_fraction Conflicts with Load-Following

Issue: When follow_electrical_load = true, the constraint CHP_output = min(CHP_size, load) conflicts with min_turn_down_fraction when load < min_turn_down_fraction * CHP_size, causing infeasibility.

Fix Implemented: Added preprocessing validation in CHP constructor that throws error when min_load < min_turn_down_fraction * min_kw (guaranteed infeasibility when min_kw is specified). Provides specific guidance: set min_turn_down_fraction = 0.0 or reduce min_kw below the calculated threshold.

Location: src/core/chp.jl lines 290-306

Tests: Added a test that confirms this error is thrown.

Absorption Chiller Monthly Constraint Bug

Issue: In add_chp_to_absorption_chiller_only_constraints(), heating load variables were fixed to zero for ALL timesteps instead of only restricted months, preventing CHP from serving heating loads year-round.

Fix Implemented: Modified constraint to only fix heating variables during months_serving_absorption_chiller_only timesteps.

Location: src/constraints/chp_constraints.jl lines 183-189

Tests: Added a test for specific monthly restrictions

CHP Results Double-Counting Absorption Chiller Heat

Issue: thermal_to_dhw_load_series (and space heating/process heat) didn't subtract heat going to absorption chiller, causing double-counting in results when heating_load_input = "DomesticHotWater".

Evidence: Test violations showed to_dhw > 0 during restricted months when it should be zero.

Fix Implemented: Added - CHPtoAbsorptionChillerByQualityKW[q,ts] to all load-specific thermal output calculations (DHW, SpaceHeating, ProcessHeat).

Location: src/results/chp.jl lines 117-143

Tests:: The tests I added were failing until this update was made.

Cooling Load Integration for CHP Heuristic Sizing

Enhancement: Extended CHP sizing heuristic to account for cooling loads served by absorption chillers, enabling more accurate CHP sizing when thermal output serves both heating and cooling demands.

Implementation:

  • Added cooling parameters (avg_cooling_load_kw, chiller_cop, include_cooling_in_size) to get_chp_defaults_prime_mover_size_class() function
  • Updated CHP() constructor to accept and pass cooling parameters from processed CoolingLoad object
  • Modified Scenario() constructor to position CHP construction after CoolingLoad processing but before AbsorptionChiller construction, allowing cooling data to flow into CHP sizing
  • Automatic detection: include_cooling_in_size defaults to true when AbsorptionChiller is present in scenario inputs
  • Cooling thermal load is converted to equivalent heating load using absorption chiller COP and CHP thermal efficiency for sizing calculations

Location:

  • src/core/chp.jl lines 153-158 (constructor), 228-236 (call to sizing function), 420-426 (sizing function parameters), 515-527 (cooling load calculations)
  • src/core/scenario.jl lines ~441-564 (CoolingLoad → CHP → AbsorptionChiller ordering)

Tests: Added Case 4 test in "CHP Sizing Heuristic" testset verifying that full Scenario construction with CHP + AbsorptionChiller produces same sizing as direct function call with equivalent parameters.

@Bill-Becker thanks very much for all of this! I added one additional change to separate absorption chiller flows from the thermal_to_load results that are in aggregate (not sure if they are used anymore but the same logic should apply to the individual loads). I also updated that changelog to note that heat flows to the absorption chiller are no longer included in the thermal_to_load results.

I approve of all of your changes. Assuming I didn't break any tests, this should be ready for approval.

zolanaj and others added 9 commits March 5, 2026 12:58
…iller flows and add absorption chiller tests
Added new optional attributes for CHP object and updated results for heating technologies. Fixed bugs related to CHP system and thermal heating load.
Added new optional attributes for CHP object and new results for heating technologies, including storage to absorption chiller series.
@zolanaj
Copy link
Collaborator Author

zolanaj commented Mar 6, 2026

@Bill-Becker while adding the storage-to-chiller flows I noticed the steam turbine flows counted toward the load, which is inconsistent with the heating technologies - I believe @adfarth has introduced this in the past. A fix for that is included in the latest commits and all tests are passing now!

Copy link
Collaborator

@Bill-Becker Bill-Becker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Alex!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants