Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quadratic constraints #102

Closed
Closed
105 changes: 80 additions & 25 deletions src/moi_nlp_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mutable struct MathOptNLPModel <: AbstractNLPModel{Float64, Vector{Float64}}
meta::NLPModelMeta{Float64, Vector{Float64}}
eval::Union{MOI.AbstractNLPEvaluator, Nothing}
lincon::LinearConstraints
quadcon::QuadraticConstraints
obj::Objective
counters::Counters
end
Expand Down Expand Up @@ -33,19 +34,19 @@ function MathOptNLPModel(jmodel::JuMP.Model; hessian::Bool = true, name::String
(nnln == 0 ? 0 : sum(length(nl_con.hess_I) for nl_con in eval.constraints)) : 0

moimodel = backend(jmodel)
nlin, lincon, lin_lcon, lin_ucon = parser_MOI(moimodel)
nlin, lincon, lin_lcon, lin_ucon, quadcon, quad_lcon, quad_ucon = parser_MOI(moimodel, nvar)

if (eval ≠ nothing) && eval.has_nlobj
obj = Objective("NONLINEAR", 0.0, spzeros(Float64, nvar), COO(), 0)
else
obj = parser_objective_MOI(moimodel, nvar)
end

ncon = nlin + nnln
lcon = vcat(lin_lcon, nl_lcon)
ucon = vcat(lin_ucon, nl_ucon)
nnzj = lincon.nnzj + nl_nnzj
nnzh = obj.nnzh + nl_nnzh
ncon = nlin + quadcon.nquad + nnln
lcon = vcat(lin_lcon, quad_lcon, nl_lcon)
ucon = vcat(lin_ucon, quad_ucon, nl_ucon)
nnzj = lincon.nnzj + quadcon.nnzj + nl_nnzj
nnzh = obj.nnzh + quadcon.nnzh + nl_nnzh

meta = NLPModelMeta(
nvar,
Expand All @@ -60,13 +61,13 @@ function MathOptNLPModel(jmodel::JuMP.Model; hessian::Bool = true, name::String
nnzh = nnzh,
lin = collect(1:nlin),
lin_nnzj = lincon.nnzj,
nln_nnzj = nl_nnzj,
nln_nnzj = quadcon.nnzj + nl_nnzj,
minimize = objective_sense(jmodel) == MOI.MIN_SENSE,
islp = (obj.type == "LINEAR") && (nnln == 0),
islp = (obj.type == "LINEAR") && (nnln == 0) && (quadcon.nquad == 0),
name = name,
)

return MathOptNLPModel(meta, eval, lincon, obj, Counters())
return MathOptNLPModel(meta, eval, lincon, quadcon, obj, Counters())
end

function NLPModels.obj(nlp::MathOptNLPModel, x::AbstractVector)
Expand Down Expand Up @@ -115,7 +116,13 @@ end

function NLPModels.cons_nln!(nlp::MathOptNLPModel, x::AbstractVector, c::AbstractVector)
increment!(nlp, :neval_cons_nln)
MOI.eval_constraint(nlp.eval, c, x)
for i = 1:(nlp.quadcon.nquad)
qcon = nlp.quadcon[i]
c[i] = 0.5 * coo_sym_dot(qcon.hessian.rows, qcon.hessian.cols, qcon.hessian.vals, x, x) + dot(qcon.b, x)
end
if nlp.meta.nnln > nlp.quadcon.nquad
MOI.eval_constraint(nlp.eval, view(c, (nlp.quadcon.nquad + 1):(nlp.meta.nnln)), x)
end
return c
end

Expand All @@ -134,11 +141,16 @@ function NLPModels.jac_nln_structure!(
rows::AbstractVector{<:Integer},
cols::AbstractVector{<:Integer},
)
jac_struct = MOI.jacobian_structure(nlp.eval)
for index = 1:(nlp.meta.nln_nnzj)
row, col = jac_struct[index]
rows[index] = row
cols[index] = col
quad_nnzj, jrows, jcols = nlp.quadcon.nnzj, nlp.quadcon.jrows, nlp.quadcon.jcols
rows[1:quad_nnzj] .= jrows
cols[1:quad_nnzj] .= jcols
if nlp.meta.nnln > nlp.quadcon.nquad
jac_struct = MOI.jacobian_structure(nlp.eval)
for index = 1:(nlp.meta.nln_nnzj - quad_nnzj)
row, col = jac_struct[index]
rows[quad_nnzj + index] = row + nlp.quadcon.nquad
cols[quad_nnzj + index] = col
end
end
return rows, cols
end
Expand All @@ -151,7 +163,22 @@ end

function NLPModels.jac_nln_coord!(nlp::MathOptNLPModel, x::AbstractVector, vals::AbstractVector)
increment!(nlp, :neval_jac_nln)
MOI.eval_constraint_jacobian(nlp.eval, vals, x)
vals .= 0.0
k = 0
for i = 1:(nlp.quadcon.nquad)
qcon = nlp.quadcon[i]
for j=1:length(qcon.vec)
vals[k + j] = qcon.b[qcon.vec[j]]
end
nnzj = length(qcon.hessian.vals)
Copy link
Member Author

@amontoison amontoison May 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nnzj should be the number of nnz of Qᵢx + bᵢ (length(set)) and not Qᵢ.

for i=1:nnzj
vals[k + i] += qcon.hessian.vals[i] * x[qcon.hessian.cols[i]]
end
k += nnzj
end
if nlp.meta.nnln > nlp.quadcon.nquad
MOI.eval_constraint_jacobian(nlp.eval, view(vals, (nlp.quadcon.nnzj + 1):(nlp.meta.nln_nnzj)), x)
end
return vals
end

Expand Down Expand Up @@ -305,10 +332,16 @@ function NLPModels.hess_structure!(
cols[index] = nlp.obj.hessian.cols[index]
end
end
if (nlp.obj.type == "NONLINEAR") || (nlp.meta.nnln > 0)
if nlp.quadcon.nquad > 0
for (index, tuple) in enumerate(nlp.quadcon.set)
rows[nlp.obj.nnzh + index] = tuple[1]
cols[nlp.obj.nnzh + index] = tuple[2]
end
end
if (nlp.obj.type == "NONLINEAR") || (nlp.meta.nnln > nlp.quadcon.nquad)
hesslag_struct = MOI.hessian_lagrangian_structure(nlp.eval)
amontoison marked this conversation as resolved.
Show resolved Hide resolved
for index = (nlp.obj.nnzh + 1):(nlp.meta.nnzh)
shift_index = index - nlp.obj.nnzh
for index = (nlp.obj.nnzh + nlp.quadcon.nnzh + 1):(nlp.meta.nnzh)
shift_index = index - nlp.obj.nnzh - nlp.quadcon.nnzh
rows[index] = hesslag_struct[shift_index][1]
cols[index] = hesslag_struct[shift_index][2]
end
Expand All @@ -327,13 +360,23 @@ function NLPModels.hess_coord!(
if nlp.obj.type == "QUADRATIC"
vals[1:(nlp.obj.nnzh)] .= obj_weight .* nlp.obj.hessian.vals
end
if (nlp.obj.type == "NONLINEAR") || (nlp.meta.nnln > 0)
if nlp.quadcon.nquad > 0
quad_nnzh = nlp.quadcon.nnzh
k = 0
for i = 1:(nlp.quadcon.nquad)
qcon = nlp.quadcon[i]
nnzh = length(qcon.hessian.vals)
vals[(k + 1):(k + nnzh)] .= qcon.hessian.vals .* y[nlp.meta.nlin + i]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We want to avoid that with the hessian_quad function!
If we have 10 quadratic constraints Q_i with same structure, you will store 10 times the required number of nnz for nothing.

k += nnzh
end
end
if (nlp.obj.type == "NONLINEAR") || (nlp.meta.nnln > nlp.quadcon.nquad)
MOI.eval_hessian_lagrangian(
nlp.eval,
view(vals, (nlp.obj.nnzh + 1):(nlp.meta.nnzh)),
view(vals, (nlp.obj.nnzh + nlp.quadcon.nnzh + 1):(nlp.meta.nnzh)),
x,
obj_weight,
view(y, nlp.meta.nln),
view(y, (nlp.meta.nlin + nlp.quadcon.nquad + 1):(nlp.meta.ncon))
)
end
return vals
Expand All @@ -354,6 +397,7 @@ function NLPModels.hess_coord!(
vals[(nlp.obj.nnzh + 1):(nlp.meta.nnzh)] .= 0.0
end
if nlp.obj.type == "NONLINEAR"
vals .= 0.0
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MOI.eval_hessian_lagrangian doesn't overwrite vals?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@amontoison amontoison May 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If vals .= 0.0 is required, we should also add it in the other NLPModels.hess_coord! function.

MOI.eval_hessian_lagrangian(nlp.eval, vals, x, obj_weight, zeros(nlp.meta.nnln))
end

Expand All @@ -372,8 +416,18 @@ function NLPModels.hprod!(
if (nlp.obj.type == "LINEAR") && (nlp.meta.nnln == 0)
hv .= 0.0
end
if (nlp.obj.type == "NONLINEAR") || (nlp.meta.nnln > 0)
MOI.eval_hessian_lagrangian_product(nlp.eval, hv, x, v, obj_weight, view(y, nlp.meta.nln))
if nlp.quadcon.nquad > 0
for i = 1:(nlp.quadcon.nquad)
qcon = nlp.quadcon[i]
for k = 1:length(qcon.hessian.vals)
hv[qcon.hessian.rows[k]] += qcon.hessian.vals[k] * v[qcon.hessian.cols[k]]
end
hv[i] *= y[nlp.meta.nlin + i]
end
end
if (nlp.obj.type == "NONLINEAR") || (nlp.meta.nnln > nlp.quadcon.nquad)
ind_nln = (nlp.meta.nlin + nlp.quadcon.nquad + 1):(nlp.meta.ncon)
MOI.eval_hessian_lagrangian_product(nlp.eval, hv, x, v, obj_weight, view(y, ind_nln))
end
if nlp.obj.type == "QUADRATIC"
nlp.meta.nnln == 0 && (hv .= 0.0)
Expand Down Expand Up @@ -404,7 +458,8 @@ function NLPModels.hprod!(
hv .*= obj_weight
end
if nlp.obj.type == "NONLINEAR"
MOI.eval_hessian_lagrangian_product(nlp.eval, hv, x, v, obj_weight, zeros(nlp.meta.nnln))
nnln = nlp.meta.nnln - nlp.quadcon.nquad
MOI.eval_hessian_lagrangian_product(nlp.eval, hv, x, v, obj_weight, zeros(nnln))
end
return hv
end
2 changes: 1 addition & 1 deletion src/moi_nls_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ function MathOptNLSModel(cmodel::JuMP.Model, F; hessian::Bool = true, name::Stri
(nnln == 0 ? 0 : sum(length(con.hess_I) for con in ceval.constraints)) : 0

moimodel = backend(cmodel)
nlin, lincon, lin_lcon, lin_ucon = parser_MOI(moimodel)
nlin, lincon, lin_lcon, lin_ucon, quadcon, quad_lcon, quad_ucon = parser_MOI(moimodel, nvar)

nequ = nlinequ + nnlnequ
Fnnzj = linequ.nnzj + nl_Fnnzj
Expand Down
Loading