Advertising Campaign Problem

Nonlinear

Problem

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

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\python
python advertising_campaign.py instances\ad_campaign_200x400.txt
Execution (Linux)
export PYTHONPATH=/opt/hexaly_14_0/bin/python
python 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.lib
advertising_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.dll
AdvertisingCampaign 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.jar
java -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.jar
java -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);
        }
    }
}