Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jobs:
- OptimizationOptimJL
- OptimizationOptimisers
- OptimizationPRIMA
- OptimizationPyCMA
- OptimizationQuadDIRECT
- OptimizationSpeedMapping
- OptimizationPolyalgorithms
Expand Down
3 changes: 3 additions & 0 deletions lib/OptimizationPyCMA/CondaPkg.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[deps]
matplotlib = ""
cma = ""
18 changes: 18 additions & 0 deletions lib/OptimizationPyCMA/Project.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name = "OptimizationPyCMA"
uuid = "fb0822aa-1fe5-41d8-99a6-e7bf6c238d3b"
authors = ["Maximilian Pochapski <[email protected]>"]
version = "0.1.0"

[deps]
CondaPkg = "992eb4ea-22a4-4c89-a5bb-47a3300528ab"
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[compat]
CondaPkg = "0.2.29"
Optimization = "4.4.0"
PythonCall = "0.9.25"
Reexport = "1.2.2"
Test = "1.11.0"
152 changes: 152 additions & 0 deletions lib/OptimizationPyCMA/src/OptimizationPyCMA.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
module OptimizationPyCMA

using Reexport
@reexport using Optimization
using PythonCall, Optimization.SciMLBase

export PyCMAOpt

struct PyCMAOpt end

# importing PyCMA
const cma = Ref{Py}()
function get_cma()
if !isassigned(cma) || cma[] === nothing
cma[] = pyimport("cma")
end
return cma[]
end

# Defining the SciMLBase interface for PyCMAOpt

SciMLBase.allowsbounds(::PyCMAOpt) = true
SciMLBase.supports_opt_cache_interface(opt::PyCMAOpt) = true
SciMLBase.requiresgradient(::PyCMAOpt) = false
SciMLBase.requireshessian(::PyCMAOpt) = false
SciMLBase.requiresconsjac(::PyCMAOpt) = false
SciMLBase.requiresconshess(::PyCMAOpt) = false

# wrapping Optimization.jl args into a python dict as arguments to PyCMA opts
function __map_optimizer_args(prob::OptimizationCache, opt::PyCMAOpt;
maxiters::Union{Number, Nothing} = nothing,
maxtime::Union{Number, Nothing} = nothing,
abstol::Union{Number, Nothing} = nothing,
reltol::Union{Number, Nothing} = nothing)
if !isnothing(reltol)
@warn "common reltol is currently not used by $(opt)"
end

mapped_args = Dict(
"verbose" => -5,
"bounds" => (prob.lb, prob.ub),
)

if !isnothing(abstol)
mapped_args["tolfun"] = abstol
end

if !isnothing(reltol)
mapped_args["tolfunrel"] = reltol
end

if !isnothing(maxtime)
mapped_args["timeout"] = maxtime
end

if !isnothing(maxiters)
mapped_args["maxiter"] = maxiters
end

return mapped_args
end

function __map_pycma_retcode(stop_dict::Dict{String, Any})
# mapping termination conditions to SciMLBase return codes
if any(k ∈ keys(stop_dict) for k in ["ftarget", "tolfun", "tolx"])
return ReturnCode.Success
elseif any(k ∈ keys(stop_dict) for k in ["maxiter", "maxfevals"])
return ReturnCode.MaxIters
elseif "timeout" ∈ keys(stop_dict)
return ReturnCode.MaxTime
elseif "callback" ∈ keys(stop_dict)
return ReturnCode.Terminated
elseif any(k ∈ keys(stop_dict) for k in ["tolupsigma", "tolconditioncov", "noeffectcoord", "noeffectaxis", "tolxstagnation", "tolflatfitness", "tolfacupx", "tolstagnation"])
return ReturnCode.Failure
else
return ReturnCode.Default
end
end

function SciMLBase.__solve(cache::OptimizationCache{
F,
RC,
LB,
UB,
LC,
UC,
S,
O,
D,
P,
C
}) where {
F,
RC,
LB,
UB,
LC,
UC,
S,
O <:
PyCMAOpt,
D,
P,
C
}
local x

# doing conversions
maxiters = Optimization._check_and_convert_maxiters(cache.solver_args.maxiters)
maxtime = Optimization._check_and_convert_maxtime(cache.solver_args.maxtime)

# wrapping the objective function
_loss = function (θ)
x = cache.f(θ, cache.p)
return first(x)
end

# converting the Optimization.jl Args to PyCMA format
opt_args = __map_optimizer_args(cache, cache.opt; cache.solver_args...,
maxiters = maxiters,
maxtime = maxtime)

# init the CMAopt class
es = get_cma().CMAEvolutionStrategy(cache.u0, 1, pydict(opt_args))
logger = es.logger

# running the optimization
t0 = time()
opt_res = es.optimize(_loss)
t1 = time()

# loading logged files from disk
logger.load()

# reading the results
opt_ret_dict = opt_res.stop()
retcode = __map_pycma_retcode(pyconvert(Dict{String, Any}, opt_ret_dict))

# logging and returning results of the optimization
stats = Optimization.OptimizationStats(;
iterations = length(logger.xmean),
time = t1 - t0,
fevals = length(logger.xmean))

SciMLBase.build_solution(cache, cache.opt,
pyconvert(Float64, logger.xrecent[-1][-1]),
pyconvert(Float64, logger.f[-1][-1]); original = opt_res,
retcode = retcode,
stats = stats)
end

end # module OptimizationPyCMA
14 changes: 14 additions & 0 deletions lib/OptimizationPyCMA/test/runtests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using OptimizationPyCMA
using Test

@testset "OptimizationPyCMA.jl" begin
rosenbrock(x, p) = (p[1] - x[1])^2 + p[2] * (x[2] - x[1]^2)^2
x0 = zeros(2)
_p = [1.0, 100.0]
l1 = rosenbrock(x0, _p)
f = OptimizationFunction(rosenbrock)
prob = OptimizationProblem(f, x0, _p, lb = [-1.0, -1.0], ub = [0.8, 0.8])
sol = solve(prob, PyCMAOpt())
@test 10 * sol.objective < l1
sol = solve(prob, PyCMAOpt(), maxiters = 100)
end
Loading