メインコンテンツへスキップ

model.addConstrs breaking sometimes after Python version change

回答済み

コメント

3件のコメント

  • Riley Clement
    • Gurobi Staff

    Hi Javier,

    I don't have a full answer at this stage, just wanted to note that it seems to work ok in Python 3.12 if you remove the square brackets from the constraint, i.e.

    m.addConstrs( (v[t] == g.quicksum(g.quicksum(sa.select(i,t,'*'))*ponderator[i] for i in NAMES) for t in TIMES) )

    I suspect the issue is coming from nested quicksums.  For stylistic reasons I try to avoid nested quicksums wherever possible, e.g.

    m.addConstrs( (v[t] == g.quicksum(v*ponderator[i] for v in sa.select(i,t,'*') for i in NAMES) for t in TIMES) )

    I'll see if any of our developers have a comment on this issue.

    - Riley

     

    0
  • Javier Jiménez Sicardo
    • Gurobi-versary
    • First Comment
    • First Question

    Hello Riley,

    Thank you for your prompt response and comment.

    The first code (removing the square brackets) works well and does what it's needed. I like adding these to quicksums to inspect before adding in a bigger model.

    The second code only works for me if I put the NAMES before the variable query, i.e.:

    m.addConstrs( (v[t] == g.quicksum( val*p[i] for i in NAMES for val in sa.select(i,t,'*')  ) for t in TIMES) )

    I've changed v to val for clarity. These are equal but I like to translate the mathematical model directly. I'll consider that in the future when I get and interiorise the change.

    So this solves the problem for me.

    I'm very curious for a technical explanation on this issue: if I recall correctly addConstrs just exhausts the generator or unpacks what it is sent, so both should be functionally identical. There's a big chance I wouldn't understand it, however.

    Thank you very much for your time,

    Javier

    0
  • Riley Clement
    • Gurobi Staff

    Hi Javier,

    There is a bit of black magic happening under the hood of gurobipy when you use addConstrs.  This function does introspection on the provided generator frame and extracting the keys correctly requires looking both at the local scope variables and the bytecode of the generator expression.

    Without looking into the details I guess that there was a small change in CPython 3.12 for how the bytecode is created and with the nested quicksums you have found an edge case which breaks what gurobipy is trying to do.

    Although addConstrs is still part of many examples found on our website, we no longer recommend it.  From another of our recent posts on addConstrs, the following comment was made by our dev Simon:

    To give some context: gurobipy is now ~15 years old; some features are there to support compact modelling statements in older versions of Python, and are maintained for users who rely on them. I would say that this particular method is now redundant, since f-strings arrived in Python 3.6 and made producing name strings very easy. For example this code from the documentation:

    constrs = m.addConstrs(
        (x[i,j] == 0 for i in range(4) for j in range(4) if i != j),
        name='c'
    )

    is exactly equivalent to this:

    constrs = gp.tupledict({
        (i, j): m.addConstr(x[i, j] == 0, name=f"c[{i},{j}]")
        for i in range(4)
        for j in range(4)
        if i != j
    })

    or this, if you don’t really need to keep track of the constraint objects returned:

    for i in range(4):
        for j in range(4):
            if i != j:
                m.addConstr(x[i, j] == 0, name=f"c[{i},{j}]")

    or this, to make better use of the Python standard library for this specific case:

    for i, j in itertools.permutations(range(4), r=2):
        m.addConstr(x[i, j] == 0, name=f"c[{i},{j}]")

    To me the latter examples are preferable; they are self-documenting and typically faster. I recommend using that style instead of addConstrs.

    - Riley

    0

サインインしてコメントを残してください。