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, :code:`gurobipy-pandas` generates names for variables and constraints automatically based on the index passed in. The behaviour is similar to :code:`addVars` and :code:`addConstrs` in :code:`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. .. doctest:: [naming] >>> import pandas as pd >>> import gurobipy as gp >>> from gurobipy import GRB >>> import gurobipy_pandas as gppd If no :code:`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. .. doctest:: [naming] >>> model = gp.Model() >>> index = pd.RangeIndex(5, 10) >>> x = gppd.add_vars(model, index) >>> model.update() >>> x 5 6 7 8 9 dtype: object If a :code:`name` argument is given, this is used as a prefix. Full names are generated based on index values. .. doctest:: [naming] >>> index = pd.RangeIndex(5, 8) >>> x = gppd.add_vars(model, index, name="x") >>> model.update() >>> x 5 6 7 Name: x, dtype: object This also works for multi-indexes. .. doctest:: [naming] >>> 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 b 6 a 7 b c Name: y, dtype: object :code:`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 :code:`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: .. doctest:: [naming] >>> 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 # doctest: +NORMALIZE_WHITESPACE 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 :code:`index_formatter='disable'`: .. doctest:: [naming] >>> height = gppd.add_vars(model, index, name="height", index_formatter='disable') >>> model.update() >>> height.gppd.VarName # doctest: +NORMALIZE_WHITESPACE 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. .. doctest:: [naming] >>> 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 # doctest: +NORMALIZE_WHITESPACE 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, :code:`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 :code:`index_formatter`. If a callable is provided for a multiindex, it will be applied to each index level.