diff --git a/CHANGELOG.md b/CHANGELOG.md index 8813e6b1..3a7cb448 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. If you make - Deprecate Python 3.8 support and enable 3.12 support. - Features and updates: + - `WakeLosses` updates + - Option added to the `WakeLosses` analysis method to correct for freestream wind speed + heterogeneity across a wind plant when estimating internal wake losses. The method relies on + a user-provided freestream wind speedup csv file. The wake loss example notebook has been + updated to illustrate how to use this option. + - `WakeLosses` analysis method updated to flag and exclude unrealistic turbine wind speed + measurements - `MonteCarloAEP` updates - Add an `n_jobs` input to the Monte Carlo AEP method to allow for the underlying models to be parallelized during each iteration for faster ML model computation. @@ -12,8 +19,12 @@ All notable changes to this project will be documented in this file. If you make IAV factor at the end of the analysis. - Add a `progress_bar` flag to `MonteCarloAEP.run()` to allow for turing on or off the simulation's default progress bar. + - Option added to the IEC power curve model in the `openoa/utils/power_curve/functions` module to + linearly interpolate power between wind speed bin centers - Implement missing `compute_wind_speed` in `openoa/utils/met_data_processing.py` and apply it to the `PlantData` reanalysis validation steps in place of the manual calculation. + - Functions for downloading hourly ERA5 and MERRA-2 reanalysis data added to the + `openoa/utils/downloader` module. - Fixes: - Add a default value for `PlantData`'s `asset_distance_matrix` and `asset_direction_matrix` to ensure projects not utilizing location data are compatible. diff --git a/README.md b/README.md index af746cb9..085b2c58 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ best practices, and engagement with subject matter experts when performing any d | `TurbineLongTermGrossEnergy`| This routine estimates the long-term turbine ideal energy (TIE) of a wind plant, defined as the long-term AEP that would be generated by the wind plant if all turbines operated normally (i.e., no downtime, derating, or severe underperformance, but still subject to wake losses and moderate performance losses), along with the uncertainty. | [^5] | | `ElectricalLosses`| The ElectricalLosses routine estimates the average electrical losses at a wind plant, along with the uncertainty, by comparing the energy produced at the wind turbines to the energy delivered to the grid. | [^5] | | `EYAGapAnalysis`| This class is used to perform a gap analysis between the estimated AEP from a pre-construction energy yield estimate (EYA) and the actual AEP. The gap analysis compares different wind plant performance categories to help understand the sources of differences between EYA AEP estimates and actual AEP, specifically availability losses, electrical losses, and TIE. | [^5] | -| `WakeLosses`| This routine estimates long-term internal wake losses experienced by a wind plant and for each individual turbine, along with the uncertainty. | [^6]. Based in part on approaches in [^7], [^8], [^9] | -| `StaticYawMisalignment`| The StaticYawMisalignment routine estimates the static yaw misalignment for individual wind turbines as a function of wind speed by comparing the estimated wind vane angle at which power is maximized to the mean wind vane angle at which the turbines operate. The routine includes uncertainty quantification. **Warning: This method has not been validated using data from wind turbines with known static yaw misalignments and the results should be treated with caution.** | Based in part on approaches in [^10], [^11], [^12], [^13], [^14] | +| `WakeLosses`| This routine estimates long-term internal wake losses experienced by a wind plant and for each individual turbine, along with the uncertainty. | [^6]. Based in part on approaches in [^7], [^8], [^9], [^10] | +| `StaticYawMisalignment`| The StaticYawMisalignment routine estimates the static yaw misalignment for individual wind turbines as a function of wind speed by comparing the estimated wind vane angle at which power is maximized to the mean wind vane angle at which the turbines operate. The routine includes uncertainty quantification. **Warning: This method has not been validated using data from wind turbines with known static yaw misalignments and the results should be treated with caution.** | Based in part on approaches in [^11], [^12], [^13], [^14], [^15] | ### PlantData Schema @@ -328,7 +328,7 @@ This project follows the [all-contributors](https://github.com/all-contributors/ [^5]: Todd, A. C., Optis, M., Bodini, N., Fields, M. J., Lee, J. C. Y., Simley, E., and Hammond, R. An independent analysis of bias sources and variability in wind plant pre‐construction energy yield estimation methods. *Wind Energy*, 25(10):1775-1790 (2022). https://doi.org/10.1002/we.2768. -[^6]: Simley, E., Fields, M. J., Perr-Sauer, J., Hammond, R., and Bodini, N. A Comparison of Preconstruction and Operational Wake Loss Estimates for Land-Based Wind Plants. Presented at the NAWEA/WindTech 2022 Conference, Newark, DE, September 20-22 (2022). https://www.nrel.gov/docs/fy23osti/83874.pdf. +[^6]: Simley, E., Fields, M. J., Young, E., Allen, J., Hammond, R., Perr-Sauer, J., and Bodini, N. A comparison of preconstruction and operational wake loss estimates for land-based wind plants. *Wind Energy*, 28(11) (2025). https://doi.org/10.1002/we.70067. [^7]: Barthelmie, R. J. and Jensen, L. E. Evaluation of wind farm efficiency and wind turbine wakes at the Nysted offshore wind farm, *Wind Energy* 13(6):573–586 (2010). https://doi.org/10.1002/we.408. @@ -336,12 +336,14 @@ This project follows the [all-contributors](https://github.com/all-contributors/ [^9]: Walker, K., Adams, N., Gribben, B., Gellatly, B., Nygaard, N. G., Henderson, A., Marchante Jimémez, M., Schmidt, S. R., Rodriguez Ruiz, J., Paredes, D., Harrington, G., Connell, N., Peronne, O., Cordoba, M., Housley, P., Cussons, R., Håkansson, M., Knauer, A., and Maguire, E.: An evaluation of the predictive accuracy of wake effects models for offshore wind farms. *Wind Energy* 19(5):979–996 (2016). https://doi.org/10.1002/we.1871. -[^10]: Bao, Y., Yang, Q., Fu, L., Chen, Q., Cheng, C., and Sun, Y. Identification of Yaw Error Inherent Misalignment for Wind Turbine Based on SCADA Data: A Data Mining Approach. Proc. 12th Asian Control Conference (ASCC), Kitakyushu, Japan, June 9-12 (2019). 1095-1100. +[^10]: Kassebaum, J. Wake Validation Through SCADA Data Analysis. Proc. American Clean Power Resource & Project Energy Assessment Virtual Summit 2021 (2021). -[^11]: Xue, J. and Wang, L. Online data-driven approach of yaw error estimation and correction of horizontal axis wind turbine. *IET J. Eng.* 2019(18):4937–4940 (2019). https://doi.org/10.1049/joe.2018.9293. +[^11]: Bao, Y., Yang, Q., Fu, L., Chen, Q., Cheng, C., and Sun, Y. Identification of Yaw Error Inherent Misalignment for Wind Turbine Based on SCADA Data: A Data Mining Approach. Proc. 12th Asian Control Conference (ASCC), Kitakyushu, Japan, June 9-12 (2019). 1095-1100. -[^12]: Astolfi, D., Castellani, F., and Terzi, L. An Operation Data-Based Method for the Diagnosis of Zero-Point Shift of Wind Turbines Yaw Angle. *J. Solar Energy Engineering* 142(2):024501 (2020). https://doi.org/10.1115/1.4045081. +[^12]: Xue, J. and Wang, L. Online data-driven approach of yaw error estimation and correction of horizontal axis wind turbine. *IET J. Eng.* 2019(18):4937–4940 (2019). https://doi.org/10.1049/joe.2018.9293. -[^13]: Jing, B., Qian, Z., Pei, Y., Zhang, L., and Yang, T. Improving wind turbine efficiency through detection and calibration of yaw misalignment. *Renewable Energy* 160:1217-1227 (2020). https://doi.org/10.1016/j.renene.2020.07.063. +[^13]: Astolfi, D., Castellani, F., and Terzi, L. An Operation Data-Based Method for the Diagnosis of Zero-Point Shift of Wind Turbines Yaw Angle. *J. Solar Energy Engineering* 142(2):024501 (2020). https://doi.org/10.1115/1.4045081. -[^14]: Gao, L. and Hong, J. Data-driven yaw misalignment correction for utility-scale wind turbines. *J. Renewable Sustainable Energy* 13(6):063302 (2021). https://doi.org/10.1063/5.0056671. +[^14]: Jing, B., Qian, Z., Pei, Y., Zhang, L., and Yang, T. Improving wind turbine efficiency through detection and calibration of yaw misalignment. *Renewable Energy* 160:1217-1227 (2020). https://doi.org/10.1016/j.renene.2020.07.063. + +[^15]: Gao, L. and Hong, J. Data-driven yaw misalignment correction for utility-scale wind turbines. *J. Renewable Sustainable Energy* 13(6):063302 (2021). https://doi.org/10.1063/5.0056671. diff --git a/examples/06_wake_loss_analysis.ipynb b/examples/06_wake_loss_analysis.ipynb index 49b35049..a42cfd02 100644 --- a/examples/06_wake_loss_analysis.ipynb +++ b/examples/06_wake_loss_analysis.ipynb @@ -23,7 +23,7 @@ "source": [ "This notebook demonstrates how to use the Wake Losses method in OpenOA to estimate operational wake losses for the La Haute Borne wind plant based on turbine-level SCADA data. Operational wake losses are estimated at the wind plant level and for each individual wind turbine during the period of record (POR) when data are available as well as for the estimated long-term wind conditions the wind plant will experience based on historical reanalysis wind resource data. \n", "\n", - "At a high-level, wake losses are estimated by comparing the actual energy produced by the wind plant (or turbine) to the potential energy that would have been produced without wake losses, which is treated as the average energy produced by the turbines experiencing freestream inflow multiplied by the number of turbines in the wind plant. Note that this method therefore assumes that without wake losses, each turbine would be capable of producing the same power as the freestream wind turbines, which may not be a valid assumption in some cases, such as in complex terrain or when wake effects from neighboring wind plants are present. \n", + "At a high-level, wake losses are estimated by comparing the actual energy produced by the wind plant (or turbine) to the potential energy that would have been produced without wake losses, which in the most basic form is treated as the average energy produced by the turbines experiencing freestream inflow multiplied by the number of turbines in the wind plant. Note that this method therefore assumes that without wake losses, each turbine would be capable of producing the same power as the freestream wind turbines, which may not be a valid assumption in some cases, such as in complex terrain or when wake effects from neighboring wind plants are present. Therefore, an alternative method for estimating the potential wind plant energy by accounting for freestream wind speed heterogeneity across the wind plant is available.\n", "\n", "The approach used to estimate wake losses is as follows.\n", "\n", @@ -31,11 +31,12 @@ "2. Next, the set of derated, curtailed, or unavailable turbines (i.e., turbines whose power production is limited not by wake losses but by operating mode) is identified for each time step using power curve outlier detection.\n", "3. For a sequence of wind direction bins, using the representative wind plant-level wind direction, the set of freestream turbines is identified based on whether any other wind turbines are located upstream of a turbine within a user-specified sector of wind directions. The average power production and average wind speed are then calculated for the set of freestream turbines operating normally (i.e., not derated) for each time step.\n", "4. Period-of-record wake losses are then calculated for the wind plant by comparing the potential energy production (calculated as the sum of the mean freestream power production for each time step multiplied by the number of turbines in the wind plant) to the actual energy production (given by the sum of the actual wind power production for each turbine and each time step). However, if specified by the user, the potential power production of the wind plant at each time step is assumed to be limited to the actual power produced by the derated turbines plus the mean power production of the freestream turbines for all other turbines in the wind plant. This same basic procedure is then used to estimate the wake losses for each individual wind turbine.\n", - "5. Lastly, the long-term corrected wake losses are estimated using historical reanalysis data. The long-term frequencies of occurence are calculated for a set of wind direction and wind speed bins, based on historical hourly reanalysis data. Using the representative wind plant wind directions from SCADA or met tower data and the freestream wind speeds based on the turbine SCADA data, the average potential and actual wind plant power production are computed for each wind direction and wind speed bin. The long-term corrected wake losses are then estimated by comparing the long-term corrected potential and actual energy production, which are in turn determined by weighting the average potential and actual power production in each wind condition bin by the long-term frequencies. This basic process is then repeated to estimate the long-term corrected wake losses for each individual turbine.\n", + "5. Optionally, the potential energy of the wind plant can be corrected to account for freestream wind speed heterogeneity across the wind plant, which is specified using wind direction dependent speedup factors at each turbine location from a user-provided csv file. To estimate the potential power of the normally operating turbines, the mean power of the normally operating freestream turbines is multiplied by the sum of the expected freestream power at all turbine locations, based on the provided speedup factors, divided by the mean expected freestream power of the normally operating freestream turbines.\n", + "6. Lastly, the long-term corrected wake losses are estimated using historical reanalysis data. The long-term frequencies of occurence are calculated for a set of wind direction and wind speed bins, based on historical hourly reanalysis data. Using the representative wind plant wind directions from SCADA or met tower data and the freestream wind speeds based on the turbine SCADA data, the average potential and actual wind plant power production are computed for each wind direction and wind speed bin. The long-term corrected wake losses are then estimated by comparing the long-term corrected potential and actual energy production, which are in turn determined by weighting the average potential and actual power production in each wind condition bin by the long-term frequencies. This basic process is then repeated to estimate the long-term corrected wake losses for each individual turbine.\n", "\n", "If uncertainty quantification (UQ) is selected, wake losses are calculated multiple times using a Monte Carlo approach using randomly chosen analysis parameters and reanalysis products as well as randomly sampled time steps each iteration. If UQ is not used, wake losses are calculated using the specified analysis parameters for the full set of available time steps once for each reanalysis product.\n", "\n", - "In this example, we will demonstrate wake loss analysis both with and without UQ." + "In this example, we will demonstrate wake loss analysis both with and without UQ. A case illustrating corrections for freestream wind speed heterogeneity is presented as well." ] }, { @@ -58,7 +59,7 @@ " \n", "
\n" ] }, @@ -68,7 +69,6 @@ { "data": { "application/javascript": [ - "'use strict';\n", "(function(root) {\n", " function now() {\n", " return new Date();\n", @@ -230,42 +230,17 @@ " \"\\n\"+\n", " \"\"}};\n", "\n", - " function display_loaded(error = null) {\n", - " const el = document.getElementById(\"e4fea4ed-8e69-4978-8f3b-8ca0a40e1cfe\");\n", + " function display_loaded() {\n", + " const el = document.getElementById(\"d3df1f55-159b-4d02-84bd-9e9eb4b3ca04\");\n", " if (el != null) {\n", - " const html = (() => {\n", - " if (typeof root.Bokeh === \"undefined\") {\n", - " if (error == null) {\n", - " return \"BokehJS is loading ...\";\n", - " } else {\n", - " return \"BokehJS failed to load.\";\n", - " }\n", - " } else {\n", - " const prefix = `BokehJS ${root.Bokeh.version}`;\n", - " if (error == null) {\n", - " return `${prefix} successfully loaded.`;\n", - " } else {\n", - " return `${prefix} encountered errors while loading and may not function as expected.`;\n", - " }\n", - " }\n", - " })();\n", - " el.innerHTML = html;\n", - "\n", - " if (error != null) {\n", - " const wrapper = document.createElement(\"div\");\n", - " wrapper.style.overflow = \"auto\";\n", - " wrapper.style.height = \"5em\";\n", - " wrapper.style.resize = \"vertical\";\n", - " const content = document.createElement(\"div\");\n", - " content.style.fontFamily = \"monospace\";\n", - " content.style.whiteSpace = \"pre-wrap\";\n", - " content.style.backgroundColor = \"rgb(255, 221, 221)\";\n", - " content.textContent = error.stack ?? error.toString();\n", - " wrapper.append(content);\n", - " el.append(wrapper);\n", + " el.textContent = \"BokehJS is loading...\";\n", + " }\n", + " if (root.Bokeh !== undefined) {\n", + " if (el != null) {\n", + " el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n", " }\n", " } else if (Date.now() < root._bokeh_timeout) {\n", - " setTimeout(() => display_loaded(error), 100);\n", + " setTimeout(display_loaded, 100)\n", " }\n", " }\n", "\n", @@ -344,9 +319,8 @@ "\n", " const inline_js = [ function(Bokeh) {\n", " /* BEGIN bokeh.min.js */\n", - " 'use strict';\n", " /*!\n", - " * Copyright (c) Anaconda, Inc., and Bokeh Contributors\n", + " * Copyright (c) 2012 - 2024, Anaconda, Inc., and Bokeh Contributors\n", " * All rights reserved.\n", " * \n", " * Redistribution and use in source and binary forms, with or without modification,\n", @@ -409,7 +383,7 @@ " if (alias != null)\n", " return alias;\n", "\n", - " const trailing = name.length > 0 && name[name.length-1] === \"/\";\n", + " const trailing = name.length > 0 && name[name.lenght-1] === \"/\";\n", " const index = aliases[name + (trailing ? \"\" : \"/\") + \"index\"];\n", " if (index != null)\n", " return index;\n", @@ -503,254 +477,185 @@ " return main;\n", " })\n", " ([\n", - " function _(t,_,n,o,r){o();t(1).__exportStar(t(2),n),t(76)},\n", + " function _(t,_,n,o,r){o();t(1).__exportStar(t(2),n),t(70)},\n", " function _(e,t,r,n,o){n();var a=function(e,t){return a=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])},a(e,t)};function i(e,t){if(\"function\"!=typeof t&&null!==t)throw new TypeError(\"Class extends value \"+String(t)+\" is not a constructor or null\");function r(){this.constructor=e}a(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}r.__extends=i;function c(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(null!=e&&\"function\"==typeof Object.getOwnPropertySymbols){var o=0;for(n=Object.getOwnPropertySymbols(e);o