JuliaNLSolvers / Optim.jl

Optimization functions for Julia
Other
1.12k stars 217 forks source link

RFC: Revise Unconstrained and Box Constrained Optimization API #16

Closed johnmyleswhite closed 11 years ago

johnmyleswhite commented 11 years ago

We currently have a mixed API for optimization: the functions I wrote work very differently from the functions that Tim wrote. To unify things, I'd like to propose a new API that I hope we can all standardize on. The proposal is quite long, but I think it touches on all of the issues we need to confront. I'm opening it as an issue because I expect we'll want to debate the design for a while before implementing anything.

To simplify the discussion, let's introduce some notation.

My API worked exclusively with pure functions, which I'll refer to as:

Tim's API employed mutating functions, which I'll refer to as:

One could also consider functions like gh! and fgh!, but I'm not currently aware of a proposed use for those things.

Using this notation, my proposed new API is the following:

Specifically, I propose creating the following types and methods:

immutable OnceDifferentiableFunction
    f::Function
    g!::Function
end

immutable CoupledOnceDifferentiableFunction
    f::Function
    g!::Function
    fg!::Function
end

immutable TwiceDifferentiableFunction
    f::Function
    g!::Function
    h!::Function
end

Using these functions, we could create methods like the following:

For pure function calls:

For mutating gradient function calls:

For mutating Hessian function calls:

For simultaneous function and mutating gradient function calls:

Using these types and functions, we should be able to express all of the computations we're doing now while doing much less memory allocation. Also, the use of multiple dispatch should make it easier to do redirection by automatically creating gradients when needed.

timholy commented 11 years ago

I think this looks promising! With the caveat that implementation often reveals things that are hard to predict in the abstract, I'd say this looks like a very nice solution to our API problem.

johnmyleswhite commented 11 years ago

I'll start to demo out a revision of the package based on this approach. I did a bit this morning and found that this proposal is kind of overkill: it's probably not worth the effort to (a) add the call* methods or (b) allow the uncoupled types because we can always synthesize "low" performance coupled functions from any reasonable inputs. I think I'm just going to work with CoupledDifferntiableFunction, since that provides the most flexibility for performance conscious programmers to optimize the naive implementation we'll synthesize based on inputs.

johnmyleswhite commented 11 years ago

Following up on the theme of simplification, my current thinking is that we'll use the following two types as the core inputs to every optimization function:

immutable DifferentiableFunction
    f::Function
    g!::Function
    fg!::Function
end

immutable TwiceDifferentiableFunction
    f::Function
    g!::Function
    fg!::Function
    h!::Function
end

The latter may need to be supplemented with higher-order coupled functions, but I'm assuming that we can avoid that for the time being.

We'll convert existing functions into these types using something like the following:

using Calculus

function DifferentiableFunction(f::Function)
    function g!(x::Vector, storage::Vector)
        Calculus.finite_difference!(f, x, storage, :central)
        return
    end
    function fg!(x::Vector, storage::Vector)
        g!(x, storage)
        return f(x)
    end
    return DifferentiableFunction(f, g!, fg!)
end

function DifferentiableFunction(f::Function, g!::Function)
    function fg!(x::Vector, storage::Vector)
        g!(x, storage)
        return f(x)
    end
    return DifferentiableFunction(f, g!, fg!)
end

function TwiceDifferentiableFunction(f::Function)
    function g!(x::Vector, storage::Vector)
        Calculus.finite_difference!(f, x, storage, :central)
        return
    end
    function fg!(x::Vector, storage::Vector)
        g!(x, storage)
        return f(x)
    end
    function h!(x::Vector, storage::Matrix)
        Calculus.finite_difference_hessian!(f, x, storage)
        return
    end
    return TwiceDifferentiableFunction(f, g!, fg!, h!)
end

function TwiceDifferentiableFunction(f::Function, g!::Function, h!::Function)
    function fg!(x::Vector, storage::Vector)
        g!(x, storage)
        return f(x)
    end
    return TwiceDifferentiableFunction(f, g!, fg!, h!)
end

This means that naive users can continue using these functions without much work, but advanced users can write high-performance code that gets passed in directly as DifferentiableFunction or TwiceDifferentiableFunction types.

johnmyleswhite commented 11 years ago

Closing this in favor of the pull request, which I will merge very soon.