Gurobi / gurobipy-pandas

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

Improve data validation #55

Open simonbowly opened 1 year ago

simonbowly commented 1 year ago

Some cases of invalid data produce error messages from the gurobipy layer, but we could possibly catch them earlier and provide a better error message by attempting a cast to numeric type. The difficulty is distinguishing from cases which are legitimate gurobipy objects (would be easier with extension types for gurobipy objects).

Example:

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

model = gp.Model()
i = pd.Index([1, 2, 3], name="i")
x = gppd.add_vars(model, i, name="x")
rhs = pd.Series(index=i, data=[1, 2, "a"])
gppd.add_constrs(model, x, GRB.EQUAL, rhs, name="c")

Output:

Traceback (most recent call last):
  File "src/gurobipy/model.pxi", line 3256, in gurobipy.Model.addLConstr
ValueError: could not convert string to float: 'a'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "src/gurobipy/model.pxi", line 3259, in gurobipy.Model.addLConstr
gurobipy.GurobiError: Invalid argument to Model.addLConstr

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/bowly/workspaces/gurobipy-pandas/bug.py", line 10, in <module>
    gppd.add_constrs(model, x, GRB.EQUAL, rhs, name="c")
  File "/Users/bowly/workspaces/gurobipy-pandas/src/gurobipy_pandas/api.py", line 198, in add_constrs
    return add_constrs_from_series(
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/bowly/workspaces/gurobipy-pandas/src/gurobipy_pandas/constraints.py", line 89, in add_constrs_from_series
    return add_constrs_from_dataframe(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/bowly/workspaces/gurobipy-pandas/src/gurobipy_pandas/constraints.py", line 57, in add_constrs_from_dataframe
    return _add_constrs_from_dataframe_args(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/bowly/workspaces/gurobipy-pandas/src/gurobipy_pandas/constraints.py", line 193, in _add_constrs_from_dataframe_args
    constrs = [
              ^
  File "/Users/bowly/workspaces/gurobipy-pandas/src/gurobipy_pandas/constraints.py", line 194, in <listcomp>
    _add_constr(
  File "/Users/bowly/workspaces/gurobipy-pandas/src/gurobipy_pandas/constraints.py", line 144, in _add_constr
    return model.addLConstr(lhs, sense, rhs, name=name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "src/gurobipy/model.pxi", line 3350, in gurobipy.Model.addLConstr
gurobipy.GurobiError: Invalid argument to Model.addLConstr

Calling pd.to_numeric(rhs) gives a potentially more informative error to the user:

Traceback (most recent call last):
  File "pandas/_libs/lib.pyx", line 2369, in pandas._libs.lib.maybe_convert_numeric
ValueError: Unable to parse string "a"

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/bowly/workspaces/gurobipy-pandas/bug.py", line 10, in <module>
    pd.to_numeric(rhs)
  File "/Users/bowly/.pyenv/versions/3.11.2/lib/python3.11/site-packages/pandas/core/tools/numeric.py", line 185, in to_numeric
    values, _ = lib.maybe_convert_numeric(
                ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "pandas/_libs/lib.pyx", line 2411, in pandas._libs.lib.maybe_convert_numeric
ValueError: Unable to parse string "a" at position 2