diff --git a/Changelog.md b/Changelog.md index cb1c516140..a8e9cc4e6d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,12 @@ The file was started with Version `0.4`. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.5.37] April 25, 2026 + +### Added + +* Customizable subsolver direction update for augmented Lagrangian method. + ## [0.5.36] April 24, 2026 ### Added diff --git a/Project.toml b/Project.toml index d711cac8dc..b2c5a1b4a1 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Manopt" uuid = "0fc0a36d-df90-57f3-8f93-d78a9fc72bb5" -version = "0.5.36" +version = "0.5.37" authors = [{family-names = "Bergmann", given-names = "Ronny", alias = "kellertuer", city = "Trondheim", affiliation = "Norwegian University of Science and Technology", country = "NO", email = "manopt@ronnybergmann.net", orcid = "https://orcid.org/0000-0001-8342-7218", website = "https://ronnybergmann.net"}] [workspace] diff --git a/src/solvers/augmented_Lagrangian_method.jl b/src/solvers/augmented_Lagrangian_method.jl index 47b10f5d96..7a2c401416 100644 --- a/src/solvers/augmented_Lagrangian_method.jl +++ b/src/solvers/augmented_Lagrangian_method.jl @@ -325,6 +325,8 @@ $(_kwargs(:sub_problem; default = "`[`DefaultManoptProblem`](@ref)`(M, sub_objec $(_kwargs(:sub_state; default = "`[`QuasiNewtonState`](@ref)` ")), where more precisely as quasi newton method, the [`QuasiNewtonLimitedMemoryDirectionUpdate`](@ref) with [`InverseBFGS`](@ref) is used. * `sub_stopping_criterion::StoppingCriterion=`[`StopAfterIteration`](@ref)`(300)`$(_sc(:Any))[`StopWhenGradientNormLess`](@ref)`(ϵ)`$(_sc(:Any))[`StopWhenStepsizeLess`](@ref)`(1e-8)`, +* `sub_direction_update::AbstractQuasiNewtonDirectionUpdate=`[`QuasiNewtonLimitedMemoryDirectionUpdate`](@ref)`(M, copy(M, p), InverseBFGS(), min(manifold_dimension(M), 30))`: + the direction update rule for the default sub solver, a quasi-Newton method with limited memory inverse BFGS. This keyword is used within the `sub_state`, so it has no effect, if you set the sub solver manually. For the `range`s of the constraints' gradient, other power manifold tangent space representations, @@ -452,6 +454,9 @@ function augmented_Lagrangian_method!( sub_cost = AugmentedLagrangianCost(cmo, ρ, μ, λ), sub_grad = AugmentedLagrangianGrad(cmo, ρ, μ, λ), sub_kwargs = (;), + sub_direction_update::AbstractQuasiNewtonDirectionUpdate = QuasiNewtonLimitedMemoryDirectionUpdate( + M, copy(M, p), InverseBFGS(), min(manifold_dimension(M), 30) + ), sub_stopping_criterion::StoppingCriterion = StopAfterIteration(300) | StopWhenGradientNormLess(ϵ) | StopWhenStepsizeLess(1.0e-8), @@ -460,9 +465,7 @@ function augmented_Lagrangian_method!( M; p = copy(M, p), X = zero_vector(M, p), - direction_update = QuasiNewtonLimitedMemoryDirectionUpdate( - M, copy(M, p), InverseBFGS(), min(manifold_dimension(M), 30) - ), + direction_update = sub_direction_update, stopping_criterion = sub_stopping_criterion, stepsize = default_stepsize(M, QuasiNewtonState), sub_kwargs..., diff --git a/test/solvers/test_augmented_lagrangian.jl b/test/solvers/test_augmented_lagrangian.jl index 977adbafe4..adbcc004b7 100644 --- a/test/solvers/test_augmented_lagrangian.jl +++ b/test/solvers/test_augmented_lagrangian.jl @@ -68,4 +68,32 @@ using LinearAlgebra: I, tr @test q isa Real @test f(M, q) < f(M, 4) end + + @testset "Customizable subsolver direction update" begin + d = 20 + M = Sphere(d - 1) + S = [ones(4)..., zeros(d - 4)...] + v0 = project(M, S) + Z = v0 * v0' + f(M, p) = -tr(transpose(p) * Z * p) / 2 + grad_f(M, p) = project(M, p, -transpose.(Z) * p / 2 - Z * p / 2) + g(M, p) = -p # in other words p ≥ 0 + mI = -Matrix{Float64}(I, d, d) + grad_g(M, p) = [project(M, p, mI[:, i]) for i in 1:d] + p0 = project(M, ones(d)) + s = augmented_Lagrangian_method( + M, + f, + grad_f, + p0; + g = g, + grad_g = grad_g, + sub_direction_update = QuasiNewtonLimitedMemoryDirectionUpdate( + M, copy(M, p0), InverseBFGS(), 5 + ), + stopping_criterion = StopAfterIteration(20), + return_state = true, + ) + @test s.sub_state.direction_update.memory_s.capacity == 5 + end end