Naming JuMP constraint in a loop

Hi,

Sorry for the rookie question

I am trying to find a way to name constraints inside a loop
I understand that it is common to define a constraint with the base name inside the @constraint
below is the MWE

using JuMP

m = Model()

hours = 1:6
areas = [:a, :b, :c]
techs = [:x, :y, :z]

# investment and production variables
@variables m begin
    investment[area in areas, tech in techs]
    production[area in areas, tech in techs, hour in hours]
end

# hourly demand balance
@constraint(m, hourly_production[area in areas, hour in hours],
    sum(production[area, tech, hour] for tech in techs) ≀ 5
)

that produces the following constraint:

2-dimensional DenseAxisArray{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape},2,...} with index sets:
    Dimension 1, [:a, :b, :c]
    Dimension 2, 1:6
And data, a 3Γ—6 Matrix{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
 hourly_production[a,1] : production[a,x,1] + production[a,y,1] + production[a,z,1] <= 5  …  hourly_production[a,6] : production[a,x,6] + production[a,y,6] + production[a,z,6] <= 5
 hourly_production[b,1] : production[b,x,1] + production[b,y,1] + production[b,z,1] <= 5     hourly_production[b,6] : production[b,x,6] + production[b,y,6] + production[b,z,6] <= 5
 hourly_production[c,1] : production[c,x,1] + production[c,y,1] + production[c,z,1] <= 5     hourly_production[c,6] : production[c,x,6] + production[c,y,6] + production[c,z,6] <= 5

and register the name in the model when I call m

julia> m
A JuMP Model
β”œ solver: none     
β”œ objective_sense: FEASIBILITY_SENSE
β”œ num_variables: 63
β”œ num_constraints: 18
β”‚ β”” AffExpr in MOI.LessThan{Float64}: 18
β”” Names registered in the model
  β”” :hourly_production, :investment, :production

Now I am trying to create the same constraint but inside a loop, which is applicable to my actual much larger model

for area in areas
    for hour in hours
        @constraint(m, hourly_production[area in areas, hour in hours],
            sum(production[area, tech, hour] for tech in techs) ≀ 5
        )
    end
end

but returns this error, which is understandable

ERROR: An object of name hourly_production is already attached to this model. If this
    is intended, consider using the anonymous construction syntax, for example,
    `x = @variable(model, [1:N], ...)` where the name of the object does
    not appear inside the macro.

interestingly, with print(m), these constraints are made

julia> print(m)
Feasibility
Subject to
 hourly_production[a,1] : production[a,x,1] + production[a,y,1] + production[a,z,1] <= 5
 hourly_production[b,1] : production[b,x,1] + production[b,y,1] + production[b,z,1] <= 5
 hourly_production[c,1] : production[c,x,1] + production[c,y,1] + production[c,z,1] <= 5
 hourly_production[a,2] : production[a,x,2] + production[a,y,2] + production[a,z,2] <= 5
 hourly_production[b,2] : production[b,x,2] + production[b,y,2] + production[b,z,2] <= 5
 hourly_production[c,2] : production[c,x,2] + production[c,y,2] + production[c,z,2] <= 5
 hourly_production[a,3] : production[a,x,3] + production[a,y,3] + production[a,z,3] <= 5
 hourly_production[b,3] : production[b,x,3] + production[b,y,3] + production[b,z,3] <= 5
 hourly_production[c,3] : production[c,x,3] + production[c,y,3] + production[c,z,3] <= 5
 hourly_production[a,4] : production[a,x,4] + production[a,y,4] + production[a,z,4] <= 5
 hourly_production[b,4] : production[b,x,4] + production[b,y,4] + production[b,z,4] <= 5
 hourly_production[c,4] : production[c,x,4] + production[c,y,4] + production[c,z,4] <= 5
 hourly_production[a,5] : production[a,x,5] + production[a,y,5] + production[a,z,5] <= 5
 hourly_production[b,5] : production[b,x,5] + production[b,y,5] + production[b,z,5] <= 5
 hourly_production[c,5] : production[c,x,5] + production[c,y,5] + production[c,z,5] <= 5
 hourly_production[a,6] : production[a,x,6] + production[a,y,6] + production[a,z,6] <= 5
 hourly_production[b,6] : production[b,x,6] + production[b,y,6] + production[b,z,6] <= 5
 hourly_production[c,6] : production[c,x,6] + production[c,y,6] + production[c,z,6] <= 5

also registered if I call m

julia> m
A JuMP Model
β”œ solver: none
β”œ objective_sense: FEASIBILITY_SENSE
β”œ num_variables: 63
β”œ num_constraints: 18
β”‚ β”” AffExpr in MOI.LessThan{Float64}: 18
β”” Names registered in the model
  β”” :hourly_production, :investment, :production

which is similar to the ones produced using the previous syntax but with the error.

if I use base_name

for area in areas
    for hour in hours
        @constraint(m, [area in areas, hour in hours],
            sum(production[area, tech, hour] for tech in techs) ≀ 5,
            base_name = "hourly_production"
        )
    end
end

then there is no error, but the generated constraints are much more than it is needed

Is this behaviour intended? I mean the constraints are registered, it is just the error that prevents it from progressing

Should I define each constraint constructor before the loop and then fill it in the loop?

The documentation Constraint names has no example of such naming in a loop

My intention is that I want to name all my constraints so that I can investigate the shadow prices

Thanks very much in advance!!

Hmm this suggested solution does not work, it has the same error message

ERROR: An object of name constraint_name is already attached to this model. If this
    is intended, consider using the anonymous construction syntax, for example,
    `x = @variable(model, [1:N], ...)` where the name of the object does
    not appear inside the macro.

and when I use print(m) it does not register the constraints fully, so only one of the term is registered in the model

julia> print(m)
Feasibility
Subject to
 constraint_name : production[a,x,1] + production[a,y,1] + production[a,z,1] <= 5

You need

for area in areas
    for hour in hours
        @constraint(
            m,
            sum(production[area, tech, hour] for tech in techs) <= 5,
            base_name = "hourly_production"
        )
    end
end

Note that

for area in areas
    for hour in hours
        @constraint(m, [area in areas, hour in hours],

is equivalent to

for area in areas
    for hour in hours
        for area in areas
            for hour in hours
                @constraint(m,

You don’t need the [area in areas, hour in hours] part :smile:

Ah I see, but if I use this approach, the hourly_production is not registered in the model

julia> m
A JuMP Model
β”œ solver: none
β”œ objective_sense: FEASIBILITY_SENSE
β”œ num_variables: 63
β”œ num_constraints: 18
β”‚ β”” AffExpr in MOI.GreaterThan{Float64}: 18
β”” Names registered in the model
  β”” :investment, :production

which then I could not check the shadow price using shadow_price(hourly_production) once I solve the model

See this part of the docs for a discussion of the difference between base_name and the registered name: Constraints Β· JuMP

Note that the built-in data structures are entirely optional. You are free to implement any data structure that you like. The benefit of this is that JuMP is very flexible. The downside is that there isn’t one way to do something. You need to pick the best approach for your problem.

You could do:

c_hourly_production = Dict{Any,Any}()
for area in areas, hour in hours
    c_hourly_production[(area, hour)] = @constraint(
        m,
        sum(production[area, tech, hour] for tech in techs) <= 5,
        base_name = "hourly_production"
    )
end
model[:c_hourly_production] = c_hourly_production

or you could do:

c_hourly_production = Matrix{Any}(undef, length(areas), length(hours))
for (i, area) in enumerate(areas)
    for (j, hour) in enumerate(hours)
        c_hourly_production[i, j] = @constraint(
            m,
            sum(production[area, tech, hour] for tech in techs) <= 5,
            base_name = "hourly_production"
        )
    end
end
model[:c_hourly_production] = c_hourly_production

or you could do:

c_hourly_production = Containers.DenseAxisArray(
    Matrix{ConstraintRef}(undef, length(areas), length(hours)),
    areas,
    hours,
)
for area in areas, hour in hours
    c_hourly_production[area, hour] = @constraint(
        m,
        sum(production[area, :, hour]) <= 5,
        base_name = "hourly_production[$area, $hour]"
    )
end
model[:c_hourly_production] = c_hourly_production
1 Like

Thank you very much @odow !!
These work wonderful !

I will see which has has the best performance on model generation in a much larger model