-
Hi, I'm not sure if this is a "discussion" or "issue"... This may be a bit naive but I thought the I get a much worse answer using Please see my code below. import numpy as np
import gurobipy as gp
import ot
# Load data
first_day = np.load('first_day_arrays.npz')
cost_matrix = first_day['cost_matrix'].T # cost matrix with shape (3001, 20)
units_hist = first_day['units_hist'] # supply vector with shape (3001,)
reqs_hist = first_day['reqs_hist'] # demand vector with shape (20,)
# Python Optimal Transport
ot_plan = ot.emd(units_hist, reqs_hist, cost_matrix)
# Gurobi
pre_one_first = np.ones(cost_matrix.shape[0])
post_one_first = np.ones(cost_matrix.shape[1])[:, None]
gurobi_model = gp.Model("first_day")
gp_plan = gurobi_model.addMVar(shape=cost_matrix.shape, lb=0, vtype=gp.GRB.INTEGER, name="x")
gurobi_model.setObjective(np.kron(pre_one_first, post_one_first.T) @ (gp_plan.reshape(-1) * cost_matrix.reshape(-1)), sense=gp.GRB.MINIMIZE)
gurobi_model.addConstr(gp_plan @ post_one_first == units_hist[:, None])
gurobi_model.addConstr(pre_one_first @ gp_plan == reqs_hist)
gurobi_model.optimize()
# Compare transport costs
print('\n\n')
print(f"Python Optimal Transport cost: {np.sum(ot_plan * cost_matrix)}")
# Result: 4.737712175590463
print(f"Gurobi cost: {gurobi_model.objVal}") # np.sum(gp_plan.X * cost_matrix) also works
# Result: 0.7318552570921428
# Compare plans
print('\n\n')
print(f'Shape of Python Optimal Transport plan: {ot_plan.shape}') # (3001, 20)
print(f'Shape of Gurobi plan: {gp_plan.X.shape}') # (3001, 20)
print(f'Are the plans equal? {np.allclose(ot_plan, gp_plan.X)}')
# Result: False Is there something I'm missing about POT (e.g., optimality not guaranteed) or is something not working as it should? scratch.zip
Thanks for your help! P.S.: Gurobi is free for academics, and if you |
Beta Was this translation helpful? Give feedback.
Replies: 4 comments 1 reply
-
Based on a suggestion by @rflamary, I converted all the input data to |
Beta Was this translation helpful? Give feedback.
-
Hello @floyebolu, |
Beta Was this translation helpful? Give feedback.
-
The transport plan from Gurobi meets the constraints. supplied = gp_plan.X.sum(axis=1)
received = gp_plan.X.sum(axis=0)
print(supplied.shape) # (3001,)
print(received.shape) # (20,)
np.allclose(supplied, units_hist)
# Result: True
np.allclose(received, reqs_hist)
# Result: True
np.allclose(gurobi_model.ObjVal, np.sum(gp_plan.X * cost_matrix))
# Result: True Thanks for all your help :) |
Beta Was this translation helpful? Give feedback.
-
I had a look at the cost matrix and it appears that you have very large values and very small ones. I guess that gurobi has tricks to handle this but the OT solver in ot.emd uses float64 that can handle a dynamic of only around 1e16 and provides the best solution at this precision. I just cliped the max values à 1e10 with |
Beta Was this translation helpful? Give feedback.
I had a look at the cost matrix and it appears that you have very large values and very small ones. I guess that gurobi has tricks to handle this but the OT solver in ot.emd uses float64 that can handle a dynamic of only around 1e16 and provides the best solution at this precision.
I just cliped the max values à 1e10 with
cost_matrix2 = np.clip(cost_matrix, a_min=-np.inf, a_max=1e10)
and the resulting OT plan and cost is now very close to gurobi at0.7318552570921427
when computing the loss with the original matrix.