Advertising Campaign Problem
NonlinearProblem
In the Advertising Campaign Problem, a set of ads can be broadcast on various time slots. Each ad has an expected marketing gain if it reaches its target audience (age group, for example). When an ad is assigned to a time slot, it reaches its target audience with a given probability. This probability increases if the ad is broadcast during several time slots. The objective is to assign an ad to each slot in order to maximize the total expected profit of the campaign.
This problem is also known as the Weapon Target Assignment Problem (WTA). For more details about this problem, see Wikipedia.
Principles learned
- Add set decision variables to model the slots assigned to each ad
- Create a nonlinear expression using the ‘prod’ and ‘iif’ operators
- Use lambda functions to compute the expected profit for each ad
Data
The instances are slightly modified versions of the Weapon Target Assignment instances available on GitHub. The format is as follows:
- First line: number of slots, number of ads
- For each ad, its marketing profit
- Each of the following lines contains:
- The index of a slot
- The index of an ad
- The probability that this ad reaches its target audience during this slot
Program
The Hexaly model for the Advertising Campaign Problem uses set decision variables. We define one set decision variable for each ad, containing all the slots assigned to this ad. We constrain the set variables to form a partition, ensuring that we assign exactly one ad for each slot.
For each ad, we compute the expected profit. For this, we use a lambda function, which provides the probability that an ad will reach its target audience during a given slot. Then, we obtain this probability over the entire campaign by applying a ‘prod’ operator. Finally, the expected profit is derived by multiplying this probability by the marketing profit. We compute our objective function by summing up the expected profit for each ad.
- Execution
-
hexaly advertising_campaign.hxm inFileName=instances/ad_campaign_200x400.txt [hxTimeLimit=] [solFileName=]
use io;
function input() {
local usage = "Usage: hexaly advertising_campaign.hxm inFileName=instanceFile"
+ " [outFileName=outputFile] [hxTimeLimit=timeLimit]";
if (inFileName == nil) throw usage;
inFile = io.openRead(inFileName);
nbSlots = inFile.readInt();
nbAds = inFile.readInt();
AdProfits[i in 0...nbAds] = inFile.readInt();
while (inFile.eof() == false) {
line = inFile.readln().trim().split(" ");
s = line[0].toInt();
a = line[1].toInt();
prob = line[2].toDouble();
probabilities[s][a] = prob;
}
}
function model() {
// Variable declaration : set of slots assigned to each ad
assignments[a in 0...nbAds] <- set(nbSlots);
// Unique ad affectation by slot
constraint partition[a in 0...nbAds](assignments[a]);
// Maximize the total profit :
// 1 - probabilities[s][a] is the probability that ad a does not reach its audience during slot s
// prod(assignments[a], s=>1 - probabilities[s][a]) is the probability that ad a does not reach its audience over the entire campaign
// 1 - prod(assignments[a], s=>1 - probabilities[s][a]) is the probability that ad a reaches its audience over the entire campaign
// AdProfits[a]*(1 - prod(assignments[a], s=>1 - probabilities[s][a])) is the expected profit of ad a over the entire campaign
// If no argument is given, the "prod" operator returns the integer 1
for [a in 0...nbAds]{
profit[a] <- AdProfits[a]*(1 - prod(assignments[a], s => 1 - probabilities[s][a]));
}
totalProfit <- sum[a in 0...nbAds](profit[a]);
maximize totalProfit;
}
function param() {
if (hxTimeLimit == nil) hxTimeLimit = 60;
}
function output() {
//Write the solution in a file with the following format:
// - nb slots, nb ads and the total expected profit
// - which slots were allocated to each ad
if (outFileName != nil) {
local outFile = io.openWrite(outFileName);
println("Solution written in file ", outFileName);
outFile.println("nb slots = " + nbSlots + "; nb ads = " + nbAds );
outFile.println("Total profit = " + totalProfit.value);
for [a in 0...nbAds] {
if (assignments[a].value.count() == 0) continue;
outFile.print("Ad " + a + " assigned to ");
for [s in assignments[a].value] {
outFile.print("slot " + s + ", ");
}
outFile.println();
}
}
}
- Execution (Windows)
-
set PYTHONPATH=%HX_HOME%\bin\pythonpython advertising_campaign.py instances\ad_campaign_200x400.txt
- Execution (Linux)
-
export PYTHONPATH=/opt/hexaly_14_0/bin/pythonpython advertising_campaign.py instances/ad_campaign_200x400.txt
import hexaly.optimizer
import sys
def read_instances(instance_filename):
with open(instance_filename) as f:
lines = f.readlines()
nb_slots = int(lines[0].split()[0])
nb_ads = int(lines[0].split()[1])
Ad_profits = [int(lines[i]) for i in range(1,nb_ads+1)]
Probabilities_data = [[0.0 for i in range(nb_ads)] for j in range(nb_slots)]
for line in lines[nb_ads+1:]:
values = line.split()
s = int(values[0])
a = int(values[1])
Probabilities_data[s][a] = float(values[2])
return nb_slots, nb_ads, Ad_profits, Probabilities_data
def main(input_file, output_file, time_limit):
# Read the instance
nb_slots, nb_ads, Ad_profits, Probabilities_data = read_instances(input_file)
with hexaly.optimizer.HexalyOptimizer() as optimizer:
# Declare the model
model = optimizer.model
# Variable declaration : set of slots assigned to each ad
assignments = [model.set(nb_slots) for a in range(nb_ads)]
assignments_array = model.array(assignments)
# Unique ad affectation by slot
model.constraint(model.partition(assignments_array))
# Maximize the total profit :
# 1 - probabilities[s][a] is the probability that ad a does not reach its audience during slot s
# prod(assignments[a], s=>1 - probabilities[s][a]) is the probability that ad a does not reach its audience over the entire campaign
# 1 - prod(assignments[a], s=>1 - probabilities[s][a]) is the probability that ad a reaches its audience over the entire campaign
# AdProfits[a]*(1 - prod(assignments[a], s=>1 - probabilities[s][a])) is the expected profit of ad a over the entire campaign
# If no argument is given, the "prod" operator returns the integer 1
profit = [None] * nb_ads
Probabilities = model.array(Probabilities_data)
for a in range(nb_ads):
profit_lambda = model.lambda_function(lambda s : 1 - model.at(Probabilities, s, a))
profit[a] = Ad_profits[a] * (1 - model.prod(assignments[a], profit_lambda))
total_profit = model.sum(profit)
# Objective
model.maximize(total_profit)
model.close()
# Parameterize the optimizer
optimizer.param.time_limit = time_limit
optimizer.solve()
# Write the solution in a file with the following format:
# - nb slots, nb ads and the total expected profit
# - which slots were allocated to each ad
if output_file != None:
with open(output_file, 'w') as f:
f.write("Nb slots = " + str(nb_slots) + "; Nb ads = " + str(nb_ads) + "; Total profit = " +
str(total_profit.value) + "\n")
for a in range(nb_ads):
if assignments[a].value.count() == 0:
continue
f.write("Ad " + str(a) + " assigned to ")
for s in assignments[a].value:
f.write("slot " + str(s) + ", ")
f.write("\n")
if __name__ == '__main__':
input_file = sys.argv[1]
output_file = sys.argv[2] if len(sys.argv) >= 3 else None
time_limit = int(sys.argv[3]) if len(sys.argv) >= 4 else 60
main(input_file, output_file, time_limit)
- Compilation / Execution (Windows)
-
cl /EHsc advertising_campaign.cpp -I%HX_HOME%\include /link %HX_HOME%\bin\hexaly140.libadvertising_campaign instances\ad_campaign_200x400.txt
- Compilation / Execution (Linux)
-
g++ advertising_campaign.cpp -I/opt/hexaly_14_0/include -lhexaly140 -lpthread -o advertising_campaign./advertising_campaign instances/ad_campaign_200x400.txt
#include "optimizer/hexalyoptimizer.h"
#include <algorithm>
#include <fstream>
#include <iostream>
#include <limits>
#include <numeric>
#include <vector>
using namespace hexaly;
using namespace std;
class AdvertisingCampaign{
public:
// Number of slots
int nbSlots;
// Number of ads
int nbAds;
// Ads profit
vector<int> adProfits;
// Probability for an ad to reach its target audience during a slot
vector<vector<double>> probabilitiesData;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variables : for each ad, a set containing the slots assigned
vector<HxExpression> assignments;
// Objective : maximize the total expected profit
HxExpression totalProfit;
void readInstance(const string& fileName) {
ifstream infile;
infile.exceptions(ifstream::failbit | ifstream::badbit);
infile.open(fileName.c_str());
infile >> nbSlots;
infile >> nbAds;
adProfits.resize(nbAds);
for (int a = 0; a < nbAds; ++a) {
int value;
infile >> value;
adProfits[a] = value;
}
probabilitiesData.resize(nbSlots);
for (int s = 0; s < nbSlots; ++s) {
probabilitiesData[s].resize(nbAds);
}
for (int i = 0; i < nbAds*nbSlots; ++i) {
int s;
int a;
double prob;
infile >> s;
infile >> a;
infile >> prob;
probabilitiesData[s][a] = prob;
}
infile.close();
}
void solve(int timeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Variable declaration : set of slots assigned to each ad
assignments.resize(nbAds);
for (unsigned int a = 0; a < nbAds; ++a) {
assignments[a] = model.setVar(nbSlots);
}
// Unique ad affectation by slot
model.constraint(model.partition(assignments.begin(), assignments.end()));
HxExpression probabilities = model.array();
for (int s = 0; s < nbSlots; ++s){
probabilities.addOperand(model.array(probabilitiesData[s].begin(), probabilitiesData[s].end()));
}
// Maximize the total profit :
// 1 - probabilities[s][a] is the probability that ad a does not reach its audience during slot s
// prod(assignments[a], s=>1 - probabilities[s][a]) is the probability that ad a does not reach its audience over the entire campaign
// 1 - prod(assignments[a], s=>1 - probabilities[s][a]) is the probability that ad a reaches its audience over the entire campaign
// AdProfits[a]*(1 - prod(assignments[a], s=>1 - probabilities[s][a])) is the expected profit of ad a over the entire campaign
// If no argument is given, the "prod" operator returns the integer 1
vector<HxExpression> profit;
profit.reserve(nbAds);
for (int a = 0; a < nbAds; ++a) {
HxExpression profitLambda = model.createLambdaFunction([&](HxExpression s){return 1 - model.at(probabilities, s, a);});
profit.push_back(adProfits[a] * (1 - model.prod(assignments[a], profitLambda)));
}
totalProfit = model.sum(profit.begin(), profit.end());
// Objective
model.maximize(totalProfit);
model.close();
// Parametrize the optimizer
optimizer.getParam().setTimeLimit(timeLimit);
optimizer.solve();
}
// Write the solution in a file with the following format:
// - instance name, time limit and total expected profit
// - which slots were allocated to each ad
void writeSolution(const string& fileName, const string& instanceFile, const string& timeLimit) {
ofstream outfile;
outfile.open(fileName.c_str());
outfile << "Instance: " << instanceFile << " ; time_limit: " << timeLimit
<< " ; Objective value: " << totalProfit.getDoubleValue() << endl;
for (int a = 0; a < nbAds; ++a) {
HxCollection assignment = assignments[a].getCollectionValue();
if (assignment.count() == 0)
continue;
outfile << "Ad " << a << " assigned to " ;
for (int s = 0; s < assignment.count(); ++s) {
outfile << "slot " << assignment[s] << ", ";
}
outfile << "" << endl;
}
}
};
int main(int argc, char** argv) {
if (argc < 2) {
cerr << "Usage: main inputFile [outputFile] [timeLimit]" << endl;
return 1;
}
const char* instanceFile = argv[1];
const char* outputFile = argc > 2 ? argv[2] : NULL;
const char* strTimeLimit = argc > 3 ? argv[3] : "60";
try {
AdvertisingCampaign model;
model.readInstance(instanceFile);
model.solve(atoi(strTimeLimit));
// If we want to write the solution
if (outputFile != NULL)
model.writeSolution(outputFile, instanceFile, strTimeLimit);
} catch (const exception& e) {
cerr << "An error occured: " << e.what() << endl;
}
return 0;
}
- Compilation / Execution (Windows)
-
copy %HX_HOME%\bin\Hexaly.NET.dll .csc AdvertisingCampaign.cs /reference:Hexaly.NET.dllAdvertisingCampaign instances\ad_campaign_200x400.txt
using System;
using System.IO;
using System.Globalization;
using Hexaly.Optimizer;
public class AdvertisingCampaign : IDisposable
{
// Number of slots
public int nbSlots;
// Number of ads
public int nbAds;
// Ads profit
public int[] adProfits;
// Probability for an ad to reach its target audience during a slot
public double[][] probabilitiesData;
// Hexaly Optimizer
HexalyOptimizer optimizer;
// Decision variables : for each ad, a set containing the slots assigned
public HxExpression[] assignments;
// Objective : maximize the total expected profit
HxExpression totalProfit;
public AdvertisingCampaign()
{
optimizer = new HexalyOptimizer();
}
public void Dispose()
{
if (optimizer != null)
optimizer.Dispose();
}
public void ReadInstance(string fileName)
{
using (StreamReader input = new StreamReader(fileName))
{
string[] firstLineSplitted = input.ReadLine().Split(' ');
nbSlots = int.Parse(firstLineSplitted[0]);
nbAds = int.Parse(firstLineSplitted[1]);
adProfits = new int[nbAds];
for (int a = 0; a < nbAds; ++a)
{
adProfits[a] = int.Parse(input.ReadLine());
}
probabilitiesData = new double[nbSlots][];
for (int s = 0; s < nbSlots; ++s)
{
probabilitiesData[s] = new double[nbAds];
}
for (int i = 0; i < nbSlots * nbAds; ++i)
{
string[] lineSplitted = input.ReadLine().Split(' ');
int s = int.Parse(lineSplitted[0]);
int a = int.Parse(lineSplitted[1]);
probabilitiesData[s][a] = double.Parse(
lineSplitted[2],
CultureInfo.InvariantCulture
);
}
}
}
public void Solve(int limit)
{
// Declare the optimization model
HxModel model = optimizer.GetModel();
// Variable declaration : set of slots assigned to each ad
assignments = new HxExpression[nbAds];
for (int a = 0; a < nbAds; ++a)
{
assignments[a] = model.Set(nbSlots);
}
// Each shot can only be assigned once
model.Constraint(model.Partition(assignments));
// Maximize the total profit :
// 1 - probabilities[s][a] is the probability that ad a does not reach its audience during slot s
// prod(assignments[a], s=>1 - probabilities[s][a]) is the probability that ad a does not reach its audience over the entire campaign
// 1 - prod(assignments[a], s=>1 - probabilities[s][a]) is the probability that ad a reaches its audience over the entire campaign
// AdProfits[a]*(1 - prod(assignments[a], s=>1 - probabilities[s][a])) is the expected profit of ad a over the entire campaign
// If no argument is given, the "prod" operator returns the integer 1
HxExpression probabilities = model.Array(probabilitiesData);
HxExpression[] profit = new HxExpression[nbAds];
for (int a = 0; a < nbAds; ++a)
{
HxExpression profitLambda = model.LambdaFunction(s => 1 - model.At(model.At(probabilities, s), a));
profit[a] = adProfits[a] * (1 - model.Prod(assignments[a], profitLambda));
}
totalProfit = model.Sum(profit);
// Objective
model.Maximize(totalProfit);
model.Close();
// Parametrize the optimizer
optimizer.GetParam().SetTimeLimit(limit);
optimizer.Solve();
}
// Write the solution in a file with the following format:
// - instance name, time limit and total expected profit
// - which slots were allocated to each ad
public void WriteSolution(string infile, string outfile)
{
using (StreamWriter output = new StreamWriter(outfile))
{
output.WriteLine("File name: " + infile + "; totalProfit = " + totalProfit.GetDoubleValue());
for (int a = 0; a < nbAds; ++a)
{
HxCollection assignment = assignments[a].GetCollectionValue();
if (assignment.Count() == 0)
continue;
output.Write("Add " + a + " assigned to ");
for (int s = 0; s < assignment.Count(); ++s)
{
output.Write("slot " + assignment[s] + ", ");
}
output.WriteLine("");
}
}
}
public static void Main(string[] args)
{
if (args.Length < 1)
{
Console.WriteLine("Usage: AdvertisingCampaign inputFile [solFile] [timeLimit]");
Environment.Exit(1);
}
string instanceFile = args[0];
string outputFile = args.Length > 1 ? args[1] : null;
string strTimeLimit = args.Length > 2 ? args[2] : "60";
using (AdvertisingCampaign model = new AdvertisingCampaign())
{
model.ReadInstance(instanceFile);
model.Solve(int.Parse(strTimeLimit));
if (outputFile != null)
model.WriteSolution(instanceFile, outputFile);
}
}
}
- Compilation / Execution (Windows)
-
javac AdvertisingCampaign.java -cp %HX_HOME%\bin\hexaly.jarjava -cp %HX_HOME%\bin\hexaly.jar;. AdvertisingCampaign instances\ad_campaign_200x400.txt
- Compilation / Execution (Linux)
-
javac AdvertisingCampaign.java -cp /opt/hexaly_14_0/bin/hexaly.jarjava -cp /opt/hexaly_14_0/bin/hexaly.jar:. AdvertisingCampaign instances/ad_campaign_200x400.txt
import java.io.*;
import java.util.*;
import com.hexaly.optimizer.*;
public class AdvertisingCampaign {
// Number of slots
public int nbSlots;
// Number of ads
public int nbAds;
// Ads profit
public int[] AdProfitsData;
// Probability for an ad to reach its target audience during a slot
public double[][] probabilitiesData;
// Hexaly Optimizer
public HexalyOptimizer optimizer;
// Decision variables : for each ad, a set containing the slots assigned
public HxExpression[] assignments;
// Objective : maximize the total expected profit
public HxExpression totalProfit;
public AdvertisingCampaign(HexalyOptimizer optimizer) {
this.optimizer = optimizer;
}
public void readInstance(String fileName) throws IOException {
try (Scanner input = new Scanner(new File(fileName))) {
input.useLocale(Locale.ROOT);
nbSlots = input.nextInt();
nbAds = input.nextInt();
AdProfitsData = new int[nbAds];
for (int a = 0; a < nbAds; ++a) {
AdProfitsData[a] = input.nextInt();
}
probabilitiesData = new double[nbSlots][nbAds];
for (int i = 0; i < nbSlots*nbAds; ++i) {
int s = input.nextInt();
int a = input.nextInt();
double prob = input.nextDouble();
probabilitiesData[s][a] = prob;
}
}
}
public void solve(int timeLimit) {
// Declare the optimization model
HxModel model = optimizer.getModel();
// Variable declaration : set of slots assigned to each ad
assignments = new HxExpression[nbAds];
HxExpression assignmentsArray = model.array();
for (int a = 0; a < nbAds; ++a) {
assignments[a] = model.setVar(nbSlots);
assignmentsArray.addOperand(assignments[a]);
}
// Unique ad affectation by slot
model.constraint(model.partition(assignmentsArray));
// Maximize the total profit :
// 1 - probabilities[s][a] is the probability that ad a does not reach its audience during slot s
// prod(assignments[a], s=>1 - probabilities[s][a]) is the probability that ad a does not reach its audience over the entire campaign
// 1 - prod(assignments[a], s=>1 - probabilities[s][a]) is the probability that ad a reaches its audience over the entire campaign
// AdProfits[a]*(1 - prod(assignments[a], s=>1 - probabilities[s][a])) is the expected profit of ad a over the entire campaign
// If no argument is given, the "prod" operator returns the integer 1
HxExpression AdProfits = model.array(AdProfitsData);
HxExpression[] profit = new HxExpression[nbAds];
HxExpression probabilities = model.array(probabilitiesData);
for (int a = 0; a < nbAds; ++a) {
HxExpression actualAd = model.createConstant(a);
HxExpression profitLambda = model.lambdaFunction(s-> model.sub(1, model.at(model.at(probabilities, s), actualAd)));
profit[a] = model.prod(model.at(AdProfits, a), model.sub(1, model.prod(assignments[a], profitLambda)));
}
totalProfit = model.sum(profit);
model.maximize(totalProfit);
model.close();
optimizer.getParam().setTimeLimit(timeLimit);
optimizer.solve();
}
// Write the solution in a file with the following format:
// - instance name, time limit and total expected profit
// - which slots were allocated to each ad
public void writeSolution(String infile, String outfile) throws IOException{
try (PrintWriter output = new PrintWriter(outfile)) {
output.println("File name: " + infile + "; totalProfit = " + totalProfit.getDoubleValue());
for (int a = 0; a < nbAds; ++a) {
HxCollection assignment = assignments[a].getCollectionValue();
if (assignment.count() == 0)
continue;
output.print("Ad " + a + " assigned to ");
for (int s = 0; s < assignment.count(); ++s) {
output.print("slot " + assignment.get(s) + ", ");
}
output.println("");
}
} catch (FileNotFoundException e) {
System.out.println("An error occurred.");
e.printStackTrace();
}
}
public static void main(String[] args) {
if (args.length < 1) {
System.err.println("Usage: java main inputFile [outputFile] [timeLimit]");
System.exit(1);
}
try (HexalyOptimizer optimizer = new HexalyOptimizer()) {
String instanceFile = args[0];
String strOutfile = args.length > 1 ? args[1] : null;
String strTimeLimit = args.length > 2 ? args[2] : "60";
AdvertisingCampaign model = new AdvertisingCampaign(optimizer);
model.readInstance(instanceFile);
model.solve(Integer.parseInt(strTimeLimit));
if (strOutfile != null)
model.writeSolution(instanceFile, strOutfile);
} catch (Exception ex) {
System.err.println(ex);
ex.printStackTrace();
throw new RuntimeException(ex);
}
}
}