0

I am working on a small optimization model with some disjunctions. The way I did in a concrete model worked well:

from pyomo.environ import *
m = ConcreteModel()
m.d1 = Disjunct()
m.d2 = Disjunct()
m.d1.sub1 = Disjunct()
m.d1.sub2 = Disjunct()
m.d1.disj = Disjunction(expr=[m.d1.sub1, m.d1.sub2])
m.disj = Disjunction(expr=[m.d1, m.d2])

But now I tranfered the concrete model into an abstract formulation. I was able to fix everything instead of nesting the disjunctions. The way I did it was like:

#Disjunct 1        
def _op_mode1(self, op_mode, t):
            m = op_mode.model()
            op_mode.c1 = po.Constraint(expr=m.x[t] == True)

#Disjunct 2       
        def _op_mode2(self, op_mode, t):
            m = op_mode.model()
            op_mode.c1 = po.Constraint(expr=m.x[t] == False)
#Disjunction 1
        def _op_modes(self,m, t):
            return [m.mode1[t], m.mode2[t]]

#Adding Components
            self.model.del_component("mode1")
            self.model.del_component("mode1_index")
            self.model.add_component("mode1", pogdp.Disjunct(self.model.T, rule=self._op_mode1))

            self.model.del_component("mode2")
            self.model.del_component("mode2_index")
            self.model.add_component("mode2", pogdp.Disjunct(self.model.T, rule=self._op_mode1))

            self.model.del_component("modes")
            self.model.del_component("modes_index")
            self.model.add_component("modes", pogdp.Disjunction(self.model.T, rule=self._op_modes))`

As I previously mentioned, this works fine. But I haven`t found any way to nest the disjunctions. Pyomo alsways complains about the second layer of the disjuncts like "sub1".

Would anybody could give me a hint?

Many greetings

Joerg

JBentz
  • 1

4 Answers4

1

The issue with the latest model above is that you are declaring m.d1 and m.d2 for each element of m.T, but they overwrite each other each time since they have the same name. You should be seeing warning messages logged for this. So if you uncomment your pprint of the model, you'll see that you only have the last ones you declared (with constraints on x[10]). So the first 9 Disjunctions in m.disjunction_ are disjunctions of Disjuncts that do not exist. The simplest fix for this is to give the disjuncts unique names when you declare them:

import pyomo.environ as pyo
import pyomo.gdp as pogdp

model = pyo.ConcreteModel()
model.T = pyo.RangeSet(0, 10)
model.x=pyo.Var(model.T,bounds=(-2, 10))
model.y=pyo.Var(model.T,bounds=(20, 30))

# This was also a duplicate declaration:
#model.disjunction_ = pogdp.Disjunction(model.T)

def d1(m, t):
    disj = pogdp.Disjunct()
    disj.c1= pyo.Constraint(expr=m.x[t] <= 10)
    m.add_component('d1_%s' % t, disj)
    return disj
def d2(m, t):
    disj = pogdp.Disjunct()
    disj.c1= pyo.Constraint(expr=m.x[t] >= 10)
    m.add_component('d2_%s' % t, disj)
    return disj

# sum x,y
def obj_rule(m):
    return pyo.quicksum(pyo.quicksum([m.x[t] + m.y[t]], linear=False) for t in
                        m.T)
model.obj = pyo.Objective(rule=obj_rule)
def _op_mode_test(m, t):
    disj1 = d1(m, t)
    disj2 = d2(m, t)
    return [disj1, disj2]

model.disjunction_ = pogdp.Disjunction(model.T, rule=_op_mode_test)

However, it would be cleaner (and probably easier down the line) to index the Disjuncts by m.T as well, since that's basically what the unique names are doing.

Emma
  • 26
  • 2
  • Thank you very much!!! That solved the problem!!! I indexed the disjunctions and added some sub disjunctions. But, is it possible that bigm is the only valid transformation for nested disjunctions? Hull provides wrong results, mbigm is probably not supporting nested disjunctions. I will write my new code in a new answer. Maybe I still miss something. – JBentz Jan 16 '23 at 12:47
  • You've found a couple bugs: 1) Both `bigm` and `hull` should correctly transform nested GDPs. The fact that they are giving different results here is a bug. You are right that `mbigm` does not currently support nested GDPs, but that leads me to: 2) I suspect that the model you posted is not what you intended. It is not actually nested: Every `Disjunction` you create is on the model proper. For it to be nested, you would need to declare the nested `Disjunction` on its parent `Disjunct`--I'm assuming you intend that to be `m.disj1[t]`. So `mbigm` *should* work on what you've written. – Emma Jan 17 '23 at 14:49
  • If you could report these on [Pyomo's Github](https://github.com/Pyomo/pyomo/issues) that would be much appreciated. – Emma Jan 17 '23 at 14:51
0

Block (and hence Disjunct rules) are passed the block (or disjunct) to be populated as the first argument. So, an "abstract" equivalent too your concrete model might look something like this:

model = AbstractModel()

@model.Disjunct()
def d1(d):
    # populate the `d` disjunct (i.e., `model.d1`) here
    pass

@model.Disjunct()
def d2(d):
    @d.Disjunct()
    def sub1(sd):
        # populate the 'sub1' disjunct here
        pass

    @d.Disjunct()
    def sub2(sd):
        # populate the 'sub2' disjunct here
        pass

    d.disj = Disjunction(expr=[d.sub1, d.sub2])

model.disj = Disjunction(expr=[model.d1, model.d2])

There is a more fundamental question as to why you are converting your model over to "abstract" form. Pyomo Abstract models were mostly devised to be familiar to people coming from modeling in AMPL. While they will work with block-structured models, as AMPL was never really designed with blocks in mind, similarly block-oriented Abstract models tend to be unnecessarily cumbersome.

jsiirola
  • 2,259
  • 2
  • 9
  • 12
  • Dear @jsiirola, first of all many thanks for your answer. We switched to an concrete model and got our model to work perfectly fine. Even quite large and indexed problems worked well in version 6.4.2. But since version 6.4.3 and 6.4.4 it is not working anymore. A simplified version of our model would be: please see my 2nd answer – JBentz Jan 12 '23 at 18:53
0

Here ist our new model:

import pyomo.environ as pyo
import pyomo.gdp as pogdp

model = pyo.ConcreteModel()
model.T = pyo.RangeSet(0,10)
model.x=pyo.Var(model.T,bounds=(-2, 10))
model.y=pyo.Var(model.T,bounds=(20, 30))

model.disjunction_=pogdp.Disjunction(model.T)

def d1(m,t):
    m.d1 = pogdp.Disjunct()
    m.d1.c1= pyo.Constraint(expr=m.x[t] <=10)
def d2(m,t):
    m.d2 = pogdp.Disjunct()
    m.d2.c1= pyo.Constraint(expr=m.x[t] >=10)

# sum x,y
def obj_rule(m):
    return pyo.quicksum(pyo.quicksum([m.x[t] + m.y[t]], linear=False) for t in m.T)
model.obj = pyo.Objective(rule=obj_rule)
def _op_mode_test(m,t):
    d1(m,t)
    d2(m,t)
    return [m.d1,m.d2]

model.disjunction_=pogdp.Disjunction(model.T,rule=_op_mode_test)

#model.pprint()
pyo.TransformationFactory('gdp.bigm').apply_to(model)

solver = pyo.SolverFactory('baron')
solver.solve(model)
print(pyo.value(model.obj))

I think it has something to do with the RangeSet. For a single step it works, but with more than one steps it throws an error: AttributeError: 'NoneType' object has no attribute 'component'

It would be great if you could have a look on it.

Many thanks

JBentz
  • 1
0

Here is the code which works pretty fine with bigm, but not with mbigm or hull transformation:

import pyomo.environ as pyo
import pyomo.gdp as pogdp

model = pyo.ConcreteModel()
model.T = pyo.RangeSet(2)
model.x=pyo.Var(model.T,bounds=(1, 10))
model.y=pyo.Var(model.T,bounds=(1, 100))


def _op_mode_sub(m, t):
    m.disj1[t].sub1 = pogdp.Disjunct()
    m.disj1[t].sub1.c1= pyo.Constraint(expr=m.y[t] == 60)
    m.disj1[t].sub2 = pogdp.Disjunct()
    m.disj1[t].sub2.c1= pyo.Constraint(expr=m.y[t] == 100)
    return [m.disj1[t].sub1, m.disj1[t].sub2]

def _op_mode(m, t):
    m.disj2[t].c1= pyo.Constraint(expr=m.y[t] >= 3)
    m.disj2[t].c2= pyo.Constraint(expr=m.y[t] <= 5)
    return [m.disj1[t], m.disj2[t]]

model.disj1 = pogdp.Disjunct(model.T)
model.disj2 = pogdp.Disjunct(model.T)
model.disjunction1sub = pogdp.Disjunction(model.T, rule=_op_mode_sub)
model.disjunction1 = pogdp.Disjunction(model.T, rule=_op_mode)


def obj_rule(m, t):
    return pyo.quicksum(pyo.quicksum([m.x[t] + m.y[t]], linear=False) for t in m.T)

model.obj = pyo.Objective(rule=obj_rule)

model.pprint()

gdp_relax=pyo.TransformationFactory('gdp.bigm')
gdp_relax.apply_to(model)

solver = pyo.SolverFactory('glpk')
solver.solve(model)
print(pyo.value(model.obj))
JBentz
  • 1
  • 1
    Note that your *Disjunctions* aren't nested: you are only nesting the Disjuncts (which doesn't really mean anything). As it is the Disjunction that defines the OR (or XOR) relationship, and that is not inside a Disjunct, it will be enforced globally. – jsiirola Jan 17 '23 at 16:57