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

[FileFormats.NL] add support for ScalarNonlinearFunction #2228

Merged
merged 7 commits into from
Jun 28, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 17 additions & 20 deletions src/FileFormats/NL/NL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ mutable struct _VariableInfo
end

"""
Model()
Model(; use_nlp_block::Bool = true)

Create a new Optimizer object.
"""
Expand All @@ -145,8 +145,9 @@ mutable struct Model <: MOI.ModelLike
Nothing,
MOI.Utilities.UniversalFallback{MOI.Utilities.Model{Float64}},
}
use_nlp_block::Bool

function Model()
function Model(; use_nlp_block::Bool = true)
return new(
"",
_NLExpr(false, _NLTerm[], Dict{MOI.VariableIndex,Float64}(), 0.0),
Expand All @@ -158,6 +159,7 @@ mutable struct Model <: MOI.ModelLike
[MOI.VariableIndex[] for _ in 1:9],
MOI.VariableIndex[],
nothing,
use_nlp_block,
)
end
end
Expand Down Expand Up @@ -194,6 +196,7 @@ const _SCALAR_FUNCTIONS = Union{
MOI.VariableIndex,
MOI.ScalarAffineFunction{Float64},
MOI.ScalarQuadraticFunction{Float64},
MOI.ScalarNonlinearFunction,
}

const _SCALAR_SETS = Union{
Expand Down Expand Up @@ -244,17 +247,12 @@ end

# ==============================================================================

struct _LinearNLPEvaluator <: MOI.AbstractNLPEvaluator end

MOI.initialize(::_LinearNLPEvaluator, ::Vector{Symbol}) = nothing

function MOI.copy_to(dest::Model, model::MOI.ModelLike)
if !MOI.is_empty(dest)
MOI.empty!(dest)
end
mapping = MOI.Utilities.IndexMap()
nlp_block =
MOI.NLPBlockData(MOI.NLPBoundsPair[], _LinearNLPEvaluator(), false)
has_nlp_objective = false
for attr in MOI.get(model, MOI.ListOfModelAttributesSet())
if attr == MOI.NLPBlock()
nlp_block = MOI.get(model, MOI.NLPBlock())
Expand All @@ -264,25 +262,24 @@ function MOI.copy_to(dest::Model, model::MOI.ModelLike)
"evaluator does not supply expression graphs.",
)
end
MOI.initialize(nlp_block.evaluator, [:ExprGraph])
if nlp_block.has_objective
dest.f = _NLExpr(MOI.objective_expr(nlp_block.evaluator))
end
has_nlp_objective = nlp_block.has_objective
for (i, bound) in enumerate(nlp_block.constraint_bounds)
expr = MOI.constraint_expr(nlp_block.evaluator, i)
push!(dest.g, _NLConstraint(expr, bound))
end
dest.nlpblock_dim = length(dest.g)
elseif attr == MOI.ObjectiveSense()
dest.sense = MOI.get(model, MOI.ObjectiveSense())
elseif attr isa MOI.ObjectiveFunction
elseif !has_nlp_objective && attr isa MOI.ObjectiveFunction
dest.f = _NLExpr(MOI.get(model, attr))
else
throw(MOI.UnsupportedAttribute(attr))
end
end
MOI.initialize(nlp_block.evaluator, [:ExprGraph])
if nlp_block.has_objective # Nonlinear objective takes precedence.
dest.f = _NLExpr(MOI.objective_expr(nlp_block.evaluator))
end
for (i, bound) in enumerate(nlp_block.constraint_bounds)
push!(
dest.g,
_NLConstraint(MOI.constraint_expr(nlp_block.evaluator, i), bound),
)
end
dest.nlpblock_dim = length(dest.g)
x_src = MOI.get(model, MOI.ListOfVariableIndices())
for x in x_src
dest.x[x] = _VariableInfo()
Expand Down
23 changes: 22 additions & 1 deletion src/FileFormats/NL/NLExpr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,12 @@ function _NLExpr(x::MOI.ScalarQuadraticFunction)
return _NLExpr(false, terms, linear, x.constant)
end

function _NLExpr(expr::MOI.ScalarNonlinearFunction)
nlexpr = _NLExpr(false, _NLTerm[], Dict{MOI.VariableIndex,Float64}(), 0.0)
_process_expr!(nlexpr, expr)
return nlexpr
end

function _NLExpr(expr::Expr)
nlexpr = _NLExpr(false, _NLTerm[], Dict{MOI.VariableIndex,Float64}(), 0.0)
_process_expr!(nlexpr, expr)
Expand Down Expand Up @@ -403,6 +409,21 @@ function _process_expr!(expr::_NLExpr, arg::Expr)
return error("Unsupported expression: $(arg)")
end

function _process_expr!(expr::_NLExpr, arg::MOI.ScalarNonlinearFunction)
if length(arg.args) == 1
f = get(_UNARY_SPECIAL_CASES, arg.head, nothing)
if f !== nothing
return _process_expr!(expr, f(arg.args[1]))
end
elseif length(arg.args) == 2
f = get(_BINARY_SPECIAL_CASES, arg.head, nothing)
if f !== nothing
return _process_expr!(expr, f(arg.args[1], arg.args[2]))
end
end
return _process_expr!(expr, vcat(arg.head, arg.args))
end

function _process_expr!(expr::_NLExpr, args::Vector{Any})
op = first(args)
N = length(args) - 1
Expand Down Expand Up @@ -431,7 +452,7 @@ function _process_expr!(expr::_NLExpr, args::Vector{Any})
# Now convert the Julia expression into an _NLExpr.
opcode = get(_JULIA_TO_AMPL, op, nothing)
if opcode === nothing
error("Unsupported operation $(op)")
throw(MOI.UnsupportedNonlinearOperator(op))
end
if op == :atan && N == 2
# Special case binary use of atan, because Julia uses method overloading
Expand Down
78 changes: 58 additions & 20 deletions src/FileFormats/NL/read.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ mutable struct _CacheModel
Float64[],
Float64[],
Float64[],
Expr[],
Union{Expr,MOI.ScalarNonlinearFunction}[],
Float64[],
Float64[],
:(),
Expand All @@ -37,7 +37,7 @@ function Base.read!(io::IO, model::Model)
while !eof(io)
_parse_section(io, cache)
end
model.model = _to_model(cache)
model.model = _to_model(cache; use_nlp_block = model.use_nlp_block)
return
end

Expand Down Expand Up @@ -163,7 +163,7 @@ function _parse_expr(io::IO, model::_CacheModel)
end
end

function _to_model(data::_CacheModel)
function _to_model(data::_CacheModel; use_nlp_block::Bool)
model = MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}())
x = MOI.add_variables(model, length(data.variable_primal))
for (xi, lb, ub) in zip(x, data.variable_lower, data.variable_upper)
Expand All @@ -183,28 +183,66 @@ function _to_model(data::_CacheModel)
end
MOI.set.(model, MOI.VariablePrimalStart(), x, data.variable_primal)
MOI.set(model, MOI.ObjectiveSense(), data.sense)
nlp = MOI.Nonlinear.Model()
MOI.Nonlinear.set_objective(nlp, data.objective)
for (i, expr) in enumerate(data.constraints)
lb, ub = data.constraint_lower[i], data.constraint_upper[i]
if lb == ub
MOI.Nonlinear.add_constraint(nlp, expr, MOI.EqualTo(lb))
elseif -Inf < lb < ub < Inf
MOI.Nonlinear.add_constraint(nlp, expr, MOI.Interval(lb, ub))
elseif -Inf == lb && ub < Inf
MOI.Nonlinear.add_constraint(nlp, expr, MOI.LessThan(ub))
else
@assert -Inf < lb && ub == Inf
MOI.Nonlinear.add_constraint(nlp, expr, MOI.GreaterThan(lb))
if use_nlp_block
nlp = MOI.Nonlinear.Model()
MOI.Nonlinear.set_objective(nlp, data.objective)
for (i, expr) in enumerate(data.constraints)
lb, ub = data.constraint_lower[i], data.constraint_upper[i]
if lb == ub
MOI.Nonlinear.add_constraint(nlp, expr, MOI.EqualTo(lb))
elseif -Inf < lb < ub < Inf
MOI.Nonlinear.add_constraint(nlp, expr, MOI.Interval(lb, ub))
elseif -Inf == lb && ub < Inf
MOI.Nonlinear.add_constraint(nlp, expr, MOI.LessThan(ub))
else
@assert -Inf < lb && ub == Inf
MOI.Nonlinear.add_constraint(nlp, expr, MOI.GreaterThan(lb))
end
end
evaluator =
MOI.Nonlinear.Evaluator(nlp, MOI.Nonlinear.SparseReverseMode(), x)
block = MOI.NLPBlockData(evaluator)
MOI.set(model, MOI.NLPBlock(), block)
else
MOI.set(
model,
MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}(),
_to_scalar_nonlinear_function(data.objective),
)
for (i, expr) in enumerate(data.constraints)
lb, ub = data.constraint_lower[i], data.constraint_upper[i]
f = _to_scalar_nonlinear_function(expr)
if lb == ub
MOI.add_constraint(model, f, MOI.EqualTo(lb))
elseif -Inf < lb < ub < Inf
MOI.add_constraint(model, f, MOI.Interval(lb, ub))
elseif -Inf == lb && ub < Inf
MOI.add_constraint(model, f, MOI.LessThan(ub))
else
@assert -Inf < lb && ub == Inf
MOI.add_constraint(model, f, MOI.GreaterThan(lb))
end
end
end
evaluator =
MOI.Nonlinear.Evaluator(nlp, MOI.Nonlinear.SparseReverseMode(), x)
block = MOI.NLPBlockData(evaluator)
MOI.set(model, MOI.NLPBlock(), block)
return model
end

_to_scalar_nonlinear_function(expr) = expr

function _to_scalar_nonlinear_function(expr::Expr)
if Meta.isexpr(expr, :call)
return MOI.ScalarNonlinearFunction(
expr.args[1],
Any[_to_scalar_nonlinear_function(arg) for arg in expr.args[2:end]],
)
elseif Meta.isexpr(expr, :ref)
return expr.args[2]
elseif expr == :()
return 0.0
end
return
end

function _parse_header(io::IO, model::_CacheModel)
# Line 1
# We don't support the binary format.
Expand Down
8 changes: 4 additions & 4 deletions src/FileFormats/NL/sol.jl
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ end
function MOI.get(
sol::SolFileResults,
attr::MOI.ConstraintPrimal,
ci::MOI.ConstraintIndex{<:MOI.ScalarQuadraticFunction},
)
ci::MOI.ConstraintIndex{F},
) where {F<:Union{MOI.ScalarQuadraticFunction,MOI.ScalarNonlinearFunction}}
MOI.check_result_index_bounds(sol, attr)
return _evaluate(sol.model.g[ci.value].expr, sol.variable_primal)
end
Expand Down Expand Up @@ -184,8 +184,8 @@ end
function MOI.get(
sol::SolFileResults,
attr::MOI.ConstraintDual,
ci::MOI.ConstraintIndex{<:MOI.ScalarQuadraticFunction},
)
ci::MOI.ConstraintIndex{F},
) where {F<:Union{MOI.ScalarQuadraticFunction,MOI.ScalarNonlinearFunction}}
MOI.check_result_index_bounds(sol, attr)
dual = sol.constraint_dual[ci.value]
return sol.model.sense == MOI.MIN_SENSE ? dual : -dual
Expand Down
Loading