Naming Variables & Constraints

It’s good practice to name variables and constraints in optimization models. One good reason is that once a model is formulated from a dataset, you can write it to LP format for checking and debugging purposes.

Of course, when working with large series and dataframes, we don’t want to name every variable individually. So, gurobipy-pandas generates names for variables and constraints automatically based on the index passed in. The behaviour is similar to addVars and addConstrs in gurobipy.

Note that the conventions below apply to all methods for adding variables and constraints (using free functions or accessors, from a series, dataframe, or index). If using a series or dataframe, naming will be based on the index of that series or dataframe.

>>> import pandas as pd
>>> import gurobipy as gp
>>> from gurobipy import GRB
>>> import gurobipy_pandas as gppd

If no name argument is provided, default names are generated by Gurobi. These names are sequential and based on the order in which variables and constraints were added to the model.

>>> model = gp.Model()
>>> index = pd.RangeIndex(5, 10)
>>> x = gppd.add_vars(model, index)
>>> model.update()
>>> x
5    <gurobi.Var C0>
6    <gurobi.Var C1>
7    <gurobi.Var C2>
8    <gurobi.Var C3>
9    <gurobi.Var C4>
dtype: object

If a name argument is given, this is used as a prefix. Full names are generated based on index values.

>>> index = pd.RangeIndex(5, 8)
>>> x = gppd.add_vars(model, index, name="x")
>>> model.update()
>>> x
5    <gurobi.Var x[5]>
6    <gurobi.Var x[6]>
7    <gurobi.Var x[7]>
Name: x, dtype: object

This also works for multi-indexes.

>>> multiindex = pd.MultiIndex.from_arrays([
... [5, 5, 6, 7, 7],
... ['a', 'b', 'a', 'b', 'c'],
... ])
>>> y = gppd.add_vars(model, multiindex, name="y")
>>> model.update()
>>> y
5  a    <gurobi.Var y[5,a]>
   b    <gurobi.Var y[5,b]>
6  a    <gurobi.Var y[6,a]>
7  b    <gurobi.Var y[7,b]>
   c    <gurobi.Var y[7,c]>
Name: y, dtype: object

gurobipy-pandas will perform some cleanup of generated names by default. Generated names are adjusted so that they are admissible to the LP file format. This requires stripping spaces and some special characters from the index values as needed. Note that the name argument you provide is not adjusted, this formatting only applies to strings automatically generated from index values. Timestamps are a common offender here, as are string indexes containing whitespace. For examples:

>>> location_index = pd.Index(["Mount Everest", "Mariana Trench"], name='location')
>>> datetime_index = pd.date_range(start=pd.Timestamp(2022, 11, 9), freq='D', periods=2, name='date')
>>> index = pd.MultiIndex.from_product([location_index, datetime_index])
>>> index
MultiIndex([( 'Mount Everest', '2022-11-09'),
            ( 'Mount Everest', '2022-11-10'),
            ('Mariana Trench', '2022-11-09'),
            ('Mariana Trench', '2022-11-10')],
           names=['location', 'date'])
>>> height = gppd.add_vars(model, index, name="height")
>>> model.update()
>>> height.gppd.VarName    
location        date
Mount Everest   2022-11-09     height[Mount_Everest,2022_11_09T00_00_00]
                2022-11-10     height[Mount_Everest,2022_11_10T00_00_00]
Mariana Trench  2022-11-09    height[Mariana_Trench,2022_11_09T00_00_00]
                2022-11-10    height[Mariana_Trench,2022_11_10T00_00_00]
Name: height, dtype: object

Notice that the variable names are formatted to avoid spaces, hyphens, and colons.

You can get the unadulterated, unmodified, unredacted string representations of all your indexes if you prefer, by passing index_formatter='disable':

>>> height = gppd.add_vars(model, index, name="height", index_formatter='disable')
>>> model.update()
>>> height.gppd.VarName    
location        date
Mount Everest   2022-11-09     height[Mount Everest,2022-11-09 00:00:00]
                2022-11-10     height[Mount Everest,2022-11-10 00:00:00]
Mariana Trench  2022-11-09    height[Mariana Trench,2022-11-09 00:00:00]
                2022-11-10    height[Mariana Trench,2022-11-10 00:00:00]
Name: height, dtype: object

Finally, you can provide custom name mappers to apply to named indexes. This is particularly useful for timestamps, when you want to keep the index as-is in your pandas data structures, but still want compact and clear variable names.

>>> index_formatter = {"date": lambda index: index.strftime("%y%m%d")}
>>> height = gppd.add_vars(model, index, name="height", index_formatter=index_formatter)
>>> model.update()
>>> height.gppd.VarName    
location        date
Mount Everest   2022-11-09     height[Mount_Everest,221109]
                2022-11-10     height[Mount_Everest,221110]
Mariana Trench  2022-11-09    height[Mariana_Trench,221109]
                2022-11-10    height[Mariana_Trench,221110]
Name: height, dtype: object

In the above example, index_formatter is a dictionary with keys corresponding to named levels in the index. Values in the dictionary are functions applied to the index in question. Each function should return an iterable of formatted values which will subsequently be used in naming (the original index is unaffected, in this case the ‘date’ index level is still a DatetimeIndex). Note also that the default mapping (whitespace and special character replacement) is applied any columns with no corresponding key in the mapper.

For a single index, you can provide a callable to index_formatter. If a callable is provided for a multiindex, it will be applied to each index level.