optimizationcallbackjuliajulia-jumpipopt

Intermediate values through callback functions


I am currently working on implementing an algorithm for solving a special type of optimization problem by using JuMP and IPOPT in Julia. This problem requires that I check some kind of statement for each iteration in IPOPT. If the statement comes true, norm(currentValue,Inf) > η*max_val I want IPOPT to terminate.

I have a vector containing many different expressions, for example like;

a = [x[1] + y^2, x[2] + 2y, x[3]^2]

I am using the callback function to get the different values of the variables at each iteration. However, for each iteration, I want to check the values inside a. I know that at the end (when optimize! has finished) I can check the values of the expressions by using value.(a). Is it possible to do something similar inside the callback function? Something like this;

 function my_callback(
        alg_mod::Cint,
        iter_count::Cint,
        obj_value::Float64,
        inf_pr::Float64,
        inf_du::Float64,
        mu::Float64,
        d_norm::Float64,
        regularization_size::Float64,
        alpha_du::Float64,
        alpha_pr::Float64,
        ls_trials::Cint)

        x, z_L, z_U = zeros(n), zeros(n), zeros(n).     # x contains the values
        g, lambda = zeros(m), zeros(m)
        scaled = true
        prob = unsafe_backend(model).inner
        Ipopt.GetIpoptCurrentIterate(prob, scaled, n, x, z_L, z_U, m, g, lambda)
        x_L_violation, x_U_violation = zeros(n), zeros(n)
        compl_x_L, compl_x_U, grad_lag_x = zeros(n), zeros(n), zeros(n)
        nlp_constraint_violation, compl_g = zeros(m), zeros(m)
        Ipopt.GetIpoptCurrentViolations(
            prob,
            scaled,
            n,
            x_L_violation,
            x_U_violation,
            compl_x_L,
            compl_x_U,
            grad_lag_x,
            m,
            nlp_constraint_violation,
            compl_g,
        )
        currentValues = a(x) # this is what I want to do but is not working...
       
        return norm(currentValue,Inf) < η*max_val.  # the statement 
    end
    MOI.set(model, Ipopt.CallbackFunction(), my_callback)
    @test MOI.get(model, MOI.TerminationStatus()) == MOI.INTERRUPTED 

So, my question is how to get the current value of an expression after each iteration in IPOPT using a callback function?


Solution

  • You can query the value of variables in a callback using callback_value. See the README for details: https://github.com/jump-dev/Ipopt.jl#solver-specific-callback.

    Then, you can evaluate JuMP expressions using value(f, expr), where f(x) returns the value of the variable x. For example:

    julia> using JuMP, Ipopt, LinearAlgebra
    
    julia> model = Model(Ipopt.Optimizer)
    A JuMP Model
    Feasibility problem with:
    Variables: 0
    Model mode: AUTOMATIC
    CachingOptimizer state: EMPTY_OPTIMIZER
    Solver name: Ipopt
    
    julia> @variable(model, x[1:3] >= 1)
    3-element Vector{VariableRef}:
     x[1]
     x[2]
     x[3]
    
    julia> @variable(model, y)
    y
    
    julia> a = [x[1] + y^2, x[2] + 2y, x[3]^2]
    3-element Vector{QuadExpr}:
     y² + x[1]
     x[2] + 2 y
     x[3]²
    
    julia> function my_callback(args...)
               function f(xi)
                   return callback_value(model, xi)
               end
               a_value = value.(f, a)
               return LinearAlgebra.norm(a_value, Inf) < 0.1
           end
    my_callback (generic function with 1 method)
    
    julia> MOI.set(model, Ipopt.CallbackFunction(), my_callback)
    
    julia> optimize!(model)
    This is Ipopt version 3.14.4, running with linear solver MUMPS 5.4.1.
    
    Number of nonzeros in equality constraint Jacobian...:        0
    Number of nonzeros in inequality constraint Jacobian.:        0
    Number of nonzeros in Lagrangian Hessian.............:        0
    
    Total number of variables............................:        4
                         variables with only lower bounds:        3
                    variables with lower and upper bounds:        0
                         variables with only upper bounds:        0
    Total number of equality constraints.................:        0
    Total number of inequality constraints...............:        0
            inequality constraints with only lower bounds:        0
       inequality constraints with lower and upper bounds:        0
            inequality constraints with only upper bounds:        0
    
    iter    objective    inf_pr   inf_du lg(mu)  ||d||  lg(rg) alpha_du alpha_pr  ls
       0  0.0000000e+00 0.00e+00 1.00e+00  -1.0 0.00e+00    -  0.00e+00 0.00e+00   0
    
    Number of Iterations....: 0
    
                                       (scaled)                 (unscaled)
    Objective...............:   0.0000000000000000e+00    0.0000000000000000e+00
    Dual infeasibility......:   1.0000000000000000e+00    1.0000000000000000e+00
    Constraint violation....:   0.0000000000000000e+00    0.0000000000000000e+00
    Variable bound violation:   0.0000000000000000e+00    0.0000000000000000e+00
    Complementarity.........:   9.9999999999998979e-03    9.9999999999998979e-03
    Overall NLP error.......:   1.0000000000000000e+00    1.0000000000000000e+00
    
    
    Number of objective function evaluations             = 1
    Number of objective gradient evaluations             = 1
    Number of equality constraint evaluations            = 0
    Number of inequality constraint evaluations          = 0
    Number of equality constraint Jacobian evaluations   = 0
    Number of inequality constraint Jacobian evaluations = 0
    Number of Lagrangian Hessian evaluations             = 0
    Total seconds in IPOPT                               = 0.116
    
    EXIT: Stopping optimization at current point as requested by user.
    
    julia> @test MOI.get(model, MOI.TerminationStatus()) == MOI.INTERRUPTED 
    Test Passed
    

    p.s., consider posting these sorts of questions on the Discourse forum: https://discourse.julialang.org/c/domain/opt/13.