Skip to content

Commit a41542a

Browse files
authored
Merge pull request #17 from Jutho/jh/overhaul
WIP: complete overhaul
2 parents 8135d1c + fefcb76 commit a41542a

File tree

12 files changed

+797
-383
lines changed

12 files changed

+797
-383
lines changed

.JuliaFormatter.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
style = "yas"

.github/workflows/ci-julia-nightly.yml

Lines changed: 0 additions & 31 deletions
This file was deleted.

.github/workflows/ci.yml

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
11
name: CI
22
on:
3-
- push
4-
- pull_request
3+
push:
4+
branches:
5+
- 'master'
6+
- 'main'
7+
- 'release-'
8+
tags: '*'
9+
pull_request:
10+
workflow_dispatch:
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: true
15+
516
jobs:
617
test:
718
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }}
@@ -10,23 +21,51 @@ jobs:
1021
fail-fast: false
1122
matrix:
1223
version:
13-
- '1.4'
14-
- '1' # automatically expands to the latest stable 1.x release of Julia
24+
- '1.8' # minimum version because of ScopedValues.jl
25+
- '1' # expands to latest version
26+
- 'lts' # expands to latest LTS version
1527
os:
1628
- ubuntu-latest
1729
- macOS-latest
1830
- windows-latest
1931
arch:
2032
- x64
2133
steps:
22-
- uses: actions/checkout@v2
23-
- uses: julia-actions/setup-julia@v1
34+
- uses: actions/checkout@v4
35+
- uses: julia-actions/setup-julia@v2
2436
with:
2537
version: ${{ matrix.version }}
2638
arch: ${{ matrix.arch }}
39+
- uses: julia-actions/cache@v2
2740
- uses: julia-actions/julia-buildpkg@latest
2841
- uses: julia-actions/julia-runtest@latest
2942
- uses: julia-actions/julia-processcoverage@v1
30-
- uses: codecov/codecov-action@v1
43+
- uses: codecov/codecov-action@v5
44+
env:
45+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
3146
with:
3247
file: lcov.info
48+
test-nightly:
49+
needs: test
50+
name: Julia nightly - ${{ matrix.os }} - ${{ matrix.arch }}
51+
runs-on: ${{ matrix.os }}
52+
strategy:
53+
fail-fast: false
54+
matrix:
55+
version:
56+
- 'nightly'
57+
os:
58+
- ubuntu-latest
59+
- macOS-latest
60+
- windows-latest
61+
arch:
62+
- x64
63+
steps:
64+
- uses: actions/checkout@v4
65+
- uses: julia-actions/setup-julia@v2
66+
with:
67+
version: ${{ matrix.version }}
68+
arch: ${{ matrix.arch }}
69+
- uses: julia-actions/cache@v2
70+
- uses: julia-actions/julia-buildpkg@latest
71+
- uses: julia-actions/julia-runtest@latest

Project.toml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
name = "OptimKit"
22
uuid = "77e91f04-9b3b-57a6-a776-40b61faaebe0"
33
authors = ["Jutho Haegeman"]
4-
version = "0.3.2"
4+
version = "0.4.0"
55

66
[deps]
77
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
88
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
9+
ScopedValues = "7e506255-f358-4e82-b7e4-beb19740aa63"
910

1011
[compat]
11-
julia = "1"
12+
Aqua = "0.8"
13+
LinearAlgebra = "1"
14+
Printf = "1"
15+
Random = "1"
16+
ScopedValues = "1"
17+
Test = "1"
18+
julia = "1.8"
1219

1320
[extras]
14-
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
21+
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
1522
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
23+
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1624

1725
[targets]
18-
test = ["Test", "Random"]
26+
test = ["Test", "Random", "Aqua"]

src/OptimKit.jl

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,44 @@
11
module OptimKit
22

3-
import LinearAlgebra
3+
using LinearAlgebra: LinearAlgebra
44
using Printf
5+
using ScopedValues
6+
using Base: @kwdef
7+
8+
# Default values for the keyword arguments using ScopedValues
9+
const LS_MAXITER = ScopedValue(10)
10+
const LS_MAXFG = ScopedValue(20)
11+
const LS_VERBOSITY = ScopedValue(1)
12+
13+
const GRADTOL = ScopedValue(1e-8)
14+
const MAXITER = ScopedValue(1_000_000)
15+
const VERBOSITY = ScopedValue(1)
516

617
_retract(x, d, α) = (x + α * d, d)
718
_inner(x, v1, v2) = v1 === v2 ? LinearAlgebra.norm(v1)^2 : LinearAlgebra.dot(v1, v2)
819
_transport!(v, xold, d, α, xnew) = v
920
_scale!(v, α) = LinearAlgebra.rmul!(v, α)
1021
_add!(vdst, vsrc, α) = LinearAlgebra.axpy!(α, vsrc, vdst)
1122

12-
_precondition(x, g) = g
23+
_precondition(x, g) = deepcopy(g)
1324
_finalize!(x, f, g, numiter) = x, f, g
1425

15-
abstract type OptimizationAlgorithm
16-
end
26+
abstract type OptimizationAlgorithm end
1727

1828
const _xlast = Ref{Any}()
1929
const _glast = Ref{Any}()
2030
const _dlast = Ref{Any}()
2131

2232
"""
23-
x, f, g, numfg, history =
24-
optimize(fg, x, algorithm; retract = _retract, inner = _inner,
25-
transport! = _transport!, scale! = _scale!, add! = _add!,
26-
isometrictransport = (transport! == _transport! && inner == _inner))
33+
optimize(fg, x, alg;
34+
precondition=_precondition,
35+
(finalize!)=_finalize!,
36+
hasconverged=DefaultHasConverged(alg.gradtol),
37+
shouldstop=DefaultShouldStop(alg.maxiter),
38+
retract=_retract, inner=_inner, (transport!)=_transport!,
39+
(scale!)=_scale!, (add!)=_add!,
40+
isometrictransport=(transport! == _transport! && inner == _inner))
41+
-> x, f, g, numfg, history
2742
2843
Optimize (minimize) the objective function returned as the first value of `fg`, where the
2944
second value contains the gradient, starting from a point `x` and using the algorithm
@@ -33,11 +48,44 @@ Returns the final point `x`, the coresponding function value `f` and gradient `g
3348
total number of calls to `fg`, and the history of the gradient norm across the different
3449
iterations.
3550
36-
Check the README of this package for further details on creating an algorithm instance, as well as for the meaning of the keyword arguments and their default values.
51+
The algorithm is run until either `hasconverged(x, f, g, norm(g))` returns `true` or
52+
`shouldstop(x, f, g, numfg, numiter, time)` returns `true`. The latter case happening before
53+
the former is considered to be a failure to converge, and a warning is issued.
54+
55+
The keyword arguments are:
56+
57+
- `precondition::Function`: A function that takes the current point `x` and the gradient `g`
58+
and returns a preconditioned gradient. By default, the identity is used.
59+
- `finalize!::Function`: A function that takes the final point `x`, the function value `f`,
60+
the gradient `g`, and the iteration number, and returns a possibly modified values for
61+
`x`, `f` and `g`. By default, the identity is used.
62+
It is the user's responsibility to ensure that the modified values do not lead to
63+
inconsistencies within the optimization algorithm.
64+
- `hasconverged::Function`: A function that takes the current point `x`, the function value `f`,
65+
the gradient `g`, and the norm of the gradient, and returns a boolean indicating whether
66+
the optimization has converged. By default, the norm of the gradient is compared to the
67+
tolerance `gradtol` as encoded in the algorithm instance.
68+
- `shouldstop::Function`: A function that takes the current point `x`, the function value `f`,
69+
the gradient `g`, the number of calls to `fg`, the iteration number, and the time spent
70+
so far, and returns a boolean indicating whether the optimization should stop. By default,
71+
the number of iterations is compared to the maximum number of iterations as encoded in the
72+
algorithm instance.
73+
74+
Check the README of this package for further details on creating an algorithm instance,
75+
as well as for the meaning of the remaining keyword arguments and their default values.
76+
77+
!!! Warning
78+
79+
The default values of `hasconverged` and `shouldstop` are provided to ensure continuity
80+
with the previous versions of this package. However, this behaviour might change in the
81+
future.
82+
83+
Also see [`GradientDescent`](@ref), [`ConjugateGradient`](@ref), [`LBFGS`](@ref).
3784
"""
3885
function optimize end
3986

4087
include("linesearches.jl")
88+
include("terminate.jl")
4189
include("gd.jl")
4290
include("cg.jl")
4391
include("lbfgs.jl")
@@ -61,23 +109,23 @@ Test the compatibility between the computation of the gradient, the retraction a
61109
62110
It is up to the user to check that the values in `dfs1` and `dfs2` match up to expected precision, by inspecting the numerical values or plotting them. If these values don't match, the linesearch in `optimize` cannot be expected to work.
63111
"""
64-
function optimtest(fg, x, d = fg(x)[2]; alpha = -0.1:0.001:0.1, retract = _retract, inner = _inner)
112+
function optimtest(fg, x, d=fg(x)[2]; alpha=-0.1:0.001:0.1, retract=_retract, inner=_inner)
65113
f0, g0 = fg(x)
66-
fs = Vector{typeof(f0)}(undef, length(alpha)-1)
114+
fs = Vector{typeof(f0)}(undef, length(alpha) - 1)
67115
dfs1 = similar(fs, length(alpha) - 1)
68116
dfs2 = similar(fs, length(alpha) - 1)
69-
for i = 1:length(alpha) - 1
117+
for i in 1:(length(alpha) - 1)
70118
a1 = alpha[i]
71-
a2 = alpha[i+1]
119+
a2 = alpha[i + 1]
72120
f1, = fg(retract(x, d, a1)[1])
73121
f2, = fg(retract(x, d, a2)[1])
74-
dfs1[i] = (f2-f1)/(a2 - a1)
75-
xmid, dmid = retract(x, d, (a1+a2)/2)
122+
dfs1[i] = (f2 - f1) / (a2 - a1)
123+
xmid, dmid = retract(x, d, (a1 + a2) / 2)
76124
fmid, gmid = fg(xmid)
77125
fs[i] = fmid
78126
dfs2[i] = inner(xmid, dmid, gmid)
79127
end
80-
alphas = collect((alpha[2:end] + alpha[1:end-1])/2)
128+
alphas = collect((alpha[2:end] + alpha[1:(end - 1)]) / 2)
81129
return alphas, fs, dfs1, dfs2
82130
end
83131

0 commit comments

Comments
 (0)