Wind Farm Layout Optimization Problem (WFLOP)

Network Design

Problem

The Wind Farm Layout Optimization Problem (WFLOP) involves determining the optimal placement of a fixed number of wind turbines within a defined area. Each turbine must be positioned inside the zone and comply with a minimum separation distance from other turbines. The objective is to maximize the Annual Energy Production (AEP), a key metric that reflects the efficiency of a wind farm in converting wind energy into electrical power. A central challenge in this optimization is minimizing wake losses, which occur when turbines interfere with each other’s wind flow.

While many approaches address the continuous version of this problem, our work focuses on a discrete approximation, enabling optimization across various types of admissible domains. In our example, we focus on circular areas, but the methodology is applicable to any geometric configuration.

Principles learned

Data

We use instances provided by the IEA Task 37 on System Engineering in Wind Energy. These include three scenarios involving 16, 36, and 64 wind turbines, to be deployed within circular areas of radius 1300 m, 2000 m, and 3000 m, respectively. We converted he original .yaml data files into .json format for easier processing.

Each .json file includes the following parameters:

  • “radius” : Radius of the deployment area
  • “D” : Rotor diameter
  • “nT” : Number of turbines
  • “prated” : Rated power output per turbine
  • “urated” : Rated wind speed
  • “ucut-in” : Cut-in wind speed
  • “ucut-out” : Cut-out wind speed
  • “wind” : Wind data, including:
    • “degrees” : Discretization of wind direction angles
    • “probs” : Probability distribution over wind directions
    • “J” : Total number of wind directions considered
    • “speed” : Inflow wind speed
    • “cT” : Thrust coefficient
    • “kY” : Dynamic coefficient based on a turbulence intensity of 0.075

The available area is uniformly discretized with spacing equal to 1.7 × D and the minimum distance between wind turbines equals 2 × D.Turbines can be placed either within the circle at discretized grid points or along the border, where one admissible location is evaluated for each degree (see below).

Model

The Hexaly model for the Wind Farm Layout Optimization Problem (WFLOP) uses a binary decision vector, where each element represents the presence (1) or absence (0) of a wind turbine at a discretized location.

A first constraint ensures that the total number of turbines placed within the admissible area equals nT.

To enforce the minimum-distance requirement, any pair of locations that are closer than the allowed threshold are constrained such that at most one turbine may be placed among them.

The wind conditions at each potential location are computed using a simplified version of Bastankhah’s Gaussian wake model and turbine power outputs are derived using a smooth, convex, and monotonically increasing simplified power curve (see IEA Task 37 Anouncements p2).

The objective function maximizes Annual Energy Production (AEP) by aggregating the expected power outputs over all wind directions, weighted by their respective probabilities.

Execution
hexaly wind_farm_layout_optimization.hxm inFileName=instances/nT16.json [solFileName=] [hxTimeLimit=]
use io;
use json;
use math;

/* Read instance data */
function readData(inFileName) {
    local usage = "Usage: hexaly wind_farm_layout_optimization.hxm "
            + "inFileName=inputFile [solFileName=outputFile] [hxTimeLimit=timeLimit]";
    if (inFileName == nil) throw usage;

    data = json.parse(inFileName);

    // Wind information 
    degrees = data.wind.degrees;     // Discretization of the degrees rose
    nbDegrees = count(degrees);      // Number of discretizations
    probabilities = data.wind.probs; // Probabilities of wind in every direction
    inflowSpeed = data.wind.speed;   // Inflow wind speed
    thrustCoeff = data.wind.cT;      // Thrust coefficient 
    dynCoeff = data.wind.kY;         // Dynamic coefficient

    // Information about the problem
    radius = data.radius;           // Radius of the perimeter to fullfil
    nbTurbines = data.nT;           // Number of Wind Turbines
    turbineDiameter = data.D;                 // Diameter of each Turbine
    turbineNominalPower = data.p_rated * 1e6; // Power of the Turbine
    minDistance = 2 * turbineDiameter;        // Minimal distance between two turbines
    optimalSpeed = data.u_rated;     // Optimal wind speed (maximum efficiency)
    minSpeed = data.u_cut_in;        // Minimal wind speed producing energy
    maxSpeed = data.u_cut_out;       // Maximal wind speed producing energy

    discDelta = 1.7 * turbineDiameter; // Distance between two points in the discretization
}

/* Create and solve the model */
function model() {
    // Decision variable : List of booleans (one for each Wind Turbine)
    chosenTurbines[0...nbLocations] <- bool();

    // Constraint : We have to choose nbTurbines locations on the nbLocations available positions
    constraint sum[location in 0...nbLocations](chosenTurbines[location]) == nbTurbines;
    // Constraint : Two WT need to be spaced by at least minDistance meters
    for [location in 0...nbLocations]{
        for [incompatibleLocation in incompatibilitySets[location]]{
            constraint chosenTurbines[location] + chosenTurbines[incompatibleLocation] <= 1;
        }
    }
    // Relative wind and power at each location for every possible wind angle
    for [location1 in 0...nbLocations]{
        wt1 = {pointsX[location1], pointsY[location1]};
        for [angle in 0...nbDegrees]{
            // Wind at location i for a wind incidence angle j
            turbineWind[location1][angle] <- inflowSpeed * (chosenTurbines[location1] - sqrt(
                    sum[location2 in 0...nbLocations](chosenTurbines[location2] * pow(wakeLoss(
                            wt1, {pointsX[location2], pointsY[location2]}, degrees[angle]), 2))));

            polynomialCase <- minSpeed <= turbineWind[location1][angle] && turbineWind[location1][angle] < optimalSpeed;
            polynomialValue <- pow((turbineWind[location1][angle] - minSpeed) / (optimalSpeed - minSpeed), 3);

            constantCase <- optimalSpeed <= turbineWind[location1][angle] && turbineWind[location1][angle] < maxSpeed;
            // Power of the wind turbine of a WT at i with a wind incidence angle j
            turbinePower[location1][angle] <- polynomialCase * turbineNominalPower * polynomialValue
                    + constantCase * turbineNominalPower;
        }
    }
    // Objective : we maximize the Annual Energy Production (in Wh)
    // To calculate the AEP, we multiply the total power produced by the number of hours in one year
    objective <- 8760 * sum[location in 0...nbLocations][angle in 0...nbDegrees](probabilities[angle] * turbinePower[location][angle]);
    maximize objective;
}

/*
 *  Build the discretization in a circular field of radius {radius} and
 *  with a distance between points of {discDelta}.
 *
 *  The discretization follows the one described in 'eawe' paper, i.e.
 *  building a regular uniform mesh inside the circle, and a point every
 *  degree on the frontier of the circle.
 */
function buildDiscretization(radius, discDelta) {
    pointsX = {};
    pointsY = {};
    // Maximum number of points in any direction from the center
    maxOnedirPoints = round(radius / discDelta) + 1;
    // Interior points
    for [cX in 0...maxOnedirPoints][sideX in {-1, 1}]{
        if (!(cX == 0 && sideX == 1)) {
            newX = sideX * cX * discDelta;
            for [cY in 0...maxOnedirPoints][sideY in {-1, 1}]{
                newY = sideY * cY * discDelta;
                if (sqrt(pow(newX, 2) + pow(newY, 2)) < radius
                        && !(cY == 0 && sideY == 1)) {
                    pointsX.add(newX);
                    pointsY.add(newY);
                }
            }
        }
    }

    // Points on the border of the circle
    for [deg in 0...360]{
        pointsX.add(radius * sin(deg * math.PI / 180));
        pointsY.add(radius * cos(deg * math.PI / 180));
    }
    return {pointsX, pointsY};
}

/*
 * Compute incompatibility sets for every location on the field.
 *
 * N_i[i] := {l in 0...nbTurbines: l != i && ||l - i|| < minDistance}
 */
function ComputeIncompatibilities(minDistance) {
    incompatibilities[0...nbLocations] = {};
    for [location1 in 0...nbLocations]{
        for [location2 in (location1+1)...nbLocations]{
            if (sqrt(pow(pointsX[location1] - pointsX[location2], 2) 
                    + pow(pointsY[location1] - pointsY[location2], 2)) < minDistance) {
                incompatibilities[location1].add(location2);
                incompatibilities[location2].add(location1);
            }
        }
    }
    return incompatibilities;
}

/*
 * Calculate the distance between two points, considering a frame of
 * reference with angle theta.
 */
function distances(location1, location2, theta) {
    // Rotation of the reference
    thetaDeg = 270 - theta;
    thetaRad = thetaDeg * math.PI / 180;
    cosWind = math.cos(-thetaRad);
    sinWind = math.sin(-thetaRad);

    // Change the reference of the coordinates
    location2X = (location2[0] * cosWind) - (location2[1] * sinWind);
    location2Y = (location2[0] * sinWind) + (location2[1] * cosWind);

    location1X = (location1[0] * cosWind) - (location1[1] * sinWind);
    location1Y = (location1[0] * sinWind) + (location1[1] * cosWind);

    return {"parallel":location1X - location2X, "perpendicular":location1Y - location2Y};
}

/*
 * Wake loss evaluated at i, caused by a Wind Turbine at l, considering
 * a wind of angle {theta}.
 * This loss is a simplified version of Bastankhah's Gaussian model.
 */
function wakeLoss(turbine1, turbine2, theta) {
    // We calculate parallel and perpendicular distances
    d = distances(turbine1, turbine2, theta);
    // If x_i - x_l < 0, the wake loss is zero
    if (d.parallel > 0) {
        // Standard deviation of the wake deficit
        sY = dynCoeff * (d.parallel) + turbineDiameter / sqrt(8);
        // Separation of the product in two components
        coeff = thrustCoeff / (8 * pow(sY / turbineDiameter, 2));
        pdt1 = 1 - sqrt(1 - coeff);
        pdt2 = exp(-1/2 * pow(d.perpendicular / sY, 2));

        return pdt1 * pdt2;
    } 
    return 0;
}

/* Read the input files and initializes all the parameters */
function input() {
    readData(inFileName);
    // Additional required inputs
    points = buildDiscretization(radius, discDelta);
    pointsX = points[0];
    pointsY = points[1];
    nbLocations = count(pointsX);
    incompatibilitySets= ComputeIncompatibilities(minDistance);
}

/* Parameterize the solver */
function param() {
    if (hxTimeLimit == nil) hxTimeLimit = 60;
}

/* 
 * Write the solution in the given file, giving :
 *    - The instance
 *    - The timelimit
 *    - The Annual Energy Production (objective function, in Wh and GWh)
 *    - Location (x,y) of every turbine
 */
function output() {    
    if (hxSolution.status == "OPTIMAL" || hxSolution.status == "FEASIBLE") {
        points = {};
        for [location in 0...nbLocations]{
            if (chosenTurbines[location].value == 1) {
                points.add({pointsX[location], pointsY[location]});
            }
        }
        // If asked, we print the result in the output file
        if (solFileName != nil) {
            outFile = io.openWrite(solFileName);
            println("Solution written in file ", solFileName);

            outFile.println("*************************************");
            outFile.println("*** WIND FARM LAYOUT OPTIMIZATION ***");
            outFile.println("*************************************\n");

            outFile.println("Annual Energy Production : " + objective.value + " Wh"
                    + " (" + round(objective.value / 1e9 * 100) / 100 + " GWh).\n");
            outFile.println("Locations of the Wind Turbines : ");

            for [location in 0...nbTurbines]{
                point = points[location];
                outFile.println("   - (" + point[0] + ", " + point[1] + ")");
            }
        }
    } else {
        println("No feasible solution have been found within the allowed time.");
    }
}
Execution (Windows)
set PYTHONPATH=%HX_HOME%\bin\python
python wind_farm_layout_optimization.py instances\nT16.json
Execution (Linux)
export PYTHONPATH=/opt/hexaly_14_0/bin/python
python wind_farm_layout_optimization.py instances/nT16.json
import hexaly.optimizer
import json
import math
import sys

def main(instance_file, output_file, time_limit):
    """
    Creates and solves the model.
    """
    # Read the input data
    degrees, nb_degrees, probabilities, inflow_speed, thrust_coeff,  dyn_coeff, \
            radius, nb_turbines, turbine_diameter, turbine_nominal_power, min_distance, \
            optimal_speed, min_speed, max_speed, disc_delta = read_data(instance_file)
    ## Additional required inputs
    points_x, points_y = build_discretization(radius, disc_delta)
    nb_locations = len(points_x)
    incompatibility_sets = compute_incompatibilities(points_x, points_y, min_distance)

    with hexaly.optimizer.HexalyOptimizer() as optimizer:
        # Declare the optimization model
        model = optimizer.model

        # Decision Variable : List of booleans (one for each Wind Turbine)
        chosen_turbines = [model.bool() for location in range(nb_locations)]

        # Constraint : We have to choose nT locations on the nb_locations available positions
        model.constraint(sum(chosen_turbines[location] for location in range(nb_locations)) == nb_turbines)
        # Constraint : Two WT need to be spaced by at least d_min meters
        for location in range(nb_locations):
            for incompatible_location in incompatibility_sets[location]:
                model.constraint(chosen_turbines[location] + chosen_turbines[incompatible_location] <= 1)

        # Relative wind and power at each location for every possible wind angle
        turbine_wind = [[0 for angle in range(nb_degrees)] for location in range(nb_locations)]
        turbine_power = [[0 for angle in range(nb_degrees)] for location in range(nb_locations)]
        for location_1 in range(nb_locations):
            wt_1 = [points_x[location_1], points_y[location_1]]
            for angle in range(nb_degrees):
                # Wind at location i for a wind incidence angle j
                w_loss_sum = sum(chosen_turbines[location_2]
                        * (wake_loss(wt_1, [points_x[location_2], points_y[location_2]],
                                degrees[angle], dyn_coeff, turbine_diameter, thrust_coeff))**2
                                for location_2 in range(nb_locations))
                turbine_wind[location_1][angle] =  inflow_speed * (chosen_turbines[location_1]
                        - model.sqrt(w_loss_sum))

                # Power of the wind turbine of a WT at i with a wind incidence angle j
                polynomial_case = (min_speed <= turbine_wind[location_1][angle]) \
                        * (turbine_wind[location_1][angle] < optimal_speed)
                polynomial_value = ((turbine_wind[location_1][angle] - min_speed) \
                        / (optimal_speed - min_speed))**3
                constant_case = (optimal_speed <= turbine_wind[location_1][angle]) \
                        * (turbine_wind[location_1][angle] < max_speed)
                turbine_power[location_1][angle] = polynomial_case \
                        * turbine_nominal_power * polynomial_value \
                        +  constant_case * turbine_nominal_power


        # Objective : we maximize the Annual Energy Production (in Wh)
        # To calculate the AEP, we multiply the total power produced by the number of hours in one year
        objective = 8760 * sum(probabilities[angle] * turbine_power[location][angle]
                for location in range(nb_locations) for angle in range(nb_degrees))
        model.maximize(objective)

        # Finalize model and solve
        model.close()
        optimizer.param.time_limit = time_limit
        optimizer.solve()

        feasible_list = ["HxSolutionStatus.FEASIBLE", "HxSolutionStatus.OPTIMAL"]
        if (str(optimizer.solution.status) in feasible_list):
            # Store the solution in a map
            points = []
            for location in range(nb_locations):
                if (chosen_turbines[location].value == 1):
                    points.append((points_x[location], points_y[location]))

            # If asked, we print the result in the output file
            if (output_file is not None):
                with open(output_file, "w") as f:
                    print("Solution written in file ", output_file)
                    # Print every important info of the resolution
                    f.write("*************************************\n")
                    f.write("*** WIND FARM LAYOUT OPTIMIZATION ***\n")
                    f.write("*************************************\n\n")

                    f.write("Annual Energy Production : " + str(objective.value)
                            + " Wh (" + str(round(objective.value / 1e9 * 100) / 100)
                            + " GWh).\n\n")
                    f.write("Locations of the Wind Turbines : \n")

                    for turbine in range(nb_turbines):
                        point = points[turbine]
                        f.write("   - (" + str(point[0]) + ", " + str(point[1]) + ")\n")

        else:
            print("No feasible solution have been found within the allowed time.")

def read_data(instance_file):
    """
    Reads the input files of the problem.
    """
    # Opening the .json file
    with open(instance_file, 'r') as file:
        data = json.load(file)

    # Wind information
    degrees = data["wind"]["degrees"]     # Discretization of the degrees rose
    nb_degrees = len(degrees)             # Number of discretizations
    probabilities = data["wind"]["probs"] # Probabilities of wind in every direction
    inflow_speed = data["wind"]["speed"]  # Inflow wind speed
    thrust_coeff = data["wind"]["cT"]     # Thrust coefficient 
    dyn_coeff = data["wind"]["kY"]        # Dynamic coefficient

    # Information about the problem
    radius = data["radius"]             # Radius of the perimeter to fullfil
    nb_turbines = data["nT"]            # Number of Wind Turbines
    turbine_diameter = data["D"]                   # Diameter of each Turbine
    turbine_nominal_power = data["p_rated"] * 1e6  # Power of the Turbine
    min_distance = 2 * turbine_diameter            # Minimal distance between two turbines
    optimal_speed = data["u_rated"]     # Optimal wind speed (maximum efficiency)
    min_speed = data["u_cut_in"]        # Minimal wind speed producing energy
    max_speed = data["u_cut_out"]       # Maximal wind speed producing energy

    disc_delta = 1.7 * turbine_diameter # Distance between two points in the discretization

    return degrees, nb_degrees, probabilities, inflow_speed, thrust_coeff, \
            dyn_coeff, radius, nb_turbines, turbine_diameter, turbine_nominal_power, \
            min_distance, optimal_speed, min_speed, max_speed, disc_delta

def build_discretization(radius, disc_delta):
    """
    Builds the discretization in a circular field of radius {radius} and with a
    distance between points of {disc_delta}.

    The discretization follows the one described in 'eawe' paper, i.e. building
    a regular uniform mesh inside the circle, and a point every degree on the
    frontier of the circle.
    """
    points_x = []
    points_y = []
    # Maximum number of points in any direction from the center
    max_onedir_points = int(radius / disc_delta + 1)
    # Interior points
    for c_x in range(max_onedir_points):
        for side_x in [-1, 1]:
            if (not(c_x == 0 and side_x == 1)):
                new_x = side_x * c_x * disc_delta
                for c_y in range(max_onedir_points):
                    for side_y in [-1, 1]:
                        new_y = side_y * c_y * disc_delta
                        if (math.sqrt(new_x**2 + new_y**2) < radius \
                                and not(c_y == 0 and side_y == 1)):
                            points_x.append(new_x)
                            points_y.append(new_y)

    # Points on the border of the circle
    for deg in range(360):
        points_x.append(radius * math.sin(deg * math.pi / 180))
        points_y.append(radius * math.cos(deg * math.pi / 180))

    return points_x, points_y

def compute_incompatibilities(points_x, points_y, min_distance):
    """
    Compute incompatibility sets for every location on the field.

    N_i[i] := {l in 0...nb_locations : l != i && ||l - i|| < min_distance}
    """
    # Number of available points
    nb_locations = len(points_x)
    incompatibilities = [[] for location in range(nb_locations)]
    # Fill the set for every location
    for location_1 in range(nb_locations):
        for location_2 in range(location_1+1, nb_locations):
            if (math.sqrt((points_x[location_1] - points_x[location_2])**2
                    + (points_y[location_1] - points_y[location_2])**2) < min_distance):
                incompatibilities[location_1].append(location_2)
                incompatibilities[location_2].append(location_1)

    return incompatibilities

def distances(location_1, location_2, theta):
    """
    This function calculates the distance between two points, considering a
    frame of reference with angle theta.
    """
    # Rotation of the reference
    theta_deg = 270 - theta
    theta_rad = theta_deg * math.pi / 180
    cos_wind = math.cos(-theta_rad)
    sin_wind = math.sin(-theta_rad)

    # Change the reference of the coordinates
    location_2_x = (location_2[0] * cos_wind) - (location_2[1] * sin_wind)
    location_2_y = (location_2[0] * sin_wind) + (location_2[1] * cos_wind)

    location_1_x = (location_1[0] * cos_wind) - (location_1[1] * sin_wind)
    location_1_y = (location_1[0] * sin_wind) + (location_1[1] * cos_wind)

    return {"parallel":location_1_x - location_2_x, "perpendicular":location_1_y - location_2_y}

def wake_loss(turbine_1, turbine_2, theta, dyn_coeff, turbine_diameter, thrust_coeff):
    """
    Wake loss evaluated at i, caused by a Wind Turbine at l, considering a wind
    of angle {theta}. 
    This loss is a simplified version of Bastankhah's Gaussian model.
    """
    # We calculate parallel and perpendicular distances
    d = distances(turbine_1, turbine_2, theta)
    # If x_i - x_l < 0, the wake loss is zero
    if (d["parallel"] > 0):
        # Standard deviation of the wake deficit
        s_y = dyn_coeff * d["parallel"] + turbine_diameter / math.sqrt(8)
        # Separation of the product in two components
        coeff = thrust_coeff / (8 * (s_y / turbine_diameter)**2)
        pdt_1 = 1 - math.sqrt(1 - coeff)
        pdt_2 = math.exp(-1/2 * (d["perpendicular"] / s_y)**2)

        return pdt_1 * pdt_2

    return 0

# Main
if __name__ == '__main__':
    if len(sys.argv) < 2:
        print("Usage: python wind_farm_layout_optimization.py inputFile "
              + "[solFile] [timeLimit]")
        sys.exit(1)
    # Get arguments
    instance_file = sys.argv[1]
    output_file = sys.argv[2] if len(sys.argv) >= 3 else None
    time_limit = int(sys.argv[3]) if len(sys.argv) >= 4 else 60
    # Create the model and solve it
    main(instance_file, output_file, time_limit)
Compilation / Execution (Windows)
cl /EHsc aircraft_landing.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly140.lib
wind_farm_layout_optimization instances\nT16.json
Compilation / Execution (Linux)
g++ wind_farm_layout_optimization.cpp -I/opt/hexaly_14_0/include -lhexaly140 -lpthread -o wind_farm_layout_optimization
./wind_farm_layout_optimization instances/nT16.json
#include "optimizer/hexalyoptimizer.h"
#include <cmath>
#include <fstream>
#include <iostream>
#include <numeric>
#include <vector>
#include "libs/lib-cpp/json.hpp"
#include <map>
#include <sstream>
#include <vector>
#include <string>
#include <any>

using namespace hexaly;
using namespace std;
using json = nlohmann::json;

class WindFarmLayoutOptimization {
public:

    // Wind information 
    vector<double> degrees;  // Discretization of the degrees rose
    int nbDegrees;                // Number of discretizations
    vector<double> probabilities; // Probabilities of wind in every direction
    double inflowSpeed;           // Inflow wind speed
    double thrustCoeff;      // Thrust coefficient 
    double dynCoeff;         // Dynamic coefficient

    // Information about the problem
    double radius;           // Radius of the perimeter to fullfil
    int nbTurbines;          // Number of Wind Turbines
    double turbineDiameter;     // Diameter of each Turbine
    double turbineNominalPower; // Power of the Turbine
    double minDistance;         // Minimal distance between two turbines
    double optimalSpeed;     // Optimal wind speed (maximum efficiency)
    double minSpeed;         // Minimal wind speed producing energy
    double maxSpeed;         // Maximal wind speed producing energy 

    // Discretization data
    double discDelta;                 // Distance between two points 
    vector<double> pointsX;           // Available locations (X-coord)
    vector<double> pointsY;           // Available locations (Y-coord)
    int nbLocations;                  // Number of available locations
    map<int, vector<int>> incompatibilitySets; // Incompatibility sets

    // Hexaly Optimizer
    HexalyOptimizer optimizer;
    // Decision variable
    std::vector<HxExpression> chosenTurbines;

    // Wind at each location
    std::vector<std::vector<HxExpression>> turbineWind;
    // Power of each wind turbine
    std::vector<std::vector<HxExpression>> turbinePower;

    // Objective function
    HxExpression objective;

    double PI = 3.141592653589793;

    /* Create and solve the model */
    void solve(int timelimit) {
        // Declare the optimization model
        HxModel model = optimizer.getModel();

        // Decision Variable : List of booleans (one for each Wind Turbine)
        chosenTurbines.resize(nbLocations);
        for (int location = 0; location < nbLocations; ++location) {
            chosenTurbines[location] = model.boolVar();
        }

        // Constraint : We have to choose nbTurbines locations on the nbLocations available positions
        HxExpression chosenTurbinesSum = model.sum();
        for (int location = 0; location < nbLocations; location++) {
            chosenTurbinesSum.addOperand(chosenTurbines[location]);
        }
        model.constraint(chosenTurbinesSum == nbTurbines);

        // Constraint : Two WT need to be spaced by at least dMin meters
        for (int location = 0; location < nbLocations; location++) {
            for (int incompatibleLocation : incompatibilitySets[location]) {
                model.constraint(chosenTurbines[location] + chosenTurbines[incompatibleLocation] <= 1);
            }
        }

        // Wind and power at each location for every possible wind angle
        turbineWind.resize(nbLocations);
        turbinePower.resize(nbLocations);
        for (int location = 0; location < nbLocations; ++location) {
            turbineWind[location].resize(nbDegrees);
            turbinePower[location].resize(nbDegrees);
        }
        for (int location1 = 0; location1 < nbLocations; location1++) {
            vector<double> wt1;
            wt1.push_back(pointsX[location1]);
            wt1.push_back(pointsY[location1]);

            for (int angle = 0; angle < nbDegrees; angle++) {
                HxExpression sumWakeLossesSqd = model.sum();

                for (int location2 = 0; location2 < nbLocations; location2++) {
                    vector<double> wt2;
                    wt2.push_back(pointsX[location2]);
                    wt2.push_back(pointsY[location2]);

                    double wakeL = wakeLoss(wt1, wt2, degrees[angle]);

                    sumWakeLossesSqd.addOperand(
                            model.pow(model.prod(wakeL, chosenTurbines[location2]), 2));
                }

                // Wind at location i for a wind incidence angle j
                turbineWind[location1][angle] = model.prod(inflowSpeed, model.sub(
                        chosenTurbines[location1], model.sqrt(sumWakeLossesSqd)));
                // Power of the wind turbine of a WT at i with a wind incidence angle j
                HxExpression polynomialCase = model.prod(
                        model.leq(minSpeed, turbineWind[location1][angle]),
                        model.lt(turbineWind[location1][angle], optimalSpeed));
                HxExpression polynomialValue = model.pow(
                        model.div(model.sub(turbineWind[location1][angle], minSpeed),
                                optimalSpeed - minSpeed), 3);
                HxExpression constantCase = model.prod(
                        model.leq(optimalSpeed, turbineWind[location1][angle]),
                        model.lt(turbineWind[location1][angle], maxSpeed));
                turbinePower[location1][angle] = model.sum(
                        model.prod(turbineNominalPower, polynomialCase, polynomialValue),
                        model.prod(turbineNominalPower, constantCase));
            }
        }

        // Objective : we maximize the Annual Energy Production (in Wh)
        objective = model.sum();
        for (int location = 0; location < nbLocations; location++) {
            for (int angle = 0; angle < nbDegrees; angle++) {
                // To calculate the AEP, we multiply the total power produced 
                // by the number of hours in one year
                objective.addOperand(8760 * probabilities[angle] * turbinePower[location][angle]);
            }
        }
        model.maximize(objective);

        // Finalize model and solve
        model.close();
        optimizer.getParam().setTimeLimit(timelimit);
        optimizer.solve();
    }

    /* Read instance data */
    void readData(const string& fileName) {
        ifstream infile;
        infile.exceptions(ifstream::failbit | ifstream::badbit);
        infile.open(fileName.c_str());

        json data;
        infile >> data;
        infile.close();

        // Wind information
        nbDegrees = data["wind"]["J"];
        inflowSpeed = data["wind"]["speed"];
        thrustCoeff = data["wind"]["cT"];
        dynCoeff = data["wind"]["kY"];

        vector<double> djson = data["wind"]["degrees"];
        vector<double> pjson = data["wind"]["probs"];

        for (int i = 0; i < djson.size(); i++) {
            degrees.push_back(djson[i]);
            probabilities.push_back(pjson[i]);
        }

        // Information about the problem
        radius = data["radius"];
        nbTurbines = data["nT"];
        turbineDiameter = data["D"];
        turbineNominalPower= (double)data["p_rated"] * 1e6;
        minDistance = 2.0 * turbineDiameter;
        optimalSpeed = data["u_rated"];
        minSpeed = data["u_cut_in"];
        maxSpeed = data["u_cut_out"];

        discDelta = turbineDiameter * 1.7;

        // Additional required inputs
        buildDiscretization();
        computeIncompatibilities();
    }

    /*
     *  Build the discretization in a circular field of radius {radius} and
     *  with a distance between points of {discDelta}.
     *
     *  The discretization follows the one described in 'eawe' paper, i.e.
     *  building a regular uniform mesh inside the circle, and a point every
     *  degree on the frontier of the circle.
     */
    void buildDiscretization() {
        // Maximum number of points in any direction from the center
        int maxOnedirPoints = round(radius / discDelta) + 1;
        vector<int> sides = {-1, 1};
        // Interior points
        for (int cX = 0; cX < maxOnedirPoints; cX++) {
            for (int sideX : sides) {
                if (!(cX == 0 && sideX == 1)) {
                    double newX = sideX * cX * discDelta;
                    for (int cY = 0; cY < maxOnedirPoints; cY++) {
                        for (int sideY : sides) {
                            double newY = sideY * cY * discDelta;
                            if (sqrt(pow(newX, 2) + pow(newY, 2)) < radius
                                    && !(cY == 0 && sideY == 1)) {
                                pointsX.push_back(newX);
                                pointsY.push_back(newY);
                            }
                        }
                    } 
                }
            }
        }
        // Points on the border of the circle
        for (int deg = 0; deg < 360; deg++) {
            double radians = deg * PI / 180;
            double x = radius * sin(radians);
            double y = radius * cos(radians);
            pointsX.push_back(x);
            pointsY.push_back(y);
        }
    }

    /*
     * Compute incompatibility sets for every location on the field.
     *
     * N_i[i] := {l in 0...nbTurbines : l != i && ||l - i|| < minDistance}
     */
    void computeIncompatibilities() {
        nbLocations = pointsX.size();

        for (int location1 = 0; location1 < nbLocations; location1++) {
            for (int location2 = location1+1; location2 < nbLocations; location2++) {
                if (sqrt(pow(pointsX[location1] - pointsX[location2], 2) 
                        + pow(pointsY[location1] - pointsY[location2], 2)) < minDistance) {
                    incompatibilitySets[location1].push_back(location2);
                    incompatibilitySets[location2].push_back(location1);
                }
            }
        }
    }

    /*
     * Calculate the distance between two points, considering a frame of
     * reference with angle theta.
     */
    map<string, double> distances(vector<double> location1,
            vector<double> location2,
            double theta) {
        map<string, double> res;
        // Rotation of the reference
        double thetaDeg = 270 - theta;
        double thetaRad = thetaDeg * PI / 180;

        double cosWind = cos(-thetaRad);
        double sinWind = sin(-thetaRad);

        // Change the reference of the coordinates
        double location2X = (location2[0] * cosWind) - (location2[1] * sinWind);
        double location2Y = (location2[0] * sinWind) + (location2[1] * cosWind);

        double location1X = (location1[0] * cosWind) - (location1[1] * sinWind);
        double location1Y = (location1[0] * sinWind) + (location1[1] * cosWind);

        res["parallel"] = location1X - location2X;
        res["perpendicular"] =  location1Y - location2Y;

        return res;

    }

    /*
     * Wake loss evaluated at i, caused by a Wind Turbine at l, considering
     * a wind of angle {theta}.
     * This loss is a simplified version of Bastankhah's Gaussian model.
     */
    double wakeLoss(vector<double> turbine1,
            vector<double> turbine2,
            double theta) {
        // We calculate parallel and perpendicular distances
        map<string, double> d = distances(turbine1, turbine2, theta);
        // If x_i - x_l < 0, the wake loss is zero
        if (d["parallel"] > 0) {
            // Standard deviation of the wake deficit
            double sY = dynCoeff * d["parallel"] + turbineDiameter / sqrt(8);
            // Separation of the product in two components
            double coeff =  thrustCoeff / (8.0 * pow(sY / turbineDiameter, 2));
            double pdt1 = 1.0 - sqrt(1.0 - coeff);
            double pdt2 = exp(-0.5 * pow(d["perpendicular"] / sY, 2));

            return pdt1 * pdt2;
        }
        return 0;
    }
 
    /* 
     * Write the solution in the given file, giving :
     *    - The instance
     *    - The timelimit
     *    - The Annual Energy Production (objective function, in Wh and GWh)
     *    - Location (x,y) of every turbine
     */
    void writeSolution(const string& fileName) {
        if (optimizer.getSolution().getStatus() == 2
                || optimizer.getSolution().getStatus() == 3) {

            vector<vector<double>> points;

            for (int i = 0; i < nbLocations; i++) {
                if (chosenTurbines[i].getValue() == 1) {
                    vector<double> point = {pointsX[i], pointsY[i]};
                    points.push_back(point);
                }
            }

            // If asked, we print the result in the output file
            ofstream outfile;
            outfile.exceptions(ofstream::failbit | ofstream::badbit);
            outfile.open(fileName.c_str());

            printf("Solution written in file %s\n\n", fileName.c_str());

            outfile << "*************************************\n";
            outfile << "*** WIND FARM LAYOUT OPTIMIZATION ***\n";
            outfile << "*************************************\n\n";

            outfile << "Annual Energy Production : " << 
                    objective.getDoubleValue() << " Wh"; 
            outfile << " (" << round(objective.getDoubleValue() / 1e9 * 100) / 100 <<
                    " GWh).\n\n";
            outfile << "Locations of the Wind Turbines : \n";

            for (int turbine = 0; turbine < nbTurbines; turbine++) {
                outfile << "   - (" << points[turbine][0] << ", " << points[turbine][1] << ")\n";
            }
        } else {
            printf("No feasible solution have been found within the allowed time.\n");
        }
        
    }
};

int main(int argc, char** argv) {
    if (argc < 2) {
        cerr << "Usage: ./wind_farm_layout_optimization inputFile [solFile] [timeLimit]" << endl;
        return 1;
    }

    const char* instanceFile = argv[1];
    const char* solFile = argc > 2 ? argv[2] : NULL;
    const char* strTimeLimit = argc > 3 ? argv[3] : "60";

    try {
        WindFarmLayoutOptimization model;

        model.readData(instanceFile);
        model.solve(atoi(strTimeLimit));

        if (solFile != NULL)
            model.writeSolution(solFile);
        return 0;
    } catch (const exception& e) {
        cerr << "An error occurred: " << e.what() << endl;
        return 1;
    }
}
Compilation / Execution (Windows)
copy %HX_HOME%\bin\Hexaly.NET.dll .
csc WindFarmLayoutOptimization.cs /reference:Hexaly.NET.dll
WindFarmLayoutOptimization instances\nT16.json
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using Hexaly.Optimizer;

using Newtonsoft.Json.Linq;
using Newtonsoft.Json;

public class WindFarmLayoutOptimization : IDisposable
{
    // Wind information
    private double[] degrees;       // Discretization of the degrees rose
    private int nbDegrees;          // Number of discretizations
    private double[] probabilities; // Probabilities of wind in every direction
    private double inflowSpeed;     // Inflow wind speed
    private double thrustCoeff;     // Thrust coefficient
    private double dynCoeff;        // Dynamic coefficient

    // Information about the problem
    private double radius;          // Radius of the perimeter to fullfil
    private int nbTurbines;         // Number of Wind Turbines
    private double turbineDiameter;     // Diameter of each Turbine
    private double turbineNominalPower; // Power of the Turbine
    private double minDistance;         // Minimal distance between two turbines
    private double optimalSpeed;    // Optimal wind speed (maximum efficiency)
    private double minSpeed;        // Minimal wind speed producing energy
    private double maxSpeed;        // Maximal wind speed producing energy

    // Discretization data
    private double discDelta;       // Distance between two points
    List<double> pointsX;           // Available locations (X-coord)
    List<double> pointsY;           // Available locations (Y-coord)
    int nbLocations;                // Number of available locations
    Dictionary<int, List<int>> incompatibilitySets; // Incompatibility sets

    // Hexaly Optimizer
    HexalyOptimizer optimizer;
    // Decision variable
    HxExpression[] chosenTurbines;

    // Wind at each location
    HxExpression[,] turbineWind;
    // Power of each wind turbine
    HxExpression[,] turbinePower;

    // Objective function
    HxExpression objective;

    public WindFarmLayoutOptimization()
    {
        optimizer = new HexalyOptimizer();
    }

    public void Dispose()
    {
        if (optimizer != null)
            optimizer.Dispose();
    }

    /* Create and solve the model */
    private void Solve(int timelimit)
    {
        // Declare the optimization model
        HxModel model = optimizer.GetModel();

        // Decision Variable : List of booleans (one for each Wind Turbine)
        chosenTurbines = new HxExpression[nbLocations];
        for (int location = 0; location < nbLocations; ++location)
        {
            chosenTurbines[location] = model.Bool();
        }

        // Constraint : We have to choose nbTurbines locations on the nbLocations available positions
        model.Constraint(model.Eq(model.Sum(chosenTurbines), nbTurbines));

        // Constraint : Two WT need to be spaced by at least minDistance meters
        for (int location = 0; location < nbLocations; location++)
        {
            foreach (int incompatibleLocation in incompatibilitySets[location])
            {
                model.Constraint(model.Leq(
                        model.Sum(chosenTurbines[location], chosenTurbines[incompatibleLocation]), 1));
            }
        }

        // Wind and power at each location for every possible wind angle
        turbineWind = new HxExpression[nbLocations, nbDegrees];
        turbinePower = new HxExpression[nbLocations, nbDegrees];
        for (int location1 = 0; location1 < nbLocations; location1++)
        {
            List<double> wt1 = new List<double>();
            wt1.Add(pointsX[location1]);
            wt1.Add(pointsY[location1]);

            for (int angle = 0; angle < nbDegrees; angle++)
            {
                HxExpression sumWakeLossesSqd = model.Sum();

                for (int location2 = 0; location2 < nbLocations; location2++)
                {
                    List<double> wt2 = new List<double>();
                    wt2.Add(pointsX[location2]);
                    wt2.Add(pointsY[location2]);

                    double wakeLoss = WakeLoss(wt1, wt2, degrees[angle]);
                    sumWakeLossesSqd.AddOperand(
                            model.Pow(model.Prod(wakeLoss, chosenTurbines[location2]), 2));
                }
                // Wind at location i for a wind incidence angle j
                turbineWind[location1, angle] = model.Prod(
                        inflowSpeed, model.Sub(chosenTurbines[location1], model.Sqrt(sumWakeLossesSqd)));
                // Power of the wind turbine of a WT at i with a wind incidence angle j
                HxExpression polynomialValue = model.Pow(model.Div(
                        model.Sub(turbineWind[location1, angle], minSpeed), optimalSpeed - minSpeed), 3);
                HxExpression polynomialCase = model.Prod(
                        model.Leq(minSpeed, turbineWind[location1, angle]),
                        model.Lt(turbineWind[location1, angle], optimalSpeed));
                HxExpression constantCase = model.Prod(
                        model.Leq(optimalSpeed, turbineWind[location1, angle]),
                        model.Lt(turbineWind[location1, angle], maxSpeed));

                turbinePower[location1, angle] = model.Sum(
                        model.Prod(turbineNominalPower, model.Prod(polynomialCase, polynomialValue)),
                        model.Prod(turbineNominalPower, constantCase));
            }
        }

        // Objective : we maximize the Annual Energy Production (in Wh)
        objective = model.Sum();
        for (int location = 0; location < nbLocations; location++)
        {
            for (int angle = 0; angle < nbDegrees; angle++)
            {
                // To calculate the AEP, we multiply the total power produced 
                // by the number of hours in one year
                objective.AddOperand(8760 * model.Prod(probabilities[angle], turbinePower[location, angle]));
            }
        }
        model.Maximize(objective);

        // Finalize model and solve
        model.Close();
        optimizer.GetParam().SetTimeLimit(timelimit);
        optimizer.Solve();
    }

    /* Read instance data */
    private void ReadData(string fileName)
    {
        JObject data = JObject.Parse(File.ReadAllText(fileName));
        JObject wind = (JObject)data["wind"];

        // Wind Information
        nbDegrees = (int)wind["J"];
        inflowSpeed = (double)wind["speed"];
        thrustCoeff = (double)wind["cT"];
        dynCoeff = (double)wind["kY"];
        degrees = wind["degrees"].ToObject<double[]>();
        probabilities = wind["probs"].ToObject<double[]>();

        // Information on the problem
        radius = (double)data["radius"];
        nbTurbines = (int)data["nT"];
        turbineDiameter = (double)data["D"];
        turbineNominalPower = (double)data["p_rated"] * 1e6;
        minDistance = 2.0 * turbineDiameter;
        optimalSpeed = (double)data["u_rated"];
        minSpeed = (double)data["u_cut_in"];
        maxSpeed = (double)data["u_cut_out"];

        discDelta = turbineDiameter * 1.7;

        // Additional required inputs
        BuildDiscretization();
        ComputeIncompatibilities();
    }

    /*
     *  Build the discretization in a circular field of radius {radius} and
     *  with a distance between points of {discDelta}.
     *
     *  The discretization follows the one described in 'eawe' paper, i.e.
     *  building a regular uniform mesh inside the circle, and a point every
     *  degree on the frontier of the circle.
     */
    private void BuildDiscretization()
    {
        // Maximum number of points in any direction from the center
        int maxOnedirPoints = (int)Math.Round(radius / discDelta) + 1;
        List<int> sides = new List<int>() {-1, 1};

        pointsX = new List<double>();
        pointsY = new List<double>();

        // Interior points
        for (int cX = 0; cX < maxOnedirPoints; cX++)
        {
            foreach (int sideX in sides)
            {
                if (!(cX == 0 && sideX == 1))
                {
                    double newX = sideX * cX * discDelta;
                    for (int cY = 0; cY < maxOnedirPoints; cY++)
                    {
                        foreach (int sideY in sides)
                        {
                            double newY = sideY * cY * discDelta;
                            if (Math.Sqrt(Math.Pow(newX, 2)
                                    + Math.Pow(newY, 2)) < radius
                                    && !(cY == 0 && sideY == 1))
                            {
                                pointsX.Add(newX);
                                pointsY.Add(newY);
                            }
                        }
                    }
                }
            }
        }
        // Points on the border of the circle
        for (int deg = 0; deg < 360; deg++)
        {
            double radians = deg * Math.PI / 180;
            double x = radius * Math.Sin(radians);
            double y = radius * Math.Cos(radians);
            pointsX.Add(x);
            pointsY.Add(y);
        }
    }

    /*
     * Compute incompatibility sets for every location on the field.
     *
     * N_i[i] := {l in 0...nbTurbines : l != i && ||l - i|| < minDistance}
     */
    private void ComputeIncompatibilities()
    {
        nbLocations = pointsX.Count;
        incompatibilitySets = new Dictionary<int, List<int>>();

        for (int location1 = 0; location1 < nbLocations; location1++)
        {
            for (int location2 = location1 + 1; location2 < nbLocations; location2++)
            {
                if (Math.Sqrt(Math.Pow(pointsX[location1] - pointsX[location2], 2)
                        + Math.Pow(pointsY[location1] - pointsY[location2], 2)) < minDistance)
                {
                    if (!incompatibilitySets.ContainsKey(location1))
                        incompatibilitySets[location1] = new List<int>();

                    if (!incompatibilitySets.ContainsKey(location2))
                        incompatibilitySets[location2] = new List<int>();

                    incompatibilitySets[location1].Add(location2);
                    incompatibilitySets[location2].Add(location1);
                }
            }
        }
    }

    /*
     * Calculate the distance between two points, considering a frame of
     * reference with angle theta.
     */
    private Dictionary<string, double> Distances(List<double> location1,
           List<double> location2,
            double theta)
    {
        Dictionary<string, double> res = new Dictionary<string, double>();
        // Rotation of the reference
        double thetaDeg = 270 - theta;
        double thetaRad = thetaDeg * Math.PI / 180;

        double cosWind = Math.Cos(-thetaRad);
        double sinWind = Math.Sin(-thetaRad);

        // Change the reference of the coordinates
        double location2X = (location2[0] * cosWind) - (location2[1] * sinWind);
        double location2Y = (location2[0] * sinWind) + (location2[1] * cosWind);

        double location1X = (location1[0] * cosWind) - (location1[1] * sinWind);
        double location1Y = (location1[0] * sinWind) + (location1[1] * cosWind);

        // Store the values in a "dictionary"
        res["parallel"] = location1X - location2X;
        res["perpendicular"] = location1Y - location2Y;

        return res;

    }

    /*
     * Wake loss evaluated at i, caused by a Wind Turbine at l, considering
     * a wind of angle {theta}.
     * This loss is a simplified version of Bastankhah's Gaussian model.
     */
    private double WakeLoss(List<double> turbine1,
            List<double> turbine2,
            double theta)
    {
        // We calculate parallel and perpendicular distances
        Dictionary<string, double> d = Distances(turbine1, turbine2, theta);
        // If x_i - x_l < 0, the wake loss is zero
        if (d["parallel"] > 0)
        {
            // Standard deviation of the wake deficit
            double sY = dynCoeff * d["parallel"] + turbineDiameter / Math.Sqrt(8.0);
            // Separation of the product in two components
            double coeff = thrustCoeff / (8.0 * Math.Pow(sY / turbineDiameter, 2));
            double pdt1 = 1.0 - Math.Sqrt(1.0 - coeff);
            double pdt2 = Math.Exp(-0.5 * Math.Pow(d["perpendicular"] / sY, 2));

            return pdt1 * pdt2;
        }
        return 0;
    }

    /* 
     * Write the solution in the given file, giving :
     *    - The Annual Energy Production (objective function, in Wh and GWh)
     *    - Location (x,y) of every turbine
     */
    private void WriteSolution(string fileName)
    {
        if (optimizer.GetSolution().GetStatus() == HxSolutionStatus.Feasible
                || optimizer.GetSolution().GetStatus() == HxSolutionStatus.Optimal)
        {
            List<List<double>> points = new List<List<double>>();
            for (int location = 0; location < nbLocations; location++)
            {
                if (chosenTurbines[location].GetValue() == 1)
                {
                    List<double> point = new List<double>() {pointsX[location], pointsY[location]};
                    points.Add(point);
                }
            }

            // If asked, we print the result in the output file
            using StreamWriter output = new StreamWriter(fileName);
            Console.WriteLine("Solution written in file " + fileName);

            output.WriteLine("*************************************");
            output.WriteLine("*** WIND FARM LAYOUT OPTIMIZATION ***");
            output.WriteLine("*************************************");
            output.WriteLine();

            output.WriteLine("Annual Energy Production : "
                    + objective.GetDoubleValue() + " Wh ("
                    + Math.Round((double)objective.GetDoubleValue() / 1e9, 2) + " GWh).");
            output.WriteLine();
            output.WriteLine("Locations of the Wind Turbines : ");

            for (int turbine = 0; turbine < nbTurbines; turbine++)
            {
                output.WriteLine("   - (" + points[turbine][0] + ", " + points[turbine][1] + ")");
            }
        }
        else
        {
            Console.WriteLine("No feasible solution have been found within the allowed time.");
        }

    }

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            Console.WriteLine("Usage: WindFarmLayoutOptimization inputFile [solFile] [timeLimit]");
            Environment.Exit(1);
        }
        string instanceFile = args[0];
        string outputFile = args.Length > 1 ? args[1] : null;
        string strTimeLimit = args.Length > 2 ? args[2] : "60";

        using (WindFarmLayoutOptimization model = new WindFarmLayoutOptimization())
        {
            model.ReadData(instanceFile);
            model.Solve(int.Parse(strTimeLimit));
            if (outputFile != null)
            {
                model.WriteSolution(outputFile);
            }
        }
    }
}
Compilation / Execution (Windows)
javac WindFarmLayoutOptimization.java -cp %HX_HOME%\bin\hexaly.jar
java -cp %HX_HOME%\bin\hexaly.jar;. WindFarmLayoutOptimization instances\nT16.json
Compilation / Execution (Linux)
javac WindFarmLayoutOptimization.java -cp /opt/hexaly_14_0/bin/hexaly.jar
java -cp /opt/hexaly_14_0/bin/hexaly.jar:. WindFarmLayoutOptimization instances/nT16.json
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.io.*;
import com.hexaly.optimizer.*;

import com.google.gson.JsonObject;
import com.google.gson.JsonArray;
import com.google.gson.JsonParser;
import com.google.gson.stream.JsonReader;


import java.lang.Math;
import java.nio.file.Files;
import java.nio.file.Paths;

public class WindFarmLayoutOptimization {
    // Wind information 
    private double[] degrees;        // Discretization of the degrees rose
    private int nbDegrees;           // Number of discretizations
    private double[] probabilities;  // Probabilities of wind in every direction
    private double inflowSpeed;      // Inflow wind speed
    private double thrustCoeff;      // Thrust coefficient 
    private double dynCoeff;         // Dynamic coefficient 

    // Information about the problem
    private double radius;           // Radius of the perimeter to fullfil
    private int nbTurbines;          // Number of Wind Turbines
    private double turbineDiameter;     // Diameter of each Turbine
    private double turbineNominalPower; // Power of the Turbine
    private double minDistance;         // Minimal distance between two turbines
    private double optimalSpeed;     // Optimal wind speed (maximum efficiency)
    private double minSpeed;         // Minimal wind speed producing energy
    private double maxSpeed;         // Maximal wind speed producing energy

    // Discretization data
    private double discDelta;               // Distance between two points
    List<Double> pointsX;                   // Available locations (X-coord)
    List<Double> pointsY;                   // Available locations (Y-coord)
    private int nbLocations;                // Number of available locations
    private Map<Integer, List<Integer>> incompatibilitySets; // Incompatibility sets

    // Hexaly Optimizer
    final HexalyOptimizer optimizer;
    // Decision variable
    private HxExpression[] chosenTurbines;

    // Wind at each location
    private HxExpression[][] turbineWind;
    // Power of each wind turbine
    private HxExpression[][] turbinePower;

    // Objective function
    private HxExpression objective;

    public WindFarmLayoutOptimization(HexalyOptimizer optimizer) throws IOException {
        this.optimizer = optimizer;
    }

    /* Create and solve the model */
    public void solve(int timelimit) {
        // Declare the optimization model
        HxModel model = optimizer.getModel();

        // Decision Variable : List of booleans (one for each Wind Turbine)
        chosenTurbines = new HxExpression[nbLocations];
        for (int location = 0; location < nbLocations; location++) {
            chosenTurbines[location] = model.boolVar();
        }
        // Constraint : We have to choose nbTurbines locations on the nbLocations available positions
        model.constraint(model.eq(model.sum(chosenTurbines), nbTurbines));

        // Constraint : Two WT need to be spaced by at least d_min meters
        for (int location = 0; location < nbLocations; location++) {
            for (int incompatibleLocation : incompatibilitySets.get(location)) {
                model.constraint(model.leq(model.sum(chosenTurbines[location], chosenTurbines[incompatibleLocation]), 1));
            }
        }
        // Wind and power at each location for every possible wind angle
        turbineWind = new HxExpression[nbLocations][nbDegrees];
        turbinePower = new HxExpression[nbLocations][nbDegrees];

        for (int location1 = 0; location1 < nbLocations; location1++) {
            List<Double> wt1 = new ArrayList<Double>();
            wt1.add(pointsX.get(location1));
            wt1.add(pointsY.get(location1));

            for (int angle = 0; angle < nbDegrees; angle++) {
                HxExpression sumWakeLossesSqd = model.sum();

                for (int location2 = 0; location2 < nbLocations; location2++) {
                    List<Double> wt2 = new ArrayList<Double>();
                    wt2.add(pointsX.get(location2));
                    wt2.add(pointsY.get(location2));

                    double wakeLoss = wakeLoss(wt1, wt2, degrees[angle]);
                    sumWakeLossesSqd.addOperand(
                            model.prod(chosenTurbines[location2], Math.pow(wakeLoss, 2)));
                }
                // Wind at location i for a wind incidence angle j
                turbineWind[location1][angle] = model.prod(inflowSpeed,
                        model.sub(chosenTurbines[location1], model.sqrt(sumWakeLossesSqd)));

                // Power of the wind turbine of a WT at i with a wind incidence angle j
                HxExpression polynomialCase = model.prod(
                        model.leq(minSpeed, turbineWind[location1][angle]),
                        model.lt(turbineWind[location1][angle], optimalSpeed));
                HxExpression polynomialValue = model.pow(
                        model.div(model.sub(turbineWind[location1][angle], minSpeed),
                        optimalSpeed - minSpeed), 3);
                HxExpression constantCase = model.prod(
                        model.leq(optimalSpeed, turbineWind[location1][angle]),
                        model.lt(turbineWind[location1][angle], maxSpeed));
                turbinePower[location1][angle] = model.sum(
                        model.prod(turbineNominalPower,
                                model.prod(polynomialCase, polynomialValue)),
                        model.prod(turbineNominalPower, constantCase));
            }
        }
        // Objective : we maximize the Annual Energy Production (in Wh)
        // To calculate the AEP, we multiply the total power produced by the number of hours in one year
        objective = model.sum();
        for (int location = 0; location < nbLocations; location++) {
            for (int angle = 0; angle < nbDegrees; angle++) {
                objective.addOperand(model.prod(8760, 
                        model.prod(probabilities[angle], turbinePower[location][angle])));
            }
        }
        model.maximize(objective);

        // Finalize model and solve
        model.close();
        optimizer.getParam().setTimeLimit(timelimit);
        optimizer.solve();
    }

    /* Read instance data */
    public void readData(String fileName) throws IOException {
        JsonReader reader = new JsonReader(
                new InputStreamReader(new FileInputStream(fileName)));
        JsonObject data = JsonParser.parseReader(reader).getAsJsonObject();

        // Wind information
        JsonObject wind = data.getAsJsonObject("wind");
        nbDegrees = wind.get("J").getAsInt();
        inflowSpeed = wind.get("speed").getAsDouble();
        thrustCoeff = wind.get("cT").getAsDouble();
        dynCoeff = wind.get("kY").getAsDouble();

        JsonArray degreesArray = (JsonArray) wind.get("degrees");
        JsonArray probsArray = (JsonArray) wind.get("probs");

        degrees = new double[nbDegrees];
        probabilities = new double[nbDegrees];
        for (int angle = 0; angle < nbDegrees; angle++) {
            degrees[angle] = degreesArray.get(angle).getAsDouble();
            probabilities[angle] = probsArray.get(angle).getAsDouble();
        }

        // Information about the problem
        radius = data.get("radius").getAsDouble();
        nbTurbines = data.get("nT").getAsInt();
        turbineDiameter = data.get("D").getAsDouble();
        turbineNominalPower = data.get("p_rated").getAsDouble() * 1e6;
        minDistance = 2.0 * turbineDiameter;
        optimalSpeed = data.get("u_rated").getAsDouble();
        minSpeed = data.get("u_cut_in").getAsDouble();
        maxSpeed = data.get("u_cut_out").getAsDouble();

        discDelta = turbineDiameter * 1.7;
        // Additional required inputs
        buildDiscretization();
        computeIncompatibilities();
    }

    /*
     *  Build the discretization in a circular field of radius {radius} and
     *  with a distance between points of {discDelta}.
     *
     *  The discretization follows the one described in 'eawe' paper, i.e.
     *  building a regular uniform mesh inside the circle, and a point every
     *  degree on the frontier of the circle.
     */
    public void buildDiscretization() {
        // Maximum number of points in any direction from the center
        int maxOnedirPoints = (int)Math.round(radius / discDelta) + 1;
        int[] sides = {-1, 1};

        pointsX = new ArrayList<Double>();
        pointsY = new ArrayList<Double>();

        // Interior points
        for (int cX = 0; cX < maxOnedirPoints; cX++) {
            for (int sideX : sides) {
                if (!(cX == 0 && sideX == 1)) {
                    double newX = sideX * cX * discDelta;
                    for (int cY = 0; cY < maxOnedirPoints; cY++) {
                        for (int sideY : sides) {
                            double newY = sideY * cY * discDelta;
                            if (Math.sqrt(Math.pow(newX, 2) 
                                    + Math.pow(newY, 2)) < radius
                                    && !(cY == 0 && sideY == 1)) {
                                pointsX.add(newX);
                                pointsY.add(newY);
                            }
                        }
                    } 
                }
            }
        }
        // Points on the border of the circle
        for (int deg = 0; deg < 360; deg++) {
            double radians = Math.toRadians(deg);
            double x = radius * Math.sin(radians);
            double y = radius * Math.cos(radians);
            pointsX.add(x);
            pointsY.add(y);
        }
    }

    /*
     * Compute incompatibility sets for every location on the field.
     *
     * N_i[i] := {l in 0...nbLocations : l != i && ||l - i|| < minDistance}
     */
    public void computeIncompatibilities() {
        nbLocations = pointsX.size();

        incompatibilitySets = new HashMap<>();
        for (int location = 0; location < nbLocations; location++) {
            incompatibilitySets.put(location, new ArrayList<>());
        }

        for (int location1 = 0; location1 < nbLocations; location1++) {
            for (int location2 = location1 + 1; location2 < nbLocations; location2++) {
                if (Math.sqrt(Math.pow(pointsX.get(location1) - pointsX.get(location2), 2)
                        + Math.pow(pointsY.get(location1) - pointsY.get(location2), 2)) < minDistance) {
                    incompatibilitySets.get(location1).add(location2);
                    incompatibilitySets.get(location2).add(location1);
                }
            }
        }
    }

    /*
     * Calculate the distance between two points, considering a frame of
     * reference with angle theta.
     */
    public Map<String, Double> distances(List<Double> location1,
            List<Double> location2, 
            double theta) {
        Map<String, Double> res = new HashMap<>();
        // Rotation of the reference
        double thetaDeg = 270 - theta;
        double thetaRad = Math.toRadians(thetaDeg);

        double cosWind = Math.cos(-thetaRad);
        double sinWind = Math.sin(-thetaRad);

        // Change the reference of the coordinates
        double location2X = (location2.get(0) * cosWind) - (location2.get(1) * sinWind);
        double location2Y = (location2.get(0) * sinWind) + (location2.get(1) * cosWind);

        double location1X = (location1.get(0) * cosWind) - (location1.get(1) * sinWind);
        double location1Y = (location1.get(0) * sinWind) + (location1.get(1) * cosWind);

        res.put("parallel", location1X - location2X);
        res.put("perpendicular", location1Y - location2Y);

        return res;

    }

    /*
     * Wake loss evaluated at i, caused by a Wind Turbine at l, considering
     * a wind of angle {theta}.
     * This loss is a simplified version of Bastankhah's Gaussian model.
     */
    public double wakeLoss(List<Double> turbine1,
            List<Double> turbine2,
            double theta) {
        // We calculate parallel and perpendicular distances
        Map<String, Double> d = distances(turbine1, turbine2, theta);
        // If x_i - x_l < 0, the wake loss is zero
        if (d.get("parallel") > 0) {
            // Standard deviation of the wake deficit
            double sY = dynCoeff * d.get("parallel") + turbineDiameter / Math.sqrt(8.0);
            // Separation of the product in two components
            double coeff = thrustCoeff / (8.0 * Math.pow(sY / turbineDiameter, 2));
            double pdt1 = 1.0 - Math.sqrt(1.0 - coeff);
            double pdt2 = Math.exp(-0.5 * Math.pow(d.get("perpendicular") / sY, 2));

            return pdt1 * pdt2;
        }
        return 0;
    }

    /* 
     * Write the solution in the given file, giving :
     *    - The instance 
     *    - The timeLimit
     *    - The Annual Energy Production (objective function, in Wh and GWh)
     *    - Location (x,y) of every turbine
     */
    public void writeSolution(String fileName) throws IOException {
        if (optimizer.getSolution().getStatus() == HxSolutionStatus.Feasible
                || optimizer.getSolution().getStatus() == HxSolutionStatus.Optimal) {
            List<List<Double>> points = new ArrayList<>();
            for (int location = 0; location < nbLocations; location++) {
                if (chosenTurbines[location].getValue() == 1) {
                    points.add(Arrays.asList(pointsX.get(location), pointsY.get(location)));
                }
            }
            // If asked, we print the result in the output file
            try (PrintWriter output = new PrintWriter(fileName)) {
                System.out.println("Solution written in file " + fileName);

                output.print("*************************************\n");
                output.print("*** WIND FARM LAYOUT OPTIMIZATION ***\n");
                output.print("*************************************\n\n");

                double r_AEP = Math.round(objective.getDoubleValue() / 1e9 * 100.0) / 100.0;
                output.print("Annual Energy Production : " + objective.getDoubleValue()
                        + " Wh (" + r_AEP + " GWh).\n\n");
                output.print("Locations of the Wind Turbines : \n");

                for (int turbine = 0; turbine < nbTurbines; turbine++) {
                    output.print("   - (" + points.get(turbine).get(0) + ", "
                            + points.get(turbine).get(1) + ")\n");
                }
            } 
        } else {
            System.err.println("No feasible solution have been found within the allowed time.");
        }
    }

    public static void main(String[] args) {
        if (args.length < 1) {
            System.err.println("Usage: java WindFarmLayoutOptimization inputFile"
                    + " [solFile] [timeLimit]");
            System.exit(1);
        }
        // Read the arguments
        String instanceFile = args[0];
        String outputFile = args.length > 1 ? args[1] : null;
        String strTimeLimit = args.length > 2 ? args[2] : "60";

        try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
            WindFarmLayoutOptimization model = new WindFarmLayoutOptimization(optimizer);

            model.readData(instanceFile);
            model.solve(Integer.parseInt(strTimeLimit));

            if (outputFile != null) {
                model.writeSolution(outputFile);
            }
        } catch (Exception ex) {
            // If an exception occurs, we catch it, print it and give its StackTrace
            System.err.println(ex);
            ex.printStackTrace();
            System.exit(1);
        }
    }
}

Explore templates

Discover the ease of use and performance of Hexaly