{ "cells": [ { "cell_type": "markdown", "id": "08138d49", "metadata": {}, "source": [ "# 01 - Yaw angle optimization\n", "\n", "**Amazon SageMaker kernel**: conda_pytorch_p36\n", "\n", "## Process description\n", "\n", "The wake from one wind turbine makes the turbines behind it less efficient. This effect is similar to the way that a speedboat is slowed by the choppy water caused by the boat in front. Wake steering is the intentional yaw misalignment of the leading turbines (i.e., pointing turbines slightly awy from oncoming wind) to deflect the energy deficit wake region away from downwind generators. Through wake steering, turbines in the front produce less power but downwind turbines can generate significantly more power.\n", "\n", "
\n", "\"wake-steering\"\n", "
Fig.1 - Angling turbines slightly away from the wind steers their wakes away from downstream turbines [2]. (Image credit: Rebecca Konte)
\n", "
\n", "\n", "Wind farm operators traditionally face their turbines directly into the wind. New research shows that angling turbines slightly away from the wind steers their wakes away from downstream turbines and improves wind farm production. (Image credit: Rebecca Konte)\n", "\n", "Wake steering has emerged in the last decade as a promising collective control strategy to increase wind farm power production. [Research from Stanford University](https://www.pnas.org/doi/10.1073/pnas.1903680116) [1] suggests that fine-tuning the alignment of turbine arrays can reduce turbulence and increase production by up to 47%, especially at low speeds. At average wind speeds, the gain can still be an impressive 7-13%. Other benefits include reducing overall intermitency and mechanical degradation.\n", "\n", "Existing yaw control algorithms attempt to optimize yaw misalignment angle set-points using steady-state wake models. Wake models are susceptible to modeling error from inadequate modeling assumptions and error in model parameteres. Moreover, even if model parameters are set properly, they are prone to change due to machine degradation and static yaw misaslignment.\n", "\n", "Machine learning can provide an alternative to steady-state wake models. If the operational space of the wind turbines is mapped (i.e., data is collected at various combinations of yaw angles), a data scientist can use low-pass filtered data from the SCADA (e.g., 10 min averages) to build a machine learning model of power generation in the wind farm.\n", "\n", "In this notebook, we will go through the steps of processing SCADA data to **build a machine learning model of power generation** in the wind farm, and will show how the model can be used to find **optimal yaw misalignment angles for varying wind conditions**." ] }, { "cell_type": "markdown", "id": "b892cb2c", "metadata": {}, "source": [ "## Wake steering\n", "\n", "A four-turbine wind farm was simulated with NREL's [Floris model](https://www.nrel.gov/wind/floris.html). The wind farm consists of 2 leading turbines and 2 trailing turbines. When wind comes with a direction of 270 degrees, the wake from the leading turbines impacts the performance of the trailing turbines. However, once the yaw angle is misaligned, the wake from the leading turbines does not directly impact the trailing turbines anymore, and overall generated power increases by 7.4%.\n", "\n", "
\n", "\"simulated-wake-steering\"\n", "
Fig.2 - Effect of wake steering for a four-turbine wind farm designed for wind coming from the West [3]
\n", "
\n", "\n", "\n", "**References**\n", "\n", "[1] Howland, Michael F., Sanjiva K. Lele, and John O. Dabiri. \"Wind farm power optimization through wake steering.\" Proceedings of the National Academy of Sciences 116.29 (2019): 14495-14500.\n", "\n", "[2] Xia, Vincent. \"Wind Farm wake steering: small re-alignments of turbines can increase output by 40%.\" enerypost.eu Available on: [https://energypost.eu/wind-farm-wake-steering-small-re-alignments-of-turbines-can-increase-output-by-40/](https://energypost.eu/wind-farm-wake-steering-small-re-alignments-of-turbines-can-increase-output-by-40/)\n", "\n", "[3] National Renewable Energy Laboratory. \"FLORIS\" Available on: [https://github.com/NREL/floris](https://github.com/NREL/floris)" ] }, { "cell_type": "markdown", "id": "0fb6b325", "metadata": {}, "source": [ "## Set up environment\n", "\n", "The first step in this notebook will be the definition of the environment variables that SageMaker will use to build and store our machine learning model. **Insert the name of the S3 bucket created by your CloudFormation template**" ] }, { "cell_type": "code", "execution_count": null, "id": "0aacbcb6", "metadata": {}, "outputs": [], "source": [ "import boto3\n", "import sagemaker\n", "import time\n", "from sagemaker.utils import name_from_base\n", "\n", "bucket = \"\" # Insert the name of your S3 bucket from CloudFormation outputs\n", "role = sagemaker.get_execution_role()\n", "sess = sagemaker.Session()\n", "region = \"us-east-1\"\n", "prefix = \"models/pytorch\"\n", "\n", "assert bucket, \"ERROR: Insert the name of your S3 bucket before moving on\"" ] }, { "cell_type": "markdown", "id": "66e42213", "metadata": {}, "source": [ "## Exploratory data analysis\n", "\n", "This notebook uses historical data that has been extracted from a SCADA system that exposes telemetry from the wind farm simulator. The data set consists of 10 min averages of measurements taken at each turbine (`turbineX_wind_speed`, `turbineX_wind_direction`, `turbineX_yaw_angle`, `turbineX_power`) and at the control station (`speed` and `direction`). Simulated historical data has already been loaded in this SageMaker environment, and we will start by running an exploratory analysis." ] }, { "cell_type": "code", "execution_count": null, "id": "cf3eca8f", "metadata": {}, "outputs": [], "source": [ "import os\n", "import pandas as pd\n", "\n", "FILE_PATH = os.path.join(os.getcwd(), \"data\", \"simulated_data.csv\")\n", "\n", "df = pd.read_csv(FILE_PATH, index_col=\"timestamp\", parse_dates=[0])\n", "df.head()" ] }, { "cell_type": "markdown", "id": "d1473fee", "metadata": {}, "source": [ "The first step will be the exploration of the data set in search for insight into the process dynamics. We will start by plotting time series data for wind conditions and power generated by the wind farm." ] }, { "cell_type": "code", "execution_count": null, "id": "c8eff01e", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "# Exploratory analysis of historical data\n", "fig, (ax1, ax2, ax3) = plt.subplots(3, 1, sharex=True, figsize=(20,12))\n", "\n", "# Subplot 1: Wind speed [m/s]\n", "resampled_speed = df[\"speed\"].resample(\"1H\").mean()\n", "ax1.plot(resampled_speed)\n", "ax1.set_xlim(resampled_speed.index.min(), resampled_speed.index.max())\n", "ax1.set_ylabel(\"Wind speed [m/s]\")\n", "ax1.grid(True)\n", "\n", "# Subplot 2: Wind direction [degrees]\n", "resampled_direction = df[\"direction\"].resample(\"1H\").mean()\n", "ax2.plot(resampled_direction)\n", "ax2.set_ylabel(\"Wind direction [deg]\")\n", "ax2.grid(True)\n", "\n", "# Subplot 3: Total power generated [kW]\n", "resampled_power = (df[\"turbine0_power\"]+df[\"turbine1_power\"]+df[\"turbine2_power\"]+df[\"turbine3_power\"]).resample(\"1H\").mean()\n", "ax3.plot(resampled_power)\n", "ax3.set_xlabel(\"Time\")\n", "ax3.set_ylabel(\"Total power [kW]\")\n", "ax3.grid(True)\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "6e5f8201", "metadata": {}, "source": [ "From the plots we can observe that there is a wide range of variation for wind speed, which goes from approximately 4 m/s to 15 m/s, but that wind direction only varies between 250 degrees to 290 degrees.\n", "\n", "We can also observe that there appears to be a positive relationship between wind speed and total power, i.e., more power is generated when wind speed is higher.\n", "\n", "Now let us plot yaw angles in this one-year period. Yaw angles have been allowed to vary to collect data at various wind conditions and yaw angle configuration (mapping of the operational space). Note that we will only plot yaw angls for `turbine0` and `turbine1` because those are the leading turbines. Wake steering in the trailing turbines will have no effect on power generation." ] }, { "cell_type": "code", "execution_count": null, "id": "85320e34", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "# Explore the effect of yaw angles in cooperative power generation\n", "fig, (ax1, ax2, ax3, ax4) = plt.subplots(1, 4, sharex=True, figsize=(20,4))\n", "\n", "# Yaw angles in turbine 0\n", "ax1.hist(df[\"turbine0_yaw_angle\"], bins=6, density=True)\n", "ax1.set_ylabel(\"Turbine 0 [deg]\")\n", "\n", "# Yaw angles in turbine 1\n", "ax2.hist(df[\"turbine1_yaw_angle\"], bins=6, density=True)\n", "ax2.set_ylabel(\"Turbine 1 [deg]\")\n", "\n", "# Yaw angles in turbine 2\n", "ax3.hist(df[\"turbine2_yaw_angle\"], bins=6, density=True)\n", "ax3.set_ylabel(\"Turbine 2 [deg]\")\n", "\n", "# Yaw angles in turbine 3\n", "ax4.hist(df[\"turbine3_yaw_angle\"], bins=6, density=True)\n", "ax4.set_ylabel(\"Turbine 3 [deg]\")\n", "\n", "plt.suptitle(\"Yaw angles\", fontsize=14)\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "23d1abae", "metadata": {}, "source": [ "There is an even distribution of data points collected for yaw angles that vary between 0 degrees and 25 degrees for turbines 0 and 1. Turbines 2 and 3 (trailing turbines) have not been subjected to wake steering.\n", "\n", "Let us have a look at the relationships between power in two turbines (a leading one and a trailing one) and speed. There is a clear positive relationship between wind speed and power -- when there is more wind, turbines generate more power." ] }, { "cell_type": "code", "execution_count": null, "id": "ef0fdb99", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "# Plot the dependency between power generated at a leading turbine and a trailing turbine, and speed\n", "pd.plotting.scatter_matrix(df[[\"speed\", \"turbine0_power\", \"turbine2_power\"]],\n", " figsize=(8,8), diagonal='kde')\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "a487aaf0", "metadata": {}, "source": [ "Now let us **correct for changes in wind conditions (speed and direction)** and see if there is a relationship between the yaw of a leading turbine and the power generated in the leading turbine and the one downwind." ] }, { "cell_type": "code", "execution_count": null, "id": "e79491dd", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "# Select only records when speed is near 8.0 m/s and wind direction is close to 270 degrees\n", "df_ = df[(df[\"speed\"] >= 7.9) & (df[\"speed\"] <= 8.1) &\n", " (df[\"direction\"] >= 268.0) & (df[\"direction\"] <= 272.0)]\n", "\n", "pd.plotting.scatter_matrix(df_[[\"turbine0_yaw_angle\", \"turbine0_power\", \"turbine2_power\"]],\n", " figsize=(8,8), diagonal='kde')\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "b6dd0b40", "metadata": {}, "source": [ "Now we observe an interesting phenomenon. At low wind speeds, the **intentional misalignment of the yaw in leading turbines decreases the power generated by that turbine but increases the power generated in the trailing turbine**. In subset of data shown above, wake steering can reduce power in turbine 0 (leading) by up to 300 kW while increasing power in turbine 2 (trailing) by up to 400 kW. It is, however, still difficult to predict the effect that wake steering will have on the power generated by the leading and trailing turbines. Machine learning can help with that part." ] }, { "cell_type": "markdown", "id": "e79961da", "metadata": {}, "source": [ "## Building a power generation model\n", "\n", "Historical data will now be used to build a machine learning model of the wind farm dynamics. The following cell shows the script of that defined process model designed in PyTorch, an open source machine learning framework. The model will take four inputs (`speed`, `direction`, `turbine0_yaw_angle` and `turbine1_yaw_angle`) and will return four outputs (`turbine0_power`, `turbine1_power`, `turbine2_power` and `turbine4_power`).\n", "\n", "Spend a minute exploring the structure of the multilayer perceptron model that is used to predict the behavior of the yaw angle optimization process." ] }, { "cell_type": "code", "execution_count": null, "id": "e4e813b0", "metadata": {}, "outputs": [], "source": [ "!pygmentize WindFarmModel.py" ] }, { "cell_type": "markdown", "id": "f4f19424", "metadata": {}, "source": [ "Before training the model, historical data will be split into a training and a validation set, which will have 90% and 10% of the data, respectively." ] }, { "cell_type": "code", "execution_count": null, "id": "e7c1348f", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "model_data = df[[\"speed\", \"direction\", \"turbine0_yaw_angle\", \"turbine1_yaw_angle\",\n", " \"turbine0_power\", \"turbine1_power\", \"turbine2_power\", \"turbine3_power\"]]\n", "train_data, validation_data = np.split(\n", " model_data.sample(frac=1, random_state=42),\n", " [int(0.9 * len(model_data))])" ] }, { "cell_type": "markdown", "id": "3e37346b", "metadata": {}, "source": [ "The `process_dataframe` function scales input and output variables before training the `WindFarmModel` to train the neural network faster and prevent issues with local optima." ] }, { "cell_type": "code", "execution_count": null, "id": "055918e8", "metadata": {}, "outputs": [], "source": [ "import torch\n", "\n", "def process_dataframe(df):\n", " \"\"\"\n", " Preprocess a pandas dataframe to extract\n", " independent and depent variables, and scale them\n", " \"\"\"\n", " X = df[['speed', 'direction', 'turbine0_yaw_angle', 'turbine1_yaw_angle']].values\n", " Y = df[['turbine0_power', 'turbine1_power', 'turbine2_power', 'turbine3_power']].values\n", " # Scale speed between 4 and 15 (min and max from dataset)\n", " X[:,0] = (X[:,0]-4.0)/11.0\n", " # Scale direction between 250 and 290\n", " X[:,1] = (X[:,1]-250.0)/40.0\n", " # Scale turbine0_yaw_angle between 0 and 25\n", " X[:,2] = X[:,2]/25.0\n", " # Scale turbine1_yaw_angle between 0 and 25\n", " X[:,3] = X[:,3]/25.0\n", "\n", " # Scale turbine power between 0 and 5 MW (nominal design conditions)\n", " Y = Y/5000.0\n", " \n", " # Convert to PyTorch tensors\n", " X = torch.from_numpy(X.astype(np.float32))\n", " Y = torch.from_numpy(Y.astype(np.float32))\n", " \n", " return (X, Y)\n", "\n", "X_train, Y_train = process_dataframe(train_data)\n", "X_validation, Y_validation = process_dataframe(validation_data)" ] }, { "cell_type": "markdown", "id": "45494c9b", "metadata": {}, "source": [ "We will now select a learning rate, a loss function, and an optimizer. In this case we will use the Adam optimizer with a learning rate of 0.005. To learn more about training neural networks and selecting hyperparameters, explore the courses at [Machine Learning University](https://aws.amazon.com/machine-learning/mlu/)." ] }, { "cell_type": "code", "execution_count": null, "id": "d36a204e", "metadata": {}, "outputs": [], "source": [ "from WindFarmModel import WindFarmModel\n", "import torch.nn as nn\n", "\n", "model = WindFarmModel()\n", "learning_rate = 0.005\n", "l = nn.MSELoss()\n", "optimizer = torch.optim.Adam(model.parameters(), lr =learning_rate)" ] }, { "cell_type": "markdown", "id": "46d6d56a", "metadata": {}, "source": [ "Now we train the model for 500 epochs. Hyperparameters were chosen to result in acceptable performance in just a few minutes, unlike most neural network models that take much longer to train. This model should take only 2 minutes to train." ] }, { "cell_type": "code", "execution_count": null, "id": "4e220d49", "metadata": {}, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "import timeit\n", "\n", "num_epochs = 500\n", "training_loss, validation_loss = [], []\n", "\n", "tic=timeit.default_timer()\n", "for epoch in range(num_epochs):\n", " # Forward feed\n", " Y_pred = model(X_train.requires_grad_())\n", " # Calculate the training loss\n", " t_loss = l(Y_pred, Y_train)\n", " training_loss.append(t_loss.item())\n", " # Calculate validation loss\n", " Y_pred = model(X_validation)\n", " v_loss = l(Y_pred, Y_validation)\n", " validation_loss.append(v_loss.item())\n", " # Backpropagation: calculate gradients\n", " t_loss.backward()\n", " # Update the weights\n", " optimizer.step()\n", " # Clear out the gradients from the last step loss.backward()\n", " optimizer.zero_grad()\n", "\n", "toc=timeit.default_timer()\n", "\n", "print(\"Model training took {:.1f} seconds\".format(toc-tic))\n", "\n", "# Plot training and validation losses\n", "plt.plot(training_loss, color=\"steelblue\", label=\"training\")\n", "plt.plot(validation_loss, color=\"firebrick\", label=\"validation\")\n", "plt.xlabel(\"Epoch\")\n", "plt.ylabel(\"Loss\"); plt.yscale('log')\n", "plt.legend()\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "id": "e4513057", "metadata": {}, "source": [ "### Evaluate process model\n", "\n", "We now can evaluate the `WindFarmModel` with various conditions (i.e., different combinations of `wind speed` and `wind direction`) and compare them to historical data. Let us evaluate the case we plotted a few cells above (wind speed at 8.0 m/s and wind direction at 270 degrees). We will compare both the aligned and yawed scenarios." ] }, { "cell_type": "code", "execution_count": null, "id": "71a50ef7", "metadata": {}, "outputs": [], "source": [ "def evaluate_pytorch_model(model, manipulated_vars):\n", " \"\"\"\n", " Return prediction for a set of manipulated variables\n", " \"\"\"\n", " X_test = manipulated_vars[['speed', 'direction', 'turbine0_yaw_angle', 'turbine1_yaw_angle']].values\n", " X_test[:,0] = (X_test[:,0]-4.0)/11.0\n", " X_test[:,1] = (X_test[:,1]-250.0)/40.0\n", " X_test[:,2] = X_test[:,2]/25.0\n", " X_test[:,3] = X_test[:,3]/25.0\n", " X_test = torch.from_numpy(X_test.astype(np.float32))\n", " Y_test = model(X_test.float()).cpu().detach().numpy()\n", " Y_test = Y_test*5000.0\n", " prediction = pd.DataFrame({\n", " \"speed\": manipulated_vars[\"speed\"].values.round(2),\n", " \"direction\": manipulated_vars[\"direction\"].values.round(2),\n", " \"turbine0_yaw_angle\": manipulated_vars[\"turbine0_yaw_angle\"].values.round(2),\n", " \"turbine1_yaw_angle\": manipulated_vars[\"turbine1_yaw_angle\"].values.round(2),\n", " \"turbine0_power\": Y_test[:,0].round(2),\n", " \"turbine1_power\": Y_test[:,1].round(2),\n", " \"turbine2_power\": Y_test[:,2].round(2),\n", " \"turbine3_power\": Y_test[:,3].round(2)\n", " })\n", " \n", " return prediction" ] }, { "cell_type": "code", "execution_count": null, "id": "80ddf4dd", "metadata": {}, "outputs": [], "source": [ "# Predict aligned scenario\n", "manipulated_vars = pd.DataFrame({\"speed\": [8.0], \"direction\": [270.0],\n", " \"turbine0_yaw_angle\": [0.0], \"turbine1_yaw_angle\": [0.0]})\n", "\n", "prediction = evaluate_pytorch_model(model, manipulated_vars)\n", "measurements = prediction.loc[0,:]\n", "\n", "print(\"At wind speed {:.2f} m/s at direction {:.0f} with no yaw misalignment\".format(\n", " measurements[\"speed\"], measurements[\"direction\"]))\n", "for ix in range(4):\n", " print(\"Turbine {:d} generated {:.2f} kW\".format(ix, measurements[\"turbine{}_power\".format(ix)]))" ] }, { "cell_type": "code", "execution_count": null, "id": "5643a1a6", "metadata": {}, "outputs": [], "source": [ "# Predict yawed scenario\n", "manipulated_vars = pd.DataFrame({\"speed\": [8.0], \"direction\": [270.0],\n", " \"turbine0_yaw_angle\": [25.0], \"turbine1_yaw_angle\": [25.0]})\n", "\n", "prediction = evaluate_pytorch_model(model, manipulated_vars)\n", "measurements = prediction.loc[0,:]\n", "\n", "print(\"At wind speed {:.2f} m/s at direction {:.0f} with 25 degree yaw misalignment\".format(\n", " measurements[\"speed\"], measurements[\"direction\"]))\n", "for ix in range(4):\n", " print(\"Turbine {:d} generated {:.2f} kW\".format(ix, measurements[\"turbine{}_power\".format(ix)]))" ] }, { "cell_type": "markdown", "id": "f0c8c930", "metadata": {}, "source": [ "Now we will persist the model so it can be reused without having to retrain the model every time. The model will be put inside a TGZ file and uploaded to an S3 bucket so we can deploy it to the edge device later." ] }, { "cell_type": "code", "execution_count": null, "id": "26e729b6", "metadata": {}, "outputs": [], "source": [ "import tarfile\n", "import os\n", "\n", "# Trace the whole module (class) and construct a ScriptModule with a single forward method\n", "module = torch.jit.trace(model.float().eval(), torch.rand(1,4).float())\n", "module.save(\"wind-farm.pth\")\n", "model_name = \"wind-farm-model\"\n", "\n", "try:\n", " os.remove(\"{:s}.tar.gz\".format(model_name))\n", "except:\n", " pass\n", "\n", "with tarfile.open(\"{:s}.tar.gz\".format(model_name), \"w:gz\") as f:\n", " f.add(\"wind-farm.pth\")" ] }, { "cell_type": "code", "execution_count": null, "id": "3ae7926a", "metadata": {}, "outputs": [], "source": [ "# Upload to S3\n", "model_path = sess.upload_data(path=\"{:s}.tar.gz\".format(model_name), bucket = bucket, key_prefix=prefix)\n", "model_s3_uri = \"s3://{:s}/{:s}/{:s}.tar.gz\".format(bucket, prefix, model_name)\n", "print(\"The model tarball is available at: \", model_s3_uri)" ] }, { "cell_type": "markdown", "id": "91af21c3", "metadata": {}, "source": [ "## Process optimization\n", "\n", "Now we will use the model to find the optimal yaw misalignment for the leading turbines in the wind farm. We will select yaw angles between 0 and 25 degrees.\n", "\n", "In this example we will use the `dual_annealing` function from `scipy` to find optimal yaw angles." ] }, { "cell_type": "code", "execution_count": null, "id": "4236c352", "metadata": {}, "outputs": [], "source": [ "from scipy.optimize import dual_annealing\n", "import pandas as pd\n", "import numpy as np\n", "import timeit\n", "\n", "def optimal_manipulated_vars(wind_speed, wind_direction):\n", " \"\"\"\n", " Maximize total generated power\n", " \"\"\"\n", " wind_speed = wind_speed\n", " wind_direction = wind_direction\n", " # Admissible bounds for manipulated variables\n", " bounds = ((0.0, 25.0), (0.0, 25.0))\n", " \n", " def objective(x):\n", " '''\n", " Objective function to maximize: Penalizes deviations from\n", " wind_speed and wind_direction and turbine power leaving the desired range\n", " '''\n", " manipulated_vars = pd.DataFrame({\"speed\": [wind_speed], \"direction\": [wind_direction],\n", " \"turbine0_yaw_angle\": [x[0]], \"turbine1_yaw_angle\": [x[1]]})\n", " prediction = evaluate_pytorch_model(model, manipulated_vars)\n", " cost = -(prediction[\"turbine0_power\"][0]+prediction[\"turbine1_power\"][0]+\n", " prediction[\"turbine2_power\"][0]+prediction[\"turbine3_power\"][0])\n", "\n", " return cost\n", " tic=timeit.default_timer()\n", " result = dual_annealing(objective, bounds=bounds, maxiter=2000)\n", " toc=timeit.default_timer()\n", " \n", " return (result['x'], result['nfev'], -result['fun'], toc-tic)" ] }, { "cell_type": "markdown", "id": "a02298d6", "metadata": {}, "source": [ "The main benefit is that now the user does not need to evaluate multiple what-if scenarios to find the yaw angle misalignment that will maximize power generated. We can run optimization to confirm that a small misalignment in the leading turbines can lead to optimal conditions in the fleet by sacrificing some performance in the leading turbines to increase generation in the trailing turbines (cooperative control)." ] }, { "cell_type": "code", "execution_count": null, "id": "c9f06242", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "np.seterr(divide='ignore', invalid='ignore')\n", "\n", "# Maximize power generation when speed is 8.0 m/s and the direction is 270 degrees\n", "x, nfev, fev, elapsed_time = optimal_manipulated_vars(8.0, 270.0)\n", "\n", "# Summarize the result\n", "print('Total Evaluations: {:d}'.format(nfev))\n", "print(\"The optimal yaw angle for turbine 0 is {:.2f}\".format(x[0]))\n", "print(\"The optimal yaw angle for turbine 1 is {:.2f}\".format(x[1]))\n", "print(\"The wind farm is predicted to produce {:.1f} kW\".format(fev))\n", "print(\"Optimization took {:.1f} seconds\".format(elapsed_time))" ] }, { "cell_type": "code", "execution_count": null, "id": "70471374", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "conda_pytorch_p36", "language": "python", "name": "conda_pytorch_p36" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.13" } }, "nbformat": 4, "nbformat_minor": 5 }