Skip to content

Commit 5bce169

Browse files
committed
Merge branch 'master' into dev-0.6
# Conflicts: # Changelog.md # Project.toml # src/plans/proximal_gradient_plan.jl
2 parents f48fa02 + 19fc393 commit 5bce169

8 files changed

Lines changed: 80 additions & 38 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
- uses: julia-actions/julia-buildpkg@v1
2222
- uses: julia-actions/julia-runtest@v1
2323
- uses: julia-actions/julia-processcoverage@v1
24-
- uses: codecov/codecov-action@v6
24+
- uses: codecov/codecov-action@v7
2525
with:
2626
token: ${{ secrets.CODECOV_TOKEN }}
2727
file: ./lcov.info

.github/workflows/spell_check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ jobs:
1010
- name: Checkout Actions Repository
1111
uses: actions/checkout@v6
1212
- name: Check spelling
13-
uses: crate-ci/typos@v1.46.0
13+
uses: crate-ci/typos@v1.47.0

Changelog.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ This is a breaking change since the JuMP extension is dropped.
3434
* the plotting functions to `Asymptote`. They can now be found in the separate package [`ManifoldAsymptote.jl`]()
3535
this way, `Manopt.jl` has less dependencies, especially the color and colorschemes dependencies are dropped
3636

37+
## [0.5.39] June 3, 2026
38+
39+
### Fixed
40+
41+
* a small bug where debug statements were printed even though they should not be due to `DebugEvery` and unified warnings to print independent of `DebugEvery`. (#609)
42+
43+
## [0.5.38] May 19, 2026
44+
45+
### Changed
46+
47+
* the `:convex` backtracking strategy for `proximal_gradient_method` now entails a slightly different condition whenever the upper bound on the sectional curvature of the manifold, input via the `k_max` kwarg, is positive. This comes with a "tolerance" type parameter, `δ`, which must be positive.
48+
3749
## [0.5.37] May 5, 2026
3850

3951
### Changed
@@ -148,7 +160,6 @@ Moved the documentation glossaries to using the new [Glossaries.jl](https://gith
148160
This also allows finally to run single test files without installing all packages manually, but instead just switching to and instantiating the test environment. (#550)
149161
* for compatibility, state also `[source]` entries consistently in the sub `Project.toml` files. (#550)
150162

151-
152163
## [0.5.28] November 17, 2025
153164

154165
### Changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "Manopt"
22
uuid = "0fc0a36d-df90-57f3-8f93-d78a9fc72bb5"
3-
version = "0.5.37"
3+
version = "0.6.0"
44

55
[workspace]
66
projects = ["test", "docs", "tutorials"]

src/plans/debug.jl

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ function (d::DebugEntryChange)(
636636
end
637637
x = get_storage(d.storage, d.field)
638638
v = d.distance(p, st, getproperty(st, d.field), x)
639-
Printf.format(d.io, Printf.Format(d.format), v)
639+
(k > 0) && Printf.format(d.io, Printf.Format(d.format), v)
640640
d.storage(p, st, k)
641641
return nothing
642642
end
@@ -992,15 +992,15 @@ end
992992
function (d::DebugTime)(::AbstractManoptProblem, ::AbstractManoptSolverState, k)
993993
if k == 0 || d.last_time == Nanosecond(0) # init
994994
d.last_time = Nanosecond(time_ns())
995-
else
995+
elseif k > 0
996996
t = time_ns()
997997
p = Nanosecond(t) - d.last_time
998998
Printf.format(
999999
d.io, Printf.Format(d.format), canonicalize(round(p, d.time_accuracy))
10001000
)
1001-
end
1002-
if d.mode == :iterative
1003-
d.last_time = Nanosecond(time_ns())
1001+
if d.mode == :iterative
1002+
d.last_time = Nanosecond(time_ns())
1003+
end
10041004
end
10051005
return nothing
10061006
end
@@ -1068,7 +1068,6 @@ end
10681068
function (d::DebugWarnIfCostIncreases)(
10691069
p::AbstractManoptProblem, st::AbstractManoptSolverState, k::Int
10701070
)
1071-
(k < 0) && (return nothing)
10721071
if d.status !== :No
10731072
cost = get_cost(p, get_iterate(st))
10741073
if cost > d.old_cost + d.tol
@@ -1300,7 +1299,7 @@ end
13001299
function (d::DebugWarnIfStepsizeCollapsed)(
13011300
amp::AbstractManoptProblem, st::AbstractManoptSolverState, k::Int
13021301
)
1303-
(k < 1) && (return nothing)
1302+
(k == 0) && (return nothing)
13041303
if d.status !== :No
13051304
if get_last_stepsize(amp, st, k) d.stop_when_stepsize_less
13061305
@warn "Backtracking stopped because the stepsize fell below the threshold $(d.stop_when_stepsize_less)."

src/plans/proximal_gradient_plan.jl

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,8 @@ A functor for backtracking line search in proximal gradient methods.
405405
* `sufficient_decrease=0.5`: sufficient decrease parameter
406406
* `contraction_factor=0.5`: step size reduction factor
407407
* `strategy=:nonconvex`: backtracking strategy, either `:convex` or `:nonconvex`
408+
* `k_max=0.0`: an upper bound to the sectional curvatures of the manifold, only for the `:convex` strategy
409+
* `δ=1e-2`: parameter for backtracking in case `k_max > 0`, only for the `:convex` strategy
408410
"""
409411
mutable struct ProximalGradientMethodBacktrackingStepsize{P, T} <: Stepsize
410412
initial_stepsize::T
@@ -415,33 +417,39 @@ mutable struct ProximalGradientMethodBacktrackingStepsize{P, T} <: Stepsize
415417
last_stepsize::T
416418
stop_when_stepsize_less::T
417419
warm_start_factor::T
418-
function ProximalGradientMethodBacktrackingStepsize(;
419-
initial_stepsize::T, sufficient_decrease::T, contraction_factor::T, strategy::Symbol,
420-
candidate_point::P, last_stepsize::T, stop_when_stepsize_less::T, warm_start_factor::T
421-
) where {P, T <: Real}
422-
return new{P, T}(
423-
initial_stepsize, sufficient_decrease, contraction_factor, strategy,
424-
candidate_point, last_stepsize, stop_when_stepsize_less, warm_start_factor
425-
)
426-
end
420+
k_max::T
421+
δ::T
422+
427423
function ProximalGradientMethodBacktrackingStepsize(
428424
M::AbstractManifold;
429-
p = rand(M),
430-
initial_stepsize::Real = 1.0, sufficient_decrease::Real = 0.5, contraction_factor::Real = 0.5,
431-
strategy::Symbol = :nonconvex, stop_when_stepsize_less::Real = 1.0e-8, warm_start_factor::Real = 1.0,
425+
initial_stepsize::T = 1.0, sufficient_decrease::T = 0.5, contraction_factor::T = 0.5,
426+
strategy::Symbol = :nonconvex, stop_when_stepsize_less::T = 1.0e-8, warm_start_factor::T = 1.0,
427+
k_max::T = 0.0,
428+
δ::T = 1.0e-2,
429+
) where {T}
430+
0 < sufficient_decrease < 1 ||
431+
throw(DomainError(sufficient_decrease, "sufficient_decrease must be in (0, 1)"))
432+
0 < contraction_factor < 1 ||
433+
throw(DomainError(contraction_factor, "contraction_factor must be in (0, 1)"))
434+
initial_stepsize > 0 ||
435+
throw(DomainError(initial_stepsize, "initial_stepsize must be positive"))
436+
strategy in [:convex, :nonconvex] ||
437+
throw(DomainError(strategy, "strategy must be either :convex or :nonconvex"))
438+
stop_when_stepsize_less > 0 || throw(
439+
DomainError(
440+
stop_when_stepsize_less, "stop_when_stepsize_less must be positive"
441+
),
432442
)
433-
T = promote_type(typeof(initial_stepsize), typeof(sufficient_decrease), typeof(contraction_factor), typeof(stop_when_stepsize_less), typeof(warm_start_factor))
434-
0 < sufficient_decrease < 1 || throw(DomainError(sufficient_decrease, "sufficient_decrease ($(sufficient_decrease)) must be in (0, 1)"))
435-
0 < contraction_factor < 1 || throw(DomainError(contraction_factor, "contraction_factor ($(contraction_factor)) must be in (0, 1)"))
436-
initial_stepsize > 0 || throw(DomainError(initial_stepsize, "initial_stepsize ($(initial_stepsize)) must be positive"))
437-
strategy in [:convex, :nonconvex] || throw(DomainError(strategy, "strategy (:$(strategy)) must be either :convex or :nonconvex"))
438-
stop_when_stepsize_less > 0 || throw(DomainError(stop_when_stepsize_less, "stop_when_stepsize_less ($(stop_when_stepsize_less)) must be positive"))
439-
warm_start_factor > 0 || throw(DomainError(warm_start_factor, "warm_start_factor ($(warm_start_factor)) must be positive"))
440-
return ProximalGradientMethodBacktrackingStepsize(;
441-
initial_stepsize = convert(T, initial_stepsize), sufficient_decrease = convert(T, sufficient_decrease),
442-
contraction_factor = convert(T, contraction_factor), strategy = strategy, candidate_point = p,
443-
last_stepsize = convert(T, initial_stepsize), stop_when_stepsize_less = convert(T, stop_when_stepsize_less),
444-
warm_start_factor = convert(T, warm_start_factor),
443+
warm_start_factor > 0 ||
444+
throw(DomainError(warm_start_factor, "warm_start_factor must be positive"))
445+
446+
(k_max > 0 && δ 0) &&
447+
throw(DomainError(δ, "the tolerance parameter δ must be positive if k_max > 0"))
448+
449+
p = rand(M)
450+
return new{typeof(p), T}(
451+
initial_stepsize, sufficient_decrease, contraction_factor, strategy, p,
452+
initial_stepsize, stop_when_stepsize_less, warm_start_factor, k_max, δ
445453
)
446454
end
447455
end
@@ -455,6 +463,7 @@ function Base.show(io::IO, pgb::ProximalGradientMethodBacktrackingStepsize)
455463
print(io, ", strategy = :$(pgb.strategy), candidate_point = ", pgb.candidate_point)
456464
print(io, ", last_stepsize = ", pgb.last_stepsize, ", stop_when_stepsize_less = ", pgb.stop_when_stepsize_less)
457465
print(io, ", warm_start_factor = ", pgb.warm_start_factor)
466+
print(io, ", k_max = ", pgb.k_max, ", δ = ", pgb.δ)
458467
return print(io, ")")
459468
end
460469
function status_summary(pgb::ProximalGradientMethodBacktrackingStepsize; context::Symbol = :default)
@@ -521,8 +530,11 @@ function (s::ProximalGradientMethodBacktrackingStepsize)(
521530
g_p = get_cost_smooth(M, objective, p)
522531
g_q = get_cost_smooth(M, objective, candidate_point)
523532

533+
534+
ζ_δ = s.k_max zero(eltype(s.k_max)) ? one(eltype(s.k_max)) : π / (2 + s.δ) * cot/ (2 + s.δ))
535+
524536
# Convex descent condition
525-
if g_q <= g_p + inner(M, p, X, log_p_q) + (1 / 2λ) * squared_distance
537+
if g_q <= g_p + inner(M, p, X, log_p_q) + (ζ_δ / 2λ) * squared_distance
526538
s.last_stepsize = λ
527539
return λ
528540
end
@@ -551,7 +563,7 @@ where ``G_{λ}(p) = (1/λ) * $(_tex(:log))_p(T_{λ}(p))`` is the gradient mappin
551563
For the convex case, the condition is:
552564
553565
```math
554-
g(T_{λ}(p)) ≤ g(p) + ⟨$(_tex(:grad)) g(p), $(_tex(:log))_p T_{λ}(p)⟩ + $(_tex(:frac, "1", "")) $(_math(:distance))^2(p, T_{λ}(p))
566+
g(T_{λ}(p)) ≤ g(p) + ⟨$(_tex(:grad)) g(p), $(_tex(:log))_p T_{λ}(p)⟩ + $(_tex(:frac, "ζ_δ", "")) $(_math(:distance))^2(p, T_{λ}(p))
555567
```
556568
557569
Returns a stepsize `λ` that satisfies the specified condition.

src/solvers/convex_bundle_method.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ function status_summary(cbms::ConvexBundleMethodState; context::Symbol = :defaul
329329
* bundle cap size: $(cbms.bundle_cap)
330330
* current bundle size: $(length(cbms.bundle))
331331
* curvature upper bound: $(cbms.k_max)
332+
* curvature lower bound: $(cbms.k_min)
332333
* descent test parameter: $(cbms.m)
333334
* diameter: $(cbms.diameter)
334335
* inverse retraction: $(cbms.inverse_retraction_method)

test/solvers/test_proximal_gradient_method.jl

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,20 @@ using Manopt, Manifolds, Test, ManifoldDiff
2121
# Trigger manually
2222
sc1.at_iteration = 2
2323
@test length(get_reason(sc1)) > 0
24+
25+
pgms.last_stepsize = 1.0
26+
g(M, q) = distance(M, q, p)^2
27+
grad_g(M, q) = -2 * log(M, q, p)
28+
h(M, q) = distance(M, q, p)
29+
prox_h(M, λ, q) = ManifoldDiff.prox_distance(M, λ, p, q, 1)
30+
f(M, q) = g(M, q) + h(M, q)
31+
ob = ManifoldProximalGradientObjective(f, g, grad_g, prox_h)
32+
mp = DefaultManoptProblem(M, ob)
33+
34+
@test sc1.last_change < sc1.threshold
35+
@test sc1.at_iteration == 2
36+
@test sc1(mp, pgms, 2) == true
37+
pgms.last_stepsize = 0.0
2438
end
2539
@testset "Proximal Gradient Backtracking" begin
2640
pgb = Manopt.ProximalGradientMethodBacktrackingStepsize(M)
@@ -78,6 +92,9 @@ using Manopt, Manifolds, Test, ManifoldDiff
7892
@test_throws DomainError Manopt.ProximalGradientMethodBacktrackingStepsize(
7993
M; warm_start_factor = -1.0
8094
)
95+
@test_throws DomainError Manopt.ProximalGradientMethodBacktrackingStepsize(
96+
M; initial_stepsize = 1.0, strategy = :convex, stop_when_stepsize_less = 2.0, k_max = 1.0, δ = -1.0
97+
)
8198

8299
@testset "Backtracking Warnings" begin
83100
dw1 = DebugWarnIfStepsizeCollapsed(:Once)
@@ -205,7 +222,9 @@ using Manopt, Manifolds, Test, ManifoldDiff
205222
inverse_retraction_method = ProjectionInverseRetraction(),
206223
stepsize = ProximalGradientMethodBacktracking(;
207224
initial_stepsize = 1.0,
208-
strategy = :convex
225+
strategy = :convex,
226+
k_max = 1.0,
227+
δ = 1.0e-2,
209228
),
210229
return_state = true
211230
)

0 commit comments

Comments
 (0)