Black-Box Optimization

LocalSolverBlackbox is a standalone tool that allows you to optimize functions that are computationally expensive. The solver is only able to evaluate the value of the function at a given point, and can only do so a small number of times. This type of problems appears in practice when you need to simulate a physical process using an external library.

Caution

If the function isn’t computationally expensive and could be evaluated thousands of times during the solution process, LocalSolverBlackBox may not be the best option. Consider using LocalSolver with a native function.

Note

Note that this functionality requires using LocalSolver through its LocalSolverBlackBox APIs, it is not available in the LSP language.

In this section we detail how to optimize a black-box function in each programming language (C++, Java, .NET, Python). To illustrate this description we will minimize the Branin function through a black-box interface. The number of evaluations will be limited to 20.

Branin function is defined by f(x) = a(x2 - b*x1² + c*x1 - r)² + s(1-t)cos(x1) + s with a=1, b=5.1/(4π²), c=5/π, s=10 and t=1/(8π). The domains of x1 and x2 are respectively [-5,10] and [0,15].

../_images/branin.png

For more details, see : branin.html

Black-Box optimization in Python

In Python, a black-function is simply a function or a method passed to LocalSolverBlackBox. This method only have a single parameter of type LSBBNativeContext object. The method uses this context to access the decision values of the current point to evaluate and returns the value of the function at this point:

def branin_eval(context):
    x = context.get(0)
    y = context.get(1)
    return math.pow(y - (5.1 / (4.0 * math.pi * math.pi)) * x * x
        + 5.0 / math.pi * x - 6, 2)
        + 10 * (1 - 1 / (8.0 * math.pi)) * math.cos(x) + 10

A LocalSolverBlackBox model is then created to optimize this black-box function. The black-box function is transformed into a LSBBExpression object using the create_native_function method. The decisions are associated to the function using a LSBBExpression object of type CALL. The model is then parametrize to use 20 evaluations of the black-box function. The solution value can be obtained after the resolution using a LSBBSolution object:

with localsolverblackbox.LocalSolverBlackBox() as ls:
   model = ls.get_model()
   x = model.float(-5,10)
   y = model.float(0,15)
   f = model.create_native_function(branin_eval)
   call = model.call()
   call.add_operand(f)
   call.add_operand(x)
   call.add_operand(y)
   model.add_objective(call, localsolverblackbox.LSBBObjectiveDirection.MINIMIZE)
   model.close()
   ls.get_param().set_evaluation_limit(20)
   ls.solve()
   sol = ls.get_solution()
   print "x=" + str(sol.get_value(x))
   print "y=" + str(sol.get_value(y))
   print "obj:" + str(sol.get_value(call))

Black-Box optimization in C++

In C++, a black-box function is passed to LocalSolverBlackBox as an object extending the LSBBNativeFunction class. This class has a single virtual method call taking as parameter a LSBBNativeContext object. The``call`` method uses this context to access the decision values of the current point to evaluate and returns the value of the function at this point:

#include <iostream>
#include "api/blackbox/localsolverblackbox.h"

using namespace localsolverblackbox;

#define PI 3.14159

class Branin : public LSBBNativeFunction {
    virtual lsdouble call(const LSBBNativeContext& context){
        lsdouble x = context.getDoubleValue(0);
        lsdouble y = context.getDoubleValue(1);
        return pow(y - (5.1 / (4.0 * PI * PI)) * x * x + 5.0 / PI * x - 6, 2)
            + 10 * (1 - 1 / (8.0 * PI)) * cos(x) + 10;
    }
};

A LocalSolverBlackBox model is then created to optimize this black-box function. The black-box function is transformed into a LSBBExpression object using the createNativeFunction method. The decisions are associated to the function using a LSBBExpression object of type O_Call. The model is then parametrized to use 20 evaluations of the black-box function. The solution value can be obtained after the resolution using a LSBBSolution object:

int main(){
    Branin braninFunction;

    LocalSolverBlackBox ls;
    LSBBModel model = ls.getModel();

    LSBBExpression x = model.floatVar(-5,10);
    LSBBExpression y = model.floatVar(0,15);
    LSBBExpression f = model.createNativeFunction(&braninFunction);
    LSBBExpression call = model.call();
    call.addOperand(f);
    call.addOperand(x);
    call.addOperand(y);
    model.addObjective(call, OD_Minimize);
    model.close();

    ls.getParam().setEvaluationLimit(20);
    ls.solve();

    LSBBSolution sol = ls.getSolution();
    std::cout << "x=" << sol.getDoubleValue(x) << std::endl;
    std::cout << "y=" << sol.getDoubleValue(y) << std::endl;
    std::cout << "obj: " << sol.getDoubleValue(call) << std::endl;
}

Black-Box optimization in .NET

In .NET, a black-box function is passed to LocalSolverBlackBox as a delegate method taking as a single parameter a LSBBNativeContext object. The method uses this context to access the decision values of the current point to evaluate and returns the value of the function at this point. In the example we use a static method:

public static double BraninEval(LSBBNativeContext context) {
    double x = context.GetDoubleValue(0);
    double y = context.GetDoubleValue(1);

    return Math.Pow(y - (5.1 / (4.0 * Math.PI * Math.PI)) * x * x
        + 5.0 / Math.PI * x - 6, 2)
        + 10 * (1 - 1 / (8.0 * Math.PI)) * Math.Cos(x) + 10;
}

A LocalSolverBlackBox model is then created to optimize this black-box function. The black-box function is transformed into a LSBBExpression object using the CreateNativeFunction method. The decisions are associated to the function using a LSBBExpression object of type Call. The model is then parametrize to use 20 evaluations of the black-box function. The solution value can be obtained after the resolution using a LSBBSolution object:

public static void Main(string[] args)
{
    LocalSolverBlackBox ls = new LocalSolverBlackBox();
    LSBBModel model = ls.GetModel();
    LSBBExpression x = model.Float(-5,10);
    LSBBExpression y = model.Float(0,15);
    LSBBExpression f = model.CreateNativeFunction(BraninEval);
    LSBBExpression call = model.Call();
    call.AddOperand(f);
    call.AddOperand(x);
    call.AddOperand(y);
    model.AddObjective(call, LSBBObjectiveDirection.Minimize);
    model.Close();
    ls.GetParam().SetEvaluationLimit(20);
    ls.Solve();
    LSBBSolution sol = ls.GetSolution();
    Console.WriteLine("x="+sol.GetDoubleValue(x));
    Console.WriteLine("y="+sol.GetDoubleValue(y));
    Console.WriteLine("obj:"+sol.GetDoubleValue(call));
}

Black-Box optimization in Java

In Java, a black-box function is passed to LocalSolverBlackBox as an object implementing the LSBBNativeFunction interface. This interface has a single method call taking as parameter a LSBBNativeContext object. The call method uses this context to access the decision values of the current point to evaluate and returns the value of the function at this point.

A LocalSolverBlackBox model is then created to optimize this black-box function. The black-box function is transformed into a LSBBExpression object using the createNativeFunction method. The decisions are associated to the function using a LSBBExpression object of type Call. The model is then parametrize to use 20 evaluations of the black-box function. The solution value can be obtained after the resolution using a LSBBSolution object:

import localsolverblackbox.*;

public class Branin{
    public static void main(String [] args) {

        LocalSolverBlackBox ls = new LocalSolverBlackBox();
        LSBBModel model = ls.getModel();
        LSBBExpression f = model.createNativeFunction(new LSBBNativeFunction(){
        public double call(LSBBNativeContext context){
            double x = context.getDoubleValue(0);
            double y = context.getDoubleValue(1);
            return Math.pow(y - (5.1 / (4.0 * Math.PI * Math.PI)) * x * x
                + 5.0 / Math.PI * x - 6, 2)
                + 10 * (1 - 1 / (8.0 * Math.PI)) * Math.cos(x) + 10;
        }
        });

        LSBBExpression x = model.floatVar(-5,10);
        LSBBExpression y = model.floatVar(0,15);
        LSBBExpression call = model.call();
        call.addOperand(f);
        call.addOperand(x);
        call.addOperand(y);
        model.addObjective(call, LSBBObjectiveDirection.Minimize);
        model.close();

        ls.getParam().setEvaluationLimit(20);
        ls.solve();

        LSBBSolution solution = ls.getSolution();
        System.out.println("x=" + solution.getDoubleValue(x));
        System.out.println("y=" + solution.getDoubleValue(y));
        System.out.println("obj:" + solution.getDoubleValue(call));
    }
}