Black Box optimization of an external C program in Julia


Rationale for black-box optim

Since Julia have powerful auto-differentiation library (such as Zygote.jl) and useful Optimization (convex or not) packages such as JuMP.jl, one may think a black-box optimization is not useful in almost any case. However, even within the pure-Julia ecosystem, one might find the function at hand having noisy/spiky differential, or, takes too long to compute not mentioning the differentials.

Another place a black-box optimization may come handy is when dealing with external programs, especially those closed source once or legacy (read: fossil) softwares.

A short example

Say we have some compiled C program with the following function:

// ./test.c
#include <math.h>

double parab(double x){
    return pow((3+x), 2);
}

we can compile a shared library:

gcc  -c -Wall -fPIC test.c -o test.o
gcc  -shared -fPIC -o test.so test.o

# gives us a ./test.so

We can use @ccall to access this function:

julia> @ccall "./test".parab(2.0::Float64)::Float64
25.0

Now, we wrap this into a function:[1]

julia> parab_c(x) = @ccall "./test".parab(x[1]::Float64)::Float64
parab_c (generic function with 1 method)

then we can black-box optimize it!

using BlackBoxOptim
julia> res = bboptimize(parab_c; SearchRange=(-5.0, 5.0), NumDimensions=1)
...
julia> best_candidate(res)
1-element Array{Float64,1}:
 -3.0

In fact, we would realize that we don't care how the external program is invoked, as long as the returned 'stuff' is stable; you might as well just parse the stdout of some weird external program into numbers and feed it into bboptimize.

[1] BlackBoxOptim.jl expects the function to take an Array even though your function may be a scalar one.