Skip to main content

tupledict behavior changed from version 10 to 11

Answered

Comments

4 comments

  • Riley Clement
    Gurobi Staff Gurobi Staff

    Hi Christian,

    tupledicts were introduced many years ago when Python2 was the dominant Python version and Python3 was still very new.  As a result our tupledict was designed to be similar to Python2 dicts (as good practice would suggest).  Fast forward to the present time, where Python2 has reached end-of-life and rarely used, and we find that tupledict in gurobipy 10 no longer aligns with the Python3 dict object as much as we would like it to.  Eg, the result of calling dict.keys() is a dict_keys (a keys view) as you point out, and so we want to align to this behavior resulting in the change you notice in gurobipy 11.

    If you want to use gurobipy 11 with the existing functionality there are three options.  The first is to use an undocumented attribute:

    import gurobipy as gp
    gp.tupledict._legacy_tupledict_behavior = True

    Note that the preceding _ indicates this attribute is "private" and not really intended to be used, and the behavior or existence can be changed in future versions of gurobipy without warning or documentation.

    The second, safer option, is to cast the dict_views to a tuplelist when you want to, eg

    x = m.addVars(5)
    my_tuplelist_keys = gp.tupledict(x.keys())

    The third option, which is the same vein as the second, is to monkeypatch this approach to happen automatically:

    import gurobipy as gp

    keys = gp.tupledict.keys

    def _keys(self):
        return gp.tuplelist(keys(self))

    gp.tupledict.keys = _keys

    After this code has run then tupledict.keys() will return a tuplelist.

    - Riley

    0
  • Christian Ruf
    Gurobi-versary
    First Comment
    First Question

    Thank you, Riley! 

    Will this be slow though? 

    0
  • Riley Clement
    Gurobi Staff Gurobi Staff

    It will be slower, but I would be surprised if you were calling it so often that this had a meaningful impact.

    We can use timeit to get timings for a test (and then take the minimum to help eliminate noise).  In the test I'm calling .keys() on a tupledict with a million variables:

    import numpy as np
    import timeit

    setup = """
    import gurobipy as gp
    m = gp.Model()
    x = m.addVars(1000000)
    m.update()
    """

    statement= """
    x.keys()
    """

    print(np.min(timeit.repeat(stmt=statement, setup=setup, number=1, repeat=50)))

    On v10 this gave me 0.247 seconds and on v11 0.262 seconds (using my macbook).

    Switching out the setup:

    setup = """
    import gurobipy as gp

    keys = gp.tupledict.keys

    def _keys(self):
        return gp.tuplelist(keys(self))

    gp.tupledict.keys = _keys

    m = gp.Model()
    x = m.addVars(1000000)
    m.update()
    """

    and rerunning with v11 produced 0.305 seconds.  Using option 2) instead of monkeypatching will run slightly faster.

    Would you expect this to have consequences for your application?

    - Riley

    0
  • Christian Ruf
    Gurobi-versary
    First Comment
    First Question

    Thank you, Riley! I also think the impact will not be noticeable. 

    0

Please sign in to leave a comment.