Modify a JuMP model via modifying expressions

Currently the doc of JuMP teaches people how to modify constraints and how to modify objective_expression. In my mind, there is a more natural way:

  1. Build an initial model (that we will revise later) partly involving some “provisional” expressions
  2. Build some “provisional” constraints and “provisional” objective with the “provisional” expressions built in step 1. (may need to memorize a dependency graph)
  3. optimize!(model) (work with the model in current status)
  4. Revise the “provisional” expressions defined in step 1.
  5. (maybe) execute a command update!(model), during which the dependent “provisional” constraints and “provisional” objective_expression are updated (according to the dependency graph).
  6. optimize!(model) (work with the revised model)
  7. goto step 4

The code is like

julia> using JuMP

julia> model = JuMP.Model();

julia> @variable(model, x[1:3]);

julia> expr = @expression(model, x[2] + 2x[1])
x[2] + 2 x[1]

julia> @constraint(model, cons, x[1] + x[3] + expr == 7)
cons : 3 x[1] + x[2] + x[3] == 7

julia> @objective(model, Min, x[2] - 2x[3] - expr)
0 x[2] - 2 x[3] - 2 x[1]

julia> print(model) # the provisional model
Min -2 x[3] - 2 x[1]
Subject to
 cons : 3 x[1] + x[2] + x[3] == 7

julia> quote JuMP.set_expression_coefficient(expr, x[1], 3) end; # execute this code (Step 4 above), then we can observe the follows

julia> # expr = x[2] + 3 x[1]

julia> quote JuMP.update!(model) end; # execute this code, then we can observe the follows

julia> # cons : x[1] + x[3] + (x[2] + 3x[1]) == 7

julia> # objective_expression : x[2] - 2x[3] - (x[2] + 3 x[1])

I don’t know whether this is proper.


Under the existing JuMP API, I would equivalently write the above procedure—from a complementary perspective, as

julia> using JuMP

julia> begin
           model = JuMP.Model();
           @variable(model, x[1:3]);
           # pre-prepare the following 3 expressions to save time
           @expression(model, cons_lhs_1, x[1] + x[3])
           @expression(model, cons_rhs, 7)
           @expression(model, obj_expr_1, x[2] - 2x[3])
           @constraint(model, cons, 0 == 1) # an initial dumb one
       end;

julia> function modify!(model, c_x1, c_x2)
           JuMP.delete(model, model[:cons])
           expr = @expression(model, model[:x][1]c_x1 + model[:x][2]c_x2)
           model[:cons] = @constraint(model, model[:cons_lhs_1] + expr == model[:cons_rhs])      
           @objective(model, Min, model[:obj_expr_1] - expr)
           return nothing
       end;

julia> modify!(model, 3, 1)

julia> print(model)
Min -2 x[3] - 3 x[1]
Subject to
 4 x[1] + x[2] + x[3] == 7

julia> modify!(model, 5, 2)

julia> print(model)
Min -x[2] - 2 x[3] - 5 x[1]
Subject to
 6 x[1] + 2 x[2] + x[3] == 7

julia> cons != model[:cons] && (cons = model[:cons]);

This cannot be supported because JuMP pastes in the value of expressions. It does not remember that :expr was used and that we should update it in future.

The recommended way to write this is probably:

julia> using JuMP

julia> begin
           model = Model()
           @variable(model, x[1:3])
           @constraint(model, cons, 0 == 7)
       end;

julia> function modify!(model, c_x1, c_x2)
           x = model[:x]
           expr = @expression(model, c_x1 * x[1] + c_x2 * x[2])
           set_normalized_coefficient.(model[:cons], x, [1 + c_x1, c_x2, 1.0])
           @objective(model, Min, x[2] - 2 * x[3] - expr)
           return
       end;

julia> modify!(model, 3, 1)

julia> print(model)
Min -2 x[3] - 3 x[1]
Subject to
 cons : 4 x[1] + x[2] + x[3] = 7

julia> modify!(model, 5, 2)

julia> print(model)
Min -x[2] - 2 x[3] - 5 x[1]
Subject to
 cons : 6 x[1] + 2 x[2] + x[3] = 7
1 Like

This method, along with set_objective_coefficient, are at a lower level, which might be not very convenient for constraints who are more complex. In the example above, x[2] occurs both in obj_expr_1 and in the varying expr, therefore this case is not simple. My approach—delete and then rebuild with @constraint reads more natural and thus foolproof.