Search code examples
pythonpyomogurobi

Simple quadratic problem in Gurobi not producing optimal result?


I am having trouble understanding why my code below is not producing the optimal result. I am attempting to create a step function but this clearly isn't working as the solution value for model.Z isn't one of the range points specified.

Any help in understanding/correcting this is greatly appreciated.

What I am trying to do

Maximize Z * X, subject to:

          / 20.5  , X <= 5
  Z(X)  = | 10    , 5 <= X <= 10
          \ 9     , 10 <= X <= 11

Even better, I'd like to solve under the following conditions (disjoint at breakpoints):

          / 20.5  , X <= 5
  Z(X)  = | 10    , 5 < X <= 10
          \ 9     , 10 < X <= 11

where X and Z are floating point numbers.

I would expect X to be 5 and Z to be 20.5, however model results are 7.37 and 15.53.

Code

from pyomo.core import *

# Break points for step-function
DOMAIN_PTS = [5., 10., 11.]
RANGE_PTS = [20.5, 10., 9.]

# Define model and variables
model = ConcreteModel()
model.X = Var(bounds=(5,11))
model.Z = Var()

# Set piecewise constraint
model.con = Piecewise(model.Z,model.X, 
                      pw_pts=DOMAIN_PTS ,
                      pw_constr_type='EQ',
                      f_rule=RANGE_PTS,
                      force_pw=True,
                      pw_repn='SOS2') 

model.obj = Objective(expr= model.Z * model.X, sense=maximize)
opt = SolverFactory('gurobi')
opt.options['NonConvex'] = 2
obj_val = opt.solve(model)

print(value(model.X))
print(value(model.Z))
print(model.obj())

Solution

  • Piecewise in Pyomo is intended to interpolate linearly between some bounds given by another variable. This means that if you give the bounds given as you're trying, your interpolating as this (sorry for such a bad graph) which basically means that you're placing a line between x=5 and x=10 a line given by Z= 31 - 2.1X, and another line between 10 and 11. In fact, Gurobi is computing the optimal result, since x its a NonNegativeReal and in such a line Z= 31 - 2.1X, x=7.37 give as result Z=15.53.

    Now, I understand that you want rather a step function than a interpolation, something similar to this (sorry for such a bad graph, again), then you need to change your DOMAIN_PTS and RANGE_PTS in order to correctly model what you want

    # Break points for step-function
    DOMAIN_PTS = [5.,5., 10., 10.,11.]
    RANGE_PTS = [20.5,10., 10., 9., 9.]
    

    In this way, you're interpolating between f(x)=20.5: 5<=x<=10 and so on.