Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions examples/optimise1.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using IntervalOptimisation, IntervalArithmetic, IntervalConstraintProgramming


# Unconstrained Optimisation
f(x, y) = (1.5 - x * (1 - y))^2 + (2.25 - x * (1 - y^2))^2 + (2.625 - x * (1 - y^3))^2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please include the source for test cases and solutions.

# f (generic function with 2 methods)

f(X) = f(X...)
# f (generic function with 2 methods)

X = (-1e6..1e6) × (-1e6..1e6)
# [-1e+06, 1e+06] × [-1e+06, 1e+06]

minimise_icp(f, X)
# ([0, 2.39527e-07], IntervalArithmetic.IntervalBox{2,Float64}[[2.99659, 2.99748] × [0.498906, 0.499523], [2.99747, 2.99834] × [0.499198, 0.500185], [2.99833, 2.99922] × [0.499198, 0.500185], [2.99921, 3.00011] × [0.499621, 0.500415], [3.0001, 3.001] × [0.499621, 0.500415], [3.00099, 3.0017] × [0.500169, 0.500566], [3.00169, 3.00242] × [0.500169, 0.500566]])



f(X) = X[1]^2 + X[2]^2
# f (generic function with 2 methods)

X = (-∞..∞) × (-∞..∞)
# [-∞, ∞] × [-∞, ∞]

minimise_icp(f, X)
# ([0, 0], IntervalArithmetic.IntervalBox{2,Float64}[[-0, 0] × [-0, 0], [0, 0] × [-0, 0]])




# Constrained Optimisation
f(X) = -1 * (X[1] + X[2])
# f (generic function with 1 method)

X = (-∞..∞) × (-∞..∞)
# [-∞, ∞] × [-∞, ∞]

constraints = [IntervalOptimisation.constraint(x->(x[1]), -∞..6), IntervalOptimisation.constraint(x->x[2], -∞..4), IntervalOptimisation.constraint(x->(x[1]*x[2]), -∞..4)]
# 3-element Array{IntervalOptimisation.constraint{Float64},1}:
# IntervalOptimisation.constraint{Float64}(#3, [-∞, 6])
# IntervalOptimisation.constraint{Float64}(#4, [-∞, 4])
# IntervalOptimisation.constraint{Float64}(#5, [-∞, 4])

minimise_icp_constrained(f, X, constraints)
# ([-6.66676, -6.66541], IntervalArithmetic.IntervalBox{2,Float64}[[5.99918, 6] × [0.666233, 0.666758], [5.99918, 6] × [0.665717, 0.666234], [5.99887, 5.99919] × [0.666233, 0.666826], [5.99984, 6] × [0.665415, 0.665718], [5.99856, 5.99888] × [0.666233, 0.666826], [5.99969, 5.99985] × [0.665415, 0.665718]])



# One-dimensional case
minimise1d(x -> (x^2 - 2)^2, -10..11)
# ([0, 1.33476e-08], IntervalArithmetic.Interval{Float64}[[-1.41426, -1.41358], [1.41364, 1.41429]])

minimise1d_deriv(x -> (x^2 - 2)^2, -10..11)
# ([0, 8.76812e-08], IntervalArithmetic.Interval{Float64}[[-1.41471, -1.41393], [1.41367, 1.41444]])
9 changes: 6 additions & 3 deletions src/IntervalOptimisation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
module IntervalOptimisation

export minimise, maximise,
minimize, maximize
minimize, maximize,
minimise1d, minimise1d_deriv,
minimise_icp, minimise_icp_constrained

include("SortedVectors.jl")
using .SortedVectors

using IntervalArithmetic, IntervalRootFinding
using IntervalArithmetic, IntervalRootFinding, DataStructures, IntervalConstraintProgramming, StaticArrays, ForwardDiff

if !isdefined(:sup)
const sup = supremum
Expand All @@ -19,8 +21,9 @@ if !isdefined(:inf)
end

include("optimise.jl")
include("optimise1.jl")

const minimize = minimise
const maximize = maximise
const maximize = maximise

end
2 changes: 1 addition & 1 deletion src/SortedVectors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ immutable SortedVector{T, F<:Function}
data::Vector{T}
by::F

function SortedVector(data::Vector{T}, by::F)
function SortedVector{T,F}(data::Vector{T}, by::F) where {T,F}
new(sort(data), by)
end
end
Expand Down
248 changes: 248 additions & 0 deletions src/optimise1.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
using IntervalRootFinding, IntervalArithmetic, StaticArrays, ForwardDiff, BenchmarkTools, Compat, IntervalOptimisation, DataStructures, IntervalConstraintProgramming

import Base.isless

struct IntervalMinima{T<:Real}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Minimum" is the singular; "minima" is the plural.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want "minimum" everywhere.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Using global_minimum instead since minimum is an existent binding.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a single minimum.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, sorry about this.

intval::Interval{T}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to write out interval

global_minimum::T
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use minimum here.

end

function isless(a::IntervalMinima{T}, b::IntervalMinima{T}) where {T<:Real}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can write this as a single-line function.

return isless(a.global_minimum, b.global_minimum)
end

function minimise1d(f::Function, x::Interval{T}; reltol=1e-3, abstol=1e-3) where {T<:Real}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary any more?

Copy link
Author

@eeshan9815 eeshan9815 Jul 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's necessary for the derivative stuff, since that's where the difference lies. Multidim functions use gradient. Should I combine them using runtime branching?
Also, multidim functions use icp, which isn't much use for 1d functions.


Q = binary_minheap(IntervalMinima{T})

global_minimum = f(interval(mid(x))).hi

arg_minima = Interval{T}[]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Minima" is correct here since there may be several.


push!(Q, IntervalMinima(x, global_minimum))

while !isempty(Q)

p = pop!(Q)

if isempty(p.intval)
continue
end

if p.global_minimum > global_minimum
continue
end

current_minimum = f(interval(mid(p.intval))).hi

if current_minimum < global_minimum
global_minimum = current_minimum
end

if diam(p.intval) < abstol
push!(arg_minima, p.intval)
else
x1, x2 = bisect(p.intval)
push!(Q, IntervalMinima(x1, f(x1).lo), IntervalMinima(x2, f(x2).lo))
end
end
lb = minimum(inf.(f.(arg_minima)))

return lb..global_minimum, arg_minima
end

function minimise1d_deriv(f::Function, x::Interval{T}; reltol=1e-3, abstol=1e-3) where {T<:Real}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be a lot of common code between this function and minimise1d. Can that be refactored out, or can these functions be combined?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add deriv as a kwarg and combine the functions


Q = binary_minheap(IntervalMinima{T})

global_minimum = f(interval(mid(x))).hi

arg_minima = Interval{T}[]

push!(Q, IntervalMinima(x, global_minimum))

while !isempty(Q)

p = pop!(Q)

if isempty(p.intval)
continue
end

if p.global_minimum > global_minimum
continue
end

deriv = ForwardDiff.derivative(f, p.intval)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be optional to use this.

if 0 ∉ deriv
continue
end
# Second derivative contractor
# doublederiv = ForwardDiff.derivative(x->ForwardDiff.derivative(f, x), p.intval)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be optional to use this. Is there a way of setting up the 2nd derivative beforehand to reduce the workload?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay.
I removed it because the second deriv evaluation is costly, and since the interval to be computed over is different for each iteration, it can slow down the function.
Also, it didn't show any improvements over the first deriv contractor

# if doublederiv < 0
# continue
# end

m = mid(p.intval)

current_minimum = f(interval(m)).hi

if current_minimum < global_minimum
global_minimum = current_minimum
end


# Contractor 1
x = m .+ extended_div((interval(-∞, global_minimum) - f(m)), deriv)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the derivative contractor as given in the Jaulin book


x = x .∩ p.intval

# # Contractor 2 (Second derivative, expanding more on the Taylor Series, not beneficial in practice)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is is not beneficial? Did you try it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it because the second deriv evaluation is costly, and since the interval to be computed over is different for each iteration, it can slow down the function.
Also, it didn't show any improvements over the first deriv contractor

# x = m .+ quadratic_roots(doublederiv/2, ForwardDiff.derivative(f, interval(m)), f(m) - interval(-∞, global_minimum))
#
# x = x .∩ p.intval

if diam(p.intval) < abstol
push!(arg_minima, p.intval)
else
if isempty(x[2])
x1, x2 = bisect(x[1])
push!(Q, IntervalMinima(x1, f(x1).lo), IntervalMinima(x2, f(x2).lo))
else
push!(Q, IntervalMinima.(x, inf.(f.(x)))...)
end

# Second Deriv contractor
# if isempty(x[2])
# x1, x2 = bisect(x[1])
# push!(Q, IntervalMinima(x1, f(x1).lo), IntervalMinima(x2, f(x2).lo))
# else
# x1, x2, x3 = x
# push!(Q, IntervalMinima(x1, f(x1).lo), IntervalMinima(x2, f(x2).lo), IntervalMinima(x3, f(x3).lo))
# end
end
end
lb = minimum(inf.(f.(arg_minima)))

return lb..global_minimum, arg_minima
end




struct IntervalBoxMinima{N, T<:Real}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minimum

intval::IntervalBox{N, T}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need a separate type here -- just reuse IntervalMinimum.

global_minimum::T
end

struct constraint{T<:Real}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial capital for types.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs?

f::Function
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a type parameter of the Constraint type for efficiency.

c::Interval{T}
end

function isless(a::IntervalBoxMinima{N, T}, b::IntervalBoxMinima{N, T}) where {N, T<:Real}
return isless(a.global_minimum, b.global_minimum)
end

function minimise_icp(f::Function, x::IntervalBox{N, T}; reltol=1e-3, abstol=1e-3) where {N, T<:Real}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this a method of minimise and dispatch on a type ICP or similar.


Q = binary_minheap(IntervalBoxMinima{N, T})

global_minimum = ∞

x = icp(f, x, -∞..global_minimum)

arg_minima = IntervalBox{N, T}[]

push!(Q, IntervalBoxMinima(x, global_minimum))

while !isempty(Q)

p = pop!(Q)

if isempty(p.intval)
continue
end

if p.global_minimum > global_minimum
continue
end

current_minimum = f(interval.(mid(p.intval))).hi

if current_minimum < global_minimum
global_minimum = current_minimum
end
# if all(0 .∉ ForwardDiff.gradient(f, p.intval.v))
# continue
# end

X = icp(f, p.intval, -∞..global_minimum)

if diam(p.intval) < abstol
push!(arg_minima, p.intval)

else
x1, x2 = bisect(X)
push!(Q, IntervalBoxMinima(x1, f(x1).lo), IntervalBoxMinima(x2, f(x2).lo))
end
end

lb = minimum(inf.(f.(arg_minima)))

return lb..global_minimum, arg_minima
end

function minimise_icp_constrained(f::Function, x::IntervalBox{N, T}, constraints::Vector{constraint{T}} = Vector{constraint{T}}(); reltol=1e-3, abstol=1e-3) where {N, T<:Real}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There seems to be a lot of common code between this function and the previous one. Can that be refactored out, or can these functions be combined?

Copy link
Author

@eeshan9815 eeshan9815 Jul 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added them differently because the GODOWN used for them is different, as we can use the mid in the unconstrained case and that causes better convergence, whereas it's not possible in the constrained case since mid might not be satisfied by the constraints.
Also, the robust constrained optimisation function will be different and will include John conditions, so I kept them separate for now.
Should I combine them using runtime checks?


Q = binary_minheap(IntervalBoxMinima{N, T})

global_minimum = ∞

for t in constraints
x = icp(t.f, x, t.c)
end

x = icp(f, x, -∞..global_minimum)

arg_minima = IntervalBox{N, T}[]

push!(Q, IntervalBoxMinima(x, global_minimum))

while !isempty(Q)

p = pop!(Q)

if isempty(p.intval)
continue
end

if p.global_minimum > global_minimum
continue
end

# current_minimum = f(interval.(mid(p.intval))).hi
current_minimum = f(p.intval).hi

if current_minimum < global_minimum
global_minimum = current_minimum
end
# if 0 .∉ ForwardDiff.gradient(f, p.intval.v)
# continue
# end
X = icp(f, p.intval, -∞..global_minimum)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


for t in constraints
X = icp(t.f, X, t.c)
end

if diam(p.intval) < abstol
push!(arg_minima, p.intval)
else
x1, x2 = bisect(X)
push!(Q, IntervalBoxMinima(x1, f(x1).lo), IntervalBoxMinima(x2, f(x2).lo))
end
end
lb = minimum(inf.(f.(arg_minima)))
return lb..global_minimum, arg_minima
end