Python: Optimization with Exponential Objective Function

Question:

I am working on assignment problems. So far, I have been using Gurobi via gurobipy. In my current problem, I have an objective function with an exponential term. In gurobipy this would be

m.setObjective(ratings.prod(assign) ** 0.5, sense=GRB.MAXIMIZE)

but ** (or pow()) is not supported in Gurobi. What are other options (with other tools than Gurobi) or workarounds?

Below is a minimal working example:

import pandas as pd
import gurobipy as gp
from gurobipy import GRB
import random

def solve():
    m = gp.Model()
    assign = m.addVars(permutations, vtype=gp.GRB.BINARY, name="assign")
    m.addConstrs((assign.sum(i, "*") == 1 for i in individuals))
    capacity = {
      'P1':40, 'P2':30, 'P3':30
    }
    m.addConstrs(
        (assign.sum("*", p) <= capacity[p] for p in groups),
        name="limit"
    )
    
    # objective function
    m.setObjective(ratings.prod(assign) ** 0.5, sense=GRB.MAXIMIZE)

    m.optimize()
    return m, assign

def get_results(assign):
    # create df with placeholder
    results = pd.DataFrame(-1, index=individuals, columns=groups)
    
    # fill in the results
    for (i, j), x_ij in assign.items():
        results.loc[i, j] = int(x_ij.X)

    return results
  
# minimal example
outcomes = []
for i in range(0,100):
    x = random.uniform(0, 1)  
    outcomes.append(x)

data = {'P1': outcomes, 
        'P2': random.sample(outcomes, len(outcomes)),
        'P3': random.sample(outcomes, len(outcomes))}

df = pd.DataFrame(data)

individuals = list(df.index)
groups = ['P1', 'P2', 'P3']

# prepare
permutations, ratings = gp.multidict({
       (i, j): df.loc[i, j]
       for i in individuals
       for j in groups
   })  
   
# run   
m, assign = solve()    
results = get_results(assign)    
printresults)
Asked By: jkortner

||

Answers:

As mentioned in comment, just take the sqrt after the fact… Unless I’m missing something fundamental in the gurobi syntax. Instead of using this:

m.setObjective(ratings.prod(assign) ** 0.5, sense=GRB.MAXIMIZE)

use this:

m.setObjective(ratings.prod(assign), sense=GRB.MAXIMIZE)

Then solve the model with this objective, which will get the same values for assign and then after the solve, get the value of the objective out of the model (not sure how to do this in gurobi syntax, so this is psuedocode:

obj_value = m.get_objective_value_somehow_in_gurobi_syntax  ??

and then just take the sqrt of that…

real_obj_value = math.sqrt(obj_value)

the values of the variables that maximize obj_value and real_obj_value are the same.

Edit: working with terms with sqrt

New plan… per comments you actually intend to use sqrt of individual terms in the sum-product, so the above strategy will not work.

However, because your decision variables are binary, you can implement a work-around….

Make a separate dictionary of "corrected values" that is just the sqrt of the individual values. (I’m assuming you still want the original values for use in some constraints, etc. so retain and use those as well) They are both indexed identically, so use the originals in the constraints and the corrected in the objective.

So in psuedocode something like:

corr_values = {k:v**0.5 for k, v in values_dict}

then just use the corrected values in your objective function, which is mathematically fine because you are only multiplying by 1 or 0:

1 * v**0.5 == (1 * v)**0.5

and you will get the outcome you desire and the low values will be weighted appropriately

Answered By: AirSquid
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.