ironzionlion
ironzionlion

Reputation: 899

How to get the result of the objective function while using a slack variable?

I have a simple Python code using Gurobi. I have added a slack variable to force model feasibility. I need to know how to get the real minimum value in my objective function.

import gurobipy as gp
from gurobipy import GRB

# Create the model
model = gp.Model("quadratic_optimization_with_slack")

# Variables
x = model.addVar(name="x", lb=0)  # x >= 0
y = model.addVar(name="y", lb=0)  # y >= 0
slack = model.addVar(name="slack", lb=0)  # Slack variable for relaxation. If slack = 0 --> Model is infeasible

# Objective: Minimize 2x^2 + y
model.setObjective(2 * x**2 + y, GRB.MINIMIZE)

# Constraints
model.addConstr(x - 5 + slack == 0, name="constraint_1")  # (slack allows relaxation) <-- Condition to be relaxed
model.addConstr(x + y == 4, name="constraint_2")

# Add slack penalty in the objective to ensure the slack value is minimum.
# The problem is that the result is not anymore the model.ObjVal, but the penalty*slack
penalty_weight = 0.00010  # Penalty for slack usage
model.setObjective(model.getObjective() + (penalty_weight * slack))

# Optimize the model
model.optimize()

According to the values:

x = 4
y = 0
slack = 1
model.ObjVal = 0.0001  # (penalty_weight * slack)

Obviously, 0.0001 is not the minimum value that my objective function 2x^2 + y can get. With this slack, it should be 2 * 4^2 + 0 = 32.

How can I get the real minimum value of my objective function?

Upvotes: 1

Views: 118

Answers (2)

lastchance
lastchance

Reputation: 6745

I think you had better state in words, not in code, exactly what your optimisation problem is.

As it stands, your expectations are wrong. Your expected minimum is wrong and you do not need a slack variable to achieve it.

If x+y=4 then your function can be rewritten, by completing the square, as

2x2+4-x = 2(x-1/4)2+31/8

This is minimized, with value 31/8, at x=1/4 (whence y=15/4). This also fits your implied constraint x<=5. Your constraint x - 5 + slack == 0 is equivalent to x == 5 - slack and so, with slack positive, to x <= 5. There is nothing unfeasible about that.

The limited, free, pip-installed, version of gurobipy gives, correctly, 3.875 (which is 31/8).

All you are doing with the slack variable is producing a value which manifestly is not the minimum value of the function. 32.0 is considerably larger than 3.875.

import gurobipy as gp
from gurobipy import GRB

model = gp.Model("quadratic_optimization_with_slack")
x = model.addVar(name="x", lb=0)  # x >= 0
y = model.addVar(name="y", lb=0)  # y >= 0

model.setObjective(2 * x**2 + y, GRB.MINIMIZE)
model.addConstr(x <= 5, name="constraint_1")
model.addConstr(x + y == 4, name="constraint_2")

model.optimize()

Output:

Restricted license - for non-production use only - expires 2026-11-23
Gurobi Optimizer version 12.0.0 build v12.0.0rc1 (win64 - Windows 10.0 (19045.2))

CPU model: Intel(R) Xeon(R) W-2123 CPU @ 3.60GHz, instruction set [SSE2|AVX|AVX2|AVX512]
Thread count: 4 physical cores, 8 logical processors, using up to 8 threads

Optimize a model with 2 rows, 2 columns and 3 nonzeros
Model fingerprint: 0x2bcf8c93
Model has 1 quadratic objective term
Coefficient statistics:
  Matrix range     [1e+00, 1e+00]
  Objective range  [1e+00, 1e+00]
  QObjective range [4e+00, 4e+00]
  Bounds range     [0e+00, 0e+00]
  RHS range        [4e+00, 5e+00]
Presolve removed 2 rows and 2 columns
Presolve time: 0.00s
Presolve: All rows and columns removed

Barrier solved model in 0 iterations and 0.00 seconds (0.00 work units)
Optimal objective 3.87500000e+00

=====================================

If you have set your heart on using a slack variable - even though it is completely unnecessary - then the following code gives exactly the same minimum result. You do not need a slack penalty.

import gurobipy as gp
from gurobipy import GRB

model = gp.Model("quadratic_optimization_with_slack")
x = model.addVar(name="x", lb=0)  # x >= 0
y = model.addVar(name="y", lb=0)  # y >= 0
slack = model.addVar(name="slack", lb=0)  # Slack variable for relaxation
model.addConstr(x - 5 + slack == 0, name="constraint_1")  # (slack allows relaxation) <-- Condition to be relaxed
model.addConstr(x + y == 4, name="constraint_2")
model.setObjective(2 * x**2 + y, GRB.MINIMIZE )
model.optimize()

Upvotes: -1

ironzionlion
ironzionlion

Reputation: 899

First let's fix an error in the code. This:

model.getObjective() only works properly after model.update() or model.write() or model.optimize(). In this code it will just return 0.

Using the getObjective() method is not recommended. Rather do something like this:

orig_obj = 2 * x**2 + y
penalty_weight = 100  # rather than 0.0001, to truly minimize value of slack
model.setObjective(orig_obj + (penalty_weight * slack), GRB.MINIMIZE)

After optimization you can use QuadExpr.getValue(), i.e. print(orig_obj.getValue())

Source: https://support.gurobi.com/hc/en-us/community/posts/30217975677713-How-to-get-the-result-of-the-objective-function-while-using-a-slack-variable

Upvotes: 0

Related Questions