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]
.
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));
}
}