<jupyter_start><jupyter_text>![dss-jakuczun](images/Jakuczun.png)  Why is optimization important?  What is optimization model? ![opt-model](images/opt-model.png)  **recommendation = optimize(forecast)** ![opt-value-chain](images/opt-model-value-chain.png)  CRM campaign use case  What is a business problem we want to solve?  Key questions  What should we offer? Company offers **many products/services** to their customers.  How should we offer the product? Company uses **different communication channels** to reach their customers. For example: email, phone, www.Company can also decide on time and message when reaching particular customer.  Whom should we offert the product? Depending on customer profile (e.g. demography, behavioral profile, etc.)the reaction to the offering for a given product via a given channel can differ.  Business process ![kampania_process](images/cmpgn_process.png)  Estimating probability of buying Proper place for *machine learning* model. The model for each tupe `(customer, product, channel)` estimates probabilty of buying (or any positive action).  Selecting optimal strategy of conducting the campaign Some constraints are given: * Cap on campaing budget.* Minimal product exposure (number of offers).* Minimum ROI must be achieved. **Here *machine learning* cannot help!**  What is `minizinc`?  [MiniZinc](https://www.minizinc.org/index.html) is a high-level **constraint modelling language**. ![model-example](images/minizinc-model-example.png)  It separates model declaration from data and solver. * File with model with `mzn` extension* File with data with `dzn` extension or `json`<jupyter_code>!minizinc --solver Gecode dss-marketing-optimization.mzn small_dataset.json<jupyter_output>vAssignment = array3d(1..2, 1..2, 1..2, [1, 0, 1, 0, 1, 0, 0, 1]);
----------
==========<jupyter_text>Many solvers are available ![minzinc-solvers](images/minzinc-solvers.png)  It has a rich library of constraints for many practical problems. ![minizinc-constraints](images/minizinc-global-constraints.png)  It is an extensible and highly powerful language ![minizinc-modular](images/minizinc-modular.png) ![minizinc-functions](images/minizinc-functions.png) ![minizinc-predicates](images/minizinc-predicates.png) ![minizinc-let](images/minizinc-let.png)  Optimization model for CRM Campaign  Packages instalation<jupyter_code>!cat requirements.txt
!pip3 install -r requirements.txt<jupyter_output>Requirement already satisfied: iminizinc in ./venv/lib/python3.10/site-packages (from -r requirements.txt (line 1)) (0.4)
Requirement already satisfied: ipykernel in ./venv/lib/python3.10/site-packages (from -r requirements.txt (line 2)) (6.5.1)
Requirement already satisfied: minizinc in ./venv/lib/python3.10/site-packages (from -r requirements.txt (line 3)) (0.5.0)
Requirement already satisfied: nest_asyncio in ./venv/lib/python3.10/site-packages (from -r requirements.txt (line 4)) (1.5.1)
Collecting argparse
  Downloading argparse-1.4.0-py2.py3-none-any.whl (23 kB)
Requirement already satisfied: ipython in ./venv/lib/python3.10/site-packages (from iminizinc->-r requirements.txt (line 1)) (7.29.0)
Requirement already satisfied: debugpy<2.0,>=1.0.0 in ./venv/lib/python3.10/site-packages (from ipykernel->-r requirements.txt (line 2)) (1.5.1)
Requirement already satisfied: jupyter-client<8.0 in ./venv/lib/python3.10/site-packages (from ipykernel->-r requirements.txt (line 2)) (7.1.0)
Req[...]<jupyter_text>`minizinc` extension for `jupyter notebook`<jupyter_code>%load_ext iminizinc<jupyter_output><empty_output><jupyter_text>Model parameters  Customers, producs and channels There are:* `pCustomerN` customer* `pProductsN` products/services* `pChannelsN` channels In `minizinc` we declare it as follows<jupyter_code>%%minizinc
int: pCustomersN;
int: pProductsN;
int: pChannelsN;<jupyter_output>Variable pCustomersN is undefined
Variable pProductsN is undefined
Variable pChannelsN is undefined<jupyter_text>Expected reve for product in channel for a customer Expected reve when given product is offered to given customer via given channel. We store this information in 3 dimensional array.<jupyter_code>%%minizinc
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 
      1..pProductsN, 
      1..pChannelsN] of float: pExpectedReve;<jupyter_output>Variable pCustomersN is undefined
Variable pProductsN is undefined
Variable pChannelsN is undefined
Variable pExpectedReve is undefined<jupyter_text>Minimal guaranteed ROI<jupyter_code>%%minizinc
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
float: pMinHurdleReve;<jupyter_output>Variable pCustomersN is undefined
Variable pProductsN is undefined
Variable pChannelsN is undefined
Variable pExpectedReve is undefined
Variable pMinHurdleReve is undefined<jupyter_text>Marketing costs Marketing costs depend only on a channel.<jupyter_code>%%minizinc
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
float: pMinHurdleReve;
array[1..pChannelsN] of float: pChannelCosts;<jupyter_output>Variable pCustomersN is undefined
Variable pProductsN is undefined
Variable pChannelsN is undefined
Variable pExpectedReve is undefined
Variable pMinHurdleReve is undefined
Variable pChannelCosts is undefined<jupyter_text>Maximum budget<jupyter_code>%%minizinc
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
float: pMinHurdleReve;
array[1..pChannelsN] of float: pChannelCosts;

float: pMaxBudget;<jupyter_output>Variable pCustomersN is undefined
Variable pProductsN is undefined
Variable pChannelsN is undefined
Variable pExpectedReve is undefined
Variable pMinHurdleReve is undefined
Variable pChannelCosts is undefined
Variable pMaxBudget is undefined<jupyter_text>Minimum exposure of a product<jupyter_code>%%minizinc
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
float: pMinHurdleReve;
array[1..pChannelsN] of float: pChannelCosts;
float: pMaxBudget;

array[1..pProductsN] of int: pProductsMinOffersN;<jupyter_output>Variable pCustomersN is undefined
Variable pProductsN is undefined
Variable pChannelsN is undefined
Variable pExpectedReve is undefined
Variable pMinHurdleReve is undefined
Variable pChannelCosts is undefined
Variable pMaxBudget is undefined
Variable pProductsMinOffersN is undefined<jupyter_text>Variables  Assigning product to customer and channel For each customer, product and channel decided if we should act. We will denote this variable as `vAssignment[customer, product, channel]`.<jupyter_code>%%minizinc
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
array[1..pChannelsN] of float: pChannelCosts;
float: pMaxBudget;
array[1..pProductsN] of int: pProductsMinOffersN;
float: pMinHurdleReve;

%Variables
array[1..pCustomersN,
      1..pProductsN,
      1..pChannelsN] of var 0..1: vAssignment;<jupyter_output>Variable pCustomersN is undefined
Variable pProductsN is undefined
Variable pChannelsN is undefined
Variable pExpectedReve is undefined
Variable pChannelCosts is undefined
Variable pMaxBudget is undefined
Variable pProductsMinOffersN is undefined
Variable pMinHurdleReve is undefined<jupyter_text>Constraints  Maximum budget Total costs of contacting customers cannot be more than `pMaxBudget`.<jupyter_code>%%minizinc
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
array[1..pChannelsN] of float: pChannelCosts;
float: pMaxBudget;
array[1..pProductsN] of int: pProductsMinOffersN;
float: pMinHurdleReve;

%Variables
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of var 0..1: vAssignment;

%Constraints
constraint sum(customer in 1..pCustomersN,
               product in 1..pProductsN,
               channel in 1..pChannelsN)(
                   vAssignment[customer, product, channel]*
                   pChannelCosts[channel]
               ) <= pMaxBudget;<jupyter_output>Variable pCustomersN is undefined
Variable pProductsN is undefined
Variable pChannelsN is undefined
Variable pExpectedReve is undefined
Variable pChannelCosts is undefined
Variable pMaxBudget is undefined
Variable pProductsMinOffersN is undefined
Variable pMinHurdleReve is undefined<jupyter_text>Minimum exposure for each product Each product must be offered given (`pProductsMinOfferN`) times.<jupyter_code>%%minizinc
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
array[1..pChannelsN] of float: pChannelCosts;
float: pMaxBudget;
array[1..pProductsN] of int: pProductsMinOffersN;
float: pMinHurdleReve;

%Variables
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of var 0..1: vAssignment;

%Constraints
constraint sum(customer in 1..pCustomersN, product in 1..pProductsN, channel in 1..pChannelsN)(vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]) <= pMaxBudget;

constraint forall(product in 1..pProductsN) (
    sum(customer in 1..pCustomersN,
        channel in 1..pChannelsN) (
              vAssignment[customer, product, channel]
    ) >= pProductsMinOffersN[product]);<jupyter_output>Variable pCustomersN is undefined
Variable pProductsN is undefined
Variable pChannelsN is undefined
Variable pExpectedReve is undefined
Variable pChannelCosts is undefined
Variable pMaxBudget is undefined
Variable pProductsMinOffersN is undefined
Variable pMinHurdleReve is undefined<jupyter_text>Minimum guaranteed ROI  Company wants to have minimum ROI.<jupyter_code>%%minizinc
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
array[1..pChannelsN] of float: pChannelCosts;
float: pMaxBudget;
array[1..pProductsN] of int: pProductsMinOffersN;
float: pMinHurdleReve;

%Variables
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of var 0..1: vAssignment;

%Constraints
constraint sum(customer in 1..pCustomersN, product in 1..pProductsN, channel in 1..pChannelsN)(vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]) <= pMaxBudget;

constraint forall(product in 1..pProductsN) (
    sum(customer in 1..pCustomersN,
        channel in 1..pChannelsN) (
              vAssignment[customer, product, channel]
    ) >= pProductsMinOffersN[product]);

constraint sum(customer in 1..pCustomersN,
               product in 1..pProductsN,
               channel in 1..pChannelsN)
            (
                vAssignment[customer, product, channel]*pExpectedReve[customer, product, channel]
            ) >= 
            (1+pMinHurdleReve)*sum(customer in 1..pCustomersN,
                                   product in 1..pProductsN,
                                   channel in 1..pChannelsN)
                                    (
                                        vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]
                                    );<jupyter_output>Variable pCustomersN is undefined
Variable pProductsN is undefined
Variable pChannelsN is undefined
Variable pExpectedReve is undefined
Variable pChannelCosts is undefined
Variable pMaxBudget is undefined
Variable pProductsMinOffersN is undefined
Variable pMinHurdleReve is undefined<jupyter_text>Objective function  We want to optimize total margin from the campaign.<jupyter_code>%%minizinc
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
array[1..pChannelsN] of float: pChannelCosts;
float: pMaxBudget;
array[1..pProductsN] of int: pProductsMinOffersN;
float: pMinHurdleReve;

%Variables
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of var 0..1: vAssignment;

%Constraints
constraint sum(customer in 1..pCustomersN, product in 1..pProductsN, channel in 1..pChannelsN)(vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]) <= pMaxBudget;

constraint forall(product in 1..pProductsN) (
    sum(customer in 1..pCustomersN,
        channel in 1..pChannelsN) (
              vAssignment[customer, product, channel]
    ) >= pProductsMinOffersN[product]);

constraint sum(customer in 1..pCustomersN,
               product in 1..pProductsN,
               channel in 1..pChannelsN)
            (
                vAssignment[customer, product, channel]*pExpectedReve[customer, product, channel]
            ) >= 
            (1+pMinHurdleReve)*sum(customer in 1..pCustomersN,
                                   product in 1..pProductsN,
                                   channel in 1..pChannelsN)
                                    (
                                        vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]
                                    );

solve maximize sum(customer in 1..pCustomersN,
                   product in 1..pProductsN,
                   channel in 1..pChannelsN)
                (
                    vAssignment[customer, product, channel]*
                    pExpectedReve[customer, product, channel]
                );<jupyter_output>Variable pCustomersN is undefined
Variable pProductsN is undefined
Variable pChannelsN is undefined
Variable pExpectedReve is undefined
Variable pChannelCosts is undefined
Variable pMaxBudget is undefined
Variable pProductsMinOffersN is undefined
Variable pMinHurdleReve is undefined<jupyter_text>Finding Solution  Using external datasets To provide values for model parameters we can use `json` format.<jupyter_code>!jq '.' small_dataset.json<jupyter_output>[1;39m{
  [0m[34;1m"pCustomersN"[0m[1;39m: [0m[0;39m2[0m[1;39m,
  [0m[34;1m"pProductsN"[0m[1;39m: [0m[0;39m2[0m[1;39m,
  [0m[34;1m"pChannelsN"[0m[1;39m: [0m[0;39m2[0m[1;39m,
  [0m[34;1m"pMaxBudget"[0m[1;39m: [0m[0;39m1[0m[1;39m,
  [0m[34;1m"pMinHurdleReve"[0m[1;39m: [0m[0;39m0.2[0m[1;39m,
  [0m[34;1m"pExpectedReve"[0m[1;39m: [0m[1;39m[
    [1;39m[
      [1;39m[
        [0;39m0.45[0m[1;39m,
        [0;39m0.2[0m[1;39m
      [1;39m][0m[1;39m,
      [1;39m[
        [0;39m0.35[0m[1;39m,
        [0;39m0.3[0m[1;39m
      [1;39m][0m[1;39m
    [1;39m][0m[1;39m,
    [1;39m[
      [1;39m[
        [0;39m0.55[0m[1;39m,
        [0;39m0.1[0m[1;39m
      [1;39m][0m[1;39m,
      [1;39m[
        [0;39m0.2[0m[1;39m,
        [0;39m0.45[0m[1;39m
      [1;39m][0m[1;39m
    [1;39m][0m[1;39m
  [1;39m][0m[1;39m,
  [0m[34;1m"pChannelCosts"[0m[1;39m: [0m[1;39m[
    [0;39m0.45[0m[1;39m,
    [0;39m0.2[0m[1[...]<jupyter_text>Let's solve the model with the `small_dataset.json` dataset<jupyter_code>%%minizinc -m bind --data small_dataset.json
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
array[1..pChannelsN] of float: pChannelCosts;
float: pMaxBudget;
array[1..pProductsN] of int: pProductsMinOffersN;
float: pMinHurdleReve;

%Variables
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of var 0..1: vAssignment;

%Constraints
constraint sum(customer in 1..pCustomersN, product in 1..pProductsN, channel in 1..pChannelsN)(vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]) <= pMaxBudget;

constraint forall(product in 1..pProductsN) (
    sum(customer in 1..pCustomersN,
        channel in 1..pChannelsN) (
              vAssignment[customer, product, channel]
    ) >= pProductsMinOffersN[product]);

constraint sum(customer in 1..pCustomersN,
               product in 1..pProductsN,
               channel in 1..pChannelsN)
            (
                vAssignment[customer, product, channel]*pExpectedReve[customer, product, channel]
            ) >= 
            (1+pMinHurdleReve)*sum(customer in 1..pCustomersN,
                                   product in 1..pProductsN,
                                   channel in 1..pChannelsN)
                                    (
                                        vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]
                                    );

solve maximize sum(customer in 1..pCustomersN,
                   product in 1..pProductsN,
                   channel in 1..pChannelsN)
                (
                    vAssignment[customer, product, channel]*
                    pExpectedReve[customer, product, channel]
                );<jupyter_output><empty_output><jupyter_text>Print the solution<jupyter_code>for customer in range(len(vAssignment)):
    for product in range(len(vAssignment[customer])):
        for channel in range(len(vAssignment[customer][product])):
            if vAssignment[product][customer][channel] > 0:
                print(f"Offer product:{product+1} to customer {customer+1} using channel {channel+1}")<jupyter_output>Offer product:1 to customer 1 using channel 1
Offer product:2 to customer 1 using channel 1
Offer product:1 to customer 2 using channel 1
Offer product:1 to customer 2 using channel 2
Offer product:2 to customer 2 using channel 2<jupyter_text>Augment model (add constraint) We can see that the model is assigning the same product (id=1) to the same customer (id=2) on both channels. Let's block this by adding extra-constraint.<jupyter_code>%%minizinc -m bind --data small_dataset.json
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
array[1..pChannelsN] of float: pChannelCosts;
float: pMaxBudget;
array[1..pProductsN] of int: pProductsMinOffersN;
float: pMinHurdleReve;

%Variables
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of var 0..1: vAssignment;

%Constraints
constraint sum(customer in 1..pCustomersN, product in 1..pProductsN, channel in 1..pChannelsN)(vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]) <= pMaxBudget;

constraint forall(product in 1..pProductsN) (
    sum(customer in 1..pCustomersN,
        channel in 1..pChannelsN) (
              vAssignment[customer, product, channel]
    ) >= pProductsMinOffersN[product]);

constraint sum(customer in 1..pCustomersN,
               product in 1..pProductsN,
               channel in 1..pChannelsN)
            (
                vAssignment[customer, product, channel]*pExpectedReve[customer, product, channel]
            ) >= 
            (1+pMinHurdleReve)*sum(customer in 1..pCustomersN,
                                   product in 1..pProductsN,
                                   channel in 1..pChannelsN)
                                    (
                                        vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]
                                    );

%product to customer only via max one channel
constraint forall(customer in 1..pCustomersN,
                  product in 1..pProductsN) (
    sum(channel in 1..pChannelsN)(
        vAssignment[customer, product, channel]
    ) <= 1 
);

solve maximize sum(customer in 1..pCustomersN,
                   product in 1..pProductsN,
                   channel in 1..pChannelsN)
                (
                    vAssignment[customer, product, channel]*
                    pExpectedReve[customer, product, channel]
                );
for customer in range(len(vAssignment)):
    for product in range(len(vAssignment[customer])):
        for channel in range(len(vAssignment[customer][product])):
            if vAssignment[product][customer][channel] > 0:
                print(f"Offer product:{product+1} to customer {customer+1} using channel {channel+1}")<jupyter_output>Offer product:1 to customer 1 using channel 1
Offer product:2 to customer 1 using channel 1
Offer product:1 to customer 2 using channel 1
Offer product:2 to customer 2 using channel 2<jupyter_text>Now the customer (id=2) gets a product offering via max one channel.  What if analysis?  What would be the solution if we reduced budget to 0.5?<jupyter_code>%%minizinc -m bind --data small_dataset_whatif.json
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
array[1..pChannelsN] of float: pChannelCosts;
float: pMaxBudget = 0.5;
array[1..pProductsN] of int: pProductsMinOffersN;
float: pMinHurdleReve;

%Variables
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of var 0..1: vAssignment;
var float: vTotalMargin;

%Constraints
constraint sum(customer in 1..pCustomersN, product in 1..pProductsN, channel in 1..pChannelsN)(vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]) <= pMaxBudget;

constraint forall(product in 1..pProductsN) (
    sum(customer in 1..pCustomersN,
        channel in 1..pChannelsN) (
              vAssignment[customer, product, channel]
    ) >= pProductsMinOffersN[product]);

constraint sum(customer in 1..pCustomersN,
               product in 1..pProductsN,
               channel in 1..pChannelsN)
            (
                vAssignment[customer, product, channel]*pExpectedReve[customer, product, channel]
            ) >= 
            (1+pMinHurdleReve)*sum(customer in 1..pCustomersN,
                                   product in 1..pProductsN,
                                   channel in 1..pChannelsN)
                                    (
                                        vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]
                                    );

%product to customer only via max one channel
constraint forall(customer in 1..pCustomersN,
                  product in 1..pProductsN) (
    sum(channel in 1..pChannelsN)(
        vAssignment[customer, product, channel]
    ) <= 1 
);

%total margin
constraint vTotalMargin = sum(customer in 1..pCustomersN,
                   product in 1..pProductsN,
                   channel in 1..pChannelsN)
                (
                    vAssignment[customer, product, channel]*
                    pExpectedReve[customer, product, channel]
                );

solve maximize sum(customer in 1..pCustomersN,
                   product in 1..pProductsN,
                   channel in 1..pChannelsN)
                (
                    vAssignment[customer, product, channel]*
                    pExpectedReve[customer, product, channel]
                );
print(f"Total reve {vTotalMargin}")
for customer in range(len(vAssignment)):
    for product in range(len(vAssignment[customer])):
        for channel in range(len(vAssignment[customer][product])):
            if vAssignment[customer][product][channel] > 0:
                print(f"Offer product:{product+1} to customer {customer+1} using channel {channel+1}")<jupyter_output>Total reve 1.0
Offer product:1 to customer 2 using channel 1
Offer product:2 to customer 2 using channel 2<jupyter_text>What would be a solution if we set budget to 1.0?<jupyter_code>%%minizinc -m bind --data small_dataset_whatif.json
%Parameters
int: pCustomersN;
int: pProductsN;
int: pChannelsN;
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of float: pExpectedReve;
array[1..pChannelsN] of float: pChannelCosts;
float: pMaxBudget = 1.0;
array[1..pProductsN] of int: pProductsMinOffersN;
float: pMinHurdleReve;

%Variables
array[1..pCustomersN, 1..pProductsN, 1..pChannelsN] of var 0..1: vAssignment;
var float: vTotalMargin;

%Constraints
constraint sum(customer in 1..pCustomersN, product in 1..pProductsN, channel in 1..pChannelsN)(vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]) <= pMaxBudget;

constraint forall(product in 1..pProductsN) (
    sum(customer in 1..pCustomersN,
        channel in 1..pChannelsN) (
              vAssignment[customer, product, channel]
    ) >= pProductsMinOffersN[product]);

constraint sum(customer in 1..pCustomersN,
               product in 1..pProductsN,
               channel in 1..pChannelsN)
            (
                vAssignment[customer, product, channel]*pExpectedReve[customer, product, channel]
            ) >= 
            (1+pMinHurdleReve)*sum(customer in 1..pCustomersN,
                                   product in 1..pProductsN,
                                   channel in 1..pChannelsN)
                                    (
                                        vAssignment[customer, product, channel]*pChannelCosts[pChannelsN]
                                    );

%product to customer only via max one channel
constraint forall(customer in 1..pCustomersN,
                  product in 1..pProductsN) (
    sum(channel in 1..pChannelsN)(
        vAssignment[customer, product, channel]
    ) <= 1 
);

%total margin
constraint vTotalMargin = sum(customer in 1..pCustomersN,
                   product in 1..pProductsN,
                   channel in 1..pChannelsN)
                (
                    vAssignment[customer, product, channel]*
                    pExpectedReve[customer, product, channel]
                );

solve maximize sum(customer in 1..pCustomersN,
                   product in 1..pProductsN,
                   channel in 1..pChannelsN)
                (
                    vAssignment[customer, product, channel]*
                    pExpectedReve[customer, product, channel]
                );
print(f"Total reve {vTotalMargin}")
for customer in range(len(vAssignment)):
    for product in range(len(vAssignment[customer])):
        for channel in range(len(vAssignment[customer][product])):
            if vAssignment[customer][product][channel] > 0:
                print(f"Offer product:{product+1} to customer {customer+1} using channel {channel+1}")<jupyter_output>Total reve 1.8
Offer product:1 to customer 1 using channel 1
Offer product:2 to customer 1 using channel 1
Offer product:1 to customer 2 using channel 1
Offer product:2 to customer 2 using channel 2<jupyter_text>Technical addon   Custom IDE You can download custom IDE using this [link](https://www.minizinc.org/doc-2.5.5/en/installation.html). It is the easiest way to use *minizinc*. ![mzn_ide](images/mzn-ide-playground4.jpg)  Using Jupyter Notebook Extension `iminizinc` allows you to run minizinc models directly in cells.After loading the extension you can use magic escape `%%minizinc`<jupyter_code>%load_ext iminizinc
n = 10
%%minizinc
include "alldifferent.mzn";
int: n;
array[1..n] of var 1..n: queens;
constraint alldifferent(queens);
constraint alldifferent([queens[i]+i | i in 1..n]);
constraint alldifferent([queens[i]-i | i in 1..n]);
solve satisfy;
%%minizinc?<jupyter_output>[0;31mDocstring:[0m
::

  %minizinc [-v] [-s] [-m {return,bind}] [-a] [-t TIME_LIMIT]
                [--solver SOLVER] [--data [DATA ...]]
                [model ...]

MiniZinc magic

positional arguments:
  model                 Model to solve

options:
  -v, --verbose         Verbose output
  -s, --statistics      Output statistics
  -m <{return,bind}>, --solution-mode <{return,bind}>
                        Whether to return solution(s) or bind them to
                        variables
  -a, --all-solutions   Return all solutions for satisfaction problems,
                        intermediate solutions for optimisation problems.
                        Implies -o.
  -t TIME_LIMIT, --time-limit TIME_LIMIT
                        Time limit in milliseconds (includes compilation and
                        solving)
  --solver SOLVER       Solver to run
  --data <[DATA ...]>   Data files
[0;31mFile:[0m      /workspaces/conf-dss2021/venv/lib/python3.10/site-packages/iminizinc/mzn.py[...]<jupyter_text>Using Python Package [minizinc](https://minizinc-python.readthedocs.io/en/latest/getting_started.html) gives you possibility to run `minizinc` directly from Python.<jupyter_code>## Required only in notebook
import nest_asyncio
nest_asyncio.apply()
from minizinc import Instance, Model, Solver

# Load n-Queens model from file
model = Model("dss-marketing-optimization.mzn")
model.add_file("small_dataset.json", parse_data=True)
# Find the MiniZinc solver configuration for Gecode
gecode = Solver.lookup("gecode")
# Create an Instance of the model for Gecode
instance = Instance(gecode, model)
result = instance.solve()
print(f"Solution objective: {result['objective']}")
for customer in range(instance["pCustomersN"]):
    for product in range(instance["pProductsN"]):
        for channel in range(instance["pChannelsN"]):
            if result["vAssignment"][customer][product][channel] > 0.5:
                print(f"Offer product:{product} to customer {customer} using channel {channel}")<jupyter_output>Solution objective: 1.8
Offer product:0 to customer 0 using channel 0
Offer product:1 to customer 0 using channel 0
Offer product:0 to customer 1 using channel 0
Offer product:1 to customer 1 using channel 1
{'fraction_non_alphanumeric': 0.10358882249317813, 'fraction_numerical': 0.02414424372215122, 'mean_word_length': 3.321194029850746, 'pattern_counts': {'":': 0, '<': 85, '<?xml version=': 0, '>': 94, 'https://': 3, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 32, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '22', 'dir': 'jupyter-structured-clean-dedup', 'id': '78448', 'license': 'MIT', 'n_tokens_mistral': 9658, 'n_tokens_neox': 8987, 'n_words': 2342, 'path': 'dss-marketing-optimization-minzinc.ipynb', 'repo_name': 'WitJakuczun/conf-dss2021-marketing-optimization-minizinc', 'size': '28951'}