diff --git a/src/copy.jl b/src/copy.jl index 19e9d25eeef..02681c3dd93 100644 --- a/src/copy.jl +++ b/src/copy.jl @@ -296,5 +296,59 @@ function MOI.copy_to(dest::MOI.ModelLike, src::Model) end function MOI.copy_to(dest::Model, src::MOI.ModelLike) - return MOI.copy_to(backend(dest), src) + index_map = MOI.copy_to(backend(dest), src) + if MOI.NLPBlock() in MOI.get(src, MOI.ListOfModelAttributesSet()) + block = MOI.get(src, MOI.NLPBlock()) + dest.nlp_model = _nlp_model_from_nlpblock(block, block.evaluator) + end + return index_map +end + +_lift_variable_from_expression(expr) = expr + +function _lift_variable_from_expression(expr::Expr) + if Meta.isexpr(expr, :ref, 2) && expr.args[1] == :x + return expr.args[2] + end + for i in 1:length(expr.args) + expr.args[i] = _lift_variable_from_expression(expr.args[i]) + end + return expr +end + +function _set_from_nlpboundspair(bound::MOI.NLPBoundsPair) + if bound.lower == bound.upper + return MOI.EqualTo(bound.lower) + elseif isfinite(bound.lower) && !isfinite(bound.upper) + return MOI.GreaterThan(bound.lower) + elseif isfinite(bound.upper) && !isfinite(bound.lower) + return MOI.LessThan(bound.upper) + else + return MOI.Interval(bound.lower, bound.upper) + end +end + +function _nlp_model_from_nlpblock(block::MOI.NLPBlockData, evaluator) + if !(:ExprGraph in MOI.features_available(evaluator)) + error( + "Unable to copy model because the nonlinear evaluator doesn't " * + "support :ExprGraph.", + ) + end + MOI.initialize(evaluator, [:ExprGraph]) + model = MOI.Nonlinear.Model() + for (i, bound) in enumerate(block.constraint_bounds) + expr = MOI.constraint_expr(evaluator, i) + f = Meta.isexpr(expr, :comparison) ? expr.args[3] : expr.args[2] + MOI.Nonlinear.add_constraint( + model, + _lift_variable_from_expression(f), + _set_from_nlpboundspair(bound), + ) + end + if block.has_objective + expr = _lift_variable_from_expression(MOI.objective_expr(evaluator)) + MOI.Nonlinear.set_objective(model, expr) + end + return model end diff --git a/test/file_formats.jl b/test/file_formats.jl index 562751f7b2e..b25280377e3 100644 --- a/test/file_formats.jl +++ b/test/file_formats.jl @@ -107,6 +107,37 @@ function test_unsupported_attribute() return end +function test_nl_round_trip() + model = Model() + @variable(model, 0 <= x <= 1) + @constraint(model, x^2 <= 2) + @objective(model, Max, x^2) + @NLconstraint(model, 2 * x >= -1) + @NLconstraint(model, 1 * x == 0.2) + @NLconstraint(model, 0 <= sin(x) <= 1) + write_to_file(model, "model.nl") + model_2 = read_from_file("model.nl") + nlp = nonlinear_model(model_2) + xi = index(x) + @test nlp.objective == MOI.Nonlinear.parse_expression(nlp, :($xi * $xi)) + @test length(nlp.constraints) == 4 + constraints = Dict( + MOI.LessThan(2.0) => + MOI.Nonlinear.parse_expression(nlp, :($xi * $xi)), + MOI.GreaterThan(0.0) => + MOI.Nonlinear.parse_expression(nlp, :(2 * $xi - -1)), + MOI.EqualTo(0.0) => + MOI.Nonlinear.parse_expression(nlp, :(1 * $xi - 0.2)), + MOI.Interval(0.0, 1.0) => + MOI.Nonlinear.parse_expression(nlp, :(sin($xi))), + ) + for (_, c) in nlp.constraints + @test c.expression == constraints[c.set] + end + rm("model.nl") + return +end + function runtests() for name in names(@__MODULE__; all = true) if !startswith("$(name)", "test_")