import gurobipy as gp
from gurobipy import GRB
%load_ext watermark
%watermark -n -u -v -iv -w
Last updated: Tue Dec 14 2021 Python implementation: CPython Python version : 3.9.9 IPython version : 7.27.0 gurobipy: 9.5.0 Watermark: 2.2.0
products = ['p1', 'p2']
clusters = ['k1', 'k2']
cp, expected_profit = gp.multidict({
('k1', 'p1'): 2000,
('k1', 'p2'): 1000,
('k2', 'p1'): 3000,
('k2', 'p2'): 2000
})
cp, expected_cost = gp.multidict({
('k1', 'p1'): 200,
('k1', 'p2'): 100,
('k2', 'p1'): 300,
('k2', 'p2'): 200
})
clusters, number_customers = gp.multidict({
('k1'): 5,
('k2'): 5
})
products, min_offers = gp.multidict({
('p1'): 2,
('p2'): 2
})
# Define the corporate hurdle-rate
R = 0.20
# Define the value of budget available for campaign
budget = 200
# Declare and initialize model
mt = gp.Model('Tactical')
# Define the decisions variables
# Allocation of product offers to customers in clusters.
y = mt.addVars(cp, name="allocate")
# Budget correction
z = mt.addVar(name="budget_correction")
Restricted license - for non-production use only - expires 2023-10-25
max_offers_c = mt.addConstrs((y.sum(k,'*') <= number_customers[k] for k in clusters), name='max_offers')
budget_c = mt.addConstr((y.prod(expected_cost) - z <= budget), name='budget')
min_offers_c = mt.addConstrs( (y.sum('*',j) >= min_offers[j] for j in products), name='min_offers')
roi_c = mt.addConstr((y.prod(expected_profit) - (1 + R)*y.prod(expected_cost) >= 0), name='roi')
# Maximize total expected profit
# The value of 𝑀 should be higher than any of the expected profits to ensure
# that the budget is increased only when the model is infeasible if this parameter
# is not increased.
M = 10000
mt.setObjective(y.prod(expected_profit) -M*z, GRB.MAXIMIZE)
# Verify model formulation
mt.write('tactical.lp')
mt.optimize()
Gurobi Optimizer version 9.5.0 build v9.5.0rc5 (linux64) Thread count: 8 physical cores, 8 logical processors, using up to 8 threads Optimize a model with 6 rows, 5 columns and 17 nonzeros Model fingerprint: 0x168ba328 Coefficient statistics: Matrix range [1e+00, 3e+03] Objective range [1e+03, 1e+04] Bounds range [0e+00, 0e+00] RHS range [2e+00, 2e+02] Presolve removed 1 rows and 0 columns Presolve time: 0.09s Presolved: 5 rows, 5 columns, 13 nonzeros Iteration Objective Primal Inf. Dual Inf. Time 0 2.5000000e+04 8.787500e+01 0.000000e+00 0s 4 -3.9940000e+06 0.000000e+00 0.000000e+00 0s Solved in 4 iterations and 0.24 seconds (0.00 work units) Optimal objective -3.994000000e+06
total_expected_profit = 0
total_expected_cost = 0
for k,p in cp:
# if y[k,p].x > 1e-6:
print(f"The number of customers in cluster {k} that gets an offer of product {p} is: {y[k,p].x}")
total_expected_profit += expected_profit[k,p]*y[k,p].x
total_expected_cost += expected_cost[k,p]*y[k,p].x
The number of customers in cluster k1 that gets an offer of product p1 is: 2.0 The number of customers in cluster k1 that gets an offer of product p2 is: 2.0 The number of customers in cluster k2 that gets an offer of product p1 is: 0.0 The number of customers in cluster k2 that gets an offer of product p2 is: 0.0
increased_budget = '${:,.2f}'.format(z.x)
optimal_ROI = round(100*total_expected_profit/total_expected_cost,2)
min_ROI = round(100*(1+R),2)
money_expected_profit = '${:,.2f}'.format(total_expected_profit)
money_expected_cost = '${:,.2f}'.format(total_expected_cost)
money_budget = '${:,.2f}'.format(budget)
print(f"The increase correction in the campaign budget is {increased_budget}.")
print(f"Optimal total expected profit is {money_expected_profit}.")
print(f"Optimal total expected cost is {money_expected_cost} with a budget of {money_budget} and an extra amount of {increased_budget}.")
print(f"Optimal ROI is {optimal_ROI}% with a minimum ROI of {min_ROI}%.")
The increase correction in the campaign budget is $400.00. Optimal total expected profit is $6,000.00. Optimal total expected cost is $600.00 with a budget of $200.00 and an extra amount of $400.00. Optimal ROI is 1000.0% with a minimum ROI of 120.0%.