From 7540372f42c29db85a53c92dbf67a37b40f61eee Mon Sep 17 00:00:00 2001 From: Maxence Gollier Date: Mon, 26 Jan 2026 11:27:16 -0500 Subject: [PATCH] add compute_obj and compute_grad options --- src/R2DH.jl | 8 ++++++-- src/R2N.jl | 8 ++++++-- src/R2_alg.jl | 8 ++++++-- src/TRDH_alg.jl | 12 ++++++++---- src/TR_alg.jl | 8 ++++++-- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/src/R2DH.jl b/src/R2DH.jl index 5c2f8f1e..ba773a0b 100644 --- a/src/R2DH.jl +++ b/src/R2DH.jl @@ -127,6 +127,8 @@ or - `γ::T = T(3)`: regularization parameter multiplier, σ := σ/γ when the iteration is very successful and σ := σγ when the iteration is unsuccessful. - `θ::T = 1/(1 + eps(T)^(1 / 5))`: is the model decrease fraction with respect to the decrease of the Cauchy model. - `m_monotone::Int = 6`: monotoneness parameter. By default, R2DH is non-monotone but the monotone variant can be used with `m_monotone = 1` +- `compute_obj::Bool = true`: (advanced) whether `f(x₀)` should be computed or not. If set to false, then the value is retrieved from `stats.solver_specific[:smooth_obj]`; +- `compute_grad::Bool = true`: (advanced) whether `∇f(x₀)` should be computed or not. If set to false, then the value is retrieved from `solver.∇fk`; The algorithm stops either when `√(ξₖ/νₖ) < atol + rtol*√(ξ₀/ν₀) ` or `ξₖ < 0` and `√(-ξₖ/νₖ) < neg_tol` where ξₖ := f(xₖ) + h(xₖ) - φ(sₖ; xₖ) - ψ(sₖ; xₖ), and √(ξₖ/νₖ) is a stationarity measure. @@ -228,6 +230,8 @@ function SolverCore.solve!( η2::T = T(0.9), γ::T = T(3), θ::T = 1/(1 + eps(T)^(1 / 5)), + compute_obj::Bool = true, + compute_grad::Bool = true, ) where {T, V} reset!(stats) @@ -293,8 +297,8 @@ function SolverCore.solve!( local ξ::T local ρk::T = zero(T) - fk = obj(nlp, xk) - grad!(nlp, xk, ∇fk) + fk = compute_obj ? obj(nlp, xk) : stats.solver_specific[:smooth_obj] + compute_grad && grad!(nlp, xk, ∇fk) ∇fk⁻ .= ∇fk spectral_test = isa(D, SpectralGradient) diff --git a/src/R2N.jl b/src/R2N.jl index 592a5fc9..13f75d4c 100644 --- a/src/R2N.jl +++ b/src/R2N.jl @@ -141,6 +141,8 @@ For advanced usage, first define a solver "R2NSolver" to preallocate the memory - `θ::T = 1/(1 + eps(T)^(1 / 5))`: is the model decrease fraction with respect to the decrease of the Cauchy model; - `opnorm_maxiter::Int = 5`: how many iterations of the power method to use to compute the operator norm of Bₖ. If a negative number is provided, then Arpack is used instead; - `m_monotone::Int = 1`: monotonicity parameter. By default, R2N is monotone but the non-monotone variant will be used if `m_monotone > 1`; +- `compute_obj::Bool = true`: (advanced) whether `f(x₀)` should be computed or not. If set to false, then the value is retrieved from `stats.solver_specific[:smooth_obj]`; +- `compute_grad::Bool = true`: (advanced) whether `∇f(x₀)` should be computed or not. If set to false, then the value is retrieved from `solver.∇fk`; - `sub_kwargs::NamedTuple = NamedTuple()`: a named tuple containing the keyword arguments to be sent to the subsolver. The solver will fail if invalid keyword arguments are provided to the subsolver. For example, if the subsolver is `R2Solver`, you can pass `sub_kwargs = (max_iter = 100, σmin = 1e-6,)`. The algorithm stops either when `√(ξₖ/νₖ) < atol + rtol*√(ξ₀/ν₀) ` or `ξₖ < 0` and `√(-ξₖ/νₖ) < neg_tol` where ξₖ := f(xₖ) + h(xₖ) - φ(sₖ; xₖ) - ψ(sₖ; xₖ), and √(ξₖ/νₖ) is a stationarity measure. @@ -218,6 +220,8 @@ function SolverCore.solve!( β::T = 1 / eps(T), θ::T = 1/(1 + eps(T)^(1 / 5)), opnorm_maxiter::Int = 5, + compute_obj::Bool = true, + compute_grad::Bool = true, sub_kwargs::NamedTuple = NamedTuple(), ) where {T, V, G} reset!(stats) @@ -285,8 +289,8 @@ function SolverCore.solve!( local ρk::T = zero(T) local prox_evals::Int = 0 - fk = obj(nlp, xk) - grad!(nlp, xk, ∇fk) + fk = compute_obj ? obj(nlp, xk) : stats.solver_specific[:smooth_obj] + compute_grad && grad!(nlp, xk, ∇fk) qn_copy!(nlp, solver, stats) quasiNewtTest = isa(nlp, QuasiNewtonModel) diff --git a/src/R2_alg.jl b/src/R2_alg.jl index 05109399..d3f56ff0 100644 --- a/src/R2_alg.jl +++ b/src/R2_alg.jl @@ -153,6 +153,8 @@ For advanced usage, first define a solver "R2Solver" to preallocate the memory u - `η2::T = T(0.9)`: successful iteration threshold; - `ν::T = eps(T)^(1 / 5)`: multiplicative inverse of the regularization parameter: ν = 1/σ; - `γ::T = T(3)`: regularization parameter multiplier, σ := σ/γ when the iteration is very successful and σ := σγ when the iteration is unsuccessful. +- `compute_obj::Bool = true`: (advanced) whether `f(x₀)` should be computed or not. If set to false, then the value is retrieved from `stats.solver_specific[:smooth_obj]`; +- `compute_grad::Bool = true`: (advanced) whether `∇f(x₀)` should be computed or not. If set to false, then the value is retrieved from `solver.∇fk`; The algorithm stops either when `√(ξₖ/νₖ) < atol + rtol*√(ξ₀/ν₀) ` or `ξₖ < 0` and `√(-ξₖ/νₖ) < neg_tol` where ξₖ := f(xₖ) + h(xₖ) - φ(sₖ; xₖ) - ψ(sₖ; xₖ), and √(ξₖ/νₖ) is a stationarity measure. @@ -323,6 +325,8 @@ function SolverCore.solve!( η2::T = T(0.9), ν::T = eps(T)^(1 / 5), γ::T = T(3), + compute_obj::Bool = true, + compute_grad::Bool = true, ) where {T, V} reset!(stats) @@ -386,8 +390,8 @@ function SolverCore.solve!( ν = 1 / σk sqrt_ξ_νInv = one(T) - fk = obj(nlp, xk) - grad!(nlp, xk, ∇fk) + fk = compute_obj ? obj(nlp, xk) : stats.solver_specific[:smooth_obj] + compute_grad && grad!(nlp, xk, ∇fk) @. mν∇fk = -ν * ∇fk set_iter!(stats, 0) diff --git a/src/TRDH_alg.jl b/src/TRDH_alg.jl index e53ba5da..23e54653 100644 --- a/src/TRDH_alg.jl +++ b/src/TRDH_alg.jl @@ -135,8 +135,10 @@ For advanced usage, first define a solver "TRDHSolver" to preallocate the memory - `η2::T = T(0.9)`: very successful iteration threshold; - `γ::T = T(3)`: trust-region radius parameter multiplier. Must satisfy `γ > 1`. The trust-region radius is updated as Δ := Δ*γ when the iteration is very successful and Δ := Δ/γ when the iteration is unsuccessful; - `reduce_TR::Bool = true`: see explanation on the stopping criterion below; -- `χ::F = NormLinf(1)`: norm used to define the trust-region;` -- `D::L = nothing`: diagonal quasi-Newton approximation used for the model φ. If nothing is provided and `reg_nlp.model` is not a diagonal quasi-Newton approximation, a spectral gradient approximation is used.` +- `χ::F = NormLinf(1)`: norm used to define the trust-region; +- `D::L = nothing`: diagonal quasi-Newton approximation used for the model φ. If nothing is provided and `reg_nlp.model` is not a diagonal quasi-Newton approximation, a spectral gradient approximation is used; +- `compute_obj::Bool = true`: (advanced) whether `f(x₀)` should be computed or not. If set to false, then the value is retrieved from `stats.solver_specific[:smooth_obj]`; +- `compute_grad::Bool = true`: (advanced) whether `∇f(x₀)` should be computed or not. If set to false, then the value is retrieved from `solver.∇fk`; The algorithm stops either when `√(ξₖ/νₖ) < atol + rtol*√(ξ₀/ν₀) ` or `ξₖ < 0` and `√(-ξₖ/νₖ) < neg_tol` where ξₖ := f(xₖ) + h(xₖ) - φ(sₖ; xₖ) - ψ(sₖ; xₖ), and √(ξₖ/νₖ) is a stationarity measure. Alternatively, if `reduce_TR = true`, then ξₖ₁ := f(xₖ) + h(xₖ) - φ(sₖ₁; xₖ) - ψ(sₖ₁; xₖ) is used instead of ξₖ, where sₖ₁ is the Cauchy point. @@ -241,6 +243,8 @@ function SolverCore.solve!( η1::T = √√eps(T), η2::T = T(0.9), γ::T = T(3), + compute_obj::Bool = true, + compute_grad::Bool = true, ) where {T, G, V} reset!(stats) @@ -315,8 +319,8 @@ function SolverCore.solve!( α = 1 / eps(T) β = 1 / eps(T) - fk = obj(nlp, xk) - grad!(nlp, xk, ∇fk) + fk = compute_obj ? obj(nlp, xk) : stats.solver_specific[:smooth_obj] + compute_grad && grad!(nlp, xk, ∇fk) ∇fk⁻ .= ∇fk dk .= D.d diff --git a/src/TR_alg.jl b/src/TR_alg.jl index 3daddd2d..718e5626 100644 --- a/src/TR_alg.jl +++ b/src/TR_alg.jl @@ -141,6 +141,8 @@ For advanced usage, first define a solver "TRSolver" to preallocate the memory u - `opnorm_maxiter::Int = 5`: how many iterations of the power method to use to compute the operator norm of Bₖ. If a negative number is provided, then Arpack is used instead; - `χ::F = NormLinf(1)`: norm used to define the trust-region;` - `subsolver::S = R2Solver`: subsolver used to solve the subproblem that appears at each iteration. +- `compute_obj::Bool = true`: (advanced) whether `f(x₀)` should be computed or not. If set to false, then the value is retrieved from `stats.solver_specific[:smooth_obj]`; +- `compute_grad::Bool = true`: (advanced) whether `∇f(x₀)` should be computed or not. If set to false, then the value is retrieved from `solver.∇fk`; - `sub_kwargs::NamedTuple = NamedTuple()`: a named tuple containing the keyword arguments to be sent to the subsolver. The solver will fail if invalid keyword arguments are provided to the subsolver. For example, if the subsolver is `R2Solver`, you can pass `sub_kwargs = (max_iter = 100, σmin = 1e-6,)`. The algorithm stops either when `√(ξₖ/νₖ) < atol + rtol*√(ξ₀/ν₀) ` or `ξₖ < 0` and `√(-ξₖ/νₖ) < neg_tol` where ξₖ := f(xₖ) + h(xₖ) - φ(sₖ; xₖ) - ψ(sₖ; xₖ), and √(ξₖ/νₖ) is a stationarity measure. @@ -213,6 +215,8 @@ function SolverCore.solve!( γ::T = T(3), sub_kwargs::NamedTuple = NamedTuple(), opnorm_maxiter::Int = 5, + compute_obj::Bool = true, + compute_grad::Bool = true, ) where {T, G, V} reset!(stats) @@ -286,8 +290,8 @@ function SolverCore.solve!( α = 1 / eps(T) β = 1 / eps(T) - fk = obj(nlp, xk) - grad!(nlp, xk, ∇fk) + fk = compute_obj ? obj(nlp, xk) : stats.solver_specific[:smooth_obj] + compute_grad && grad!(nlp, xk, ∇fk) ∇fk⁻ .= ∇fk quasiNewtTest = isa(nlp, QuasiNewtonModel)