 # Procedural Nonlinear Constaint Generation - String to NLconstraint

I am implementing a large scale nonlinear program solver using the JuMP interface to IPOPT.

The Nonlinear Modeling section of JuMP v0.21.0 documentation clearly explicates that all expressions used in NLconstraint and NLobjective must be scalar operations. http://www.juliaopt.org/JuMP.jl/stable/nlp/

For this particular appreciation, manual derivation of scalar NLconstraint expressions is prohibitive due to their quantity and complexity.

As an attempt to work around this limitation, scalar nonlinear expressions were derived and delivered as strings using meta programming/symbolic math packages; The thought being that these strings could be parsed, evaluated and passed to NLconstraint. In practice this does not work.

The question is thus -

# How can a string be parsed into an expression ingestible by NLconstraint/NLobjective?

With julia’s meta-programming faculty there must be a way to accomplish this without altering nonlinear modeling/autodiff source code.

The task is illustrated on the toy problem below.

## Suppose the nonlinear constraint has been delivered as a string taking one of the following forms, parse it such that it may be passed to NLconstraint

“x^3 + 5.0*x*x”

or

“x1^3 + 5.0*x1*x2”

### Working Toy Problem

``````using JuMP
using Ipopt
m = Model(with_optimizer(Ipopt.Optimizer))
@variable(m, x[1:2])

set_lower_bound(x, 0.0)
set_upper_bound(x, 2.0)
set_lower_bound(x, -3000.0)
set_upper_bound(x, 3000.0)
@NLobjective(m, Max, 5*x^2*x-3*x^2)
@NLconstraint(m, x^3 + 5.0*x*x <= 3.0)
optimize!(m)
``````

#### Optimal Solution

obj val: 1.5689952589360394
x val: 1.0697545858077664
x val: 0.3320013320392786

``````println("obj val: ", objective_value(m0))
println("x: ", value(x))
println("x: ", value(x))
``````

### Here are three methods which are unsuccessful in converting a string to an expression ingestible by NLconstraint

#### Attempt 1

``````m = Model(with_optimizer(Ipopt.Optimizer))
@variable(m, x[1:2])
f = eval(Meta.parse("x^3 + 5.0*x*x"))
@NLconstraint(m, f <= 3.0)
optimize!(m)
``````

#### Attempt 2

``````m = Model(with_optimizer(Ipopt.Optimizer))
@variable(m, x[1:2])
@NLconstraint(m, eval(Meta.parse("x^3 + 5.0*x*x")) <= 3.0)
optimize!(m)
``````

#### Attempt 3

``````m = Model(with_optimizer(Ipopt.Optimizer))
@variable(m, x[1:2])
function f(x1, x2) eval(Meta.parse("x1^3 + 5.0*x1*x2")) end
register(m, :my_f, 2, f, autodiff=true)
@NLconstraint(m, my_f(x, x) <= 3.0)
optimize!(m)
``````
1 Like

The relevant section of the documentation is here: http://www.juliaopt.org/JuMP.jl/v0.21.0/nlp/#Raw-expression-input-1. You must create a Julia `Expr` object with the JuMP variables spliced into the expression tree. For example:

``````m = Model(Ipopt.Optimizer)
@variable(m, x[1:2])
# Use Meta.parse and custom transformations to create this object from a string. eval() is not needed.
expr = :(\$(x)^3 + 5.0 * \$(x) * \$(x))
``````

The string name of the variable is irrelevant in this setting as far as JuMP is concerned. The same code would work for anonymous variables `x = @variable(m, [1:2])` that have no names.

1 Like

Your comment makes sense; I am now struggling with the implementation details surrounding this comment:

``````# Use Meta.parse and custom transformations to create this object from a string. eval() is not needed.
``````

Could more granular guidance be offered on how to go about transforming the below string (or a similar modified string)

``````str = "x1^3 + 5.0*x1*x2"
``````

into

``````expr = :(\$(x)^3 + 5.0 * \$(x) * \$(x))
``````

given

``````m = Model(Ipopt.Optimizer)
@variable(m, x[1:2])
``````

For example, the following results in “Unrecognized expression x. JuMP variable objects and input coefficients should be spliced directly into expressions.”

``````str = "\$(x)^3 + 5.0*\$(x)*\$(x)"
expr = Meta.parse(str)
``````
``````using JuMP

substitute_args(ex, vars) = ex
substitute_args(ex::Symbol, vars) = get(vars, ex, ex)
function substitute_args(ex::Expr, vars)
for (i, arg) in enumerate(ex.args)
ex.args[i] = substitute_args(arg, vars)
end
return ex
end

model = Model()
@variable(model, x[1:2])
str = "x1^3 + 5 * x1 * x2"
ex = Meta.parse(str)
vars = Dict(:x1 => x, :x2 => x)

set_NL_objective(
model,
MOI.MIN_SENSE,
substitute_args(ex, vars)
)
``````
1 Like

The forloop makes sense as does the dictionary associating symbols in the expression with jump variables.

Would you be able to elaborate on how the function “substitute_args” works? Specifically I do not understand -

1. Why are these two lines needed before the definition of “substitute_args”?
“”"
substitute_args(ex, vars) = ex
substitute_args(ex::Symbol, vars) = get(vars, ex, ex)
“”"
2. How can the function “substitute_args” be called within the definition of “substitute_args”?

Why are these two lines needed before the definition of “substitute_args”?

They are different methods of the same function: https://docs.julialang.org/en/v1.3/manual/methods/

This is a fundamentally cool feature of Julia, so it’s worth learning in detail.

How can the function “substitute_args” be called within the definition of “substitute_args”?

Most programming languages allow this. See, e.g., https://en.wikipedia.org/wiki/Recursion_(computer_science)

2 Likes

Thank you for this. After taking time to digest and implement, this reply explains in full how the substitute_args function works.

Thanks you for these answers, they solve the stated problem.

An interesting follow on has risen from development of a unit test exercising the core functionality of `substitute_args`.

The expectation is that that `ex_out_desired` will be identical to `ex_in` after it is acted upon by `substitute_args`. Though the two expressions appear equal, the test fails per below stack trace.

What nuance am I missing that causes `ex_out_desired == ex_out` to evaluate to false? How could this unit test be restructured to verify that `substitute_args` has indeed spliced the JuMP variables into the expression tree?

Trace:

``````JuMP Variable Substitution: Test Failed at /home/test/substitute_args.jl:22
Expression: ex_out_desired == ex_out
Evaluated: s + s == s + s
``````

Unit Test:

``````using Test
using JuMP

sa(ex, vars) = ex
sa(ex::Symbol, vars) = get(vars, ex, ex)
function sa(ex::Expr, vars)
for (i, arg) in enumerate(ex.args)
ex.args[i] = sa(arg, vars)
end
return ex
end

@testset "Substitute Arguments Tests" begin
@testset "JuMP Variable Substitution" begin
md = Model()
@variable(md, s[1:2])

ex_in = Meta.parse("x + y")
ex_out_desired = :(s + s)
vars = Dict(:x => s, :y => s)
sa(ex_in, vars)
@test ex_in == ex_out_desired

end
end
``````

You need to interpolate the variable into `ex_out_desired`: `:(\$(s) + \$(s))`.

You can see the expression with `dump(ex_out_desired)`.

1 Like

Excellent, this worked.