Skip to content

Conversation

@elenya-grant
Copy link
Collaborator

@elenya-grant elenya-grant commented Nov 20, 2025

Added FLORIS wind plant model

Added wind plant performance model for FLORIS.

This model was developed with an emphasis on usability for design sweeps, meaning that some inputs in floris_config file are over-written to use possible design variables such as number of wind turbines and turbine hub-height. Aka - this is not able to accommodate all the possible use-cases that a user may have with running FLORIS in H2I to keep the initial implementation somewhat simple.

Type of Contribution

  • Feature Enhancement
    • New Technology Model
  • Bug Fix
  • Documentation Update
  • CI Changes
  • Other (please describe):

General PR Checklist

  • CHANGELOG.md has been updated to describe the changes made in this PR
  • 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)
  • Tests pass (If not, and this is expected, please elaborate in the tests section)
  • Added tests for new functionality or bug fixes
  • PR description thoroughly describes the new feature, bug fix, etc.

New Technology Checklist

  • Performance Model: Technology performance model has been implemented and follows H2Integrate patterns (if applicable)
  • [-] Cost Model: Technology cost model has been implemented (if applicable)
  • Tests: Unit tests have been added for the new technology
    • Performance model tests (if applicable)
    • [-] Cost model tests (if applicable)
    • Integration tests with H2Integrate system
  • Example: A working example demonstrating the new technology has been created
    • Example has been tested and runs successfully in test_all_examples.py
    • [-] Example is documented with clear explanations in examples/README.md
      • Input file comments
      • Run file comments
  • Documentation:
    • Technology documentation page added to docs/technology_models/
    • Technology added to the main technology models list in docs/technology_models/technology_overview.md
  • Integration: Technology has been properly integrated into H2Integrate
    • Added to supported_models.py
    • [-] If a new commodity_type is added, update create_financial_model in h2integrate_model.py
    • Follows established naming conventions outlined in docs/developer_guide/coding_guidelines.md

Related issues

Impacted areas of the software

Modified files

  • h2integrate/core/model_baseclasses.py
    • CacheBaseConfig.__attrs_post_init__(): updated to only create cache_dir if caching is enabled.
  • h2integrate/converters/hopp/test/test_hopp_caching.py
    • test_hopp_wrapper_cache_filenames: added cleanup to test to remove folder and files created within test
  • h2integrate/preprocess/test/test_wind_turbine_file_tools.py: added more detailed test for testing floris preprocessing tools
  • docs/misc_resources/turbine_models_library_preprocessing.ipynb: added docs to show usage of export_turbine_to_floris_format() function

New files

  • h2integrate/converters/wind/floris.py
    • FlorisWindPlantPerformanceModel: performance model for FLORIS.
    • FlorisWindPlantPerformanceConfig: configuration class for FlorisWindPlantPerformanceModel
  • examples/floris_example/*: example files to run FLORIS
  • tests/h2integrate/test_all_examples.py::test_floris_example: integration test for floris example
  • h2integrate/converters/wind/test/test_floris_wind.py: unit tests for FlorisWindPlantPerformanceModel
  • library/floris_v4_default_template.yaml: floris config with wake parameters
  • library/floris_turbine_Vestas_660kW.yaml: floris config for vestas 660 kW turbine.
  • h2integrate/converters/wind/tools/resource_tools.py: new file containing tool related to wind resource:
    • calculate_air_density(): estimate air density for a specific elevation
    • weighted_average_wind_data_for_hubheight(): weighted average of wind resource data of two hub-heights
    • average_wind_data_for_hubheight(): average wind data over two resource heights
  • h2integrate/converters/wind/tools/test/test_resource_tools.py: tests for all methods in h2integrate/converters/wind/tools/resource_tools.py

Additional supporting information

Test results, if applicable

@elenya-grant elenya-grant marked this pull request as ready for review December 1, 2025 23:32
@review-notebook-app
Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@elenya-grant elenya-grant added ready for review This PR is ready for input from folks and removed needs modifications This PR has been reviewed, at least partially, and is ready for PR author response labels Jan 6, 2026
Copy link
Collaborator

@johnjasa johnjasa left a comment

Choose a reason for hiding this comment

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

Thanks for this! I love that we're moving closer and closer to having all the technology models we need integrated into H2I well.

I've made some changes directly to the code and had a few small comments. I welcome feedback from others but think this is in a good spot and can be built upon as needed in further PRs.

What other features need to come in for us to fully replace the HOPP technology models in the examples with a mix of wind/solar/battery? Would we be able to do that once this PR comes in or does more functionality need to be added to H2I?

default_turbulence_intensity: float = field()
operational_losses: float = field(validator=range_val(0.0, 100.0))
hub_height: float = field(default=-1, validator=validators.ge(-1))
floris_operation_model: str = field(default="cosine-loss")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Non-blocking suggestion: rename to operation_model as the floris is unnecessary because we're already in that technology model?

Comment on lines 219 to 221
# NOTE: could update air density based on elevation if elevation is included
# in the resource data.
# would need to duplicate the ``calculate_air_density`` function from HOPP
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you please make an issue for this and add context/links to the HOPP code where useful?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done - Created Issue #439

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

jk - I'm adding it into the floris wrapper now.

)

# make layout for number of turbines
if self.layout_mode == "basicgrid":
Copy link
Collaborator

Choose a reason for hiding this comment

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

@elenya-grant, where have you landed on how layout_mode is used here? Correct me if I'm wrong, but currently the only supported layout_mode is basicgrid? I say this because if it's not, then x_pos and y_pos are not created and they're later used for floris_farm

floris_config["farm"].update(floris_farm)

# initialize FLORIS
floris_config["flow_field"].update({"turbulence_intensities": []})
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this line necessary if you're setting the turbulence intensities as part of the time_series wind data? Does this blank turbulence intensities list just get overwritten? I'd suggest removing it if it's not strictly needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

the turbulence intensity thing is a bit complicated. But, simply, this does need to be here or else it'll fail because its a required input to the Floris TimeSeries object and a user may not include it in the floris_wake_config.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

on your question about layout_mode - The only layout mode option is basicgrid and so an error will be thrown if the layout mode isn't basic grid (because x_pos and y_pos won't exist). Once we get more layout options, I'd be nice to incorporate handling layout stuff into the WindPerformanceBaseClass and have a shared WindPerformanceBaseConfig that can be used with FlorisWindPlantPerformanceModel and PYSAMWindPlantPerformanceModel

Copy link
Collaborator

Choose a reason for hiding this comment

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

Understood, thanks for your explanation here. I agree with your approach

return rho


def calculate_air_density_losses(elevation_m: float) -> float:
Copy link
Collaborator

Choose a reason for hiding this comment

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

It looks like this and the above function aren't used anywhere? I'd suggest removing until they're used.

"pressure": "atm",
"precipitation_rate": "mm/h",
"relative_humidity": "percent",
"is_day": "percent",
Copy link
Collaborator

@johnjasa johnjasa Jan 6, 2026

Choose a reason for hiding this comment

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

I looked into this and agree with @elenya-grant's findings:

Test 1: Setting unitless input to 0.5
============================================================
Input (unitless): [0.5]
Output (unitless): [0.5]

============================================================
Test 2: Setting percent input to 50
============================================================
Input (percent): [50.]
Output (percent): [50.]

============================================================
Test 3: Set unitless=0.5, read as percent
============================================================
Set as unitless: 0.5
Read as unitless: [0.5]
Read as percent: [50.]

============================================================
Test 4: Set percent=50, read as unitless
============================================================
Set as percent: 50.0
Read as percent: [50.]
Read as unitless: [0.5]

@elenya-grant
Copy link
Collaborator Author

Thanks for this! I love that we're moving closer and closer to having all the technology models we need integrated into H2I well.

I've made some changes directly to the code and had a few small comments. I welcome feedback from others but think this is in a good spot and can be built upon as needed in further PRs.

What other features need to come in for us to fully replace the HOPP technology models in the examples with a mix of wind/solar/battery? Would we be able to do that once this PR comes in or does more functionality need to be added to H2I?

I think we need whatever the HOPP dispatch/battery functionality is then we can move away from it. We may also need to add more wind layout options if any of the examples that use HOPP use a layout that isn't basicgrid

Copy link
Collaborator

@genevievestarke genevievestarke left a comment

Choose a reason for hiding this comment

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

I think this looks good to be merged in! My last question is if hub_height is something that could be optimized since it's exposed to OpenMDAO?

"`layout` dictionary. Please set the layout in "
"floris_wake_config['farm']['layout_x'] and floris_wake_config['farm']['layout_y']"
" to empty lists"
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I like this error message!

Copy link
Collaborator

@jaredthomas68 jaredthomas68 left a comment

Choose a reason for hiding this comment

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

I have a few outstanding concerns, but looking good overall.

"pressure": "atm",
"precipitation_rate": "mm/h",
"relative_humidity": "percent",
"is_day": "percent",
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think your results @johnjasa show demonstrate exactly what I said. Percent is between 0 and 100, unitless the range may be 0 to 1. The results above show setting as percent=50 results in a unitless of 0.5.

I think the docs page may be wrong. I've run into issues with this elsewhere. I think unitless = percent/100 and percent is unitless*100, see the test in openmdao for percentage where a unitless of 0.05 corresponds to a percentage of 5.

Image

@elenya-grant
Copy link
Collaborator Author

@jaredthomas68:

Thanks for your review. Here's a summary of the things I did to address your feedback:

Copy link
Collaborator

@jaredthomas68 jaredthomas68 left a comment

Choose a reason for hiding this comment

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

Looks good, thanks!

@elenya-grant elenya-grant merged commit cb539a7 into NatLabRockies:develop Jan 9, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for review This PR is ready for input from folks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants