GGaudio04
GGaudio04

Reputation: 83

Generate constraints in pyomo with a loop and changing inputs

As an input for my optimization model I have a node structure that links variables with each other as in (simplified version):

from __future__ import annotations
import typing as T

import pyomo.environ as po


class DummyNode:
    def __init__(self, name: str):
        self.CHILDREN: T.List[DummyNode] = []
        self.MOTHER: DummyNode = None
        self.NAME = name

    def insert_child_below(self, child: DummyNode):
        child.MOTHER = self
        self.CHILDREN.append(child)


A = DummyNode("A")
B = DummyNode("B")
C = DummyNode("C")

A.insert_child_below(B)
A.insert_child_below(C)

I need to iteratively generate constraints that are able to capture the relationships between the variables. I have tried the following (dummy model for the purpose of this question, constraints dont really make sense):

model = po.AbstractModel("dummy")

for element in [A, B, C]:
    # generate variable for each node
    setattr(model, element.NAME, po.Var())

    # set constraint for each node, linking it to its mother
    def constraint(model):
        return (
            getattr(model, element.NAME)
            + (getattr(model, element.MOTHER.NAME) if element.MOTHER is not None else 0)
            == 3
        )

    setattr(model, f"{element.NAME}_cons", po.Constraint(rule=constraint))


out = model.construct()

Nevertheless, I guess because of the way pyomo constructs models, I end up generating the same constraint three times:

3 Constraint Declarations
    A_cons : Size=1, Index=None, Active=True
        Key  : Lower : Body  : Upper : Active
        None :   3.0 : C + A :   3.0 :   True
    B_cons : Size=1, Index=None, Active=True
        Key  : Lower : Body  : Upper : Active
        None :   3.0 : C + A :   3.0 :   True
    C_cons : Size=1, Index=None, Active=True
        Key  : Lower : Body  : Upper : Active
        None :   3.0 : C + A :   3.0 :   True

I've tried saving the constraint functions to an external dict, but that hasn't worked either. Any ideas?

Upvotes: 0

Views: 631

Answers (1)

AirSquid
AirSquid

Reputation: 11938

If you change your construct to a ConcreteModel it works as you intended. If you "bring your own data", I think the ConcreteModel is by far the best way to go anyhow.

That said, A couple of ideas...

  • I think using the getattr and setattr are confusing and I'm not sure if there are any hidden pitfalls with directly setting attributes within the model. I don't see it documented. Buyer beware!

  • It seems odd to use the nodes names as the variables. It seems much more intuitive to have a set of nodes and then whatever variables make sense around that like flow, or such. If you make the nodes a set, things seem to fall into place... And you can still use node-based methods as needed.

  • Caution on below: I've overwritten the __repr__ method for node to clean up the output, but I've broken the __repr__ contract because you cannot distinguish duplicates with this. You may need to alter that or prevent dupe names.

Example:

import pyomo.environ as po

class DummyNode:
    def __init__(self, name: str):
        self.CHILDREN: T.List[DummyNode] = []
        self.MOTHER: DummyNode = None
        self.NAME = name

    def insert_child_below(self, child: 'DummyNode'):
        child.MOTHER = self
        self.CHILDREN.append(child)

    def __repr__(self):
        return self.NAME


A = DummyNode("A")
B = DummyNode("B")
C = DummyNode("C")

A.insert_child_below(B)
A.insert_child_below(C)

model = po.ConcreteModel("dummy")

# SETS
model.N = po.Set(initialize=[A, B, C])

# VARS
model.flow = po.Var(model.N, domain=po.NonNegativeReals)

# CONSTRAINTS
# Example:  flow limit to parent...
@model.Constraint(model.N)
def flow_limit(model, n: DummyNode):
    if n.MOTHER is None:
        return model.flow[n] <= 3
    else:
        return model.flow[n] <= model.flow[n.MOTHER]

model.pprint()

Yields:

1 Set Declarations
    N : Size=1, Index=None, Ordered=Insertion
        Key  : Dimen : Domain : Size : Members
        None :     1 :    Any :    3 : {A, B, C}

1 Var Declarations
    flow : Size=3, Index=N
        Key : Lower : Value : Upper : Fixed : Stale : Domain
          A :     0 :  None :  None : False :  True : NonNegativeReals
          B :     0 :  None :  None : False :  True : NonNegativeReals
          C :     0 :  None :  None : False :  True : NonNegativeReals

1 Constraint Declarations
    flow_limit : Size=3, Index=N, Active=True
        Key : Lower : Body              : Upper : Active
          A :  -Inf :           flow[A] :   3.0 :   True
          B :  -Inf : flow[B] - flow[A] :   0.0 :   True
          C :  -Inf : flow[C] - flow[A] :   0.0 :   True

Upvotes: 1

Related Questions