Skip to content

Feature: Add Smarteole LES simulation timeseries and brief model validation study#256

Merged
misi9170 merged 46 commits intoNatLabRockies:developfrom
Bartdoekemeijer:feature/timeseries_model_validation
Mar 11, 2026
Merged

Feature: Add Smarteole LES simulation timeseries and brief model validation study#256
misi9170 merged 46 commits intoNatLabRockies:developfrom
Bartdoekemeijer:feature/timeseries_model_validation

Conversation

@Bartdoekemeijer
Copy link
Copy Markdown
Collaborator

@Bartdoekemeijer Bartdoekemeijer commented Jan 6, 2026

This PR is ready to be merged.

Feature or improvement description
This PR is made together with Robin de Jong, TUDelft as part of the European SUDOCO project. The purpose of this is to add an example that demonstrates how one can use FLASC to compare a dynamic timeseries simulation (here: Whiffle's ASPIRE LES) against SCADA. Whiffle's LES is a strong contender to become the golden standard for wake modeling in the industry and therefore benchmarking it is crucial. For certain applications it is a direct contender to FLORIS and other wake models.

Related issue, if one exists
N/A

Impacted areas of the software

  • Fundamentally very little new functionality is added. It's worth discussing if some functions inside the Jupyter notebook should be moved to a separate class.
  • This PR adds an extra module for the conversion of a dynamic timeseries into a steady-state table (flasc/data_processing/timeseries_to_grid_solutions.py). However, I think FLASC already has capabilities to do this so I'd like to eliminate duplicate code before merging.

Additional supporting information
The attached example are actual results from an ASPIRE LES simulation of SMARTEOLE for the same dates of the experiment.

Test results, if applicable
The current validation results are actually quite exciting already!

                                      Absolute cumulative energy (MWh)
+-------------+---------------+-------------+-----------------+-------------------+-----------------------+
| Selection   |   SCADA (MWh) |   LES (MWh) |   LES error (%) |   FLORIS CC (MWh) |   FLORIS CC error (%) |
|-------------+---------------+-------------+-----------------+-------------------+-----------------------|
| Entire farm |       1355.41 |     1287.20 |           -5.03 |           1324.38 |                 -2.29 |
| Turbine 00  |        219.83 |      210.66 |           -4.17 |            207.10 |                 -5.79 |
| Turbine 01  |        196.56 |      184.18 |           -6.30 |            187.31 |                 -4.71 |
| Turbine 02  |        188.43 |      171.55 |           -8.96 |            180.33 |                 -4.30 |
| Turbine 03  |        168.75 |      153.86 |           -8.82 |            165.69 |                 -1.82 |
| Turbine 04  |        195.91 |      189.60 |           -3.22 |            192.62 |                 -1.68 |
| Turbine 05  |        181.76 |      177.90 |           -2.13 |            178.36 |                 -1.87 |
| Turbine 06  |        204.17 |      199.45 |           -2.31 |            212.97 |                  4.31 |
+-------------+---------------+-------------+-----------------+-------------------+-----------------------+


                                      Cumulative energy wake loss (%)
+-------------+-------------+-----------+--------------------+-----------------+--------------------------+
| Selection   |   SCADA (%) |   LES (%) |   LES error (p.p.) |   FLORIS CC (%) |   FLORIS CC error (p.p.) |
|-------------+-------------+-----------+--------------------+-----------------+--------------------------|
| Entire farm |        3.00 |      2.92 |              -0.08 |            3.75 |                     0.76 |
| Turbine 00  |       -3.09 |     -3.75 |              -0.67 |            1.73 |                     4.81 |
| Turbine 01  |       -0.74 |      0.36 |               1.11 |            2.76 |                     3.51 |
| Turbine 02  |       -0.35 |      3.09 |               3.43 |            2.56 |                     2.90 |
| Turbine 03  |        3.70 |      7.34 |               3.64 |            4.01 |                     0.31 |
| Turbine 04  |        6.28 |      4.86 |              -1.42 |            6.66 |                     0.37 |
| Turbine 05  |        8.31 |      5.42 |              -2.89 |            7.96 |                    -0.35 |
| Turbine 06  |        6.63 |      4.00 |              -2.63 |            0.88 |                    -5.75 |
+-------------+-------------+-----------+--------------------+-----------------+--------------------------+

LES seems to underestimate total AEP (MWh), I'm guessing due to a slightly lower estimated inflow wind speed compared to what was actually seen in the field. However, it is excellent at estimating farm-wide wake loss. Also individual turbine wake losses are quite well estimated, showing much smaller deviations than FLORIS. Generally LES outperforms FLORIS' Cumulative Curl by quite a margin in relative wake losses! This is also seen in the energy ratios.

image

Release checklist:

  • Update the version in
    • pyproject.toml
  • Create a tag in the NREL/FLASC repository
  • Upload the SMARTEOLE LES timeseries files and the import script to a separate location, ZENODO perhaps?
  • Documentation: add more information about SMARTEOLE LES simulation set-up
  • Can we get rid of this new file flasc/data_processing/timeseries_to_grid_solutions.py altogether by just using functions that already exist in FLASC?

@Bartdoekemeijer
Copy link
Copy Markdown
Collaborator Author

Hi @paulf81, @misi9170 -- Happy New Years! 🥳

Just wanted to get the ball rolling on this PR. It's still more of a concept and would love your steer on this so we can turn it into something that fits FLASC best. There's no rush -- would be helpful to get some steer in the next month or so! Also happy to have a meeting on this so that I can walk you through it, if that's easier. 👍

@paulf81
Copy link
Copy Markdown
Collaborator

paulf81 commented Jan 6, 2026

thank you @Bartdoekemeijer ! I'm very excited to dig into this, I'll try to make some time to look/think through it all soon

Copy link
Copy Markdown

@Robin9697 Robin9697 left a comment

Choose a reason for hiding this comment

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

Some comments on examples_smarteole/12_model_les_wake_loss_validation.ipynb. It's mostly small things and some code clean-up. However, I do suggest two changes about the general method:

  1. If we are assuming that the wind farm is fully operational, I think it would make more sense to compensate for the filtered out data by using a weighted mean instead of the sum to compute the AEP.
  2. To match the atmospheric conditions as much as possible, I would suggest copying all the NaN values in the SCADA dataset to the LES dataset.

I have left flasc/data_processing/timeseries_to_grid_solutions.py out of the review for now, since we can hopefully replace it with functions that already exist in FLASC.

Comment thread examples_smarteole/12_model_les_wake_loss_validation.ipynb Outdated
Comment thread examples_smarteole/12_model_les_wake_loss_validation.ipynb Outdated
Comment thread examples_smarteole/12_model_les_wake_loss_validation.ipynb Outdated
Comment thread examples_smarteole/12_model_les_wake_loss_validation.ipynb Outdated
Comment thread examples_smarteole/12_model_les_wake_loss_validation.ipynb Outdated
Comment thread examples_smarteole/12_model_les_wake_loss_validation.ipynb Outdated
Comment thread examples_smarteole/12_model_les_wake_loss_validation.ipynb Outdated
Comment thread examples_smarteole/12_model_les_wake_loss_validation.ipynb Outdated
Comment thread examples_smarteole/12_model_les_wake_loss_validation.ipynb Outdated
Comment thread examples_smarteole/12_model_les_wake_loss_validation.ipynb
Comment thread examples_smarteole/12_model_les_wake_loss_validation.ipynb Outdated
@Bartdoekemeijer
Copy link
Copy Markdown
Collaborator Author

Bartdoekemeijer commented Mar 2, 2026

Reminder to myself: add functionality to limit wind speed range to specific wake conditions.

Edit: added this. Defaults to using all wind speeds, full dataset.

@Bartdoekemeijer
Copy link
Copy Markdown
Collaborator Author

Bartdoekemeijer commented Mar 4, 2026

OK we're mostly there! Jupyter notebook has been updated and should be useable now. Added extensive explanations throughout the notebook and added docstrings to a part of the functions.

What's left:

  • Add docstrings to all new functions.
  • Upload the SMARTEOLE LES timeseries files, the formatted LES ".csv" and the Python import script to ZENODO. The Python import script may require additional docstrings and explanation.
  • Add codeblock in example 12 to automatically download LES .csv from Zenodo in Python.
  • FLASC documentation: add more information about SMARTEOLE LES simulation set-up.
  • @Robin9697 Review final Jupyter notebook.

Optional:

  • Add option to turn "cumulative production" into AEP. I would say adding an option to compare_cumulative_production_and_relative_wake_loss() with, for example, scale_to_aep=False as the default, and scale_to_aep_method=0 being taking the mean across all turbines at every separate timestep, and scale_to_aep_method=1 being taking the mean across all timesteps for a single turbine. This can use some more thinking through.

@misi9170
Copy link
Copy Markdown
Collaborator

misi9170 commented Mar 9, 2026

Hi @Bartdoekemeijer and @Robin9697 !

Thanks for submitting this! I've left a couple of comments throughout the code. I have not actually run the example, but I believe @paulf81 will do that. To merge this, we'll need to have the floris_tools_test.py passing again. I've also identified a couple of places where we may be duplicating some functionality, which has been a bit of a challenge with FLASC in the past so I don't necessarily expect you to solve this now.

I guess the next steps are:

@Bartdoekemeijer
Copy link
Copy Markdown
Collaborator Author

Hi @Bartdoekemeijer and @Robin9697 !

Thanks for submitting this! I've left a couple of comments throughout the code. I have not actually run the example, but I believe @paulf81 will do that. To merge this, we'll need to have the floris_tools_test.py passing again. I've also identified a couple of places where we may be duplicating some functionality, which has been a bit of a challenge with FLASC in the past so I don't necessarily expect you to solve this now.

I guess the next steps are:

Thank you for the quick review @misi9170! I have processed all your comments, fixed formatting through ruff and ensured all the tests are now passing. Let me know what else is needed!

@paulf81
Copy link
Copy Markdown
Collaborator

paulf81 commented Mar 10, 2026

Hi @Bartdoekemeijer @Robin9697 and @misi9170 . I reviewed the example and discussed and ran it on my machine. The only hiccup I had was I found I needed two additional dependencies:

    "xarray~=2025.6.1",
    "netcdf4~=1.6",

In order to run: examples_smarteole/data/SMARTEOLE-LES-simulation-data/import_aspire_les_data.py

I commited an update to pyproject.toml with these included using our standard ~= versioning if that works for you @misi9170

UPDATE: Following these changes, download and notebook all worked as expected

@misi9170
Copy link
Copy Markdown
Collaborator

misi9170 commented Mar 10, 2026

@paulf81 I don't have any issues adding requirements. It looks like "netcdf4~=1.6" should be compatible with versions dating back to 2023, which is plenty long enough, I think. For xarray, specifying "xarray~=2025.6.1" is going to lock FLASC and FLASC users to the June 2025 versions of xarray. That seems like a pretty strong requirement---does it need to be that strong? (it doesn't help here that xarray isn't really following normal semantic versioning rules, which makes the ~= specification less powerful)

@paulf81
Copy link
Copy Markdown
Collaborator

paulf81 commented Mar 10, 2026

@paulf81 I don't have any issues adding requirements. It looks like "netcdf4~=1.6" should be compatible with versions dating back to 2023, which is plenty long enough, I think. For xarray, specifying "xarray~=2025.6.1" is going to lock FLASC and FLASC users to the June 2025 versions of xarray. That seems like a pretty strong requirement---does it need to be that strong? (it doesn't help here that xarray isn't really following normal semantic versioning rules, which makes the ~= specification less powerful)

Yeah you're right, maybe we just specify xarray since I don't know how semantic versioning would work with this year convention? I can make that change

@misi9170 misi9170 self-requested a review March 10, 2026 22:05
@paulf81 paulf81 self-requested a review March 10, 2026 22:09
@misi9170
Copy link
Copy Markdown
Collaborator

misi9170 commented Mar 11, 2026

I've now also successfully run the example in a fresh conda environment. I set up the environment with the latest python (3.14.3), and then installed flasc locally (pip install -e . from within the top directory). This installed xarray 2026.2.0 and netCDF4 1.7.4.

I then had some minor trouble trying to download the data from an internal VPN (failed to certify the SSL certificate when accessing the Zenodo DOI). Switching off the VPN worked. I've added a note to the __main__ portion of import_aspire_les_data.py to this effect.

To run the example notebook, I then had to add the standard ipykernel package to my conda environment. This would have been needed to run any example, but is not listed as a requirement of FLASC. I think that is Ok, since the FLASC package itself does not require running notebooks (only the examples do). Moreover, this is not an issue that is specific to this PR, as ipykernel would be needed to run any of the examples.

After that, the notebook ran through fine. I'll also just point out here that the notebook is before being run, so it has does not have figures included. That is my preference, because it makes version control better, but it does mean that people won't be able to view the notebook output without downloading the data and running it. Various of our example notebooks in FLASC were committed after running, so include outputs (and figures).

Compare this (the new example notebook, no outputs) to this (an existing example notebook, contains outputs).

@Bartdoekemeijer
Copy link
Copy Markdown
Collaborator Author

I created a clean environment with Python 3.13 on Windows 11. The company firewall blocks automated Zenodo downloads, so I cannot verify that, but everything else works as intended!

Minor comments:

  1. It is important for the user to have run steps 00 through 04 in the Smarteole folder before running 12. I think this is fine.
  2. There appear to be two empty python code cells in the Jupyter notebook example 12. Not a problem, more like a clean-up.

@misi9170 misi9170 merged commit a64a1df into NatLabRockies:develop Mar 11, 2026
5 checks passed
@misi9170
Copy link
Copy Markdown
Collaborator

misi9170 commented Mar 11, 2026

I created a clean environment with Python 3.13 on Windows 11. The company firewall blocks automated Zenodo downloads, so I cannot verify that, but everything else works as intended!

Minor comments:

1. It is important for the user to have run steps 00 through 04 in the Smarteole folder before running 12. I think this is fine.

2. There appear to be two empty python code cells in the Jupyter notebook example 12. Not a problem, more like a clean-up.

Thanks @Bartdoekemeijer ! I think that's all good. I added a brief note to the downloading script to mention VPN/SSL issues with accessing data, and I'll leave it to users to figure out how to avoid those. I didn't see the empty python cells, but if they're there, not a big deal. I've merged, and will release a minor version of FLASC later today.

@misi9170 misi9170 mentioned this pull request Mar 11, 2026
2 tasks
@Bartdoekemeijer
Copy link
Copy Markdown
Collaborator Author

Thank you @misi9170 and @paulf81!!

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

Labels

examples Changes to FLASC examples new-feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants