Skip to content

Nuclear with thermal connection with HTSE - #807

Open
jaredthomas68 wants to merge 71 commits into
NatLabRockies:developfrom
jaredthomas68:nuchtse
Open

Nuclear with thermal connection with HTSE#807
jaredthomas68 wants to merge 71 commits into
NatLabRockies:developfrom
jaredthomas68:nuchtse

Conversation

@jaredthomas68

@jaredthomas68 jaredthomas68 commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Nuclear with thermal connection with HTSE

This pull request adds a nuclear model capable of thermal integration and high temperature steam electrolysis. The models are coupled, but can be used individually and coupled with other components. This PR replaces #761.

Section 1: Type of Contribution

  • Feature Enhancement
    • Framework
    • New Model
    • Updated Model
    • Tools/Utilities
    • Other (please describe):
  • Bug Fix
  • Documentation Update
  • CI Changes
  • Other (please describe):

Section 2: Draft PR Checklist

  • Open draft PR
  • Describe the feature that will be added
  • Fill out TODO list steps
  • Describe requested feedback from reviewers on draft PR
  • Complete Section 8: New Model Checklist (if applicable)

TODO:

  • Step 1
  • Step 2

Type of Reviewer Feedback Requested (on Draft PR)

Structural feedback:

Implementation feedback:

Other feedback:

Section 3: General PR Checklist

  • PR description thoroughly describes the new feature, bug fix, etc.
  • Added tests for new functionality or bug fixes
  • Tests pass (If not, and this is expected, please elaborate in the Section 6: Test Results)
  • Documentation
    • Docstrings are up-to-date
    • Related docs/ files are up-to-date, or added when necessary
    • Documentation has been rebuilt successfully
    • Examples have been updated (if applicable)
  • CHANGELOG.md
    • At least one complete sentence has been provided to describe the changes made in this PR
    • After the above, a hyperlink has been provided to the PR using the following format:
      "A complete thought. [PR XYZ]((https://github.com/NatLabRockies/H2Integrate/pull/XYZ)", where
      XYZ should be replaced with the actual number.

Section 4: Related Issues

Section 5: Impacted Areas of the Software

Section 5.1: New Files

  • examples/36_nuclear_reactor_htse/driver_config.yaml

    • driver.optimization / design_variables: Adds optimization inputs for the thermal nuclear + HTSE example so reactor dispatch and HTSE sizing can be varied.
  • examples/36_nuclear_reactor_htse/nuclear_reactor_thermal_htse.yaml

    • config references: Adds the top-level example entrypoint that links the driver, plant, and technology configuration files.
  • examples/36_nuclear_reactor_htse/plant_config.yaml

    • technology_interconnections / finance_parameters: Adds the plant wiring and finance subgroup definitions for coupled nuclear electricity, process heat, HTSE hydrogen production, and grid sales.
  • examples/36_nuclear_reactor_htse/run_nuclear_reactor_thermal.py

    • module script body: Adds a runnable example that updates HTSE refurbishment inputs, executes the model, reports LCOE/LCOH, and plots electricity, heat, and hydrogen outputs.
  • examples/36_nuclear_reactor_htse/tech_config.yaml

    • technologies: Adds the thermal nuclear reactor, HTSE, feedstock, demand, and grid component definitions needed to run the integrated example.
  • h2integrate/converters/hydrogen/htse_electrolyzer.py

    • HTSEPerformanceModel: Adds a simplified HTSE performance model that converts available heat and electricity into hydrogen production, water use, efficiency, and replacement outputs.
    • HTSECostModel: Adds a size-based HTSE cost model that computes CapEx and fixed OpEx from installed electrolyzer capacity.
  • h2integrate/converters/hydrogen/test/test_htse_electrolyzer.py

    • HTSE unit tests: Adds coverage for rated operation, turndown shutoff, and cost-model behavior for the new HTSE models.
  • h2integrate/converters/nuclear/nuclear_plant_thermal.py

    • SimpleThermalNuclearReactorPerformanceModel: Adds a thermal nuclear reactor model that dispatches between electricity generation and process heat delivery.
    • SimpleThermalNuclearReactorCostModel: Adds a reactor cost model that computes capital, fixed operating, and variable operating costs.
  • h2integrate/converters/nuclear/test/test_nuclear_plant_thermal.py

    • thermal reactor unit tests: Adds coverage for heat-mode dispatch, electricity-mode dispatch, rated-capacity clipping, and reactor cost calculations.

Section 5.2: Modified Files

  • examples/test/test_all_examples.py

    • test_nuclear_reactor_htse_example: Adds an integration test for the new thermal nuclear + HTSE example to validate annual electricity, hydrogen output, grid exports, and thermal-balance behavior.
  • h2integrate/converters/hydrogen/__init__.py

    • module exports: Adds HTSEPerformanceModel and HTSECostModel to the hydrogen converter package exports.
  • h2integrate/converters/nuclear/__init__.py

    • module exports: Adds SimpleThermalNuclearReactorPerformanceModel and SimpleThermalNuclearReactorCostModel to the nuclear converter package exports.
  • h2integrate/core/supported_models.py

    • supported_models: Registers the new HTSE and thermal nuclear model classes so they can be instantiated from YAML configuration.
  • h2integrate/transporters/pipe.py

    • PipePerformanceModel.setup: Updates water pipe units from galUS to galUS/h so water transport matches the HTSE feedstock rate units.

Section 6: Additional Supporting Information

Section 7: Test Results, if applicable

Section 8 (Optional): New Model Checklist

  • Model Structure:
    • Follows established naming conventions outlined in docs/developer_guide/coding_guidelines.md
    • Used attrs class to define the Config to load in attributes for the model
      • If applicable: inherit from BaseConfig or CostModelBaseConfig
    • Added: initialize() method, setup() method, compute() method
      • If applicable: inherit from CostModelBaseClass
  • Integration: Model has been properly integrated into H2Integrate
    • Add the new model to the appropriate __init__.py file to ensure it is properly imported and used in supported_models.py
    • Added to supported_models.py
    • If a new commodity_type is added, update create_financial_model in h2integrate_model.py
  • Tests: Unit tests have been added for the new model
    • Pytest-style unit tests
    • Unit tests are in a "test" folder within the folder a new model was added to
    • If applicable add integration tests
  • Example: If applicable, a working example demonstrating the new model has been created
    • Input file comments
    • Run file comments
    • Example has been tested and runs successfully in test_all_examples.py
  • Documentation:
    • Write docstrings using the Google style
    • Model added to the main models list in docs/user_guide/model_overview.md
      • Model documentation page added to the appropriate docs/ section
      • <model_name>.md is added to the _toc.yml
    • Run generate_class_hierarchy.py to update the class hierarchy diagram in docs/developer_guide/class_structure.md

Daniel Mark Mikkelson and others added 30 commits April 14, 2026 10:58
… how it uses electricity when heat is not available.
…optimization and we need a single analysis test value in this case
@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@jaredthomas68
jaredthomas68 requested review from bayc and johnjasa July 21, 2026 15:20
@jaredthomas68 jaredthomas68 mentioned this pull request Jul 21, 2026
52 tasks
@jaredthomas68
jaredthomas68 requested a review from vijay092 July 21, 2026 15:47
@johnjasa
johnjasa requested a review from elenya-grant July 23, 2026 17:49

@bayc bayc left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Overall, looks great Jared, thanks for getting this pulled together! Added a couple of non-blocking comments regarding the documentation.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would be nice to have the pump and heat exchanger labeled for those not as familiar with the setup.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Label the heat exchanger? Also, what does the red dashed line represent?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Done. I also made a note in the doc page that the red dashed arrow represents the heat extraction (upstream of the LPT).

@jaredthomas68
jaredthomas68 requested review from johnjasa and removed request for johnjasa July 24, 2026 21:17
@jaredthomas68
jaredthomas68 requested review from johnjasa and removed request for johnjasa July 27, 2026 14:33

@johnjasa johnjasa left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for this great PR, @mikkdm and @jaredthomas68! It's great to see it all come together.

I have some minor suggestions for clarity and cleanup, nothing huge. I'm requesting changes to make sure I come back when the CI is passing for one last go-through!


## Performance Model

Use this model by setting:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Use this model by setting:
To use this model, within your `tech_config.yaml` file set:


### Dispatch and sizing behavior

Installed size is first inferred from:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Installed size is first inferred from:
Installed size is first calculated as:

The model forms:

$$
\text{total\_specific\_energy} = nominal\_heat\_required + nominal\_electricity\_required

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Specific energy has certain meaning in some contexts, like energy per mass (energy density). Is that the expected meaning here? If not, I'd suggest a different phrase, like total_energy_required or similar for clarity.

Available heat is used first up to the requested `heat_demand`. The remaining required energy is supplied electrically when possible. Hydrogen production is then limited by the combined energy available and by the turndown threshold.

```{note}
The current implementation is intentionally simple and should be interpreted as a reduced-order plant representation, not a detailed SOEC stack model with thermal transients, degradation coupling, startup dynamics, or detailed balance-of-plant behavior.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For those unfamiliar with SOEC, could you please define it somewhere on this page? It's used here and in one diagram


## Cost Model

Use this model by setting:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
Use this model by setting:
To use this model, in your `tech_config.yaml` file, set:


heat_available_kw = inputs["heat_in"]
electricity_available_kw = inputs["electricity_in"]
# hydrogen_demand = inputs["hydrogen_command_value"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
# hydrogen_demand = inputs["hydrogen_command_value"]

Comment on lines +246 to +252
# heat_demand_kw = hydrogen_out * self.config.nominal_heat_required
# electricity_demand_kw = hydrogen_out * total_specific_energy - actual_heat_kw
# electricity_demand_kw = np.minimum(electricity_demand_kw, electricity_available_kw)
outputs["electricity_consumed"] = electricity_demand_kw
outputs["hydrogen_out"] = hydrogen_produced
# outputs["heat_demand"] = np.minimum(rated_hydrogen_production,
# inputs["hydrogen_command_value"]) * self.config.nominal_heat_required

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Remove commented out code?

hydrogen_produced = np.where(hydrogen_produced >= min_turn_down, hydrogen_produced, 0.0)

# Density of liquid water at 20°C (293.15 K) and 1 atm (101325 Pa)
PropsSI("D", "T", 293.15, "P", 101325, "Water")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Remove this line?

Comment on lines +114 to +117
self.add_output("high_pressure_heat_demanded", val=0.0, shape=self.n_timesteps, units="kW")
self.add_output("high_pressure_heat", val=0.0, shape=self.n_timesteps, units="kW")
self.add_output("low_pressure_heat", val=0.0, shape=self.n_timesteps, units="kW")
self.add_output("heat_out", val=0.0, shape=self.n_timesteps, units="kW")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If you change these units to "MW" we can directly output the arrays instead of manually converting from kW to MW below. I'll note it there too.

Comment on lines +167 to +170
high_pressure_heat_kw = np.full(self.n_timesteps, available_process_heat_mw * 1000.0)
low_pressure_heat_kw = low_pressure_heat_remaining_mw * 1000.0
electricity_out_kw = electricity_out_mw * 1000.0
heat_out_kw = heat_out_mw * 1000.0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can remove these manual conversions between kW and MW with the change noted above; OpenMDAO handles the units conversion.

@johnjasa

Copy link
Copy Markdown
Collaborator

Oh, and I'd suggest @jaredthomas68 to add a high-res version of this figure you have in the Q3 presentation slides to the docs; it's nice!

image

@vijay092 vijay092 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Very nice addition, @jaredthomas68! I only looked at nuclear_plant_thermal.py and left some line level comments.


operating_mode: str = field(validator=contains(["heat", "electricity"]))
electricity_command_value: float = field(validator=gt_zero)
high_pressure_electrical_efficiency: float = field(validator=gt_zero)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we need a <= 1 condition here?


delivered_electricity_kwh = np.sum(inputs["electricity_out"]) * (self.dt / 3600.0)
annual_variable_om = variable_om_per_kwh * delivered_electricity_kwh
outputs["VarOpEx"] = np.full(self.plant_life, annual_variable_om)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm wondering if like the performance model, we need to use the fraction of the year simulated?

"rated_capacity",
val=self.config.rated_capacity,
units=self.commodity_rate_units,
desc="Available reactor thermal capacity",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this the electrical capacity or thermal?

available_process_heat_mw
- (electricity_out_mw - high_pressure_electricity_mw) / lp_eff
)
heat_out_mw = np.clip(heat_out_mw, 0.0, available_process_heat_mw)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we need to use minimum_heat_extract in electricity mode?

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.

5 participants