Gurobi / gurobipy-pandas

Convenience wrapper for building optimization models from pandas data
https://gurobipy-pandas.readthedocs.io
Apache License 2.0
95 stars 15 forks source link

Use pandas machinery for pd_add_constrs expression parsing #11

Closed rluce closed 2 years ago

rluce commented 2 years ago

IMO it would be very good to have this string expression based API for adding constraints using a column relation (see regression.py for context).

fit = (
    relation.grb.addVars(model, name="U")
    .grb.addVars(model, name="V")
    .join(y_train)
    .grb.addConstrs(model, "target == MX + U - V", name="fit")
)

This currently works but with a really basic implementation and I can see it having a lot of dodgy edge-cases.

pandas.DataFrame.eval has some well-defined behaviour (e.g. clear split between column names and local variables) but doesn't work out of the box for this use case as it is targeted at numeric types. We should try to leverage at least some of pandas' in-built functions here to avoid surprises.

simonbowly commented 2 years ago

This may be simpler than I thought: just split the string on the comparator and use pd.eval() for left and right substrings.

simonbowly commented 2 years ago

Unfortunately this cannot work, Dataframe.eval seems entirely incompatible with arithmetic on object dtypes. We have to roll our own eval logic:

>>> import gurobipy as gp
>>> import pandas as pd
>>> import gurobipy_pandas
>>> df = pd.RangeIndex(3).grb.pd_add_vars(m, name='x').to_frame()
>>> df
                   x
0  <gurobi.Var x[0]>
1  <gurobi.Var x[1]>
2  <gurobi.Var x[2]>
>>> df.eval("x + 1")
...
TypeError: unsupported operand type(s) for +: 'object' and '<class 'int'>'
>>> df.eval("x + 1", parser='python')
...
TypeError: unsupported operand type(s) for +: 'object' and '<class 'int'>'
>>> df.eval("x + 1", engine='python')
...
TypeError: unsupported operand type(s) for +: 'object' and '<class 'int'>'