pythonoptimizationpyomopulp

Extending Pyomo Constraint


I am new to Pyomo and used Pulp for my model creation till now. I wanted to understand the procedural equivalent in Pyomo of steps I used to follow in Pulp to extend a constraint.

Pulp Implementation:

con = pulp.LpConstraintVar(name="Cons1", sense=pulp.LpConstraintEQ, rhs=200)
model.addConstraint(con.constraint)

In pulp this line of code will essentially create a empty constraint for me, which will look like this (I know will be infeasible if run at this stage, but won't run the code until cons is complete):

Cons1: 0 == 200

I can then add variables to this constraint based on my requirement. For example if we consider x is a variable to be added here, I can do this operation:

con.addVariable(x, 2)

It will result in me having extended (updated) the previous constraint to:

Cons1: 2x + 0 == 200

I can keep on calling addVariable to add as many variables I want inside multiple parts of code and it will work.

I wanted to understand how can I do similar thing in Pyomo, as this gives me better control over my constraint as compared to normal syntax->

model.Cons1= Constraint(expr = model.x*2 == 200)

Solution

  • You can use a named expression (Expression) to facilitate this:

    >>> model = ConcreteModel()
    >>> model.x = Var()
    >>> model.Cons1_body = Expression(expr=0)
    >>> model.Cons1 = Constraint(expr=model.Cons1_body == 200)
    >>> model.Cons1_body += model.x * 2
    >>> model.pprint()
    1 Var Declarations
        x : Size=1, Index=None
            Key  : Lower : Value : Upper : Fixed : Stale : Domain
            None :  None :  None :  None : False :  True :  Reals
    
    1 Expression Declarations
        Cons1_body : Size=1, Index=None
            Key  : Expression
            None :        2*x
    
    1 Constraint Declarations
        Cons1 : Size=1, Index=None, Active=True
            Key  : Lower : Body       : Upper : Active
            None : 200.0 : Cons1_body : 200.0 :   True
    
    3 Declarations: x Cons1_body Cons1
    

    The complexity here arises from the philosophy in Pyomo that expressions should be immutable - that is, once created, the structure of tree that represents the expression graph can't be changed. This means that actions like replacing variables or adding terms requires creating a (partially) new expression graph. The way this impacts Constraints is that Pyomo doesn't really have a native column generation API. Instead, you would replace the Constraint expression with a new expression:

    model.Cons1 = Constraint(expr = model.x*2 == 200)
    model.Cons1 = model.Cons1.expr.arg(0) + model.y * 3 == model.Cons1.expr.arg(1)
    

    The named expression (Expression) component provides a structured way to break the expression immutability: named expressions behave like "pointers" when they are used in expressions, and the pointer is allowed to be changed without rebuilding the expression tree(s) that the named expression appear in.

    Some notes: