The urgent need to transition towards renewable energy sources to combat climate change has placed wind energy at the forefront of sustainable solutions. Wind turbines offer a promising avenue for clean energy, but their installation and optimization require careful consideration of costs, land usage, and energy output. This project addresses these challenges by developing a DynamicModule that acts as a tool allowing for optimisation in purchasing and placement of wind turbines.

Introduction

The project is structured in several stages:
◼
  • Problem Definition and Variable Identification: Understanding key variables that contribute to the optimisation in order to effectively maximise cost-efficiency (which would entail defining “efficiency”).
  • ◼
  • Model Development: Creating functions to model both the maximising variable and the constraints.
  • ◼
  • Optimization: Implementing optimization algorithms to determine the best configurations/combinations for a given situation.
  • ◼
  • Interface Design and Visualisation: Developing an interactive interface with sliders for real-time parameter adjustments along with intriguing visualisations.
  • The model will be optimizing in a situation of 2 wind turbines, each with their given power ratings, individual unit costs, ground area, and failure rates, in addition to the constraints of cost (budget) and area (available land). The final output will be the optimal, most cost and land effective combination of the 2 turbines to purchase.

    Important Note

    To run individual cells in this Notebook, we will predefine a set of values below. These values serve as placeholders to allow for cell-by-cell evaluation. In the final code (at the end of the Notebook), these values will be dynamically set and updated by the user through interactive sliders, ensuring real-time responsiveness and flexibility.
    In[]:=
    power1:=750​​power2:=1250​​failure1:=2​​failure2:=4​​cost1:=0.965​​cost2:=0.605​​dia1:=44​​dia2:=66​​budgetC:=5​​landC:=81000

    Defining the objective function

    In optimization, the most crucial step is to accurately define the objective function, that is, a mathematical expression representing the goal of the optimization (what needs to be maximized/minimized). My objective function is in terms of the turbines’ power rating, given it is representative of the energy output of the turbine.
    The coefficients power1 and power2  are the individual power ratings of turbines 1 and 2 respectively. Each is multiplied by the number of turbines 1 and 2, as given by x and y, to output a sum of total power rating.
    In[]:=
    objectiveLinear[x_,y_]:=((1-(failure1/100))*(24*365*(power1*x)))+((1-(failure2/100))*(24*365*(power2*y)));
    failure1 and failure2 represent the annual failure rates of each turbine, expressed as the percentage of time each turbine is non-operational. Consequently, we adjusted the power rating units to kilowatt-hours (kWh), reflecting the annual energy output of the turbines (by multiplying by the number of hours per year).

    The logarithmic objective function

    The law of diminishing marginal returns posits that increasing investment in a particular input will eventually yield progressively smaller increments in output. This principle is applicable to wind farms due to the wake effect—a phenomenon where aerodynamic interference from upstream turbines causes wind speed deficits and more intense turbulence, thereby reducing the efficiency of other turbines. This decrease in efficiency mirrors the diminishing returns observed in economic models. Additional factors such as turbine maintenance and land constraints further exacerbate these diminishing returns.
    We can use a Cobb-Douglas function which is commonly used to represent diminishing returns:
    f(x,y)=A
    α
    x
    β
    y
    Transforming this monotonically, we can see how the natural logarithm form represents the same principle:
    Log[f(x,y)]=Log[A]+αLog[x]+βLog[y]
    In our objective function:
    In[]:=
    objectiveLog[x_,y_]:=((1-(failure1/100))*(24*365*(power1 Log[1+x])))+((1-(failure2/100))*(24*365*(power2 Log[1+y])));

    Defining constraints and the optimization model

    Constraints

    Our constraints, which are also functions, must be expressed as inequalities given the maximum possible values in mind.
    ◼
  • The cost constraint:
  • cost1 and cost2 correspond to per unit costs of turbine 1 and 2 respectively, whereas budgetC is the budget.
    In[]:=
    cost[x_,y_]:=cost1*x+cost2*y<=budgetC;
    ◼
  • The area constraint:
  • We input the rotor diameters dia1 and dia2 to calculate an estimated ground area (including an added 10m buffer zone as a safety measure). landC is the total land area available.
    In[]:=
    realArea1=Pi*
    2
    ((dia1/2)+10)
    ;​​realArea2=Pi*((dia2/2)+10)^2;
    In[]:=
    area[x_,y_]:=realArea1*x+realArea2*y<=landC;
    These constraints are integral to the problem as they define the feasible region within which the solution must lie. By constraining the budget and available land area, we ensure that the optimization respects real-world limitations and does not propose impractical or unimplementable configurations.

    The optimization model

    The optimization model uses the NMaximize function to determine the optimal values for the number of each type of turbine (x and y) that maximize our objective function objectiveLog (representative of maximum power) while satisfying the given constraints cost and area.
    In[]:=
    solutionLog=NMaximize[{objectiveLog[x,y], cost[x,y], area[x,y], x>=0,y>=0, x∈Integers, y∈Integers},{x, y}];
    Removing the first element of the solution, which is the maximum value of the objective function, leaving only the values of x and y.
    In[]:=
    format1Log=Delete[solutionLog, 1];
    The Floor function converts the solution values to integers by flooring them (closest smaller integer to a number n). We then create an association to store the floored values.
    In[]:=
    flooredSolutionLog=AssociationThread[Keys[format1Log], Floor[Values[format1Log]]];
    To retrieve, for instance, the optimal number of turbine 1 types (the value of x), we can use the following:
    In[]:=
    Part[First[flooredSolutionLog],1]
    Out[]=
    2
    The linear objective function undergoes the same process:
    In[]:=
    solutionLinear=NMaximize[{objectiveLinear[x,y], cost[x,y], area[x,y], x>=0,y>=0, x∈Integers, y∈Integers},{x, y}];​​format1Linear=Delete[solutionLinear, 1];​​flooredSolutionLinear=AssociationThread[Keys[format1Linear],Floor[Values[format1Linear]]];

    Visualizing the results

    Using the Dataset function

    To effectively display the results of our optimization model, we utilize associations. These allow us to create a structured format that pairs each key (such as “Power Rating” or “Failure Rate”) with its corresponding value (such as power1 or failure1), ensuring easy access.
    Creating the associations for the logarithmic objective function. • Each key represents a column header in our table, and each value represents the data that will be displayed in that column. • The Style function is used to format the text and values. • Each association is nested within another association that groups the data by turbine type, which are to be displayed as rows.
    In[]:=
    finalAssociationLog = {​​<|Style["Turbine #1"]-><|​​"Power Rating (kW)"->Style[power1],​​"Failure Rate (Annually, %)"->Style[failure1],​​"Cost Per Unit (mill. USD)"->Style[cost1],​​"Ground Area (​
    2
    m
    ​)"-> Style[Round[realArea1,0.1]],​​"Optimal No. of Units"->Style[Part[First[flooredSolutionLog],1],Blue,Bold]​​|>,​​Style["Turbine #2"] -> <|​​"Power Rating (kW)"->Style[power2],​​"Failure Rate (Annually, %)"->Style[failure2],​​"Cost Per Unit (mill. USD)"->Style[cost2],​​"Ground Area (​
    2
    m
    ​)"-> Style[Round[realArea2,0.1]],​​"Optimal No. of Units"->Style[Part[First[flooredSolutionLog],2],Red,Bold]​​|>|>};
    For the linear objective function:
    In[]:=
    finalAssociationLinear = {​​<|Style["Turbine #1"]-><|​​"Power Rating (kW)"->Style[power1],​​"Failure Rate (Annually, %)"->Style[failure1],​​"Cost Per Unit (mill. USD)"->Style[cost1],​​"Ground Area (​
    2
    m
    ​)"-> Style[Round[realArea1,0.1]],​​"Optimal No. of Units"->Style[Part[First[flooredSolutionLinear],1],Blue,Bold]​​|>,​​Style["Turbine #2"] -> <|​​"Power Rating (kW)"->Style[power2],​​"Failure Rate (Annually, %)"->Style[failure2],​​"Cost Per Unit (mill. USD)"->Style[cost2],​​"Ground Area (​
    2
    m
    ​)"-> Style[Round[realArea2,0.1]],​​"Optimal No. of Units"->Style[Part[First[flooredSolutionLinear],2],Red,Bold]​​|>|>};
    The Dataset function in Wolfram Language takes an association (or a list of associations) and converts it into a structured table format. As a result, each column represents each turbine parameter for both turbines (rating, failure rate, cost, and ground area) along with the final optimal number of units for each turbine type.
    Dataset interprets the nested associations as rows and columns, automatically formatting the data into a table.
    In[]:=
    Dataset[finalAssociationLog]
    In[]:=
    Dataset[finalAssociationLinear]

    The 2D field plot

    The goal of the 2D field plot is to provide a visual representation of the wind turbine layout within the specified land area (using area values of each type of turbine as well). For this purpose, we utilized the PackingRectangles package, a tool designed to efficiently pack rectangles of customized areas within a given area. This package allowed us to simulate the placement and return a clear 2D plot.
    Combining a list of elements to be packed: the floored values of the square root of the ground areas (which represent with/breadth of each turbine as a square) for both turbines (copied by the optimal number of each turbine using the Table function).
    Uses the built-in PackRectangles function which requires the rectangle area in which joinedElementsLog is to be packed into, along with joinedElementsLog itself (widths and breaths of all rectangles to be packed) as arguments.
    Groups all the rectangles in the set of packingLog with the same area.
    To actually visualize the 2D field plot, we use the Graphics function to display the packed rectangles. This plot includes both turbine types and the available land area, ensuring no overlap and a clear visual distinction.
    For the linear objective function:

    The 3D objective function plot

    In this section, we visualize the objective function in a 3D plot. The goal is to illustrate the relationship between the number of turbines (x and y) and the resulting power rating value. Additionally, we plot the optimal combination of x and y values as a point in this 3D space.
    We use Plot3D to create a 3D plot of the objectiveLog function. The Graphics3D function along with Point adds a red point to the 3D plot representing the optimal combination of x and y values.
    The curved shape of the natural logarithm-based objective function reflects the diminishing marginal returns.
    For the linear objective function:

    Creating the interface

    The entire project is in a larger DynamicModule function, which is also used to display the interface. This will consist of user-inputted options for: initializing to the data for existing turbines, the objective function to be used, the power ratings, failure rates, costs, diameters of the 2 turbines, land, and budget constraints (as sliders/pop-up menus).
    ​
    The first step is to define all variables as a list in the first argument of the DynamicModule. This consists of variables corresponding to the attributes of each turbine (labelled power1 for the power rating of turbine 1, and so on), certain default values (including default values for existing wind turbines), and constraints/objective functions.
    NOTE: Do not evaluate.
    This equates all variables to a default when the DynamicModule is first evaluated. NOTE: Do not evaluate.
    Our interface is largely stored in a panel using a Panel function, with a Column function nested within.
    ​
    Through research, I identified two existing wind turbine models, the NEG Micon NM 44/750 and the Suzlon S 66-1250, and their respective data for each of the parameters required by our optimizer.
    The first element is a pop-up menu for both turbines allowing for different sets of initialized values of turbine parameters between default values, and those of the 2 existing turbines. We use a Switch function to set different initialized values for both turbines. The Dynamic function in this code allows the displayed values to update in real-time as the user interacts with the interface, ensuring that any changes made to the sliders or popup menus are immediately reflected in the output.
    ​
    ​NOTE: Do not evaluate.
    In the next row, the following creates popup menu that allows the user to dynamically switch between the “Log” and “Linear” objective functions. NOTE: Do not evaluate.
    Next in the Column function is a series of labeled sliders for adjusting the power ratings, failure rates, cost per unit, and rotor diameters of two turbines, as well as constraints for budget and available land, dynamically displaying the current values. NOTE: Do not evaluate.
    All the previous sections culminate in the following Dynamic function (within a Panel, followed by Column for design purposes) within the first DynamicModule function. This part of the code encapsulates all the objective and constraint functions, optimization algorithms, and visualizations developed in Sections 1, 2, and 3. This placement ensures that the calculations and visualizations are dynamically updated in real-time based on user inputs from the sliders and menus in the interface.
    The final output of the interface is stored as the final argument in the same Dynamic function. This code uses a Switch statement to toggle between the “Log” and “Linear” objective options, each displaying their respective data and visualizations: As covered, the Dataset function for the table, the Graphics function for the 2D plot, and plotLog/plotLinear for the 3D plot.
    NOTE: Do not evaluate.

    Conclusion and future direction

    This project has successfully created a dynamic optimization tool for the optimal purchase of wind turbines, considering critical factors such as power rating, cost, land usage, and failure rates. By employing interactive sliders and real-time visualizations, the tool provides a versatile platform for exploring various configurations, even emphasizing the significance of considering diminishing marginal returns in wind energy planning. As for future developments, I have a few ideas: integrating weather patterns and wind speed data, incorporating detailed maintenance and operational cost estimates, expanding the constraint set to address grid connectivity and regulatory concerns, and enhancing the database with a broader range of turbine models. These advancements will ensure the tool remains relevant and highly useful in promoting economically viable and environmentally sustainable wind energy solutions.

    Acknowledgements

    I would like to thank Carlos Angulo for his mentoring, as well as Niccolò Monti for his idea of using the PackingRectangles Paclet to visualize the 2D output.

    Citations

    ◼
  • Bauer, L. (n.d.). NEG Micon NM 44/750 - 750,00 kW - Wind turbine. https://en.wind-turbine-models.com/turbines/1299-neg-micon-nm-44-750
  • ◼
  • Bauer, L. (n.d.-b). Suzlon S 66-1250 - 1,25 MW - Wind turbine. https://en.wind-turbine-models.com/turbines/97-suzlon-s-66-1250
  • ◼
  • RectanglePacking | Wolfram Language Paclet Repository. (n.d.). Wolfram Language Paclet Repository. https://resources.wolframcloud.com/PacletRepository/resources/JasonB/RectanglePacking/
  • ◼
  • González-Longatt, F., Wall, P., & Terzija, V. (2012). Wake effect in wind farm performance: Steady-state and dynamic behavior. Renewable Energy, 39(1), 329–338. https://doi.org/10.1016/j.renene.2011.08.053
  • CITE THIS NOTEBOOK

    Optimizing wind turbine purchasing for cost-efficient solutions​
    by Manvik Bhadoria​
    Wolfram Community, STAFF PICKS, July 12, 2024
    ​https://community.wolfram.com/groups/-/m/t/3215276