How to get constraints from one JuMP model and use them in another JuMP model

Hello,

I have two JuMP model like following:

m1=Model(solver=ClpSolver())
@variable(m1, x[1:3]>=0)
@constraint(m1, sum(x[i] for i in 1:3)>=0)
@constraint(m1, sum(x[i] for i in [1,3])>=0)
@constraint(m1, sum(x[i] for i in [2,3])>=0)
@objective(m1, Min, sum(x[i] for i in 1:3))

And the second model is

m2=Model(solver=IpoptSolver())
@variable(m2, x[1:3]>=0)
@NLconstraint(m2, sum(x[i]^2 for i in 1:3)>=0)
@NLconstraint(m2, sum(x[i]^2 for i in [1,3])>=0)
@NLconstraint(m2, sum(x[i]^2 for i in [2,3])>=0)
@NLobjective(m2, Min, sum(x[i]^2 for i in 1:3))

Now I want to add last two constraints of model “m1” to model “m2”, that is I want to make model “m2” looks like the following:

m2=Model(solver=IpoptSolver())
@variable(m2, x[1:3]>=0)
@NLconstraint(m2, sum(x[i]^2 for i in 1:3)>=0)
@NLconstraint(m2, sum(x[i]^2 for i in [1,3])>=0)
@NLconstraint(m2, sum(x[i]^2 for i in [2,3])>=0)
@constraint(m2, sum(x[i] for i in [1,3])>=0)
@constraint(m2, sum(x[i] for i in [2,3])>=0)
@NLobjective(m2, Min, sum(x[i]^2 for i in 1:3))

I do not know how to do this. I will appreciate your opinion/suggestion on this issue. Thank you in advance.

In general, no. Constraints are tied to a model and cannot be shared. Why do you want to do this?

If you want to add identical constraints to different models and you don’t want to write it out twice, we suggest using a function like:

function add_constraint(model, indices, lower_bound)
    x = model[:x]
    @constraint(model, sum(x[i] for i in indices) >= lower_bound)
end

add_constraint(m1, [2, 3], 10)
add_constraint(m2, [2, 3], 10)

P.s. you can put format code by enclosing it in triple backticks like so:

```julia
Code goes here
```

1 Like

Thank you very much for your reply on the constraint adding issue. Thanks again for showing me the proper way of code formatting.

Since I had two models and they share some identical constraints. Is there anyway to extract sparse matrix corresponding to linear constraints of a JuMP model? Then later I can use that matrix to add constraints in another JuMP model. Can I use functions like MathProgBase. getconstrmatrix(m::Model)?

You will need to pass MathProgBase.getconstrmatrix(JuMP.internalmodel(model)).

Note that a few caveats apply

  • you will need to call solve(model) or JuMP.build(model) first
  • some solvers won’t support getconstrmatrix
  • this will not work in the upcoming release of JuMP

Another approach, which will also break in the upcoming release of JuMP, is to look through the
model.linconstr field in a JuMP model. It stores everything you need.

2 Likes

Thank you very much for your reply. You are right as I checked
MathProgBase.getconstrmatrix(JuMP.internalmodel(model))
method fails while I was using Ipopt solver. As you said getconstrmatrix will not work from upcoming release of JuMP, so its better to not rely on this method.

By this time, I got a solution method of my stated problem. Its the following:

A=JuMP.prepConstrMatrix(m1)
n=MathProgBase.numconstr(m1)

for i in (n-1):n
    @constraint(m2, A[i:i,:]*x[1:3].>=0)
end

Its solves the problem apparently but I cannot guarantee its correctness. Here I could not put constraints bound (i.e., lower bound or upper bound) automatically. Do you have any suggestion about the constraints bounds? And also I could not find way to get model.linconstr field. Thanks.

Ipopt (or any nonlinear solver) also handles things differently.

The real question should be: Why do you want to do this? And can you change your code to avoid doing this?

Somewhat related: if I have a collection of JuMP models i = 1...n and I want to “combine” (exact way tbd) them into a single model, is there a “best” or suggested way of doing this? My attempt was to collect the expressions of variables/objectives/constraints and try to remap them. I’ve been able to extract the objectives as expressions and remap variables from the collection of models to a new monolithic model. However, I have trouble on constraints. E.g., if a model i has @variable(modeli, y[1] >=0) and @constraint(modeli, y[1] >= 1), my method is not sure how to handle this. Is there a nice way of doing this for constraints?

MWE:

using JuMP, OrderedCollections
#=
start utilities --------------------------------------------------------------------------------------------------------------
=#
function substitute_variables(expr, mapping::Dict{JuMP.VariableRef, JuMP.VariableRef})
    if expr isa JuMP.QuadExpr
        # Handle Quadratic Expressions
        new_quad_terms = OrderedCollections.OrderedDict(
            UnorderedPair(get(mapping, pair.a, pair.a), get(mapping, pair.b, pair.b)) => coef
            for (pair, coef) in expr.terms
        )
        # Recursively substitute the affine part
        new_aff_expr = substitute_variables(expr.aff, mapping)
        return JuMP.QuadExpr(new_aff_expr, new_quad_terms)
    elseif expr isa JuMP.AffExpr
        # Handle Affine Expressions
        new_terms = OrderedCollections.OrderedDict(
            get(mapping, var, var) => coef for (var, coef) in expr.terms
        )
        return JuMP.AffExpr(expr.constant, new_terms...)
    elseif expr isa JuMP.VariableRef
        # Handle Single Variables
        return get(mapping, expr, expr)
    else
        # nonlinear expressions...see: https://jump.dev/JuMP.jl/stable/manual/nonlinear/#Nonlinear-expressions-in-detail
        error("Unsupported expression type: $(typeof(expr))")
    end
end
#=
end utilities --------------------------------------------------------------------------------------------------------------
=#


# Step 1: Define individual models
n = 3  # Number of individual models
models = []
for i in 1:n
    model = Model()
    @variable(model, y[1:2] >= 0)  # Each model has a variable array `y` of size 2
    @objective(model, Min, y[1]^2 + i * y[2])  # Example objective
    @constraint(model, y[1] + i <= 10)  # Example constraint
    @constraint(model, y[2] + i >= 10)  # Example constraint
    push!(models, model)
end

# Step 2: Create the big model
big_model = Model()

# Step 3: Create variables for the big model and build a variable mapping
variable_mapping = Dict{JuMP.VariableRef, JuMP.VariableRef}()

for (model_idx, model) in enumerate(models)
    # Iterate over all variables in the model
    for var in all_variables(model)
        # Get the variable's symbolic name
        var_name = Symbol(var)  # e.g., "y[1]", "y[2]"

        # Create a unique name for the big model variable
        unique_var_name = Symbol("m$(model_idx)_$(var_name)")

        # Get the variable's properties
        lb = JuMP.has_lower_bound(var) ? JuMP.lower_bound(var) : -Inf
        ub = JuMP.has_upper_bound(var) ? JuMP.upper_bound(var) : Inf
        is_bin = JuMP.is_binary(var)
        is_int = JuMP.is_integer(var)

        # Create the variable in the big model with the same properties
        if JuMP.has_lower_bound(var) && JuMP.has_upper_bound(var)
            new_var = @variable(big_model, lower_bound=lb, upper_bound=ub, base_name=string(unique_var_name))
        elseif JuMP.has_lower_bound(var) && !JuMP.has_upper_bound(var)
            new_var = @variable(big_model, lower_bound=lb, base_name=string(unique_var_name))
        elseif !JuMP.has_lower_bound(var) && JuMP.has_upper_bound(var)
            new_var = @variable(big_model, upper_bound=ub, base_name=string(unique_var_name))
        else
            new_var = @variable(big_model, base_name=string(unique_var_name))
        end
        
        # Map the small model variable to the newly created big model variable
        variable_mapping[var] = new_var
    end
end

# Verify the variable mapping
println("Variable Mapping:")
for (small_var, big_var) in variable_mapping
    println("Small model variable $small_var -> Big model variable $big_var")
end

# -------------------------------------------------------------------------------------------------------------------------------
# Step 4: Combine objectives and constraints from all models
combined_objective = 0.0  # Initialize the combined objective

for (model_idx, model) in enumerate(models)
    # Extract the objective function
    obj_expr = JuMP.objective_function(model)

    # Perform substitution for variables using the variable_mapping
    substituted_obj_expr = substitute_variables(obj_expr, variable_mapping)

    # Add the substituted objective to the combined objective
    combined_objective += substituted_obj_expr
end

# Add the combined objective to the big model
@objective(big_model, Min, combined_objective)

# Step 5: Extract and add constraints from individual models to the big model
for (model_idx, model) in enumerate(models)
    println("model=$model_idx")
    for (con_idx, con) in enumerate(JuMP.all_constraints(model, include_variable_in_set_constraints=true))
        println("con=$con, $con_idx")
        # Get the full constraint object
        constraint_obj = JuMP.constraint_object(con)

        # Access the left-hand side (expression) and right-hand side (set)
        con_expr = constraint_obj.func   # The function or expression on the LHS
        @show con_expr
        con_set = constraint_obj.set    # The set defining the constraint (e.g., `<=`, `>=`, `==`)
        @show con_set

        # Substitute variables in the constraint expression
        substituted_con_expr = substitute_variables(con_expr, variable_mapping)

        # Add the substituted constraint to the big model
        @constraint(big_model, substituted_con_expr in con_set)
    end
end

# Verify the big model
println("Big Model Objective:")
println(combined_objective)

println("Big Model Constraints:")
for con in JuMP.all_constraints(big_model)
    println(con)
end

gives an error

model=1
con=y[2] ≥ 9, 1
con_expr = y[2]
con_set = MathOptInterface.GreaterThan{Float64}(9.0)
con=y[1] ≤ 9, 2
con_expr = y[1]
con_set = MathOptInterface.LessThan{Float64}(9.0)
con=y[1] ≥ 0, 3
con_expr = y[1]
con_set = MathOptInterface.GreaterThan{Float64}(0.0)
ERROR: MathOptInterface.LowerBoundAlreadySet{MathOptInterface.GreaterThan{Float64}, MathOptInterface.GreaterThan{Float64}}: Cannot add `VariableIndex`-in-`MathOptInterface.GreaterThan{Float64}` constraint for variable MOI.VariableIndex(1) as a `VariableIndex`-in-`MathOptInterface.GreaterThan{Float64}` constraint was already set for this variable and both constraints set a lower bound.
Stacktrace:
  [1] _throw_if_lower_bound_set_inner(variable::MathOptInterface.VariableIndex, S2::Type, mask::UInt16, T::Type)
    @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/gLl4d/src/Utilities/variables_container.jl:120
  [2] _throw_if_lower_bound_set
    @ ~/.julia/packages/MathOptInterface/gLl4d/src/Utilities/variables_container.jl:131 [inlined]
  [3] add_constraint
    @ ~/.julia/packages/MathOptInterface/gLl4d/src/Utilities/variables_container.jl:267 [inlined]
  [4] add_constraint
    @ ~/.julia/packages/MathOptInterface/gLl4d/src/Utilities/model.jl:348 [inlined]
  [5] add_constraint
    @ ~/.julia/packages/MathOptInterface/gLl4d/src/Utilities/universalfallback.jl:835 [inlined]
  [6] add_constraint(m::MathOptInterface.Utilities.CachingOptimizer{…}, func::MathOptInterface.VariableIndex, set::MathOptInterface.GreaterThan{…})
    @ MathOptInterface.Utilities ~/.julia/packages/MathOptInterface/gLl4d/src/Utilities/cachingoptimizer.jl:551
  [7] _moi_add_constraint(model::MathOptInterface.Utilities.CachingOptimizer{…}, f::MathOptInterface.VariableIndex, s::MathOptInterface.GreaterThan{…})
    @ JuMP ~/.julia/packages/JuMP/V9nZm/src/constraints.jl:1013
  [8] add_constraint(model::Model, con::ScalarConstraint{VariableRef, MathOptInterface.GreaterThan{Float64}}, name::String)
    @ JuMP ~/.julia/packages/JuMP/V9nZm/src/constraints.jl:1036
  [9] macro expansion
    @ ~/.julia/packages/JuMP/V9nZm/src/macros/@constraint.jl:173 [inlined]
 [10] macro expansion
    @ ~/.julia/packages/JuMP/V9nZm/src/macros.jl:393 [inlined]
 [11] top-level scope
    @ ./REPL[165]:18
Some type information was truncated. Use `show(err)` to see complete types.

Somewhat related: if I have a collection of JuMP models i = 1...n and I want to “combine” (exact way tbd) them into a single model, is there a “best” or suggested way of doing this?

You cannot combine JuMP models. Can you start a new thread and describe why you want to do this?