From 7166f1d5a0b84d4db4abc9d2a07b2da4c693d936 Mon Sep 17 00:00:00 2001 From: fpacaud Date: Thu, 25 Jul 2024 14:48:01 +0200 Subject: [PATCH 1/6] add ScaledModel --- src/NLPModelsModifiers.jl | 1 + src/scaled-model.jl | 236 ++++++++++++++++++++++++++++++++++++++ test/nlp/scaled-model.jl | 38 ++++++ test/runtests.jl | 1 + 4 files changed, 276 insertions(+) create mode 100644 src/scaled-model.jl create mode 100644 test/nlp/scaled-model.jl diff --git a/src/NLPModelsModifiers.jl b/src/NLPModelsModifiers.jl index 2a81e16b..a713f7c0 100644 --- a/src/NLPModelsModifiers.jl +++ b/src/NLPModelsModifiers.jl @@ -18,6 +18,7 @@ include("feasibility-form-nls.jl") include("feasibility-residual.jl") include("quasi-newton.jl") include("slack-model.jl") +include("scaled-model.jl") include("model-interaction.jl") end # module diff --git a/src/scaled-model.jl b/src/scaled-model.jl new file mode 100644 index 00000000..e2aafdaf --- /dev/null +++ b/src/scaled-model.jl @@ -0,0 +1,236 @@ +export ScaledModel + +struct IpoptScaling{T} + max_gradient::T +end + +function _set_constraints_scaling!(cons, Ji, Jj, Jx, max_gradient) + # Return a vector storing at index i norm(∇cᵢ, Inf) + for (i, j, x) in zip(Ji, Jj, Jx) + cons[i] = max(cons[i], abs(x)) + end + # Compute scaling as min(1, max_gradient / norm(∇cᵢ, Inf) ) + for i in eachindex(cons) + cons[i] = min(1.0, max_gradient / cons[i]) + end +end + +function _set_jacobian_scaling!(Jx, Ji, Jj, cons) + k = 0 + for (i, j) in zip(Ji, Jj) + Jx[k += 1] = cons[i] + end +end + +function scale_model!(scaling::IpoptScaling{T}, nlp) where T + n, m = NLPModels.get_nvar(nlp), NLPModels.get_ncon(nlp) + nnzj = NLPModels.get_nnzj(nlp) + x0 = NLPModels.get_x0(nlp) + g = NLPModels.grad(nlp, x0) + scaling_obj = min(one(T), scaling.max_gradient / norm(g, Inf)) + scaling_cons = similar(x0, m) + scaling_jac = similar(x0, nnzj) + fill!(scaling_cons, zero(T)) + Ji, Jj = NLPModels.jac_structure(nlp) + NLPModels.jac_coord!(nlp, x0, scaling_jac) + _set_constraints_scaling!(scaling_cons, Ji, Jj, scaling_jac, scaling.max_gradient) + _set_jacobian_scaling!(scaling_jac, Ji, Jj, scaling_cons) + return (scaling_obj, scaling_cons, scaling_jac) +end + +@doc raw""" + ScaledModel + +Scale the nonlinear program +```math +\begin{aligned} + min_x \quad & f(x)\\ +\mathrm{s.t.} \quad &  c♭ ≤ c(x) ≤ c♯ \\ + & x ≥ 0 +\end{aligned} +``` +as +```math +\begin{aligned} + min_x \quad & σf . f(x)\\ +\mathrm{s.t.} \quad &  σc . c♭ ≤ σc . c(x) ≤ σc . c♯ \\ + & x ≥ 0 +\end{aligned} +``` +with ``σf`` a scalar defined as +``` +σf = min(1, max_gradient / norm(g0, Inf)) + +``` +and ``σc`` a vector whose size is equal to the number of constraints in the model. +For ``i=1, ..., m``, +``` +σc[i] = min(1, max_gradient / norm(J0[i, :], Inf)) + +``` + +The vector ``g0 = ∇f(x0)`` and the matrix ``J0 = ∇c(x0)`` are resp. +the gradient and the Jacobian evaluated at the initial point ``x0``. + +""" +struct ScaledModel{T, S, M} <: NLPModels.AbstractNLPModel{T, S} + nlp::M + meta::NLPModels.NLPModelMeta{T, S} + counters::NLPModels.Counters + scaling_obj::T + scaling_cons::S # [size m] + scaling_jac::S # [size nnzj] + buffer_cons::S # [size m] +end + +NLPModels.show_header(io::IO, nlp::ScaledModel) = + println(io, "ScaledModel - Model with scaled objective and constraints") + + +function ScaledModel( + nlp::NLPModels.AbstractNLPModel{T, S}; + scaling=IpoptScaling(T(100)), +) where {T, S} + n, m = NLPModels.get_nvar(nlp), NLPModels.get_ncon(nlp) + x0 = NLPModels.get_x0(nlp) + buffer_cons = S(undef, m) + scaling_obj, scaling_cons, scaling_jac = scale_model!(scaling, nlp) + meta = NLPModels.NLPModelMeta( + n; + lvar=NLPModels.get_lvar(nlp), + uvar=NLPModels.get_uvar(nlp), + x0=NLPModels.get_x0(nlp), + y0 = NLPModels.get_y0(nlp) .* scaling_cons, + nnzj=NLPModels.get_nnzj(nlp), + nnzh=NLPModels.get_nnzh(nlp), + ncon=m, + lcon=NLPModels.get_lcon(nlp) .* scaling_cons, + ucon=NLPModels.get_ucon(nlp) .* scaling_cons, + minimize=true, + ) + + return ScaledModel( + nlp, + meta, + NLPModels.Counters(), + scaling_obj, + scaling_cons, + scaling_jac, + buffer_cons, + ) +end + +function NLPModels.obj(nlp::ScaledModel{T, S}, x::AbstractVector) where {T, S <: AbstractVector{T}} + @lencheck nlp.meta.nvar x + return nlp.scaling_obj * NLPModels.obj(nlp.nlp, x) +end + +function NLPModels.cons!(nlp::ScaledModel, x::AbstractVector, c::AbstractVector) + @lencheck nlp.meta.nvar x + @lencheck nlp.meta.ncon c + NLPModels.cons!(nlp.nlp, x, c) + c .*= nlp.scaling_cons + return c +end + +function NLPModels.grad!(nlp::ScaledModel, x::AbstractVector, g::AbstractVector) + @lencheck nlp.meta.nvar x g + NLPModels.grad!(nlp.nlp, x, g) + g .*= nlp.scaling_obj + return g +end + +function NLPModels.jprod!(nlp::ScaledModel, x::AbstractVector, v::AbstractVector, Jv::AbstractVector) + @lencheck nlp.meta.nvar x v + @lencheck nlp.meta.ncon Jv + NLPModels.jprod!(nlp.nlp, x, v, Jv) + Jv .*= nlp.scaling_cons + return Jv +end + +function NLPModels.jtprod!(nlp::ScaledModel, x::AbstractVector, v::AbstractVector, Jtv::AbstractVector) + @lencheck nlp.meta.nvar x Jtv + @lencheck nlp.meta.ncon v + v_scaled = nlp.buffer_cons + v_scaled .= v .* nlp.scaling_cons + NLPModels.jtprod!(nlp.nlp, x, v_scaled, Jtv) + return Jtv +end + +function NLPModels.jac_structure!(nlp::ScaledModel, jrows::AbstractVector, jcols::AbstractVector) + NLPModels.jac_structure!(nlp.nlp, jrows, jcols) + return jrows, jcols +end + +function NLPModels.jac_coord!(nlp::ScaledModel, x::AbstractVector, jac::AbstractVector) + NLPModels.jac_coord!(nlp.nlp, x, jac) + jac .*= nlp.scaling_jac + return jac +end + +function NLPModels.hess_structure!(nlp::ScaledModel, hrows::AbstractVector, hcols::AbstractVector) + @lencheck nlp.meta.nnzh hrows hcols + NLPModels.hess_structure!(nlp.nlp, hrows, hcols) + return hrows, hcols +end + +function NLPModels.hess_coord!( + nlp::ScaledModel, + x::AbstractVector, + vals::AbstractVector; + obj_weight::Real=one(eltype(x)), +) + @lencheck nlp.meta.nvar x + @lencheck nlp.meta.nnzh vals + σ = obj_weight * nlp.scaling_obj + NLPModels.hess_coord!(nlp.nlp, x, vals; obj_weight=σ) + return vals +end + +function NLPModels.hess_coord!( + nlp::ScaledModel, + x::AbstractVector, + y::AbstractVector, + vals::AbstractVector; + obj_weight::Real=one(eltype(x)), +) + @lencheck nlp.meta.nvar x + @lencheck nlp.meta.ncon y + @lencheck nlp.meta.nnzh vals + y_scaled = nlp.buffer_cons + y_scaled .= y .* nlp.scaling_cons + σ = obj_weight * nlp.scaling_obj + NLPModels.hess_coord!(nlp.nlp, x, y_scaled, vals; obj_weight=σ) + return vals +end + +function NLPModels.hprod!( + nlp::ScaledModel, + x::AbstractVector, + v::AbstractVector, + hv::AbstractVector; + obj_weight::Real = one(eltype(x)), +) + @lencheck nlp.meta.nvar x v hv + σ = obj_weight * nlp.scaling_obj + NLPModels.hprod!(nlp.nlp, x, v, hv; obj_weight = σ) + return hv +end + +function NLPModels.hprod!( + nlp::ScaledModel, + x::AbstractVector, + y::AbstractVector, + v::AbstractVector, + hv::AbstractVector; + obj_weight::Real = one(eltype(x)), +) + @lencheck nlp.meta.nvar x v hv + @lencheck nlp.meta.ncon y + y_scaled = nlp.buffer_cons + y_scaled .= y .* nlp.scaling_cons + σ = obj_weight * nlp.scaling_obj + NLPModels.hprod!(nlp.nlp, x, y, v, hv; obj_weight = σ) + return hv +end + diff --git a/test/nlp/scaled-model.jl b/test/nlp/scaled-model.jl new file mode 100644 index 00000000..604fc7e1 --- /dev/null +++ b/test/nlp/scaled-model.jl @@ -0,0 +1,38 @@ +@testset "ScaledModel NLP tests" begin + @testset "API" for T in [Float64, Float32], M in [NLPModelMeta, SimpleNLPMeta] + nlp = ScaledModel(SimpleNLPModel(T, M)) + σ_obj, σ_cons = nlp.scaling_obj, nlp.scaling_cons + + f(x) = σ_obj * (x[1] - 2)^2 + (x[2] - 1)^2 + ∇f(x) = [σ_obj * 2 * (x[1] - 2); σ_obj * 2 * (x[2] - 1)] + H(x) = T[(σ_obj * 2.0) 0; 0 (σ_obj * 2.0)] + c(x) = [σ_cons[1] * (x[1] - 2x[2] + 1); σ_cons[2] * (-x[1]^2 / 4 - x[2]^2 + 1)] + J(x) = [σ_cons[1] -2.0*σ_cons[1]; (-0.5 *σ_cons[1] * x[1]) (-2.0*σ_cons[2] * x[2])] + H(x, y) = H(x) + σ_cons[2] * y[2] * T[-0.5 0; 0 -2.0] + + n = nlp.meta.nvar + m = nlp.meta.ncon + @test nlp.meta.x0 == T[2; 2] + + x = randn(T, n) + y = randn(T, m) + v = randn(T, n) + w = randn(T, m) + Jv = zeros(T, m) + Jtw = zeros(T, n) + Hv = zeros(T, n) + Hvals = zeros(T, nlp.meta.nnzh) + + # Basic methods + @test obj(nlp, x) ≈ f(x) + @test grad(nlp, x) ≈ ∇f(x) + @test hess(nlp, x) ≈ H(x) + @test hprod(nlp, x, v) ≈ H(x) * v + @test cons(nlp, x) ≈ c(x) + @test jac(nlp, x) ≈ J(x) + @test jprod(nlp, x, v) ≈ J(x) * v + @test jtprod(nlp, x, w) ≈ J(x)' * w + @test hess(nlp, x, y) ≈ H(x, y) + @test hprod(nlp, x, y, v) ≈ H(x, y) * v + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 46967957..e0fd9b96 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,6 +4,7 @@ using LinearOperators, NLPModels, NLPModelsModifiers include("nlp/simple-model.jl") include("nlp/quasi-newton.jl") include("nlp/slack-model.jl") +include("nlp/scaled-model.jl") include("nls/simple-model.jl") include("nls/feasibility-form-nls.jl") From 87436bbdfd923de0c42dcfa7ddb52d125568ab8c Mon Sep 17 00:00:00 2001 From: fpacaud Date: Fri, 26 Jul 2024 16:01:33 +0200 Subject: [PATCH 2/6] address PR's reviews --- src/scaled-model.jl | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/src/scaled-model.jl b/src/scaled-model.jl index e2aafdaf..bd3b1c54 100644 --- a/src/scaled-model.jl +++ b/src/scaled-model.jl @@ -1,6 +1,6 @@ export ScaledModel -struct IpoptScaling{T} +struct ConservativeScaling{T} max_gradient::T end @@ -22,7 +22,7 @@ function _set_jacobian_scaling!(Jx, Ji, Jj, cons) end end -function scale_model!(scaling::IpoptScaling{T}, nlp) where T +function scale_model!(scaling::ConservativeScaling{T}, nlp) where T n, m = NLPModels.get_nvar(nlp), NLPModels.get_ncon(nlp) nnzj = NLPModels.get_nnzj(nlp) x0 = NLPModels.get_x0(nlp) @@ -44,23 +44,22 @@ end Scale the nonlinear program ```math \begin{aligned} - min_x \quad & f(x)\\ -\mathrm{s.t.} \quad &  c♭ ≤ c(x) ≤ c♯ \\ - & x ≥ 0 + min_x \quad & f(x)\\ +\mathrm{s.t.} \quad &  c_L ≤ c(x) ≤ c_U,\\ + & ℓ ≤ x ≥ u, \end{aligned} ``` as ```math \begin{aligned} - min_x \quad & σf . f(x)\\ -\mathrm{s.t.} \quad &  σc . c♭ ≤ σc . c(x) ≤ σc . c♯ \\ - & x ≥ 0 + min_x \quad & σf . f(x)\\ +\mathrm{s.t.} \quad &  σc . c_L ≤ σc . c(x) ≤ σc . c_U, \\ + & ℓ ≤ x ≥ u, \end{aligned} ``` -with ``σf`` a scalar defined as +with ``σf`` a positive scalar defined as ``` σf = min(1, max_gradient / norm(g0, Inf)) - ``` and ``σc`` a vector whose size is equal to the number of constraints in the model. For ``i=1, ..., m``, @@ -71,6 +70,7 @@ For ``i=1, ..., m``, The vector ``g0 = ∇f(x0)`` and the matrix ``J0 = ∇c(x0)`` are resp. the gradient and the Jacobian evaluated at the initial point ``x0``. +By default, the threshold parameter `max_gradient` is set to 100.0. """ struct ScaledModel{T, S, M} <: NLPModels.AbstractNLPModel{T, S} @@ -83,13 +83,9 @@ struct ScaledModel{T, S, M} <: NLPModels.AbstractNLPModel{T, S} buffer_cons::S # [size m] end -NLPModels.show_header(io::IO, nlp::ScaledModel) = - println(io, "ScaledModel - Model with scaled objective and constraints") - - function ScaledModel( nlp::NLPModels.AbstractNLPModel{T, S}; - scaling=IpoptScaling(T(100)), + scaling=ConservativeScaling(T(100)), ) where {T, S} n, m = NLPModels.get_nvar(nlp), NLPModels.get_ncon(nlp) x0 = NLPModels.get_x0(nlp) @@ -106,7 +102,8 @@ function ScaledModel( ncon=m, lcon=NLPModels.get_lcon(nlp) .* scaling_cons, ucon=NLPModels.get_ucon(nlp) .* scaling_cons, - minimize=true, + minimize=nlp.meta.minimize, + name="scaled-" * nlp.meta.name, ) return ScaledModel( From 94bd6dd9fa5d394e8b183f496ed2cc4a8178b760 Mon Sep 17 00:00:00 2001 From: fpacaud Date: Wed, 22 Jul 2026 14:12:27 +0200 Subject: [PATCH 3/6] add support for linear and nonlinear API --- src/scaled-model.jl | 130 +++++++++++++++++++++++++++++++++------ test/nlp/simple-model.jl | 32 ++++++++++ 2 files changed, 144 insertions(+), 18 deletions(-) diff --git a/src/scaled-model.jl b/src/scaled-model.jl index bd3b1c54..243f9dcf 100644 --- a/src/scaled-model.jl +++ b/src/scaled-model.jl @@ -78,9 +78,13 @@ struct ScaledModel{T, S, M} <: NLPModels.AbstractNLPModel{T, S} meta::NLPModels.NLPModelMeta{T, S} counters::NLPModels.Counters scaling_obj::T - scaling_cons::S # [size m] - scaling_jac::S # [size nnzj] - buffer_cons::S # [size m] + scaling_cons::S # [size m] + scaling_cons_lin::S # [size nlin] + scaling_cons_nln::S # [size nnln] + scaling_jac::S # [size nnzj] + scaling_jac_lin::S # [size lin_nnzj] + scaling_jac_nln::S # [size nln_nnzj] + buffer_cons::S # [size m] end function ScaledModel( @@ -90,19 +94,32 @@ function ScaledModel( n, m = NLPModels.get_nvar(nlp), NLPModels.get_ncon(nlp) x0 = NLPModels.get_x0(nlp) buffer_cons = S(undef, m) + + # Compute scaling for the problem as a whole. scaling_obj, scaling_cons, scaling_jac = scale_model!(scaling, nlp) + + # Get scaling for linear and nonlinear constraints. + scaling_cons_lin = scaling_cons[nlp.meta.lin] + scaling_cons_nln = scaling_cons[nlp.meta.nln] + scaling_jac_lin = zeros(T, nlp.meta.lin_nnzj) + Jlin_i, Jlin_j = NLPModels.jac_lin_structure(nlp) + k = 0 + for (i, j) in zip(Jlin_i, Jlin_j) + scaling_jac_lin[k += 1] = scaling_cons_lin[i] + end + scaling_jac_nln = zeros(T, nlp.meta.nln_nnzj) + Jnln_i, Jnln_j = NLPModels.jac_nln_structure(nlp) + k = 0 + for (i, j) in zip(Jnln_i, Jnln_j) + scaling_jac_lin[k += 1] = scaling_cons_nln[i] + end + + # Copy metadata from original problem, with some modifications. meta = NLPModels.NLPModelMeta( - n; - lvar=NLPModels.get_lvar(nlp), - uvar=NLPModels.get_uvar(nlp), - x0=NLPModels.get_x0(nlp), + nlp.meta; y0 = NLPModels.get_y0(nlp) .* scaling_cons, - nnzj=NLPModels.get_nnzj(nlp), - nnzh=NLPModels.get_nnzh(nlp), - ncon=m, - lcon=NLPModels.get_lcon(nlp) .* scaling_cons, - ucon=NLPModels.get_ucon(nlp) .* scaling_cons, - minimize=nlp.meta.minimize, + lcon = NLPModels.get_lcon(nlp) .* scaling_cons, + ucon = NLPModels.get_ucon(nlp) .* scaling_cons, name="scaled-" * nlp.meta.name, ) @@ -112,7 +129,11 @@ function ScaledModel( NLPModels.Counters(), scaling_obj, scaling_cons, + scaling_cons_lin, + scaling_cons_nln, scaling_jac, + scaling_jac_lin, + scaling_jac_nln, buffer_cons, ) end @@ -122,6 +143,13 @@ function NLPModels.obj(nlp::ScaledModel{T, S}, x::AbstractVector) where {T, S <: return nlp.scaling_obj * NLPModels.obj(nlp.nlp, x) end +function NLPModels.grad!(nlp::ScaledModel, x::AbstractVector, g::AbstractVector) + @lencheck nlp.meta.nvar x g + NLPModels.grad!(nlp.nlp, x, g) + g .*= nlp.scaling_obj + return g +end + function NLPModels.cons!(nlp::ScaledModel, x::AbstractVector, c::AbstractVector) @lencheck nlp.meta.nvar x @lencheck nlp.meta.ncon c @@ -130,11 +158,20 @@ function NLPModels.cons!(nlp::ScaledModel, x::AbstractVector, c::AbstractVector) return c end -function NLPModels.grad!(nlp::ScaledModel, x::AbstractVector, g::AbstractVector) - @lencheck nlp.meta.nvar x g - NLPModels.grad!(nlp.nlp, x, g) - g .*= nlp.scaling_obj - return g +function NLPModels.cons_lin!(nlp::ScaledModel, x::AbstractVector, c::AbstractVector) + @lencheck nlp.meta.nvar x + @lencheck nlp.meta.nlin c + NLPModels.cons_lin!(nlp.nlp, x, c) + c .*= nlp.scaling_cons_lin + return c +end + +function NLPModels.cons_nln!(nlp::ScaledModel, x::AbstractVector, c::AbstractVector) + @lencheck nlp.meta.nvar x + @lencheck nlp.meta.nnln c + NLPModels.cons_nln!(nlp.nlp, x, c) + c .*= nlp.scaling_cons_nln + return c end function NLPModels.jprod!(nlp::ScaledModel, x::AbstractVector, v::AbstractVector, Jv::AbstractVector) @@ -145,6 +182,22 @@ function NLPModels.jprod!(nlp::ScaledModel, x::AbstractVector, v::AbstractVector return Jv end +function NLPModels.jprod_lin!(nlp::ScaledModel, x::AbstractVector, v::AbstractVector, Jv::AbstractVector) + @lencheck nlp.meta.nvar x v + @lencheck nlp.meta.nlin Jv + NLPModels.jprod_lin!(nlp.nlp, x, v, Jv) + Jv .*= nlp.scaling_cons_lin + return Jv +end + +function NLPModels.jprod_nln!(nlp::ScaledModel, x::AbstractVector, v::AbstractVector, Jv::AbstractVector) + @lencheck nlp.meta.nvar x v + @lencheck nlp.meta.nnln Jv + NLPModels.jprod_lin!(nlp.nlp, x, v, Jv) + Jv .*= nlp.scaling_cons_nln + return Jv +end + function NLPModels.jtprod!(nlp::ScaledModel, x::AbstractVector, v::AbstractVector, Jtv::AbstractVector) @lencheck nlp.meta.nvar x Jtv @lencheck nlp.meta.ncon v @@ -154,17 +207,58 @@ function NLPModels.jtprod!(nlp::ScaledModel, x::AbstractVector, v::AbstractVecto return Jtv end +function NLPModels.jtprod_lin!(nlp::ScaledModel, x::AbstractVector, v::AbstractVector, Jtv::AbstractVector) + @lencheck nlp.meta.nvar x Jtv + @lencheck nlp.meta.nlin v + v_scaled = view(nlp.buffer_cons, 1:nlp.meta.nlin) + v_scaled .= v .* nlp.scaling_cons_lin + NLPModels.jtprod_lin!(nlp.nlp, x, v_scaled, Jtv) + return Jtv +end + +function NLPModels.jtprod_nln!(nlp::ScaledModel, x::AbstractVector, v::AbstractVector, Jtv::AbstractVector) + @lencheck nlp.meta.nvar x Jtv + @lencheck nlp.meta.nnln v + v_scaled = view(nlp.buffer_cons, 1:nlp.meta.nnln) + v_scaled .= v .* nlp.scaling_cons_nln + NLPModels.jtprod_nln!(nlp.nlp, x, v_scaled, Jtv) + return Jtv +end + function NLPModels.jac_structure!(nlp::ScaledModel, jrows::AbstractVector, jcols::AbstractVector) + @lencheck nlp.meta.nnzj jrows jcols NLPModels.jac_structure!(nlp.nlp, jrows, jcols) return jrows, jcols end +function NLPModels.jac_lin_structure!(nlp::ScaledModel, jrows::AbstractVector, jcols::AbstractVector) + NLPModels.jac_lin_structure!(nlp.nlp, jrows, jcols) + return jrows, jcols +end + +function NLPModels.jac_nln_structure!(nlp::ScaledModel, jrows::AbstractVector, jcols::AbstractVector) + NLPModels.jac_nln_structure!(nlp.nlp, jrows, jcols) + return jrows, jcols +end + function NLPModels.jac_coord!(nlp::ScaledModel, x::AbstractVector, jac::AbstractVector) NLPModels.jac_coord!(nlp.nlp, x, jac) jac .*= nlp.scaling_jac return jac end +function NLPModels.jac_lin_coord!(nlp::ScaledModel, x::AbstractVector, jac::AbstractVector) + NLPModels.jac_lin_coord!(nlp.nlp, x, jac) + jac .*= nlp.scaling_jac_lin + return jac +end + +function NLPModels.jac_nln_coord!(nlp::ScaledModel, x::AbstractVector, jac::AbstractVector) + NLPModels.jac_nln_coord!(nlp.nlp, x, jac) + jac .*= nlp.scaling_jac_nln + return jac +end + function NLPModels.hess_structure!(nlp::ScaledModel, hrows::AbstractVector, hcols::AbstractVector) @lencheck nlp.meta.nnzh hrows hcols NLPModels.hess_structure!(nlp.nlp, hrows, hcols) diff --git a/test/nlp/simple-model.jl b/test/nlp/simple-model.jl index 56ab5030..ea0caa52 100644 --- a/test/nlp/simple-model.jl +++ b/test/nlp/simple-model.jl @@ -60,6 +60,18 @@ mutable struct SimpleNLPMeta{T, S} <: AbstractNLPModelMeta{T, S} minimize::Bool islp::Bool name::String + variable_bounds_analysis::Bool + constraint_bounds_analysis::Bool + + sparse_jacobian::Bool + sparse_hessian::Bool + + grad_available::Bool + jac_available::Bool + hess_available::Bool + jprod_available::Bool + jtprod_available::Bool + hprod_available::Bool function SimpleNLPMeta{T, S}( nvar::Int; x0::S = fill!(S(undef, nvar), zero(T)), @@ -81,6 +93,16 @@ mutable struct SimpleNLPMeta{T, S} <: AbstractNLPModelMeta{T, S} minimize = true, islp = false, name = "Generic", + variable_bounds_analysis::Bool = true, + constraint_bounds_analysis::Bool = true, + sparse_jacobian::Bool = true, + sparse_hessian::Bool = true, + grad_available::Bool = true, + jac_available::Bool = (ncon > 0), + hess_available::Bool = true, + jprod_available::Bool = (ncon > 0), + jtprod_available::Bool = (ncon > 0), + hprod_available::Bool = true, ) where {T, S} if (nvar < 1) || (ncon < 0) error("Nonsensical dimensions") @@ -147,6 +169,16 @@ mutable struct SimpleNLPMeta{T, S} <: AbstractNLPModelMeta{T, S} minimize, islp, name, + variable_bounds_analysis, + constraint_bounds_analysis, + sparse_jacobian, + sparse_hessian, + grad_available, + jac_available, + hess_available, + jprod_available, + jtprod_available, + hprod_available, ) end end From 6b8c2937e2b4454aedbb0e57cc5ad9ca526f5dbc Mon Sep 17 00:00:00 2001 From: fpacaud Date: Thu, 23 Jul 2026 10:08:45 +0200 Subject: [PATCH 4/6] address PR's comments --- src/scaled-model.jl | 18 +++++++++++++----- test/nlp/scaled-model.jl | 16 +++++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/scaled-model.jl b/src/scaled-model.jl index 243f9dcf..7be4ec11 100644 --- a/src/scaled-model.jl +++ b/src/scaled-model.jl @@ -5,7 +5,7 @@ struct ConservativeScaling{T} end function _set_constraints_scaling!(cons, Ji, Jj, Jx, max_gradient) - # Return a vector storing at index i norm(∇cᵢ, Inf) + # Store norm(∇cᵢ, Inf) at index i of vector cons for (i, j, x) in zip(Ji, Jj, Jx) cons[i] = max(cons[i], abs(x)) end @@ -15,10 +15,10 @@ function _set_constraints_scaling!(cons, Ji, Jj, Jx, max_gradient) end end -function _set_jacobian_scaling!(Jx, Ji, Jj, cons) +function _set_jacobian_scaling!(Jx, Ji, Jj, scaling) k = 0 for (i, j) in zip(Ji, Jj) - Jx[k += 1] = cons[i] + Jx[k += 1] = scaling[i] end end @@ -72,6 +72,14 @@ The vector ``g0 = ∇f(x0)`` and the matrix ``J0 = ∇c(x0)`` are resp. the gradient and the Jacobian evaluated at the initial point ``x0``. By default, the threshold parameter `max_gradient` is set to 100.0. +The method has been originally proposed in Ipopt [1]. + +## Reference + +[1] Wächter, A., & Biegler, L. T. (2006). +On the implementation of an interior-point filter line-search algorithm for large-scale nonlinear programming. +Mathematical programming, 106(1), 25-57. + """ struct ScaledModel{T, S, M} <: NLPModels.AbstractNLPModel{T, S} nlp::M @@ -88,8 +96,8 @@ struct ScaledModel{T, S, M} <: NLPModels.AbstractNLPModel{T, S} end function ScaledModel( - nlp::NLPModels.AbstractNLPModel{T, S}; - scaling=ConservativeScaling(T(100)), + nlp::NLPModels.AbstractNLPModel{T, S}; + scaling=ConservativeScaling(T(100)), ) where {T, S} n, m = NLPModels.get_nvar(nlp), NLPModels.get_ncon(nlp) x0 = NLPModels.get_x0(nlp) diff --git a/test/nlp/scaled-model.jl b/test/nlp/scaled-model.jl index 604fc7e1..418516ca 100644 --- a/test/nlp/scaled-model.jl +++ b/test/nlp/scaled-model.jl @@ -1,14 +1,16 @@ @testset "ScaledModel NLP tests" begin @testset "API" for T in [Float64, Float32], M in [NLPModelMeta, SimpleNLPMeta] - nlp = ScaledModel(SimpleNLPModel(T, M)) + original_nlp = SimpleNLPModel(T, M) + nlp = ScaledModel(original_nlp) σ_obj, σ_cons = nlp.scaling_obj, nlp.scaling_cons - f(x) = σ_obj * (x[1] - 2)^2 + (x[2] - 1)^2 - ∇f(x) = [σ_obj * 2 * (x[1] - 2); σ_obj * 2 * (x[2] - 1)] - H(x) = T[(σ_obj * 2.0) 0; 0 (σ_obj * 2.0)] - c(x) = [σ_cons[1] * (x[1] - 2x[2] + 1); σ_cons[2] * (-x[1]^2 / 4 - x[2]^2 + 1)] - J(x) = [σ_cons[1] -2.0*σ_cons[1]; (-0.5 *σ_cons[1] * x[1]) (-2.0*σ_cons[2] * x[2])] - H(x, y) = H(x) + σ_cons[2] * y[2] * T[-0.5 0; 0 -2.0] + # Hand-code the scaled problem from the original NLP. + f(x) = σ_obj * NLPModels.obj(original_nlp, x) + ∇f(x) = σ_obj .* NLPModels.grad(original_nlp, x) + H(x) = σ_obj .* NLPModels.hess(original_nlp, x) + c(x) = σ_cons .* NLPModels.cons(original_nlp, x) + J(x) = Diagonal(σ_cons) * NLPModels.jac(original_nlp, x) + H(x, y) = NLPModels.hess(original_nlp, x, σ_cons .* y; obj_weight=σ_obj) n = nlp.meta.nvar m = nlp.meta.ncon From 9f3543c926462177c442754c6cd26e8d9aae9798 Mon Sep 17 00:00:00 2001 From: fpacaud Date: Thu, 23 Jul 2026 10:15:56 +0200 Subject: [PATCH 5/6] rename cons as scaling --- src/scaled-model.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/scaled-model.jl b/src/scaled-model.jl index 7be4ec11..323c841a 100644 --- a/src/scaled-model.jl +++ b/src/scaled-model.jl @@ -4,14 +4,14 @@ struct ConservativeScaling{T} max_gradient::T end -function _set_constraints_scaling!(cons, Ji, Jj, Jx, max_gradient) - # Store norm(∇cᵢ, Inf) at index i of vector cons +function _set_constraints_scaling!(scaling, Ji, Jj, Jx, max_gradient) + # Store norm(∇cᵢ, Inf) at index i of vector scaling for (i, j, x) in zip(Ji, Jj, Jx) - cons[i] = max(cons[i], abs(x)) + scaling[i] = max(scaling[i], abs(x)) end # Compute scaling as min(1, max_gradient / norm(∇cᵢ, Inf) ) - for i in eachindex(cons) - cons[i] = min(1.0, max_gradient / cons[i]) + for i in eachindex(scaling) + scaling[i] = min(1.0, max_gradient / scaling[i]) end end From fadaf721c53daf23831440646d5ca9e2d6f70da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Pacaud?= Date: Thu, 30 Jul 2026 14:15:13 +0200 Subject: [PATCH 6/6] Update src/scaled-model.jl Co-authored-by: Maxence Gollier <134112149+MaxenceGollier@users.noreply.github.com> --- src/scaled-model.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scaled-model.jl b/src/scaled-model.jl index 323c841a..8e794ec9 100644 --- a/src/scaled-model.jl +++ b/src/scaled-model.jl @@ -119,7 +119,7 @@ function ScaledModel( Jnln_i, Jnln_j = NLPModels.jac_nln_structure(nlp) k = 0 for (i, j) in zip(Jnln_i, Jnln_j) - scaling_jac_lin[k += 1] = scaling_cons_nln[i] + scaling_jac_nln[k += 1] = scaling_cons_nln[i] end # Copy metadata from original problem, with some modifications.