Revenue Management Problem

Problem

In the Revenue Management Problem, a company wants to maximize its income from the sale of a product over a time horizon split into several periods. It must decide the total amount of product to buy at the beginning of the time horizon. Then, at each period, it must decide the number of units to sell during this period. The total number of units sold throughout the time horizon must not exceed the initially purchased amount. The product price increases over time. To earn more profit, the company should then reserve some products for later customers instead of selling them all early. To make the wisest possible decision at each period, it must then consider the demand of later periods. Since the demand is stochastic, the company runs a large number of simulations to get a robust estimate of the income for a given unit repartition.

Principles learned

Data

The time horizon consists of 3 periods, and the initial product cost is $80.

The demand for each period t is defined by the equation Dₜ=μₜXYₜ, where:

  • Yₜ has an exponential distribution with a rate parameter λ=1.
  • X has a gamma distribution with a shape parameter k=1 and a scale parameter θ=1, which is equivalent to the standard exponential distribution.
  • μₜ is the mean demand for this period.

The price and mean demand for each period are given in the table below:

Period123
Price100300400
Mean demand502030
Price and mean demand for each period

To get a robust estimate of the income, the simulation must be run a large number of times (1,000,000) using a Monte Carlo method. Consequently, each simulation takes several seconds to run. Since this evaluation function is very costly, we cannot afford to run it a large number of times, and we must choose each evaluated point wisely.

Program

The Hexaly model for the Revenue Management Problem uses three integer decisions. The first one corresponds to the initial quantity purchased at the beginning of the time horizon. The second one determines the amount of product to reserve for periods 2 and 3, and the third one represents the amount of product reserved for period 3. To ensure feasibility, each variable is constrained to be lower or equal to the previous one.

The objective function is the return value of the simulation function. We must then make it into an external function to use in the model. The arguments passed to the external function are the three integer decision variables of the problem. Since the simulation is computationally expensive, we enable Hexaly’s surrogate modeling features to get the best performance possible.

As the external function is provided by the user, Hexaly cannot compute any lower bounds on this model. However, if users know a lower bound on their external function, they can specify it for the solver to use. In this example, we know that the simulation will never return a negative value. We can then set the lower bound of the external function to 0.

A few other parameters can be useful when using surrogate modeling. Instead of setting a time limit, we can choose a maximum number of evaluations. We can also specify some previously evaluated points to warm start the solver. In this example, we limit the number of function evaluations to 30, and we indicate to the solver that the point (100, 50, 30) generates a mean revenue of 4740.99.

Execution
hexaly revenue_management.hxm [evaluationLimit=] [solFileName=]
use io;
use random;

/* Define input data */
function input() {
    nbPeriods = 3;
    prices = { 100, 300, 400 };
    meanDemands = { 50, 20, 30 };
    purchasePrice = 80;
    evaluatedPoints = {{
        "point": { 100, 50, 30 },
        "value": 4740.99
    }};
    nbSimulations = round(1e6);
    seed = 1;

    // Create random module
    rdm = random.create();
}

// External function
function revenueManagement(v1, v2, v3) {
    // Initial quantity purchased
    nbUnitsPurchased = v1;
    // Number of units that should be left for future periods
    nbUnitsReserved = { v2, v3, 0 };

    // Set seed for reproducibility
    rdm.init(seed);
    // Create distribution
    rateParam = 1.0;
    scaleParam = 1.0;
    X[i in 0...nbSimulations] = gammaSample(scaleParam);
    Y[i in 0...nbSimulations][j in 0...nbPeriods] = exponentialSample(rateParam);

    // Run simulations
    sumProfit = 0.0;
    for [i in 0...nbSimulations] {
        remainingCapacity = nbUnitsPurchased;
        for [j in 0...nbPeriods] {
            // Generate demand for period j
            demandJ = round(meanDemands[j] * X[i] * Y[i][j]);
            nbUnitsSold = min(max(remainingCapacity - nbUnitsReserved[j], 0),
                    demandJ);
            remainingCapacity -= nbUnitsSold;
            sumProfit += prices[j] * nbUnitsSold;
        }
    }

    // Calculate mean revenue
    meanProfit = sumProfit / nbSimulations;
    meanRevenue = meanProfit - purchasePrice * nbUnitsPurchased;

    return meanRevenue;
}

function exponentialSample(rateParam) {
    u = rdm.nextUniform(0, 1);
    return log(1 - u) / (-rateParam);
}

function gammaSample(scaleParam) {
    return exponentialSample(scaleParam);
}

/* Declare the optimization model */
function model() {
    // Declare decision variables
    variables[i in 0...nbPeriods] <- int(0, 100);

    // Create the function
    funcExpr <- doubleExternalFunction(revenueManagement);
    // Call function
    funcArgs[0] <- funcExpr;
    funcArgs[i in 1...nbPeriods+1] <- variables[i-1];
    funcCall <- call[arg in funcArgs](arg);

    // Declare constraints
    for [i in 1...nbPeriods]
        constraint variables[i] <= variables[i - 1];

    // Maximize function call
    maximize funcCall;

    // Enable surrogate modeling
    context = funcExpr.context;
    surrogateParams = context.enableSurrogateModeling();

    // Set lower bound
    context.lowerBound = 0.0;
}

function param() {
    // Set the maximum number of evaluations
    if (evaluationLimit == nil) surrogateParams.evaluationLimit = 30;
    else surrogateParams.evaluationLimit = evaluationLimit;

    // Add evaluation points
    for [k in 0...count(evaluatedPoints)] {
        evaluationPoint = surrogateParams.createEvaluationPoint();
        for [i in 0...nbPeriods]
            evaluationPoint.addArgument(evaluatedPoints[k]["point"][i]);
        evaluationPoint.returnValue = evaluatedPoints[k]["value"];
    }
}

/* Write the solution in a file */
function output() {
    if (solFileName != nil) {
        local solFile = io.openWrite(solFileName);
        solFile.println("obj=", funcCall.value);
        solFile.println("b=", variables[0].value);
        for [i in 1...nbPeriods]
            solFile.println("r", i + 1, "=", variables[i].value);
    }
}
Execution (Windows)
set PYTHONPATH=%HX_HOME%\bin\python
python revenue_management.py
Execution (Linux)
export PYTHONPATH=/opt/hexaly_13_0/bin/python
python revenue_management.py
import hexaly.optimizer
import sys
import math
import random


class RevenueManagementFunction:

    def __init__(self, seed):
        self.nb_periods = 3
        self.prices = [100, 300, 400]
        self.mean_demands = [50, 20, 30]
        self.purchase_price = 80
        self.evaluated_points = [{
            "point": [100, 50, 30],
            "value": 4740.99
        }]
        self.nb_simulations = int(1e6)
        self.seed = seed

    # External function
    def evaluate(self, argument_values):
        variables = [argument_values.get(i) for i in range(argument_values.count())]
        # Initial quantity purchased
        nb_units_purchased = variables[0]
        # Number of units that should be left for future periods
        nb_units_reserved = variables[1:] + [0]

        # Set seed for reproducibility
        random.seed(self.seed)
        # Create distribution
        X = [gamma_sample() for i in range(self.nb_simulations)]
        Y = [[exponential_sample() for i in range(self.nb_periods)]
             for j in range(self.nb_simulations)]

        # Run simulations
        sum_profit = 0.0
        for i in range(self.nb_simulations):
            remaining_capacity = nb_units_purchased
            for j in range(self.nb_periods):
                # Generate demand for period j
                demand_j = int(self.mean_demands[j] * X[i] * Y[i][j])
                nb_units_sold = min(
                    max(remaining_capacity - nb_units_reserved[j], 0),
                    demand_j)
                remaining_capacity = remaining_capacity - nb_units_sold
                sum_profit += self.prices[j] * nb_units_sold

        # Calculate mean revenue
        mean_profit = sum_profit / self.nb_simulations
        mean_revenue = mean_profit - self.purchase_price * nb_units_purchased

        return mean_revenue


def exponential_sample(rate_param=1.0):
    u = random.random()
    return math.log(1 - u) / (-rate_param)


def gamma_sample(scale_param=1.0):
    return exponential_sample(scale_param)


def solve(evaluation_limit, time_limit, output_file):
    with hexaly.optimizer.HexalyOptimizer() as optimizer:
        #
        # Declare the optimization model
        #
        model = optimizer.model

        # Generate data
        revenue_management = RevenueManagementFunction(1)
        nb_periods = revenue_management.nb_periods
        # Declare decision variables
        variables = [model.int(0, 100) for _ in range(nb_periods)]

        # Create the function
        func_expr = model.create_double_external_function(revenue_management.evaluate)
        # Call function
        func_call = model.call(func_expr)
        func_call.add_operands(variables)

        # Declare constraints
        for i in range(1, nb_periods):
            model.constraint(variables[i] <= variables[i - 1])

        # Maximize function call
        model.maximize(func_call)

        # Enable surrogate modeling
        context = func_expr.external_context
        surrogate_params = context.enable_surrogate_modeling()

        # Set lower bound
        context.lower_bound = 0.0

        model.close()

        # Parametrize the optimizer
        if time_limit is not None:
            optimizer.param.time_limit = time_limit

        # Set the maximum number of evaluations
        surrogate_params.evaluation_limit = evaluation_limit

        # Add evaluation points
        for evaluated_point in revenue_management.evaluated_points:
            evaluation_point = surrogate_params.create_evaluation_point()
            for i in range(nb_periods):
                evaluation_point.add_argument(evaluated_point["point"][i])
            evaluation_point.set_return_value(evaluated_point["value"])

        optimizer.solve()

        # Write the solution in a file
        if output_file is not None:
            with open(output_file, 'w') as f:
                f.write("obj=%f\n" % func_call.value)
                f.write("b=%f\n" % variables[0].value)
                for i in range(1, nb_periods):
                    f.write("r%f=%f\n" % (i + 1, variables[i].value))


if __name__ == '__main__':
    output_file = sys.argv[1] if len(sys.argv) > 1 else None
    time_limit = int(sys.argv[2]) if len(sys.argv) > 2 else None
    evaluation_limit = int(sys.argv[3]) if len(sys.argv) > 3 else 30

    solve(evaluation_limit, time_limit, output_file)
Compilation / Execution (Windows)
cl /EHsc revenue_management.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly130.lib
revenue_management
Compilation / Execution (Linux)
g++ revenue_management.cpp -I/opt/hexaly_13_0/include -lhexaly130 -lpthread -o revenue_management
./revenue_management
#include "optimizer/hexalyoptimizer.h"
#include <cmath>
#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <vector>

using namespace hexaly;
using namespace std;

struct EvaluatedPoint {
public:
    EvaluatedPoint(vector<int> point, double value) : point(point), value(value) {}
    const int getPoint(int index) { return point[index]; }
    const double getValue() { return value; }

private:
    vector<int> point;
    double value;
};

/* External function */
class RevenueManagementFunction : public HxExternalFunction<hxdouble> {
private:
    int seed;
    const int nbPeriods = 3;
    const int purchasePrice = 80;
    const int nbSimulations = (int)1e6;
    vector<EvaluatedPoint> evaluatedPoints;

    const int prices(int index) {
        const int p[] = {100, 300, 400};
        return p[index];
    }

    const int meanDemands(int index) {
        const int d[] = {50, 20, 30};
        return d[index];
    }

    double exponentialSample(double rateParam = 1.0) {
        double u = (double)rand() / RAND_MAX;
        return log(1 - u) / (-rateParam);
    }

    double gammaSample(double scaleParam = 1.0) { return exponentialSample(scaleParam); }

public:
    RevenueManagementFunction(int seed) : seed(seed) {
        evaluatedPoints.push_back(EvaluatedPoint({100, 50, 30}, 4740.99));
    }

    const unsigned int getNbPeriods() { return nbPeriods; }
    const vector<EvaluatedPoint> getEvaluatedPoints() { return evaluatedPoints; }

    hxdouble call(const HxExternalArgumentValues& argumentValues) override {
        // Initial quantity purchased
        int nbUnitsPurchased = argumentValues.getIntValue(0);
        // Number of units that should be left for future periods
        vector<int> nbUnitsReserved(nbPeriods, 0);
        for (unsigned int j = 0; j < nbPeriods - 1; ++j) {
            nbUnitsReserved[j] = argumentValues.getIntValue(j + 1);
        }
        // Set seed for reproducibility
        srand(seed);
        // Create distribution
        vector<double> X;
        for (unsigned int i = 0; i < nbSimulations; ++i) {
            X.push_back(gammaSample());
        }
        vector<vector<double>> Y;
        for (unsigned int i = 0; i < nbSimulations; ++i) {
            vector<double> yt;
            for (unsigned int j = 0; j < nbPeriods; ++j) {
                yt.push_back(exponentialSample());
            }
            Y.push_back(yt);
        }

        // Run simulations
        double sumProfit = 0;
        for (unsigned int i = 0; i < nbSimulations; ++i) {
            int remainingCapacity = nbUnitsPurchased;
            for (unsigned int j = 0; j < nbPeriods; ++j) {
                // Generate demand for period j
                int demand = (int)(meanDemands(j) * X[i] * Y[i][j]);
                int nbUnitsSold = min(max(remainingCapacity - nbUnitsReserved[j], 0), demand);
                remainingCapacity = remainingCapacity - nbUnitsSold;
                sumProfit += prices(j) * nbUnitsSold;
            }
        }
        // Calculate mean revenue
        double meanProfit = sumProfit / nbSimulations;
        double meanRevenue = meanProfit - purchasePrice * nbUnitsPurchased;

        return meanRevenue;
    }
};

class RevenueManagement {
public:
    // Hexaly Optimizer
    HexalyOptimizer optimizer;

    // Hexaly Program variables
    vector<HxExpression> variables;
    HxExpression funcCall;

    void solve(int timeLimit, int evaluationLimit) {
        // Declare the optimization model
        HxModel model = optimizer.getModel();

        // Generate data
        RevenueManagementFunction revenueManagement(1);
        unsigned int nbPeriods = revenueManagement.getNbPeriods();
        // Declare decision variables
        for (unsigned int i = 0; i < nbPeriods; ++i) {
            variables.push_back(model.intVar(0, 100));
        }

        // Create the function
        HxExpression func = model.createExternalFunction(&revenueManagement);
        // Call function
        funcCall = model.call(func);
        for (unsigned int i = 0; i < nbPeriods; ++i) {
            funcCall.addOperand(variables[i]);
        }

        // Declare constraints
        for (unsigned int i = 1; i < nbPeriods; ++i) {
            model.constraint(variables[i] <= variables[i - 1]);
        }

        // Maximize function call
        model.maximize(funcCall);

        // Enable surrogate modeling
        HxExternalContext context = func.getExternalContext();
        HxSurrogateParameters surrogateParams = context.enableSurrogateModeling();

        // Set lower bound
        context.setLowerBound(0.0);

        model.close();

        // Parametrize the optimizer
        if (timeLimit != 0) {
            optimizer.getParam().setTimeLimit(timeLimit);
        }

        // Set the maximum number of evaluations
        surrogateParams.setEvaluationLimit(evaluationLimit);

        // Add evaluation points
        for (EvaluatedPoint evaluatedPoint : revenueManagement.getEvaluatedPoints()) {
            HxEvaluationPoint evaluationPoint = surrogateParams.createEvaluationPoint();
            for (int i = 0; i < nbPeriods; ++i) {
                evaluationPoint.addArgument((hxint)evaluatedPoint.getPoint(i));
            }
            evaluationPoint.setReturnValue(evaluatedPoint.getValue());
        }

        optimizer.solve();
    }

    /* Write the solution in a file */
    void writeSolution(const string& fileName) {
        ofstream outfile;
        outfile.exceptions(ofstream::failbit | ofstream::badbit);
        outfile.open(fileName.c_str());
        outfile << "obj=" << funcCall.getDoubleValue() << endl;
        outfile << "b=" << variables[0].getIntValue() << endl;
        for (unsigned int i = 1; i < variables.size(); ++i) {
            outfile << "r" << (i + 1) << "=" << variables[i].getIntValue() << endl;
        }
    }
};

int main(int argc, char** argv) {
    const char* solFile = argc > 1 ? argv[1] : NULL;
    const char* strTimeLimit = argc > 2 ? argv[2] : "0";
    const char* strEvaluationLimit = argc > 3 ? argv[3] : "30";

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

public class RevenueManagement : IDisposable
{
    public class EvaluatedPoint
    {
        private int[] point;
        private double value;

        public EvaluatedPoint(int[] point, double value)
        {
            this.point = point;
            this.value = value;
        }

        public int GetPoint(int index)
        {
            return point[index];
        }

        public double GetValue()
        {
            return value;
        }
    }

    /* External function */
    public class RevenueManagementFunction
    {
        private int seed;
        private const int nbPeriods = 3;
        private const int purchasePrice = 80;
        private const int nbSimulations = (int)1e6;
        private readonly int[] prices = { 100, 300, 400 };
        private readonly int[] meanDemands = { 50, 20, 30 };
        private List<EvaluatedPoint> evaluatedPoints = new List<EvaluatedPoint>();

        public RevenueManagementFunction(int seed)
        {
            this.seed = seed;
            int[] point = { 100, 50, 30 };
            evaluatedPoints.Add(new EvaluatedPoint(point, 4740.99));
        }

        public double Call(HxExternalArgumentValues argumentValues)
        {
            // Initial quantity purchased
            int nbUnitsPurchased = (int)argumentValues.GetIntValue(0);
            // Number of units that should be left for future periods
            int[] nbUnitsReserved = new int[nbPeriods];
            for (int j = 0; j < nbPeriods - 1; ++j)
                nbUnitsReserved[j] = (int)argumentValues.GetIntValue(j + 1);
            nbUnitsReserved[nbPeriods - 1] = 0;
            // Set seed for reproducibility
            Random rng = new Random(seed);
            // Create distribution
            double[] X = new double[nbSimulations];
            for (int i = 0; i < nbSimulations; ++i)
                X[i] = GammaSample(rng);
            double[,] Y = new double[nbSimulations, nbPeriods];
            for (int i = 0; i < nbSimulations; ++i)
            {
                for (int j = 0; j < nbPeriods; ++j)
                    Y[i, j] = ExponentialSample(rng);
            }

            // Run simulations
            double sumProfit = 0;
            for (int i = 0; i < nbSimulations; ++i)
            {
                int remainingCapacity = nbUnitsPurchased;
                for (int j = 0; j < nbPeriods; ++j)
                {
                    // Generate demand for period j
                    int demand = (int)(meanDemands[j] * X[i] * Y[i, j]);
                    int nbUnitsSold = Math.Min(
                        Math.Max(remainingCapacity - nbUnitsReserved[j], 0),
                        demand
                    );
                    remainingCapacity = remainingCapacity - nbUnitsSold;
                    sumProfit += prices[j] * nbUnitsSold;
                }
            }
            // Calculate mean revenue
            double meanProfit = sumProfit / nbSimulations;
            double meanRevenue = meanProfit - purchasePrice * nbUnitsPurchased;

            return meanRevenue;
        }

        private static double ExponentialSample(Random rng, double rateParam = 1.0)
        {
            double u = rng.NextDouble();
            return Math.Log(1 - u) / (-rateParam);
        }

        private static double GammaSample(Random rng, double scaleParam = 1.0)
        {
            return ExponentialSample(rng, scaleParam);
        }

        public int GetNbPeriods()
        {
            return nbPeriods;
        }

        public List<EvaluatedPoint> GetEvaluatedPoints()
        {
            return evaluatedPoints;
        }
    }

    // Hexaly Optimizer
    private HexalyOptimizer optimizer;

    // Hexaly Program variables
    private HxExpression[] variables;
    private HxExpression funcCall;

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

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

    public void Solve(int timeLimit, int evaluationLimit)
    {
        // Declare the optimization model
        HxModel model = optimizer.GetModel();

        // Generate data
        RevenueManagementFunction revenueManagement = new RevenueManagementFunction(1);
        int nbPeriods = revenueManagement.GetNbPeriods();
        // Declare decision variables
        variables = new HxExpression[nbPeriods];
        for (int i = 0; i < nbPeriods; ++i)
            variables[i] = model.Int(0, 100);

        // Create the function
        HxDoubleExternalFunction func = new HxDoubleExternalFunction(revenueManagement.Call);
        HxExpression funcExpr = model.DoubleExternalFunction(func);
        // Call function
        funcCall = model.Call(funcExpr);
        for (int i = 0; i < nbPeriods; ++i)
            funcCall.AddOperand(variables[i]);

        // Declare constraints
        for (int i = 1; i < nbPeriods; ++i)
            model.Constraint(variables[i] <= variables[i - 1]);

        // Maximize function call
        model.Maximize(funcCall);

        // Enable surrogate modeling
        HxExternalContext context = funcExpr.GetExternalContext();
        HxSurrogateParameters surrogateParams = context.EnableSurrogateModeling();

        // Set lower bound
        context.SetLowerBound(0.0);

        model.Close();

        // Parametrize the optimizer
        if (timeLimit != 0)
            optimizer.GetParam().SetTimeLimit(timeLimit);

        // Set the maximum number of evaluations
        surrogateParams.SetEvaluationLimit(evaluationLimit);

        // Add evaluation points
        foreach (EvaluatedPoint evaluatedPoint in revenueManagement.GetEvaluatedPoints())
        {
            HxEvaluationPoint evaluationPoint = surrogateParams.CreateEvaluationPoint();
            for (int i = 0; i < nbPeriods; ++i)
                evaluationPoint.AddArgument(evaluatedPoint.GetPoint(i));
            evaluationPoint.SetReturnValue(evaluatedPoint.GetValue());
        }

        optimizer.Solve();
    }

    /* Write the solution in a file */
    public void WriteSolution(string fileName)
    {
        using (StreamWriter output = new StreamWriter(fileName))
        {
            output.WriteLine("obj=" + funcCall.GetDoubleValue());
            output.WriteLine("b=" + variables[0].GetIntValue());
            for (int i = 1; i < variables.Length; ++i)
                output.WriteLine("r" + i + "=" + variables[i].GetIntValue());
        }
    }

    public static void Main(string[] args)
    {
        string outputFile = args.Length > 0 ? args[0] : null;
        string strTimeLimit = args.Length > 1 ? args[1] : "0";
        string strEvaluationLimit = args.Length > 2 ? args[2] : "30";

        using (RevenueManagement model = new RevenueManagement())
        {
            model.Solve(int.Parse(strTimeLimit), int.Parse(strEvaluationLimit));
            if (outputFile != null)
                model.WriteSolution(outputFile);
        }
    }
}
Compilation / Execution (Windows)
javac RevenueManagement.java -cp %HX_HOME%\bin\hexaly.jar
java -cp %HX_HOME%\bin\hexaly.jar;. RevenueManagement
Compilation / Execution (Linux)
javac RevenueManagement.java -cp /opt/hexaly_13_0/bin/hexaly.jar
java -cp /opt/hexaly_13_0/bin/hexaly.jar:. RevenueManagement
import java.io.*;
import java.lang.Math;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import com.hexaly.optimizer.*;

public class RevenueManagement {

    private static class EvaluatedPoint {
        private int[] point;
        private double value;

        public EvaluatedPoint(int[] point, double value) {
            this.point = point;
            this.value = value;
        }
    }

    /* External function */
    private static class RevenueManagementFunction implements HxDoubleExternalFunction {

        private int seed;
        private int nbPeriods = 3;
        private int purchasePrice = 80;
        private int nbSimulations = (int) 1e6;
        private int[] prices = { 100, 300, 400 };
        private int[] meanDemands = { 50, 20, 30 };
        private List<EvaluatedPoint> evaluatedPoints = new ArrayList<EvaluatedPoint>();

        public RevenueManagementFunction(int seed) {
            this.seed = seed;
            int[] point = { 100, 50, 30 };
            evaluatedPoints.add(new EvaluatedPoint(point, 4740.99));
        }

        @Override
        public double call(HxExternalArgumentValues argumentValues) {
            // Initial quantity purchased
            int nbUnitsPurchased = (int) argumentValues.getIntValue(0);
            // Number of units that should be left for future periods
            int[] nbUnitsReserved = new int[nbPeriods];
            for (int j = 0; j < nbPeriods - 1; ++j) {
                nbUnitsReserved[j] = (int) argumentValues.getIntValue(j + 1);
            }
            nbUnitsReserved[nbPeriods - 1] = 0;

            // Set seed for reproducibility
            Random rng = new Random(seed);
            // Create distribution
            double rateParam = 1.0;
            double scaleParam = 1.0;
            double[] X = new double[nbSimulations];
            for (int i = 0; i < nbSimulations; ++i) {
                X[i] = gammaSample(rng, rateParam);
            }
            double[][] Y = new double[nbSimulations][nbPeriods];
            for (int i = 0; i < nbSimulations; ++i) {
                for (int j = 0; j < nbPeriods; ++j) {
                    Y[i][j] = exponentialSample(rng, scaleParam);
                }
            }

            // Run simulations
            double sumProfit = 0;
            for (int i = 0; i < nbSimulations; ++i) {
                int remainingCapacity = nbUnitsPurchased;
                for (int j = 0; j < nbPeriods; ++j) {
                    // Generate demand for period j
                    int demand = (int) (meanDemands[j] * X[i] * Y[i][j]);
                    int nbUnitsSold = Math.min(Math.max(remainingCapacity - nbUnitsReserved[j], 0), demand);
                    remainingCapacity = remainingCapacity - nbUnitsSold;
                    sumProfit += prices[j] * nbUnitsSold;
                }
            }
            // Calculate mean revenue
            double meanProfit = sumProfit / nbSimulations;
            double meanRevenue = meanProfit - purchasePrice * nbUnitsPurchased;

            return meanRevenue;
        }

        private static double exponentialSample(Random rng, double rateParam) {
            double u = rng.nextDouble();
            return Math.log(1 - u) / (-rateParam);
        }

        private static double gammaSample(Random rng, double scaleParam) {
            return exponentialSample(rng, scaleParam);
        }
    }

    // Hexaly Optimizer
    private final HexalyOptimizer optimizer;

    // Hexaly Program variables
    private HxExpression[] variables;
    private HxExpression funcCall;

    private RevenueManagement(HexalyOptimizer optimizer) {
        this.optimizer = optimizer;
    }

    private void solve(int timeLimit, int evaluationLimit) {
        // Declare the optimization model
        HxModel model = optimizer.getModel();

        // Generate data
        RevenueManagementFunction revenueManagement = new RevenueManagementFunction(1);
        int nbPeriods = revenueManagement.nbPeriods;
        // Declare decision variables
        variables = new HxExpression[nbPeriods];
        for (int i = 0; i < nbPeriods; ++i) {
            variables[i] = model.intVar(0, 100);
        }

        // Create the function
        HxExpression func = model.doubleExternalFunction(revenueManagement);
        // Call function with operands
        funcCall = model.call(func);
        for (int i = 0; i < nbPeriods; ++i) {
            funcCall.addOperand(variables[i]);
        }

        // Declare constraints
        for (int i = 1; i < nbPeriods; ++i) {
            model.constraint(model.leq(variables[i], variables[i - 1]));
        }

        // Maximize function call
        model.maximize(funcCall);

        // Enable surrogate modeling
        HxExternalContext context = func.getExternalContext();
        HxSurrogateParameters surrogateParams = context.enableSurrogateModeling();

        // Set lower bound
        context.setLowerBound(0.0);

        model.close();

        // Parametrize the optimizer
        if (timeLimit != 0) {
            optimizer.getParam().setTimeLimit(timeLimit);
        }

        // Set the maximum number of evaluations
        surrogateParams.setEvaluationLimit(evaluationLimit);

        // Add evaluation points
        for (EvaluatedPoint evaluatedPoint : revenueManagement.evaluatedPoints) {
            HxEvaluationPoint evaluationPoint = surrogateParams.createEvaluationPoint();
            for (int i = 0; i < nbPeriods; ++i) {
                evaluationPoint.addArgument(evaluatedPoint.point[i]);
            }
            evaluationPoint.setReturnValue(evaluatedPoint.value);
        }

        optimizer.solve();
    }

    /* Write the solution in a file */
    private void writeSolution(String fileName) throws IOException {
        try (PrintWriter output = new PrintWriter(fileName)) {
            output.println("obj=" + funcCall.getDoubleValue());
            output.println("b=" + variables[0].getIntValue());
            for (int i = 1; i < variables.length; ++i) {
                output.println("r" + i + "=" + variables[i].getIntValue());
            }
        }
    }

    public static void main(String[] args) {
        String outputFile = args.length > 0 ? args[0] : null;
        String strTimeLimit = args.length > 1 ? args[1] : "0";
        String strEvaluationLimit = args.length > 2 ? args[2] : "30";

        try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
            RevenueManagement model = new RevenueManagement(optimizer);
            model.solve(Integer.parseInt(strTimeLimit), Integer.parseInt(strEvaluationLimit));
            if (outputFile != null) {
                model.writeSolution(outputFile);
            }
        } catch (Exception ex) {
            System.err.println(ex);
            ex.printStackTrace();
            System.exit(1);
        }
    }
}

Discover the ease of use and performance of Hexaly