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

How can I add constraints that include two indices simultaneously ? #57

Open Amoozegar opened 11 months ago

Amoozegar commented 11 months ago

I need to add constraints that include two indices on a same variable. Here is an example:

image

simonbowly commented 11 months ago

Hi @Amoozegar, for a continuous index you can use pandas' shift method to align the left and right hand sides of your constraint on the same index:

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

J = 5
model = gp.Model()

F = gppd.add_vars(model, pd.RangeIndex(1, J+1), name="F")

index = pd.RangeIndex(1, J)      # 1 .. J-1 index for constraints
constraints = gppd.add_constrs(
    model,
    F.shift(-1).loc[index],      # Shift F[j+1] term onto index j
    GRB.LESS_EQUAL,
    F.loc[index]
)

After updating the model you can check the result as follows:

>>> model.update()
>>> constraints.apply(model.getRow)
1    -1.0 F[1] + F[2]
2    -1.0 F[2] + F[3]
3    -1.0 F[3] + F[4]
4    -1.0 F[4] + F[5]
dtype: object
>>> constraints.gppd.Sense
1    <
2    <
3    <
4    <
dtype: object
>>> constraints.gppd.RHS
1    0.0
2    0.0
3    0.0
4    0.0
dtype: float64
rrandall1471 commented 11 months ago

@Amoozegar I would do it a bit differently than @simonbowly

If I started with the following df/series of variables:

image

I would do it with the following pandas .assign and .shift to do it all in one command, with the built in gurobipy-pandas accessor to add the constraint:

(
    df.assign(f_j_plus_1=lambda df: df["f_j"].shift(-1))
    .dropna()
    .gppd.add_constrs(
        model, "f_j_plus_1", grb.GRB.LESS_EQUAL, "f_j", name="constr_name"
    )
)

Which yields the following constraints:

Subject To
 constr_name[0]: - f[0] + f[1] <= 0
 constr_name[1]: - f[1] + f[2] <= 0
 constr_name[2]: - f[2] + f[3] <= 0
 constr_name[3]: - f[3] + f[4] <= 0
 constr_name[4]: - f[4] + f[5] <= 0
 constr_name[5]: - f[5] + f[6] <= 0
 constr_name[6]: - f[6] + f[7] <= 0
 constr_name[7]: - f[7] + f[8] <= 0
 constr_name[8]: - f[8] + f[9] <= 0
Amoozegar commented 11 months ago

Thanks for the response. How about formulating "Fi,j+1,z <= Fi,j,z" ?

rrandall1471 commented 11 months ago

Sure. You have to use a groupby then a shift to do that. Let's say we start with the following variables over i, j, and z

image

I can create a new column that contains the shifted variables using the following code:

df.assign(f_ij_plus_1z=lambda df: df["f_ijz"].groupby(['i', 'z']).shift(-1)).dropna()

which would yield the following DataFrame.

image

Then finally I can take that same code and using the gurobipy-pandas accessor can create the constraints:

(
    df.assign(f_ij_plus_1z=lambda df: df["f_ijz"].groupby(['i', 'z']).shift(-1))
    .dropna()
    .gppd.add_constrs(
        model, "f_ij_plus_1z", grb.GRB.LESS_EQUAL, "f_ijz", name="constr_name"
    )
)

And you can see it in the LP file written out:

Subject To
 constr_name[0,0,0]: - f[0,0,0] + f[0,1,0] <= 0
 constr_name[0,0,1]: - f[0,0,1] + f[0,1,1] <= 0
 constr_name[0,0,2]: - f[0,0,2] + f[0,1,2] <= 0
 constr_name[0,1,0]: - f[0,1,0] + f[0,2,0] <= 0
 constr_name[0,1,1]: - f[0,1,1] + f[0,2,1] <= 0
 constr_name[0,1,2]: - f[0,1,2] + f[0,2,2] <= 0
 constr_name[0,2,0]: - f[0,2,0] + f[0,3,0] <= 0
 constr_name[0,2,1]: - f[0,2,1] + f[0,3,1] <= 0
 constr_name[0,2,2]: - f[0,2,2] + f[0,3,2] <= 0
 constr_name[1,0,0]: - f[1,0,0] + f[1,1,0] <= 0
 constr_name[1,0,1]: - f[1,0,1] + f[1,1,1] <= 0
 constr_name[1,0,2]: - f[1,0,2] + f[1,1,2] <= 0
 constr_name[1,1,0]: - f[1,1,0] + f[1,2,0] <= 0
 constr_name[1,1,1]: - f[1,1,1] + f[1,2,1] <= 0
 constr_name[1,1,2]: - f[1,1,2] + f[1,2,2] <= 0
 constr_name[1,2,0]: - f[1,2,0] + f[1,3,0] <= 0
 constr_name[1,2,1]: - f[1,2,1] + f[1,3,1] <= 0
 constr_name[1,2,2]: - f[1,2,2] + f[1,3,2] <= 0
 constr_name[2,0,0]: - f[2,0,0] + f[2,1,0] <= 0
 constr_name[2,0,1]: - f[2,0,1] + f[2,1,1] <= 0
 constr_name[2,0,2]: - f[2,0,2] + f[2,1,2] <= 0
 constr_name[2,1,0]: - f[2,1,0] + f[2,2,0] <= 0
 constr_name[2,1,1]: - f[2,1,1] + f[2,2,1] <= 0
 constr_name[2,1,2]: - f[2,1,2] + f[2,2,2] <= 0
 constr_name[2,2,0]: - f[2,2,0] + f[2,3,0] <= 0
 constr_name[2,2,1]: - f[2,2,1] + f[2,3,1] <= 0
 constr_name[2,2,2]: - f[2,2,2] + f[2,3,2] <= 0
simonbowly commented 9 months ago

I think it's worth adding these to the documentation as examples. Probably a time-indexed formulation of some kind is the logical place to showcase it.

rrandall1471 commented 9 months ago

I think it's worth adding these to the documentation as examples. Probably a time-indexed formulation of some kind is the logical place to showcase it.

That makes sense.