diff --git a/Changelog.md b/Changelog.md index 61774e1efb..7f71db88da 100644 --- a/Changelog.md +++ b/Changelog.md @@ -6,6 +6,16 @@ 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.6.6] August 25, 2026 + +### Fixed + +* `augmented_Lagrangian_method` set the penalty parameter `ρ` of the sub problem's cost to the + constant `1/3` instead of the current `alms.ρ`, while its gradient did receive `alms.ρ`. (#637) +* `set_parameter!` for `:μ` and `:λ` tied the dual variable's type to the type parameter of + `AbstractConstrainedFunction`. It hence never applied to `AugmentedLagrangianCost` and, falling + back to a no-op, silently left its `μ` and `λ` at their initial values. (#637) + ## [0.6.5] August 22, 2026 ### Added @@ -1467,4 +1477,4 @@ This is the first version with an actual Changelog entry First public release, first registered and [announced version](https://discourse.julialang.org/t/ann-manopt-jl/24906) of `Manopt.jl`. This version still also included what is now `Manifolds.jl`. -The first commit that started `Manopt.jl` was done on November 25, 2016. \ No newline at end of file +The first commit that started `Manopt.jl` was done on November 25, 2016. diff --git a/Project.toml b/Project.toml index aebbeb8b0c..11f0d70373 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "Manopt" uuid = "0fc0a36d-df90-57f3-8f93-d78a9fc72bb5" -version = "0.6.5" +version = "0.6.6" [workspace] projects = ["test", "docs", "tutorials"] diff --git a/src/base/function/constrained.jl b/src/base/function/constrained.jl index cec00bf082..87038675b5 100644 --- a/src/base/function/constrained.jl +++ b/src/base/function/constrained.jl @@ -8,12 +8,12 @@ constraints of type `T`. """ abstract type AbstractConstrainedFunction{T} <: AbstractManifoldFunction end -function set_parameter!(acf::AbstractConstrainedFunction{T}, ::Val{:μ}, μ::T) where {T} +function set_parameter!(acf::AbstractConstrainedFunction, ::Val{:μ}, μ) acf.μ = μ return acf end get_parameter(acf::AbstractConstrainedFunction, ::Val{:μ}) = acf.μ -function set_parameter!(acf::AbstractConstrainedFunction{T}, ::Val{:λ}, λ::T) where {T} +function set_parameter!(acf::AbstractConstrainedFunction, ::Val{:λ}, λ) acf.λ = λ return acf end diff --git a/src/solvers/augmented_Lagrangian_method.jl b/src/solvers/augmented_Lagrangian_method.jl index c94615c4b2..8bcfdc127f 100644 --- a/src/solvers/augmented_Lagrangian_method.jl +++ b/src/solvers/augmented_Lagrangian_method.jl @@ -480,7 +480,7 @@ end function step_solver!(mp::AbstractManoptProblem, alms::AugmentedLagrangianMethodState, iter) M = get_manifold(mp) # use subsolver to minimize the augmented Lagrangian - set_parameter!(alms.sub_problem, Val(:Objective), Val(:Cost), Val(:ρ), 1 / 3) + set_parameter!(alms.sub_problem, Val(:Objective), Val(:Cost), Val(:ρ), alms.ρ) set_parameter!(alms.sub_problem, Val(:Objective), Val(:Cost), Val(:μ), alms.μ) set_parameter!(alms.sub_problem, Val(:Objective), Val(:Cost), Val(:λ), alms.λ) set_parameter!(alms.sub_problem, Val(:Objective), Val(:Gradient), Val(:ρ), alms.ρ) diff --git a/test/solvers/test_augmented_lagrangian.jl b/test/solvers/test_augmented_lagrangian.jl index ba10a4cd4e..a64eb5b07f 100644 --- a/test/solvers/test_augmented_lagrangian.jl +++ b/test/solvers/test_augmented_lagrangian.jl @@ -79,4 +79,28 @@ using LinearAlgebra: I, tr @test q isa Real @test f(M, q) < f(M, 4) end + @testset "Sub problem penalty parameters" begin + # The cost and the gradient of the sub problem have to be set up with the same penalty + # parameter ρ. If they disagree, the sub solver minimises a different function than the + # one it computes gradients for, and since ρ grows geometrically the two drift apart. + M = Euclidean(2) + f(M, p) = p[1] + grad_f(M, p) = [1.0, 0.0] + h(M, p) = [p[1]^2 + p[2]^2 - 1.0] + grad_h(M, p) = [[2 * p[1], 2 * p[2]]] + s = augmented_Lagrangian_method( + M, f, grad_f, [0.5, 0.5]; + h = h, grad_h = grad_h, equality_constraints = 1, + stopping_criterion = StopAfterIteration(20), return_state = true, + ) + sub_objective = Manopt.get_objective(s.sub_problem) + sub_cost = Manopt.get_cost_function(sub_objective) + # the gradient is wrapped for the in-place dispatch, so it needs one more unwrap + sub_gradient = sub_objective.functions.gradient.f + @test sub_cost.ρ == sub_gradient.ρ + @test sub_cost.μ == sub_gradient.μ + @test sub_cost.λ == sub_gradient.λ + # with matching parameters the solver reaches the minimiser (-1, 0) + @test distance(M, get_solver_result(s), [-1.0, 0.0]) < 1.0e-3 + end end