Global optimization without derivatives
Popularity
45 Stars
Updated Last
1 Year Ago
Started In
January 2018

This package is registered in HolyLabRegistry---you'll likely want to add that as an additional registry.

QuadDIRECT is an algorithm for global optimization without requiring derivatives. It was inspired by DIRECT and MCS:

DIRECT: Jones, Donald R., Cary D. Perttunen, and Bruce E. Stuckman. "Lipschitzian optimization without the Lipschitz constant." Journal of Optimization Theory and Applications 79.1 (1993): 157-181.

MCS: Huyer, Waltraud, and Arnold Neumaier. "Global optimization by multilevel coordinate search." Journal of Global Optimization 14.4 (1999): 331-355.

There is no formal published description (yet), but it expands upon DIRECT by:

• allowing boxes to be of different sizes, and supporting boxes that extend to infinity
• not assuming that the coordinates form a metric space: each dimension is treated independently of the others
• it splits boxes at points suspected of being minima as judged by a local one-dimensional quadratic model of the function
• once enough boxes have been created, it attempts "local search," building a dense quadratic model and performing a quasi-Newton optimization.

Unlike MCS,

• the geometry is more similar to DIRECT, in that each box is affiliated with a different function evaluation point
• it uses the "Pareto front" concept of DIRECT, rather than a heuristic splitting scheme based on levels
• the local search is not disconnected from the box-splitting: the function evaluations used in local search get entered into the tree structure, providing the opportunity to re-use those function evaluations for further improvements

As a simple demonstration, consider the "6-hump camel function":

```julia> function camel(x)
# 6-hump camel function. Typically evaluated over [-3,3] × [-2,2].
x1, x2 = x[1], x[2]
x1s = x1*x1
x2s = x2*x2
return (4 - 2.1*x1s + x1s*x1s/3)*x1s + x1*x2 + (-4 + 4*x2s)*x2s
end```

Here the value scale was cut off at 5 so that the structure of the minima can be seen. You can explore this function with

```julia> using QuadDIRECT

julia> lower, upper = [-3,-2], [3,2]   # domain over which we allow solutions
([-3, -2], [3, 2])

julia> splits = ([-2, 0, 2], [-1, 0, 1])   # initial locations to evaluate function
([-2, 0, 2], [-1, 0, 1])

julia> root, x0 = analyze(camel, splits, lower, upper)
(BoxRoot@[NaN, NaN], [0.0, 0.0])```

This creates a tree structure (currently) with a few hundred boxes, each corresponding to a single function evaluation:

You can see that it has concentrated its function evaluations near the local minima, and that all of the minima were explored. This plot was generated by utilities in `QuadDIRECT/src/plotting.jl`, which have to be loaded separatedly from the `QuadDIRECT` module.

You can inspect the minimum like this:

```julia> box = minimum(root)
Box-1.0316284406055976@[0.0898781, -0.71269]

julia> value(box)
-1.0316284406055976

julia> position(box, x0)
2-element Array{Float64,1}:
0.0898781
-0.71269```

These match the known global optima to reasonably high precision.

There's also a convenience function `minimize` which just returns the location and value of the minimum.

For more examples, see the `demo` directory.

### Usage guidance, benchmarking, and convergence

In global optimization, "convergence" is a tricky subject: if the function might be non-convex, there is no principled way to say you're done---that your best minimum so far is the best minimum there is---without some additional information about the function. QuadDIRECT terminates if no further improvements have been made after `ndims` iterations. If you're benchmarking QuadDIRECT, here are a few tips:

• if you know the value of the global minimum, you can use it to specify a termination criterion, e.g., within 1% relative or 0.01 absolute value of the global minimum. See the `fvalue` option for `analyze`.
• You can count function evaluations by wrapping your function with `fc = CountedFunction(f)` and then use `numevals(fc)` to see how many evaluations were used. This is slightly more accurate than `length(leaves(root))` because there are a few cases where the function value is not stored in the tree structure.
• Use `LoggedFunction` to keep a record of every function value computed. The latter is particularly useful if you want to ask how many evaluations were required to reach a value of particular quality.