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
3 changes: 0 additions & 3 deletions .JuliaFormatter.toml

This file was deleted.

40 changes: 5 additions & 35 deletions .github/workflows/FormatCheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,11 @@ on:
tags: '*'
pull_request:

concurrency:
# Skip intermediate builds: always, but for the master branch and tags.
# Cancel intermediate builds: always, but for the master branch and tags.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' && github.refs != 'refs/tags/*'}}

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
julia-version: [1]
julia-arch: [x86]
os: [ubuntu-latest]
runic:
runs-on: ubuntu-latest
steps:
- uses: julia-actions/setup-julia@latest
with:
version: ${{ matrix.julia-version }}

- uses: actions/checkout@v4
- name: Install JuliaFormatter and format
# This will use the latest version by default but you can set the version like so:
#
# julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.13.0"))'
run: |
julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter"))'
julia -e 'using JuliaFormatter; format(".", verbose=true)'
- name: Format check
run: |
julia -e '
out = Cmd(`git diff`) |> read |> String
if out == ""
exit(0)
else
@error "Some files have not been formatted !!!"
write(stdout, out)
exit(1)
end'
- uses: fredrikekre/runic-action@v1
with:
version: '1'
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![Build Status](https://github.com/fides-dev/Fides.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/fides-dev/Fides.jl/actions/workflows/CI.yml?query=branch%3Amain)
[![Aqua QA](https://raw.githubusercontent.com/JuliaTesting/Aqua.jl/master/badge.svg)](https://github.com/JuliaTesting/Aqua.jl)
[![codecov](https://codecov.io/gh/fides-dev/Fides.jl/graph/badge.svg?token=J7PXRF30JG)](https://codecov.io/gh/fides-dev/Fides.jl)
[![SciML Code Style](https://img.shields.io/static/v1?label=code%20style&message=SciML&color=9558b2&labelColor=389826)](https://github.com/SciML/SciMLStyle)
[![code style: runic](https://img.shields.io/badge/code_style-%E1%9A%B1%E1%9A%A2%E1%9A%BE%E1%9B%81%E1%9A%B2-black)](https://github.com/fredrikekre/Runic.jl)

Fides.jl is a Julia wrapper of the Python package [Fides.py](https://github.com/fides-dev/fides), which implements an Interior Trust Region Reflective algorithm for bounds constrained optimization problems based on [1, 2]. Fides targets problems on the form:

Expand All @@ -15,7 +15,7 @@ Fides.jl is a Julia wrapper of the Python package [Fides.py](https://github.com/

Where `f` is a continues at least twice-differentiable function, and `lb` and `ub` are the lower and upper bounds respectively.

## Highlights
## Main features

- Boundary-constrained interior trust-region optimization.
- Recursive reflective and truncated constraint management.
Expand Down
40 changes: 23 additions & 17 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,28 @@ using Documenter

DocMeta.setdocmeta!(Fides, :DocTestSetup, :(using Fides); recursive = true)

format = Documenter.HTML(;prettyurls = get(ENV, "CI", "false") == "true",
assets = String["assets/custom_theme.css"],
repolink = "https://github.com/fides-dev/Fides.jl",
edit_link = "main")
format = Documenter.HTML(;
prettyurls = get(ENV, "CI", "false") == "true",
assets = String["assets/custom_theme.css"],
repolink = "https://github.com/fides-dev/Fides.jl",
edit_link = "main"
)

makedocs(; modules = [Fides],
repo = "https://github.com/fides-dev/Fides.jl/blob/{commit}{path}#{line}",
checkdocs = :exports,
warnonly = false,
format = format,
sitename = "Fides.jl",
pages = [
"Home" => "index.md",
"Tutorial" => "tutorial.md",
"API" => "API.md",
],)
makedocs(;
modules = [Fides],
repo = "https://github.com/fides-dev/Fides.jl/blob/{commit}{path}#{line}",
checkdocs = :exports,
warnonly = false,
format = format,
sitename = "Fides.jl",
pages = [
"Home" => "index.md",
"Tutorial" => "tutorial.md",
"API" => "API.md",
],
)

deploydocs(; repo = "github.com/fides-dev/Fides.jl.git",
devbranch = "main",)
deploydocs(;
repo = "github.com/fides-dev/Fides.jl.git",
devbranch = "main",
)
1 change: 1 addition & 0 deletions src/Fides.jl
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const np_py = PythonCall.pynew()
function __init__()
PythonCall.pycopy!(fides_py, PythonCall.pyimport("fides"))
PythonCall.pycopy!(np_py, PythonCall.pyimport("numpy"))
return nothing
end

const STEPBACK_STRATEGIES = ["mixed", "refine", "reflect", "reflect_single", "truncate"]
Expand Down
16 changes: 10 additions & 6 deletions src/hessian_update.jl
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ struct BFGS{T <: Union{Nothing, AbstractMatrix}}
enforce_curv_cond::Bool
init_with_hess::Bool
end
function BFGS(; init_hess::Union{Nothing, AbstractMatrix} = nothing,
enforce_curv_cond::Bool = true)
function BFGS(;
init_hess::Union{Nothing, AbstractMatrix} = nothing, enforce_curv_cond::Bool = true
)
init_with_hess = _get_init_with_hess(init_hess)
return BFGS(init_hess, enforce_curv_cond, init_with_hess)
end
Expand All @@ -128,8 +129,9 @@ struct DFP{T <: Union{Nothing, AbstractMatrix}}
enforce_curv_cond::Bool
init_with_hess::Bool
end
function DFP(; init_hess::Union{Nothing, AbstractMatrix} = nothing,
enforce_curv_cond::Bool = true)
function DFP(;
init_hess::Union{Nothing, AbstractMatrix} = nothing, enforce_curv_cond::Bool = true
)
init_with_hess = _get_init_with_hess(init_hess)
return DFP(init_hess, enforce_curv_cond, init_with_hess)
end
Expand Down Expand Up @@ -162,8 +164,10 @@ struct Broyden{T <: Union{Nothing, AbstractMatrix}}
enforce_curv_cond::Bool
init_with_hess::Bool
end
function Broyden(phi::AbstractFloat; init_hess::Union{Nothing, AbstractMatrix} = nothing,
enforce_curv_cond::Bool = true)
function Broyden(
phi::AbstractFloat; init_hess::Union{Nothing, AbstractMatrix} = nothing,
enforce_curv_cond::Bool = true
)
init_with_hess = _get_init_with_hess(init_hess)
return Broyden(phi, init_hess, enforce_curv_cond, init_with_hess)
end
27 changes: 17 additions & 10 deletions src/options.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,15 @@ struct FidesOptions{T <: Union{String, Nothing}}
gamma2::Float64
history_file::T
end
function FidesOptions(; maxiter::Integer = 1000, fatol::Float64 = 1e-8,
frtol::Float64 = 1e-8, gatol::Float64 = 1e-6, grtol::Float64 = 0.0,
function FidesOptions(;
maxiter::Integer = 1000, fatol::Float64 = 1.0e-8,
frtol::Float64 = 1.0e-8, gatol::Float64 = 1.0e-6, grtol::Float64 = 0.0,
xtol::Float64 = 0.0, maxtime::Float64 = Inf, verbose = "warning",
subspace_solver::String = "2D", stepback_strategy::String = "reflect",
delta_init::Float64 = 1.0, mu::Float64 = 0.25, eta::Float64 = 0.75,
theta_max = 0.95, gamma1::Float64 = 0.25, gamma2::Float64 = 2.0,
history_file = nothing)::FidesOptions
history_file = nothing
)::FidesOptions
if !(stepback_strategy in STEPBACK_STRATEGIES)
throw(ArgumentError("$(stepback_strategy) is not a valid stepback strategy. \
Valid options are $(STEPBACK_STRATEGIES)"))
Expand All @@ -75,9 +77,10 @@ function FidesOptions(; maxiter::Integer = 1000, fatol::Float64 = 1e-8,
options are $(LOGGING_LEVELS)"))
end

return FidesOptions(maxiter, fatol, frtol, gatol, grtol, xtol, maxtime, verbose,
stepback_strategy, subspace_solver, delta_init, mu, eta, theta_max,
gamma1, gamma2, history_file)
return FidesOptions(
maxiter, fatol, frtol, gatol, grtol, xtol, maxtime, verbose, stepback_strategy,
subspace_solver, delta_init, mu, eta, theta_max, gamma1, gamma2, history_file
)
end

"""
Expand Down Expand Up @@ -113,8 +116,12 @@ end

function _get_verbose_py(verbose::String)::Int64
@assert verbose in LOGGING_LEVELS "Incorrect verbose level $verbose"
verbose == "warning" && return 30
verbose == "info" && return 20
verbose == "error" && return 40
verbose == "debug" && return 10
if verbose == "warning"
return 30
elseif verbose == "info"
return 20
elseif verbose == "error"
return 40
end
return 10
end
36 changes: 22 additions & 14 deletions src/problem.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ struct FidesProblem{T <: AbstractVector}
ub::T
user_hessian::Bool
end
function FidesProblem(f::Function, grad!::Function, x0::InputVector; hess! = nothing,
lb = nothing, ub = nothing)
function FidesProblem(
f::Function, grad!::Function, x0::InputVector; hess! = nothing, lb = nothing,
ub = nothing
)
_lb = _get_bounds(x0, lb, :lower)
_ub = _get_bounds(x0, ub, :upper)
# To ensure correct input type to f, grad!, hess! a variable having the same type as
Expand All @@ -81,7 +83,8 @@ function FidesProblem(f::Function, grad!::Function, x0::InputVector; hess! = not
return FidesProblem(fides_objective, fides_objective_py, x0, _lb, _ub, user_hessian)
end
function FidesProblem(
fides_objective::Function, x0::InputVector; lb = nothing, ub = nothing)
fides_objective::Function, x0::InputVector; lb = nothing, ub = nothing
)
_lb = _get_bounds(x0, lb, :lower)
_ub = _get_bounds(x0, ub, :upper)
# Get number of output arguments
Expand All @@ -103,11 +106,12 @@ function FidesProblem(
end

function _get_fides_objective(
f::Function, grad!::Function, hess!::Union{Function, Nothing},
xinput::InputVector, py::Bool)::Function
f::Function, grad!::Function, hess!::Union{Function, Nothing}, xinput::InputVector,
py::Bool
)::Function
if !isnothing(hess!)
fides_objective = (x) -> let _grad! = grad!, _f = f, _hess! = hess!,
_xinput = xinput, _py = py
_xinput = xinput, _py = py

return _fides_objective(x, _f, _grad!, _hess!, _xinput, _py)
end
Expand All @@ -118,15 +122,17 @@ function _get_fides_objective(
end
return fides_objective
end
function _get_fides_objective(f_grad::Function, ::Nothing, xinput::InputVector,
py::Bool)::Function
function _get_fides_objective(
f_grad::Function, ::Nothing, xinput::InputVector, py::Bool
)::Function
fides_objective = (x) -> let _f_grad = f_grad, _xinput = xinput, _py = py
return _fides_objective(x, _f_grad, nothing, _xinput, _py)
end
return fides_objective
end
function _get_fides_objective(f_grad_hess::Function, xinput::InputVector,
py::Bool)::Function
function _get_fides_objective(
f_grad_hess::Function, xinput::InputVector, py::Bool
)::Function
fides_objective = (x) -> let _f_grad_hess = f_grad_hess, _xinput = xinput, _py = py
return _fides_objective(x, _f_grad_hess, _xinput, _py)
end
Expand All @@ -139,8 +145,9 @@ function _fides_objective(x, f::Function, grad!::Function, xinput::InputVector,
g = _grad_fides(xinput, grad!)
return _get_fides_results(obj, g, py)
end
function _fides_objective(x, f::Function, grad!::Function, hess!::Function,
xinput::InputVector, py::Bool)
function _fides_objective(
x, f::Function, grad!::Function, hess!::Function, xinput::InputVector, py::Bool
)
_get_xinput!(xinput, x)
obj = f(xinput)
g = _grad_fides(xinput, grad!)
Expand Down Expand Up @@ -171,8 +178,9 @@ function _hess_fides(x::InputVector, hess!::Function)::Matrix
return H
end

function _get_bounds(x0::InputVector, bound::Union{InputVector, Nothing},
which_bound::Symbol)::AbstractVector
function _get_bounds(
x0::InputVector, bound::Union{InputVector, Nothing}, which_bound::Symbol
)::AbstractVector
@assert which_bound in [:lower, :upper] "Only lower and upper bounds are supported"
!isnothing(bound) && return bound
_bound = similar(x0)
Expand Down
4 changes: 2 additions & 2 deletions src/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Base.show
function Base.show(io::IO, prob::FidesProblem)
nps = length(prob.x0)
header = styled"{bold:FidesProblem} with $(nps) parameters to estimate"
print(io, styled"$(header)")
return print(io, styled"$(header)")
end
function Base.show(io::IO, res::FidesSolution)
header = styled"{bold:FidesSolution}"
Expand All @@ -12,5 +12,5 @@ function Base.show(io::IO, res::FidesSolution)
opt2 = @sprintf("Parameters estimated = %d\n", length(res.xmin))
opt3 = @sprintf("Optimiser iterations = %d\n", res.niterations)
opt4 = @sprintf("Runtime = %.1es\n", res.runtime)
print(io, styled"$(header)$(optheader)$(opt1)$(opt2)$(opt3)$(opt4)")
return print(io, styled"$(header)$(optheader)$(opt1)$(opt2)$(opt3)$(opt4)")
end
31 changes: 18 additions & 13 deletions src/solve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ approximations can be found in the

See also [FidesOptions](@ref).
"""
function solve(prob::FidesProblem, hess_update::HessianUpdate;
options::FidesOptions = FidesOptions())::FidesSolution
function solve(
prob::FidesProblem, hess_update::HessianUpdate; options::FidesOptions = FidesOptions()
)::FidesSolution
if prob.user_hessian == false && hess_update isa CustomHessian
throw(ArgumentError("\
The FidesProblem does not have a user provided Hessian. In this case \
Expand All @@ -58,21 +59,23 @@ function solve(prob::FidesProblem, hess_update::HessianUpdate;
return _solve(prob, hess_update, options)
end

function _solve(prob::FidesProblem, hess_update::HessianUpdate,
options::FidesOptions)::FidesSolution
function _solve(
prob::FidesProblem, hess_update::HessianUpdate, options::FidesOptions
)::FidesSolution
@unpack fides_objective_py, lb, ub = prob
verbose_py = _get_verbose_py(options.verbose_level)
options_py = _fides_options(options)
if !(hess_update isa CustomHessian)
hess_update_py = _get_hess_update_py(hess_update)
fides_opt_py = fides_py.Optimizer(fides_objective_py, np_py.asarray(ub),
np_py.asarray(lb), options = options_py,
hessian_update = hess_update_py,
verbose = verbose_py)
fides_opt_py = fides_py.Optimizer(
fides_objective_py, np_py.asarray(ub), np_py.asarray(lb), options = options_py,
hessian_update = hess_update_py, verbose = verbose_py
)
else
fides_opt_py = fides_py.Optimizer(fides_objective_py, np_py.asarray(ub),
np_py.asarray(lb), options = options_py,
verbose = verbose_py)
fides_opt_py = fides_py.Optimizer(
fides_objective_py, np_py.asarray(ub), np_py.asarray(lb), options = options_py,
verbose = verbose_py
)
end

if hess_update isa CustomHessian || hess_update.init_with_hess == false
Expand All @@ -86,10 +89,12 @@ function _solve(prob::FidesProblem, hess_update::HessianUpdate,
res = fides_opt_py.minimize(np_py.asarray(prob.x0), hess0 = hess_init_py)
end
end
return FidesSolution(PythonCall.pyconvert(Float64, res[0]),
return FidesSolution(
PythonCall.pyconvert(Float64, res[0]),
PythonCall.pyconvert(Vector{Float64}, res[1]),
PythonCall.pyconvert(Int64, fides_opt_py.iteration), runtime,
PythonCall.pyconvert(Symbol, fides_opt_py.exitflag._name_))
PythonCall.pyconvert(Symbol, fides_opt_py.exitflag._name_)
)
end

function _get_hess_update_py(hess_update::Union{BB, BG, SR1})
Expand Down
10 changes: 6 additions & 4 deletions test/component_arrays.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ ub = ComponentArray(p1 = 10.0, p2 = 10.0)
@testset "ComponentArrays" begin
prob1 = FidesProblem(rosenbrock_comp, rosenbrock_comp_grad!, x0; lb = lb, ub = ub)
sol1 = solve(prob1, Fides.BFGS())
@test all(.≈(sol1.xmin, [1.0, 1.0]; atol = 1e-6))
@test all(.≈(sol1.xmin, [1.0, 1.0]; atol = 1.0e-6))

prob2 = FidesProblem(rosenbrock_comp, rosenbrock_comp_grad!, x0;
hess! = rosenbrock_comp_hess!, lb = lb, ub = ub)
prob2 = FidesProblem(
rosenbrock_comp, rosenbrock_comp_grad!, x0; hess! = rosenbrock_comp_hess!, lb = lb,
ub = ub
)
sol2 = solve(prob2, Fides.CustomHessian())
@test all(.≈(sol1.xmin, [1.0, 1.0]; atol = 1e-6))
@test all(.≈(sol1.xmin, [1.0, 1.0]; atol = 1.0e-6))
end
Loading
Loading