# Marketing Campaign Optimization

## Objective and Prerequisites

Companies across almost every industry are looking to optimize their marketing campaigns. In this Jupyter Notebook, we’ll explore a marketing campaign optimization problem that is common in the banking and financial services industry, which involves determining which products to offer to individual customers in order to maximize total expected profit while satisfying various business constraints. You’ll learn how to formulate a mathematical optimization model of the problem (using machine learning predictive response models as parameters) and solve it using the Gurobi Optimizer.

This modeling example is at the beginner level, where we assume that you know Python and that you have some knowledge about building mathematical optimization models. The reader should also consult the  [documentation](https://www.gurobi.com/resources/?category-filter=documentation)
of the Gurobi Python API.

**Download the Repository**

You can download the repository containing this and other examples by clicking [here](https://github.com/Gurobi/modeling-examples/archive/master.zip).

## Motivation

The main goal of marketing in the banking and financial services industry is to offer "the right product to the right customer at the right time". However,
actually being able to achieve this goal is a complicated and challenging undertaking. What makes this particularly difficult is that companies have multiple products and operate under a complex set of business constraints. Choosing which products to offer to which customers in order to maximize the marketing return on investment and satisfy the business constraints is enormously complex.

Consider a major bank  that has made a deliberate effort to become a customer-focused institution, as opposed to a vertical product driven company. The goal of the bank is "to be the best at helping customers become financially better off by providing relevant solutions to their unique needs". A direct consequence of this goal is that marketing campaigns are multiple product campaigns as opposed to single product campaigns.
This transforms the data science and campaign targeting process from a fairly simple application of individual response models into a significantly more complex process that invloves  choosing which product to offer to which customer and through which channel.

The marketing team of the bank are used to applying business rules to target customers directly.
For example,
they target
customers solely on their product gaps or on marketers' business intuition.
The bank's
marketers have also applied RFM type analysis where general recency, frequency, and monetary measurements as well as product gaps are used to target customers for specific offers.

The marketing team's current approach, which
is widely used,
relies on predictive response models to target customers for offers. These models estimate the probability that a customer will respond to a specific offer and can significantly increase the response rate to a product offering. However, simply knowing a customer's probability of responding to a particular offer is not enough when a company has several products to promote and other business constraints to consider in its marketing planning.

Generally speaking, marketing teams also face the problem of knowing which product to offer to a customer, not just which customer to offer a product. In practice, many ad hoc rules are used:

* prioritization rules based on response rates or estimated expected profitability measures

* business rules to prioritize products that can be marketed

* product response models to select customers for a particular campaign.

One approach that is easily implemented but may not produce optimal customer contact plans relies on a measure of expected offer profitability to choose which products to offer customers. However, a shortcoming of this approach is its inability to effectively handle complex constraints on the customer contact plan.

To address this marketing campaign optimization problem, M. D. Cohen [1] proposed a MIP approach with data from Scotiabank. The marketing campaign optimization problem considered eleven unique offers: five investment, three lending, and three day-to-day banking offers. The investment offers included Guaranteed Investment Certificates (GICs), mutual funds, Registered Education Savings Program (RESP) and two unique discount brokerage offers. The lending offers included a mortgage and two credit card offers.
The day-to-day banking offers included one of two Scotia online banking service offers and a deposit account acquisition. The term campaign is used here to imply one large pro-active customer contact campaign that is comprised of eleven distinct offers; it can be thought of as eleven single product campaigns that are being offered at generally the same time to a non-overlapping set of customers. Approximately 2.5 million customers were included in the potential target market for the campaign.

In this Jupyter Notebook, we will use this MIP approach to address the bank’s marketing campaign optimization problem. It should be noted that this approach could be used by virtually any company across various industries to optimize their marketing campaigns, while taking into account their business constraints.

## Problem Description
The bank's marketing team needs to determine what products to offer to each customer in a way that maximizes the marketing campaign return on investment while considering the following constraints:

* limits on funding available for the campaign.
* restrictions on the minimum number of product offers that can be made in a campaign.
* campaign return-on-investment hurdle rates that must be met.

## Solution Approach

Mathematical programming is a declarative approach where the modeler formulates a mathematical optimization model that captures the key aspects of a complex decision problem. The Gurobi Optimizer solves such models using state-of-the-art mathematics and computer science.

A mathematical optimization model has five components, namely:

* Sets and indices.
* Parameters.
* Decision variables.
* Objective function(s).
* Constraints.

We now present a MIP approach for this marketing campaign optimization problem.

The MIP solution approach proposed by Cohen [1] is an improvement over the
traditional myopic
approach of picking the customers that have the largest expected value for a particular product because it
produces a globally optimal solution
from the viewpoint of the bank and allows for the effective implementation of business constraints across customers and business units. The approach accounts for limited resources and other business constraints.

We assume that the estimates for customer/offer expected incremental profit, costs, and business constraints serve as inputs to the marketing campaign optimization approach. The optimization phase is independent of the construction of these inputs.

The MIP approach
involves
a tactical and an operational problem.
For the tactical problem, we aggregate customers based on the individual expected profit parameters.  The estimated individual expected profit can be determined with data science techniques such as predictive response models. The key idea is to cluster the estimated individual expected profits and then consider the cluster centroids as representative of the data for all the individual customers within a single cluster. This aggregation enables the problem to be formulated as a linear programming problem so that rather than assigning offers to individual customers, the model identifies proportions within each cluster for each product offer that maximizes the marketing campaign return on investment while considering the business constraints. Typically, the number of customers in a cluster will be in the hundreds of thousands, which is the main decision variable of the tactical problem, consequently these variables can be considered as a continuous; therefore, the linear programming approach is justified.

The operational problem can be formulated as a MIP model, where the estimated individual expected profits and the output of the tactical model can be used as inputs to assign *products offers* to individual customers of each cluster in such a way that the  total marketing campaign return on investment is maximized.

## Tactical Model Formulation

### Sets and Indices
$k \in K$: Index and set of clusters.

$j \in J$: Index and set of products.

### Parameters
$\pi_{k,j}$: Expected profit to the bank from the offer of product $j \in J$ to an average customer of cluster $k \in K$.

$\nu_{k,j}$: Average variable cost associated with the offer of product  $j \in J$ to an average customer of cluster $k \in K$.

$N_{k}$: Number of customers in cluster $k \in K$.

$Q_{j}$: Minimum number of offers of product $j \in J$.

$R$: Corporate hurdle rate. This hurdle rate is used for the ROI calculation of the marketing campaign.

$B$: Marketing campaign budget.

$M$: Big M penalty. This penalty is associated with corrections on the budget that are necessary to satisfy other business constraints.

### Decision Variables
$y_{k,j} \geq 0$: Number of customers in cluster $k \in K$ that are offered product $j \in J$.

$z \geq 0$: Increase in budget in order to have a feasible campaign.

### Objective Function
- **Total profit**. Maximize total expected profit from marketing campaign and heavily penalize any correction to the budget.

\begin{equation}
\text{Max} \quad Z = \sum_{k \in K} \sum_{j \in J} \pi_{k,j} \cdot y_{k,j} - M \cdot z
\tag{0}
\end{equation}

### Constraints

- **Number of offers**. Maximum number of offers of products for each cluster is limited by the number of customers in the cluster.

\begin{equation}
\sum_{j \in J} y_{k,j} \leq N_{k} \quad \forall k \in K
\tag{1}
\end{equation}

- **Budget**. The marketing campaign budget constraint enforces that the total cost of the campaign should be less than the budget campaign. There is the possibility of increasing the budget to ensure the feasibility of the model, the minimum number of offers for all the product may require this increase in the budget.

\begin{equation}
\sum_{k \in K} \sum_{j \in J} \nu_{k,j} \cdot y_{k,j} \leq B + z
\tag{2}
\end{equation}

- **Offers limit**. Minimum number of offers of each product.

\begin{equation}
\sum_{k \in K} y_{k,j} \geq Q_{j}  \quad \forall j \in J
\tag{3}
\end{equation}

- **ROI**. The minimum ROI constraint ensures that the ratio of total profits over cost is at least one plus the corporate hurdle rate.

\begin{equation}
\sum_{k \in K} \sum_{j \in J} \pi_{k,j} \cdot y_{k,j} \geq (1+R) \cdot \sum_{k \in K} \sum_{j \in J} \nu_{k,j} \cdot y_{k,j}
\tag{4}
\end{equation}

## Operational Model Formulation

Once the optimal values $y_{k,j}$, for all $j \in J$ and $k \in K$, of the Tactical model have been found, we should determine which individual customers in cluster $k$ should get an offer of a product. Suppose that for a given cluster $k \in K$, the allocation of offers of product $j_1$ and $j_2$ are positive, i.e. $y_{k,j_1} > 0$ and $y_{k,j_2} > 0$. Then, $y_{k,j_1}$ and $y_{k,j_2}$ of customers in cluster $k$ must be offered product $j_1$ and $j_2$, respectively. The optimal way to do that is to solve an assignment problem using the estimated expected profit for the individual customers and not the one for clusters.

We now provide a formulation of the operational problem.

### Sets and Indices
$i \in I^{k}$: Index and set of customers in cluster $k \in K$.

$j \in J^{k}$: Index and subset of products offered to customers in cluster $k \in K$ , where $J^{k} = \{ j \in J: y_{k,j} > 0 \}$ .

### Parameters

$r_{k,i,j}$: Expected individual profit of customer $i \in I^{k}$ from  offer of product $j \in J^{k}$.

$Y_{k,j} = \lfloor y_{k,j} \rfloor $: Number of customers in cluster k that will get an offer of product $j \in J^{k}$.

### Decision Variables
$x_{k,i,j} \in \{0,1 \}$: This variable is equal to 1, if product $j \in J^{k}$  is offered to customer $i \in I^{k}$, and 0 otherwise.

### Objective Function
- **Total profit**. Maximize total individual profit.

\begin{equation}
\text{Max} \quad Z = \sum_{k \in K} \sum_{i \in I^{k}} \sum_{j \in J^{k}} r_{k,i,j} \cdot x_{k,i,j}
\tag{0}
\end{equation}

### Constraints

- **Product offers**. Allocate offers of a product to customers of each cluster.

\begin{equation}
\sum_{i \in  I^{k}}  x_{k,i,j} = Y_{k,j}  \quad \forall j \in J^{k}, k \in K
\tag{1}
\end{equation}

- **Offers limit**. At most one product may be offered to a customer of a cluster.

\begin{equation}
\sum_{j \in J^{k}} x_{k,i,j} \leq 1 \quad \forall i \in I^{k}, k \in K
\tag{2}
\end{equation}

- **Binary constraints**. Either a product offer is given to a customer of cluster k or not.

\begin{equation}
x_{k,i,j} \in \{0,1 \} \quad \forall i \in I^{k},  j \in J^{k}, k \in K
\tag{3}
\end{equation}

## Problem Instance

We consider two products, ten customers, and two clusters of customers. The corporate hurdle-rate is twenty percent.

### Tactical problem data

The following table defines the expected profit of an average customer in each cluster when offered a product.

|  | Product 1 | Product 2 |
| --- | --- |  --- |
| cluster 1 | $\$2,000$ | $\$1,000$ |
| cluster 2 | $\$3,000$ | $\$2,000$ |

The expected cost of offering a product to an average customer in a cluster is determined by the following table.

|  | Product 1 | Product 2 |
| --- | --- |  --- |
| cluster 1 | $\$200$ | $\$100$ |
| cluster 2 | $\$300$ | $\$200$ |

The budget available for the marketing campaign is $\$200$.

The number of customers in each cluster is given by the following table.

|  | Num. Customers |
| --- | --- |
| cluster 1 | 5 |
| cluster 2 | 5 |

The minimum number of offers of each product is provided in the following table,

|  | Min Offers |
| --- | --- |
| product 1 | 2 |
| product 2 | 2 |

### Operational problem data

The following table shows the expected profit of each customer in each cluster when offered a product.

|  | Product 1 | Product 2 |
| --- | --- |  --- |
| cluster 1, customer 1 | $\$2,050$ | $\$1,050$ |
| cluster 1, customer 2 | $\$1,950$ | $\$950$ |
| cluster 1, customer 3 | $\$2,000$ | $\$1,000$ |
| cluster 1, customer 4 | $\$2,100$ | $\$1,100$ |
| cluster 1, customer 5 | $\$1,900$ | $\$900$ |
| cluster 2, customer 6 | $\$3,000$ | $\$2,000$ |
| cluster 2, customer 7 | $\$2,900$ | $\$1,900$ |
| cluster 2, customer 8 | $\$3,050$ | $\$2,050$ |
| cluster 2, customer 9 | $\$3,100$ | $\$2,100$ |
| cluster 2, customer 10 | $\$2,950$ | $\$1,950$ |

The following table shows the cost of offering a product to a customer in a cluster.

|  | Product 1 | Product 2 |
| --- | --- |  --- |
| cluster 1, customer 1 | $\$205$ | $\$105$ |
| cluster 1, customer 2 | $\$195$ | $\$95$ |
| cluster 1, customer 3 | $\$200$ | $\$100$ |
| cluster 1, customer 4 | $\$210$ | $\$110$ |
| cluster 1, customer 5 | $\$190$ | $\$90$ |
| cluster 2, customer 6 | $\$300$ | $\$200$ |
| cluster 2, customer 7 | $\$290$ | $\$190$ |
| cluster 2, customer 8 | $\$305$ | $\$205$ |
| cluster 2, customer 9 | $\$310$ | $\$210$ |
| cluster 2, customer 10 | $\$295$ | $\$195$ |

## Python Implementation

We now import the Gurobi Python Module. Then, we initialize the data structures with the given data.

```python
%pip install gurobipy
```

```python
import gurobipy as gp
from gurobipy import GRB

# tested with Gurobi v9.0.0 and Python 3.7.0

### SETS

products = ['p1', 'p2']
clusters = ['k1', 'k2']
```

### Expected profit

The following tables shows the expected profit of a customer in each cluster when offered a product.

|  | Product 1 | Product 2 |
| --- | --- |  --- |
| cluster 1 | $\$2,000$ | $\$1,000$ |
| cluster 2 | $\$3,000$ | $\$2,000$ |

```python
### Parameters

# Expected profit
cp, expected_profit = gp.multidict({
    ('k1', 'p1'): 2000,
    ('k1', 'p2'): 1000,
    ('k2', 'p1'): 3000,
    ('k2', 'p2'): 2000
})

```

### Expected cost

The expected cost of offering a product to a customer in a cluster is shown in the following table.

|  | Product 1 | Product 2 |
| --- | --- |  --- |
| cluster 1 | $\$200$ | $\$100$ |
| cluster 2 | $\$300$ | $\$200$ |

```python
# Expected cost

cp, expected_cost = gp.multidict({
    ('k1', 'p1'): 200,
    ('k1', 'p2'): 100,
    ('k2', 'p1'): 300,
    ('k2', 'p2'): 200
})
```

### Number of customers
The number of customers in each cluster can be seen in the following table.

|  | Num. Customers |
| --- | --- |
| cluster 1 | 5 |
| cluster 2 | 5 |

```python
# Num of customers in each cluster

clusters, number_customers = gp.multidict({
    ('k1'): 5,
    ('k2'): 5
})
```

### Minimum number of offers

The minimum number of offers of each product is provided in the following table.

|  | Min Offers |
| --- | --- |
| product 1 | 2 |
| product 2 | 2 |

```python
# Minimum number offers for each product

products, min_offers = gp.multidict({
    ('p1'): 2,
    ('p2'): 2
})
```

### Scalars

The corporate hurdle-rate is twenty percent ($R = 0.20$).

The budget available for the marketing campaign is $\$200$.

```python
# Scalars

R = 0.20

#tight budget
budget = 200
```

## Tactical Model Formulation

### Decision Variables
$y_{k,j} \geq 0$: Number of customers in cluster $k \in K$ that are offered product $j \in J$.

$z \geq 0$: Increase in budget in order to have a feasible campaign.

```python
# Declare and initialize model
mt = gp.Model('Tactical')

### Decisions variables

# Allocation of product offers to customers in clusters.

y = mt.addVars(cp, name="allocate")

# Budget correction

z = mt.addVar(name="budget_correction")
```

    Using license file c:\gurobi\gurobi.lic

### Constraints

- **Number of offers**. Maximum number of offers of products for each cluster.

\begin{equation}
\sum_{j \in J} y_{k,j} \leq N_{k} \quad \forall k \in K
\tag{1}
\end{equation}

Where

$y_{k,j} \geq 0$: Number of customers in cluster $k \in K$ that are offered product $j \in J$.

$N_{k}$: Number of customers in cluster $k \in K$.

```python
### Constraints

# Constraint on number of offers at each cluster

maxOffers_cons = mt.addConstrs((y.sum(k,'*') <= number_customers[k]  for k in clusters), name='maxOffers')

```

### Constraints

- **Budget**. The marketing campaign budget constraint enforces that the total cost of the campaign should be less than the budget campaign. There is the possibility of increasing the budget to ensure the feasibility of the model, the minimum number of offers for all the product may require this increase in the budget.

\begin{equation}
\sum_{k \in K} \sum_{j \in J} \nu_{k,j} \cdot y_{k,j} \leq B + z
\tag{2}
\end{equation}

Where

$y_{k,j} \geq 0$: Number of customers in cluster $k \in K$ that are offered product $j \in J$.

$z \geq 0$: Increase in budget in order to have a feasible campaign.

$\nu_{k,j}$: Average variable cost associated with the offer of product  $j \in J$ to an average customer of cluster $k \in K$.

$B$: Marketing campaign budget.

```python
# Budget constraint

budget_con = mt.addConstr((y.prod(expected_cost) - z <= budget), name='budget')

```

### Constraints

- **Offers limit**. Minimum number of offers of each product.

\begin{equation}
\sum_{k \in K} y_{k,j} \geq Q_{j}  \quad \forall j \in J
\tag{3}
\end{equation}

Where

$y_{k,j} \geq 0$: Number of customers in cluster $k \in K$ that are offered product $j \in J$.

$Q_{j}$: Minimum number of offers of product $j \in J$.

```python
# Constraints on min number of offers of each product

minOffers_cons = mt.addConstrs( (y.sum('*',j) >= min_offers[j] for j in products), name='min_offers')

```

### Constraints

- **ROI**. The minimum ROI constraint ensures that the ratio of total profits over cost is at least  one plus the corporate hurdle rate.

\begin{equation}
\sum_{k \in K} \sum_{j \in J} \pi_{k,j} \cdot y_{k,j} \geq (1+R) \cdot \sum_{k \in K} \sum_{j \in J} \nu_{k,j} \cdot y_{k,j}
\tag{4}
\end{equation}

Where

$y_{k,j} \geq 0$: Number of customers in cluster $k \in K$ that are offered product $j \in J$.

$\pi_{k,j}$: Expected profit to the bank from the offer of product $j \in J$ to an average customer of cluster $k \in K$.

$\nu_{k,j}$: Average variable cost associated with the offer of product  $j \in J$ to an average customer of cluster $k \in K$.

$R$: Corporate hurdle rate.

```python
# Constraint to ensure minimum ROI

ROI_con = mt.addConstr((y.prod(expected_profit) - (1 + R)*y.prod(expected_cost) >= 0), name='ROI')
```

### Objective Function
- **Total profit**. Maximize total expected profit from marketing campaign and heavily penalize any correction to the budget.

\begin{equation}
\text{Max} \quad Z = \sum_{k \in K} \sum_{j \in J} \pi_{k,j} \cdot y_{k,j} - M \cdot z
\tag{0}
\end{equation}

Where

$y_{k,j} \geq 0$: Number of customers in cluster $k \in K$ that are offered product $j \in J$.

$z \geq 0$: Increase in budget in order to have a feasible campaign.

$\pi_{k,j}$: Expected profit to the bank from the offer of product $j \in J$ to an average customer of cluster $k \in K$.

**Note:** The value of $M$ 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.

```python
### Objective function

# Maximize total expected profit

M = 10000

mt.setObjective(y.prod(expected_profit) -M*z, GRB.MAXIMIZE)
```

```python
# Verify model formulation

mt.write('tactical.lp')
```

```python
# Run optimization engine

mt.optimize()
```

    Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (win64)
    Thread count: 4 physical cores, 8 logical processors, using up to 8 threads
    Optimize a model with 6 rows, 5 columns and 17 nonzeros
    Model fingerprint: 0x1eac2f22
    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.01s
    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.01 seconds
    Optimal objective -3.994000000e+06

```python
### Output Reports

# Optimal allocation of product offers to clusters

total_expected_profit = 0
total_expected_cost = 0

print("\nOptimal allocation of product offers to clusters.")
print("___________________________________________________")
for k,p in cp:
    if y[k,p].x > 1e-6:
        #print(y[k,p].varName, y[k,p].x)
        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

increased_budget = '${:,.2f}'.format(z.x)
print(f"\nThe increase correction in the campaign budget is {increased_budget}.")

# Financial reports

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"\nFinancial reports.")
print("___________________________________________________")
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}%.")
```

    Optimal allocation of product offers to clusters.
    ___________________________________________________
    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 increase correction in the campaign budget is $400.00.

    Financial reports.
    ___________________________________________________
    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%.

## Analysis

The cost of allocating products to clusters required an increase in the budget of $\$400$. The total expected profit is
$\$6,000$. The total expected cost is $\$600$ which is equal to the original budget of $\$200$ plus the increase of $\$400$. The expected ROI is 1,000 % which is much higher than the minimum ROI required.

## Operational Model Formulation

### Customer expected profit

```python
### Sets

customers = ['c1', 'c2','c3','c4','c5','c6','c7','c8','c9','c10']

### Parameters

# Expected profit from a product offering for each customer in each cluster
ccp, customer_profit = gp.multidict({
    ('k1', 'c1', 'p1'): 2050,
    ('k1', 'c1', 'p2'): 1050,
    ('k1', 'c2', 'p1'): 1950,
    ('k1', 'c2', 'p2'): 950,
    ('k1', 'c3', 'p1'): 2000,
    ('k1', 'c3', 'p2'): 1000,
    ('k1', 'c4', 'p1'): 2100,
    ('k1', 'c4', 'p2'): 1100,
    ('k1', 'c5', 'p1'): 1900,
    ('k1', 'c5', 'p2'): 900,
    ('k2', 'c6', 'p1'): 3000,
    ('k2', 'c6', 'p2'): 2000,
    ('k2', 'c7', 'p1'): 2900,
    ('k2', 'c7', 'p2'): 1900,
    ('k2', 'c8', 'p1'): 3050,
    ('k2', 'c8','p2'): 2050,
    ('k2', 'c9', 'p1'): 3100,
    ('k2', 'c9', 'p2'): 3100,
    ('k2', 'c10', 'p1'): 2950,
    ('k2', 'c10', 'p2'): 2950
})
```

### Customer offering cost

```python
# Customer cost of offering a product at a cluster

ccp, customer_cost = gp.multidict({
    ('k1', 'c1', 'p1'): 205,
    ('k1', 'c1', 'p2'): 105,
    ('k1', 'c2', 'p1'): 195,
    ('k1', 'c2', 'p2'): 95,
    ('k1', 'c3', 'p1'): 200,
    ('k1', 'c3', 'p2'): 100,
    ('k1', 'c4', 'p1'): 210,
    ('k1', 'c4', 'p2'): 110,
    ('k1', 'c5', 'p1'): 190,
    ('k1', 'c5', 'p2'): 90,
    ('k2', 'c6', 'p1'): 300,
    ('k2', 'c6', 'p2'): 200,
    ('k2', 'c7', 'p1'): 290,
    ('k2', 'c7', 'p2'): 190,
    ('k2', 'c8', 'p1'): 305,
    ('k2', 'c8','p2'): 205,
    ('k2', 'c9', 'p1'): 310,
    ('k2', 'c9', 'p2'): 310,
    ('k2', 'c10', 'p1'): 295,
    ('k2', 'c10', 'p2'): 295
})
```

## Operational Model Formulation

### Decision Variables
$x_{k,i,j} \in \{0,1 \}$: This variable is equal to 1, if product $j \in J^{k}$  is offered to customer $i \in I^{k}$, and 0 otherwise.

```python
# Declare and initialize model
mo = gp.Model('Operational')

### Decision variables

x = mo.addVars(ccp, vtype=GRB.BINARY, name="assign")
```

### Constraints

- **Product offers**. Allocate offers of a product to customers of each cluster.

\begin{equation}
\sum_{i \in  I^{k}}  x_{k,i,j} = Y_{k,j}  \quad \forall j \in J^{k}, k \in K
\tag{1}
\end{equation}

Where

$x_{k,i,j} \in \{0,1 \}$: This variable is equal to 1, if product $j \in J^{k}$  is offered to customer $i \in I^{k}$, and 0 otherwise.

$Y_{k,j} = \lfloor y_{k,j} \rfloor $: Number of customers in cluster k that will get an offer of product $j \in J^{k}$.

```python
# Product offers constraint

productOffers = {}

for k in clusters:
    for j in products:
            productOffers[k,j] = mo.addConstr(gp.quicksum(x[k,i,j] for kk,i,jj in ccp if (kk ==k and jj == j)) ==
                                              int(y[k,j].x), name='prodOffers_' + str(k) + ',' + str(j) )
```

### Constraints

- **Offers limit**. At most one product may be offered to a customer of a cluster.

\begin{equation}
\sum_{j \in J^{k}} x_{k,i,j} \leq 1 \quad \forall i \in I^{k}, k \in K
\tag{2}
\end{equation}

Where

$x_{k,i,j} \in \{0,1 \}$: This variable is equal to 1, if product $j \in J^{k}$  is offered to customer $i \in I^{k}$, and 0 otherwise.

```python
# limit on the number of offers to each customer in a cluster.

ki = [('k1', 'c1'),
      ('k1', 'c2'),
      ('k1', 'c3'),
      ('k1', 'c4'),
      ('k1', 'c5'),
      ('k2', 'c6'),
      ('k2', 'c7'),
      ('k2', 'c8'),
      ('k2', 'c9'),
      ('k2', 'c10')]

customerOffers = {}

for k,i in ki:
    customerOffers[k,i] = mo.addConstr(gp.quicksum(x[k,i,j] for kk,ii,j in ccp if (kk == k and ii == i) ) <= 1,
                                          name ='custOffers_' + str(k) + ',' + str(i) )
```

### Objective Function

- **Total profit**. Maximize total individual expected profit.

\begin{equation}
\text{Max} \quad Z = \sum_{k \in K}  \sum_{i \in I^{k}} \sum_{j \in J^{k}} r_{k,i,j} \cdot x_{k,i,j}
\tag{0}
\end{equation}

Where

$x_{k,i,j} \in \{0,1 \}$: This variable is equal to 1, if product $j \in J^{k}$  is offered to customer $i \in I^{k}$, and 0 otherwise.

$r_{k,i,j}$: Expected individual profit of customer $i \in I^{k}$ from  offer of product $j \in J^{k}$.

```python
### Objective function

# Maximoze total profit

mo.setObjective(x.prod(customer_profit), GRB.MAXIMIZE)
```

```python
# Verify model formulation

mo.write('operational.lp')

# Run optimization engine

mo.optimize()
```

    Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (win64)
    Thread count: 4 physical cores, 8 logical processors, using up to 8 threads
    Optimize a model with 14 rows, 20 columns and 40 nonzeros
    Model fingerprint: 0xe1e9d99f
    Variable types: 0 continuous, 20 integer (20 binary)
    Coefficient statistics:
      Matrix range     [1e+00, 1e+00]
      Objective range  [9e+02, 3e+03]
      Bounds range     [1e+00, 1e+00]
      RHS range        [1e+00, 2e+00]
    Found heuristic solution: objective 6050.0000000
    Presolve removed 7 rows and 10 columns
    Presolve time: 0.00s
    Presolved: 7 rows, 10 columns, 20 nonzeros
    Variable types: 0 continuous, 10 integer (10 binary)

    Root relaxation: objective 6.100000e+03, 2 iterations, 0.00 seconds

        Nodes    |    Current Node    |     Objective Bounds      |     Work
     Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

    *    0     0               0    6100.0000000 6100.00000  0.00%     -    0s

    Explored 0 nodes (2 simplex iterations) in 0.02 seconds
    Thread count was 8 (of 8 available processors)

    Solution count 2: 6100 6050

    Optimal solution found (tolerance 1.00e-04)
    Best objective 6.100000000000e+03, best bound 6.100000000000e+03, gap 0.0000%

```python
### Output Reports

# Optimal assignment of product offers to customers

total_customer_profit = 0
total_customer_cost = 0

kvalue = None
first = True
num_assignments = 0

print("\nOptimal assignment of product offers to customers.")
print("___________________________________________________")
for k,i,j in ccp:
    if k != kvalue:
        prevk = kvalue
        kvalue = k
        if not first:
            print("___________________________________________________")
            print(f"Number of assignments in cluster {prevk} is {num_assignments}")
            print("___________________________________________________")
            num_assignments = 0
        if first:
            first = False
    if x[k,i,j].x > 0.5:
        #print(x[k,i,j].varName, x[k,i,j].x)
        profit = '${:,.2f}'.format(customer_profit[k,i,j])
        cost = '${:,.2f}'.format(customer_cost[k,i,j])
        print(f"Customer {i} in cluster {k} gets an offer of product {j}:")
        print(f"The expected profit is {profit} at a cost of {cost}")
        total_customer_profit += customer_profit[k,i,j]*x[k,i,j].x
        total_customer_cost += customer_cost[k,i,j]*x[k,i,j].x
        num_assignments += 1
print("___________________________________________________")
print(f"Number of assignments in cluster {kvalue} is {num_assignments}")
print("___________________________________________________\n")

# Financial reports

customers_ROI = round(100*total_customer_profit/total_customer_cost,2)

money_customers_profit = '${:,.2f}'.format(total_customer_profit)
money_customers_cost = '${:,.2f}'.format(total_customer_cost)

print(f"\nFinancial reports.")
print("___________________________________________________")
print(f"Optimal total customers profit is {money_customers_profit}.")
print(f"Optimal total customers cost is {money_customers_cost} with a budget of {money_budget} and an extra amount of {increased_budget}.")
print(f"Optimal ROI is {customers_ROI}% with a minimum ROI of  {min_ROI}%.")

```

    Optimal assignment of product offers to customers.
    ___________________________________________________
    Customer c1 in cluster k1 gets an offer of product p2:
    The expected profit is $1,050.00 at a cost of $105.00
    Customer c2 in cluster k1 gets an offer of product p2:
    The expected profit is $950.00 at a cost of $95.00
    Customer c3 in cluster k1 gets an offer of product p1:
    The expected profit is $2,000.00 at a cost of $200.00
    Customer c4 in cluster k1 gets an offer of product p1:
    The expected profit is $2,100.00 at a cost of $210.00
    ___________________________________________________
    Number of assignments in cluster k1 is 4
    ___________________________________________________
    ___________________________________________________
    Number of assignments in cluster k2 is 0
    ___________________________________________________

    Financial reports.
    ___________________________________________________
    Optimal total customers profit is $6,100.00.
    Optimal total customers cost is $610.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%.

## Analysis
Each customer got, at most, one product offer.  Product p2 is offered to customers c1 and c2, and product p1 is offered to customers c3 and c4. Products p1 and p2 are offerred to at least two customers -this is a constraint from the tactical model. Observe that to ensure these hard business constraints, the budget needs to be increased by $\$400$

The cost of assigning  products to customers is $\$610$, which slightly violates the total budget available of $\$600$. The total customers profit is $\$6,100$. The  ROI is 1,000 %, which is much higher than the minimum ROI required.

If the total available budget needs to be enforced, the following constraint can be added to the operational model:

- **Budget**. Enforce budget constraint.

\begin{equation}
\sum_{k \in K}  \sum_{i \in I^{k}} \sum_{j \in J^{k}} c_{k,i,j} \cdot x_{k,i,j} \leq B'
\tag{4}
\end{equation}

The new budget is the original budget plus the correction, that is  $B' = B + z$

## Scenario 1
Enforce total budget available constraint. In this case, the operational model is:

### Objective function

- **Total profit**. Maximize total individual expected profit.

\begin{equation}
\text{Max} \quad Z = \sum_{k \in K}  \sum_{i \in I^{k}} \sum_{j \in J^{k}} r_{k,i,j} \cdot x_{k,i,j}
\tag{0}
\end{equation}

### Constraints

- **Product offers**. Allocate offers of a product to customers of each cluster.

\begin{equation}
\sum_{i \in  I^{k}}  x_{k,i,j} = Y_{k,j}  \quad \forall j \in J^{k}, k \in K
\tag{1}
\end{equation}

- **Offers limit**. At most one product may be offered to a customer in each cluster.

\begin{equation}
\sum_{j \in J^{k}} x_{k,i,j} \leq 1 \quad \forall i \in I^{k}, k \in K
\tag{2}
\end{equation}

- **Budget**. Enforce budget constraint.

\begin{equation}
\sum_{k \in K}  \sum_{i \in I^{k}} \sum_{j \in J^{k}} c_{k,i,j} \cdot x_{k,i,j} \leq B'
\tag{3}
\end{equation}

```python
### Operational model enforcing constraint for total budget available

# Declare and initialize model
mob = gp.Model('OperationalB')

### Decision variables

xb = mob.addVars(ccp, vtype=GRB.BINARY, name="assign")
```

```python
# Product offers constraint

productOffersb = {}

for k in clusters:
    for j in products:
            productOffersb[k,j] = mob.addConstr(gp.quicksum(xb[k,i,j] for kk,i,jj in ccp if (kk ==k and jj == j)) ==
                                              int(y[k,j].x), name='prodOffersb_' + str(k) + ',' + str(j) )
```

```python
# limit on the number of offers to each customer in a cluster.

customerOffersb = {}

for k,i in ki:
    customerOffersb[k,i] = mob.addConstr(gp.quicksum(xb[k,i,j] for kk,ii,j in ccp if (kk == k and ii == i) ) <= 1,
                                          name ='custOffersb_' + str(k) + ',' + str(i) )
```

```python
# budget constraint

# New budget
new_budget = budget + z.x

totBudget = mob.addConstr(xb.prod(customer_cost) <= new_budget, name='total_budget')
```

```python
### Objective function

# Maximize total profit

mob.setObjective(xb.prod(customer_profit), GRB.MAXIMIZE)
```

```python
# Verify model formulation

mob.write('operationalB.lp')

# Run optimization engine

mob.optimize()
```

    Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (win64)
    Thread count: 4 physical cores, 8 logical processors, using up to 8 threads
    Optimize a model with 15 rows, 20 columns and 60 nonzeros
    Model fingerprint: 0x96da858f
    Variable types: 0 continuous, 20 integer (20 binary)
    Coefficient statistics:
      Matrix range     [1e+00, 3e+02]
      Objective range  [9e+02, 3e+03]
      Bounds range     [1e+00, 1e+00]
      RHS range        [1e+00, 6e+02]
    Found heuristic solution: objective 5950.0000000
    Presolve removed 7 rows and 10 columns
    Presolve time: 0.00s
    Presolved: 8 rows, 10 columns, 28 nonzeros
    Variable types: 0 continuous, 10 integer (10 binary)

    Root relaxation: objective 6.000000e+03, 2 iterations, 0.00 seconds

        Nodes    |    Current Node    |     Objective Bounds      |     Work
     Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

    *    0     0               0    6000.0000000 6000.00000  0.00%     -    0s

    Explored 0 nodes (2 simplex iterations) in 0.02 seconds
    Thread count was 8 (of 8 available processors)

    Solution count 2: 6000 5950

    Optimal solution found (tolerance 1.00e-04)
    Best objective 6.000000000000e+03, best bound 6.000000000000e+03, gap 0.0000%

```python
### Output Reports

# Optimal assignment of product offers to customers

total_customer_profitb = 0
total_customer_costb = 0

kvalueb = None
firstb = True
num_assignmentsb = 0

print("\nOptimal assignment of product offers to customers.")
print("___________________________________________________")
for k,i,j in ccp:
    if k != kvalueb:
        prevkb = kvalueb
        kvalueb = k
        if not firstb:
            print("___________________________________________________")
            print(f"Number of assignments in cluster {prevkb} is {num_assignmentsb}")
            print("___________________________________________________")
            num_assignmentsb = 0
        if firstb:
            firstb = False
    if xb[k,i,j].x > 0.5:
        #print(x[k,i,j].varName, x[k,i,j].x)
        profitb = '${:,.2f}'.format(customer_profit[k,i,j])
        costb = '${:,.2f}'.format(customer_cost[k,i,j])
        print(f"Customer {i} in cluster {k} gets an offer of product {j}:")
        print(f"The expected profit is {profitb} at a cost of {costb}")
        total_customer_profitb += customer_profit[k,i,j]*xb[k,i,j].x
        total_customer_costb += customer_cost[k,i,j]*xb[k,i,j].x
        num_assignmentsb += 1
print("___________________________________________________")
print(f"Number of assignments in cluster {kvalueb} is {num_assignmentsb}")
print("___________________________________________________\n")

# Financial reports

customers_ROIb = round(100*total_customer_profitb/total_customer_costb,2)

money_customers_profitb = '${:,.2f}'.format(total_customer_profitb)
money_customers_costb = '${:,.2f}'.format(total_customer_costb)

print(f"\nFinancial reports.")
print("___________________________________________________")
print(f"Optimal total customers profit is {money_customers_profitb}.")
print(f"Optimal total customers cost is {money_customers_costb} with a budget of {money_budget} and an extra amount of {increased_budget}.")
print(f"Optimal ROI is {customers_ROIb}% with a minimum ROI of  {min_ROI}%.")
```

    Optimal assignment of product offers to customers.
    ___________________________________________________
    Customer c1 in cluster k1 gets an offer of product p2:
    The expected profit is $1,050.00 at a cost of $105.00
    Customer c2 in cluster k1 gets an offer of product p1:
    The expected profit is $1,950.00 at a cost of $195.00
    Customer c4 in cluster k1 gets an offer of product p1:
    The expected profit is $2,100.00 at a cost of $210.00
    Customer c5 in cluster k1 gets an offer of product p2:
    The expected profit is $900.00 at a cost of $90.00
    ___________________________________________________
    Number of assignments in cluster k1 is 4
    ___________________________________________________
    ___________________________________________________
    Number of assignments in cluster k2 is 0
    ___________________________________________________

    Financial reports.
    ___________________________________________________
    Optimal total customers profit is $6,000.00.
    Optimal total customers 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%.

## Analysis
Each customer got, at most, one product offer. Products p1 and p2 are offerred to at least two customers. The cost of assigning  products to customers is $\$600$, which is equal to the total budget available. The total customers profit is $\$6,000$. The  ROI is 1,000 %, which is much higher than the minimum ROI required.

In this scenario, we enforce the total available budget constraint and we get a different distribution for the assignment.
Product p1 is offered to customers c2 and c4, and product p2 is offered to customers c1 and c5.

##  Conclusion

In this Jupyter Notebook, we discussed the importance of marketing campaigns for the banking industry. We discussed that machine learning predictive response models can be used to provide the input data of a marketing campaign optimization problem. We showed how the marketing campaign optimization problem can be decomposed into a tactical problem and an operational problem.

The tactical problem is formulated as a linear programming problem where we aggregate data generated by the machine learning predictive response models.

The solution of the tactical problem determines what products to offer to customers in clusters while maximizing the marketing campaign expected profits and considering the following constraints:

* limits on funding available for the campaign.
* restrictions on the minimum number of product offers that can be made in a campaign.
* campaign return-on-investment hurdle rates that must be met.

The operational problem is formulated as a MIP model, where the expected customer  profits and the output of the tactical model can be used as inputs to assign products offers to individual customers in such way that the total customers profit is maximized. We considered two cases for the operational problem. In the first case, the total available budget determined by tactical problem is not enforced. This means that the optimal solution of this problem might have slight violations of the total available budget. In the second case, we enforce a total available budget.

##  References

[1] M. D. Cohen. *Exploiting response models—optimizing cross-sell and up-sell opportunities in banking.* Information Systems. Vol. 29. issue 4, June 2004, Pages 327-341

Copyright © 2020 Gurobi Optimization, LLC

```python

```


```julia
using PicoQuant
```

## Contraction functions

In this notebook we provide some details on the different contraction functions in QuantEx and how they are used. We then illustrate how these contraction functions may be compared. We begin by creating a function to generate toy tensor networks which we can contract.

```julia
# Function for creating an n qubit GHZ circuit
function create_basic_circuit(n::Int)
    circuit_network = TensorNetworkCircuit(n)

    for i = 1:n
        add_gate!(circuit_network, gate_tensor(:H), [i])
    end

    for i = 1:n-1
        add_gate!(circuit_network, gate_tensor(:CX), [i, i+1])
    end

    add_input!(circuit_network, "0"^n)
    add_output!(circuit_network, "0"^n)

    circuit_network
end
```

### Full Wavefunction Contraction

One of the more basic contraction functions is `full_wavefunction_contraction!`. To use this function, the tensor network circuit we want to contract must contain input nodes. This function operates by first contracting all input nodes together to produce a a single node representing the wavefunction of the input state. Then, all of the quantum gate nodes in the network are contracted with the wavefucntion node in the order in which they appear in the associated quantum circuit.

```julia
circuit_network = create_basic_circuit(3)

full_wavefunction_contraction!(circuit_network)
```

### Netcon Contraction

The `netcon_contraction!` function uses the netcon implementation in TensorOperation.jl to find the optimal contraction plan for the given tensor network. The network is then contracted according to this contraction plan. The netcon method is based on an exhaustive search of all possible contraction plans, the number of which grows rapidly with the number of node in the tensor network. The largest network this method will work for contains 36 nodes.

```julia
circuit_network = create_basic_circuit(3)

netcon_contraction!(circuit_network)
```

### BGreedy Contraction

The Boltzmann greedy function, `bgreedy_contraction!`, will return the best of `N` contraction plans, for the given tensor network, which are sampled using the Boltzmann greedy method. This method builds a contraction plan by always randomly selecting a pair of tensors to contract next using a probabilty distribution which favours cheap contractions. In particular, the probability that a pair of tensors $A$ and $B$ are selected for contraction is proportional to a Boltzmann type distribution where the cost of contraction plays the role of energy:
\begin{equation} P(A, B) \propto \exp(-\mbox{cost}_\alpha(A, B)/\tau), \end{equation}

where the contraction cost is given by the following formula ($C$ denotes the tensor resulting from contracting $A$ and $B$):
\begin{equation} \mbox{cost}_\alpha(A, B) = \mbox{size}(C) - \alpha (\mbox{size}(A) + \mbox{size}(B)) \end{equation}

```julia
circuit_network = create_basic_circuit(3)

α=1; τ=1; N=10

bgreedy_contraction!(circuit_network, α, τ, N)
```

### QuickBB Contraction

It was shown by Markov and Shi (https://doi.org/10.1137/050644756) that finding the optimal contraction plan for a tensor network is closely related to computing the tree width of the networks associated line graph. Thus, algorithms for finding tree decompositions of graphs, with a minimal tree width, can be adapted to find an optimal contraction plan for a tensor network. The `QuickBB_contraction!` does this by first creating a MetaGraph for the associated line graph of the given tensor network. Then the 'Quick Branch and Bound' method for finding the tree width of a graph, is used to find an edge elimination ordering for the network. This method was introduced by Gogate and Dechter in https://arxiv.org/abs/1207.4109. The edge elimination ordering is then converted to a contractio plan which is then used to contract the tensor network.

(This method is still under development)

```julia
circuit_network = create_basic_circuit(3)

QuickBB_contraction!(circuit_network)
```

### KaHyPar Contraction

The `KaHyPar_contraction!` function will contract a tensor network using the following top-down greedy method. First, the Karlsruhe Hypergraph Partitioning package (KaHyPar) is used to partition the nodes of the given tensor network into $k$ disjoint sets in a way that minimises the number of edges connecting the partitions (Other objective functions to be minimised, such as total edge dimension, to be added in future). The size of each parition will be at most $(1+\epsilon)$ times the average size of all partitions.

Then a new tensor network is created, containing a single node for each partition and whose edges correspond to the edges connecting distinct partitions in the given tensor network. The netcon algorithm is then used to find the optimal contraction plan for this partition network. This contraction plan forms the final steps of the contraction plan for our TensorNetworkCircuit. To create the rest of the full contraction plan, the partition nodes in the contraction plan need to be replaced with the contraction plan for the associated partition/subnetwork. To do this, the described KaHyPar method is called recursively on the partitions. As a base case, if the number of nodes in a network is less than a given theshold $V$, then netcon is used to find the contraction plan for it.

(This method is still under development)

```julia
circuit_network = create_basic_circuit(3)

k=2; ϵ=1; V=7

KaHyPar_contraction!(circuit_network, k, ϵ, V)
```

### Comparing Contraction Functions

The the quality of the contraction plans used/found by the different contraction methods may vary significantly if we change the network being contracted. We can compare the contraction plans by looking at the number of floating point operations it takes to contract the network according to a particular plan. Also, we could compare by looking at the maximum size of any intermediate tensors created by contracting the network. Both of these metrics are measured by the backend when a network is contracted. Below we illustrate a simple comparison of the contraction methods by repeatedly contracting the GHZ circuit for varying number of qubits.

```julia
# A dictionary of the different contraction functions to compare.
contraction_functions = Dict(:full_wavefunction => full_wavefunction_contraction!,
                             :netcon => netcon_contraction!,
                             :bgreedy => bgreedy_contraction!)

# Maximum size of a GHZ circuit to contract.
max_qubits = 9

# Allocate memory for the metrics.
flops = Dict(label => Array{Real, 1}(undef, max_qubits) for label in keys(contraction_functions))
max_tensor_size = Dict(label => Array{Real, 1}(undef, max_qubits) for label in keys(contraction_functions))

# Use each contraction function to contract a range of circuits and record metrics.
for num_qubits = 1:max_qubits
    for (label, contraction_function) in contraction_functions

        network = create_basic_circuit(num_qubits)
        contraction_function(network)

        flops[label][num_qubits] = network.backend.metrics.flops
        max_tensor_size[label][num_qubits] = network.backend.metrics.max_tensor_size

    end
end
```

```julia
using Plots
```

Below we plot the results of the above comparison

```julia
# Create a plot comparing the floating point operations required by the different methods to contract the network
plt_flops = Plots.plot(title="flops", xlabel="number_of_qubits", ylabel="flops", legend=:topleft)
for function_label in keys(contraction_functions)
    plot!(plt_flops, 1:max_qubits, flops[function_label], label=String(function_label))
end
plt_flops

# Create a plot comparing the maximum size of a tensor required by the different methods to contract the network
plt_max_size = Plots.plot(title="max tensor size", xlabel="number_of_qubits", ylabel="max size", legend=:topleft)
for function_label in keys(contraction_functions)
    plot!(plt_max_size, 1:max_qubits, max_tensor_size[function_label], label=String(function_label))
end
plt_max_size

Plots.plot(plt_flops, plt_max_size, layout=(1,2))
```


# The IPython Notebook
* Facilitates interactive computing using a web-browser
* Developing,
* Documenting,
* Executing code and
* Communicating the results

## Starting the Notebook Server
** `ipython notebook`**

A new notebook document is created with following features

 * Notebook name,
 * Menu bar,
 * Toolbar and
 * Empty code cell

Structure of a notebook document
------------------------------------
### There are four types of cells:
* Code cells,
* Markdown cells,
* Raw cells and
* Heading cells

### Code cells
* A code cell allows you to edit and write new code, with full syntax highlighting and tab completion

```python
from __future__ import division, print_function
import numpy as np
a = np.random.rand(10)
print(type(a))
print (a)
```

    <type 'numpy.ndarray'>
    [ 0.82183566  0.4249464   0.66023236  0.24285299  0.11646147  0.26724622
      0.43001898  0.08840917  0.83570893  0.12698752]

```python
np.sin(a)
```

    array([ 0.19420961,  0.80987413,  0.37662322,  0.61959548,  0.4818346 ,
            0.52143703,  0.76786701,  0.43159441,  0.29240385,  0.71729244])

* Its possible to use np.sin(_);
* (_) will represent last output,
* (__) will represent second to last output,
* (_N) represent Out [N]

```python
%matplotlib inline
import matplotlib.pyplot as plt
#import numpy as np
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x)
plt.plot (x, y)
```

### Markdown Cells
1. It is possible to document the computational process in a literate way, alternating descriptive text with code
2. Within Markdown cells, you can also include mathematics in a straightforward way, using standard LaTeX notation
3. Standard mathematics environments defined by LaTeX and AMS-LaTeX (the amsmath package) also work

$\sin^2\theta+\cos^2\theta= 1 $

$$\sin^2\theta+\cos^2{\theta} = 1 $$

\begin{equation}
y = 3x^3+2x^2+x+10
\end{equation}

\begin{align}
y_1 & = x_1^3+x_2^2+x+5 \\
y_2 & = x_1^4+x_2^3+x+10
\end{align}

$$P_{r-j} = \begin{cases}
0 &  \text{if $r-j$ is odd},\\
r!\,(-1)^{(r-j)/2}&  \text{if $r-j$ is even}.
\end{cases}$$

## Inserting Images

### Raw Cells
* Raw cells provide a place in which you can write output directly.
* Raw cells are not evaluated by the notebook.
* When passed through _nbconvert_, raw cells arrive in the destination format unmodified.
* For example, this allows you to type full LaTeX into a raw cell, which will only be rendered by LaTeX after conversion by nbconvert.

### Heading Cells
* You can provide a conceptual structure for your computational document as a whole using different levels of headings
* There are 6 levels available, from level 1 (top level) down to level 6 (paragraph)

### Keyboard Shortcuts
1. ** `Shift-Enter`: Run cell **
2. ** `Ctrl-Enter`: Run cell in-place **
3. ** `Alt-Enter`: Run cell, insert below **
4. ** `Esc`** and **`Enter`: Command mode** and **edit mode **

## Converting notebooks to other formats
** `$ ipython nbconvert --to FORMAT notebook.ipynb` **
* The default output format is html
* For example, for this notebook I can give the following command to export it to pdf format (via LaTeX)
** `$ ipython nbconvert --to html session1.ipynb` **

```python

```


\title{(some) LaTeX environments \par for Jupyter notebook}
\author{@jfbercher}
\maketitle

# Introduction

This extension for IPython 4.x or Jupyter enables to use some LaTeX commands and environments in the notebook's markdown cells.
\begin{enumerate}
\item **LaTeX commands and environments**
\begin{itemize}
\item support for some LaTeX commands within markdown cells, *e.g.* `\textit`, `\textbf`, `\underline`, `author`, `\title`, LaTeX comments
\item support for **theorems-like environments**, support for labels and **cross references**
\item support for **lists**: *enumerate, itemize*,
\item limited support for a **figure environment**,
\item support for an environment *listing*,
\item additional *textboxa* environment
\end{itemize}
\item **Citations and bibliography**
\begin{itemize}
\item support for `\cite` with creation of a References section
\end{itemize}
\item it is possible mix markdown and LaTeX markup
\item **Document-wide numbering of equations and environments, support for `\label` and `\ref`**
\item **Configuration toolbar**
\item **LaTeX_envs dropdown menu for a quick insertion of environments**
\item Support for **User $\LaTeX$ definitions file**
\item Environments title/numbering can be customized by users in ``user_envs.json`` config file
\item **Export to HTML and LaTeX with a customized exporter**
\item Styles can be customized in the `latex_env.css` stylesheet
\item Limited **autocompletion** for \$, (, {, [
\end{enumerate}

A simple illustration is as follows: one can type the following in a markdown cell
\begin{listing}
The dot-product is defined by equation (\ref{eq:dotp}) in theorem \ref{theo:dotp} just below:
\begin{theorem}[Dot Product] \label{theo:dotp}
Let $u$ and $v$ be two vectors of $\mathbb{R}^n$. The dot product can be expressed as
\begin{equation}
\label{eq:dotp}
u^Tv = |u||v| \cos \theta,
\end{equation}
where $\theta$ is the angle between $u$ and $v$ ...
\end{theorem}
\end{listing}
and have it rendered as

The dot-product is defined by equation (\ref{eq:dotp}) in theorem \ref{theo:dotp} just below:
\begin{theorem}[Dot Product] \label{theo:dotp}
Let $u$ and $v$ be two vectors of $\mathbb{R}^n$. The dot product can be expressed as
\begin{equation}
\label{eq:dotp}
u^Tv = |u||v| \cos \theta,
\end{equation}
where $\theta$ is the angle between $u$ and $v$ ...
\end{theorem}

## ** What's new **

**February 9, 2017** - version 1.3.7

- Enable customizing hotkeys for inserting environments (via `nbextensions_configurator`)
- LaTeX\_envs menu can be customized by editing `envsLatex.json` in `nbextensions/latex_envs` directory)
- Autocompletion for \$,(,{,[
- Updates to ensure compatibility with nbTranslate
- Recognize \\ [..\\ ] and \\ (..\\ ) as LaTeX equations delimiters in nb

**November 2, 2016** - version 1.3.1

- Support for **user environments configuration** file (`user_envs.json` in `nbextensions/latex\_envs` directory). This file is included by the html export template.
- Support for **book/report style numbering** of environments, e.g. "Example 4.2" is example 2 in section 4.
- Support for `\author`, `\title`, and `maketitle`. Author and title are saved in notebook metadata, used in html/latex exports. The maketitle command also formats a title in the LiveNotebook.
- Added a Toogle menu in the config toolbar to:
    - toggle use of user's environments config file
    - toggle `report-style` numbering

**September 18, 2016** - version 1.3

- Support for **user personal LaTeX definitions** file (`latexdefs.tex` in current directory). This file is included by the html and latex export templates.
- Style for nested enumerate environments added in `latex_envs.css`
- Added a Toogle menu in the config toolbar to:
    - toggle the display of the LaTeX_envs dropdown menu,
    - toggle the display of labels keys,
    - toggle use of user's LaTeX definition file
- **Cross references now use the true environment number instead of the reference//label key**. **References are updated immediately**. This works **document wide** and works for pre and post references
- Support for optional parameters in theorem-like environments
- Support for spacings in textmode, eg `\par`, `\vspace, \hspace`
- Support for LaTeX comments % in markdown cells
- Reworked loading and merging of system/document configuration parameters

**August 28, 2016** - version 1.2

- **Added support for nested environments of the same type**. Nesting environments of different type was already possible, but there was a limitation for nesting environments of the same kind; eg itemize in itemize in itemize. This was due to to the fact that regular expressions are not suited to recursivity. I have developped a series of functions that enable to extract nested environments and thus cope with such situations.

- Corrected various issues, eg [#731](https://github.com/ipython-contrib/jupyter_contrib_nbextensions/issues/731), [#720](https://github.com/ipython-contrib/jupyter_contrib_nbextensions/issues/720) where the content of nested environments was incorrectly converted to markdown.

- Completely reworked the configuration toolbar. Re-added tips.
- Added a toggle-button for the LaTeX\_envs menu
- Added system parameters that can be specified using the [nbextensions_configurator](https://github.com/Jupyter-contrib/jupyter_nbextensions_configurator/tree/master/src/jupyter_nbextensions_configurator/static/nbextensions_configurator). Thus reworked the configuration loading/saving.
- Reworked extension loading. It now detects if the notebook is fully loaded before loading itself.

**August 03, 2016** - version 1.13

- Added a template to also keep the toc2 features when exporting to html:
```
jupyter nbconvert --to html_toclenvs FILE.ipynb
```
- Added a dropdown menu that enables to insert all main LaTeX\_envs environments using a simple click. Two keybards shortcuts (Ctrl-E and Ctrl-I) for equations and itemize are also provided. More environments and shortcuts can be added in the file `envsLatex.js`.

- Added a link in the general help menu that points to the documentation.

**July 27, 2016** - version 1.1

- In this version I have reworked **equation numbering**. In the previous version, I used a specialized counter and detected equations rendering for updating this counter. Meanwhile, this feature has been introduced in `MathJax` and now we rely on MathJax implementation. rendering is significantly faster. We still have keep the capability of displaying only equation labels (instead of numbers). The numbering is automatically updated and is document-wide.
- I have completely reworked the **notebook conversion** to plain $\LaTeX$ and html. We provide specialized exporters, pre and post processors, templates. We also added entry-points to simplify the conversion process. It is now as simple as
```bash
jupyter nbconvert --to html_with_lenvs FILE.ipynb
```
to convert `FILE.ipynb` into html while keeping all the features of the `latex_envs` notebook extension in the converted version.

# Main features
## Implementation principle

The main idea is to override the standard Markdown renderer in order to add a *small* parsing of LaTeX expressions and environments. This heavily uses regular expressions. The LaTeX expression are then rendered using an html version. For instance `\underline {something}` is rendered as `<u> something </u>`, that is \underline{something}. The environments are replaced by an html tag with a class derived from the name of the environment. For example, a `definition` denvronment will be replaced by an html rendering corresponding to the class `latex_definition`. The styles associated with the different classes are specified in `latex_env.css`. These substitutions are implemented in `thsInNb4.js`.

## Support for simple LaTeX commands

We also added some LaTeX commands (e.g. `\textit`, `\textbf`, `\underline`) -- this is useful in the case of copy-paste from a LaTeX document. The extension also supports some textmode spacings, namely `\par`, `\vspace, \hspace` as well as `\title`, `\author`, `maketitle` and  LaTeX comments % in markdown cells. Labels and cross-references are supported, including for equations.

## Available environments

- **theorems-like environments**: *property, theorem, lemma, corollary, proposition, definition,remark, problem,           exercise, example*,
- **lists**: *enumerate, itemize*,
- limited support for a *figure* environment,
- an environment *listing*,
- *textboxa*, wich is a `textbox` environment defined as a demonstration (see below).

More environments can be added easily in the user\_envs config file `user_envs.json` or directly in the javascript source file `thmsInNb4.js`. The rendering is done according to the stylesheet `latex_env.css`, which can be customized.
\begin{remark}
When exporting to html, the `latex_env.css` file honored is the file on the github CDN. However, customized css can be added in a `custom.css` file that must reside in the same directory as the notebook itself. The reason for that is that the `css` file must be in the same directory as the notebook file for inclusion, which means copying it in each working directory. As the rendering of the html file obtained is done using the original javascript code, the same is true for the source files; therefore it is better to curstomize environments in  `user_envs.json` which is taken into account when exporting to html.
\end{remark}

## Automatic numerotation, labels and references

Several counters for numerotation are implemented: counters for         problem, exercise, example, property, theorem, lemma, corollary, proposition, definition, remark, and figure are available.
Mathjax-equations with a label are also numbered document-wide.
An anchor is created for any label which enables to links things within the document: `\label` and `\ref` are both supported. A limitation was that numbering was updated (incremented) each time a cell is rendered. Document-wide automatic updating is implemented since version 1.3. A toolbar button is provided to reset the counters and refresh the rendering of the whole document (this is still useful for citations and bibliography refresh).

\label{example:mixing} A simple example is as follows, featuring automatic numerotation, and the use of labels and references. Also note that standard markdown can be present in the environment and is interpreted.
*The rendering is done according to the stylesheet `latex_env.css`, which of course, can be customized to specific uses and tastes*.

\begin{listing}
\begin{definition} \label{def:FT}
Let $x[n]$ be a sequence of length $N$. Then, its **Fourier transform** is given by
\begin{equation}
\label{eq:FT}
X[k]= \frac{1}{N} \sum_{n=0}^{N-1} x[n] e^{-j2\pi \frac{kn}{N}}
\end{equation}
\end{definition}
\end{listing}

\begin{definition} \label{def:FT}
Let $x[n]$ be a sequence of length $N$. Then, its **Fourier transform** is given by
\begin{equation}
\label{eq:FT2}
X[k]= \frac{1}{N} \sum_{n=0}^{N-1} x[n] e^{-j2\pi \frac{kn}{N}}
\end{equation}
\end{definition}

It is now possible to refer to the definition and to the equation by their labels,  as in:
\begin{listing}
As an example of Definition \ref{def:FT}, consider the Fourier transform (\ref{eq:FT2}) of a pure cosine wave given by
$$
x[n]= \cos(2\pi k_0 n/N),
$$
where $k_0$ is an integer.
\end{listing}

As an example of Definition \ref{def:FT}, consider the Fourier transform (\ref{eq:FT2}) of a pure cosine wave given by
$$
x[n]= \cos(2\pi k_0 n/N),
$$
where $k_0$ is an integer.
Its Fourier transform is given by
$$
X[k] = \frac{1}{2} \left( \delta[k-k_0] + \delta[k-k_0] \right),
$$
modulo $N$.

## Bibliography

### Usage

It is possible to cite bibliographic references using the standard LaTeX `\cite` mechanism. The extension looks for the references in a bibTeX file, by default `biblio.bib` in the same directory as the notebook. The name of this file can be modified in the configuration toolbar. It is then possible to cite works in the notebook, e.g.
\begin{listing}
The main paper on IPython is definitively \cite{PER-GRA:2007}. Other interesting references are certainly \cite{mckinney2012python, rossant2013learning}. Interestingly, a presentation of the IPython notebook has also be published recently in Nature \cite{shen2014interactive}.
\end{listing}
The main paper on IPython is definitively \cite{PER-GRA:2007}. Other interesting references are certainly \cite{mckinney2012python, rossant2013learning}. Interestingly, a presentation of the IPython notebook has also be published recently in Nature \cite{shen2014interactive}.

### Implementation

The implemention uses several snippets from the nice [icalico-document-tools](https://bitbucket.org/ipre/calico/downloads/) extension that also considers the rendering of citations in the notebook. We also use a modified version of the [bibtex-js](https://code.google.com/p/bibtex-js/) parser for reading the references in the bibTeX file. The different functions are implemented in `bibInNb4.js`. The rendering of citations calls can adopt three styles (Numbered, by key or apa-like) -- this can be selected in the configuration toolbar. It is also possible to customize the rendering of references in the reference list. A citation template is provided in the beginning of file `latex_envs.js`:
```
var cit_tpl = {
// feel free to add more types and customize the templates
    'INPROCEEDINGS': '%AUTHOR:InitialsGiven%, ``_%TITLE%_\'\', %BOOKTITLE%, %MONTH% %YEAR%.',
    ... etc
```
The keys are the main types of documents, eg inproceedings, article, inbook, etc. To each key is associated a string where the %KEYWORDS% are the fields of the bibtex entry. The keywords are replaced by the correponding bibtex entry value. The template string can formatted with additional words and effects (markdown or LaTeX are commands are supported)

## Figure environment

Finally, it is sometimes useful to integrate a figure within a markdown cell. The standard markdown markup for that is ``, but a limitation is that the image can not be resized, can not be referenced and is not numbered. Furthermore it can be useful for re-using existing code. Threfore we have added a limited support for the `figure` environment. This enables to do something like
\begin{listing}
\begin{figure}
\centerline{\includegraphics[width=10cm]{example.png}}
\caption{\label{fig:example} This is an example of figure included using LaTeX commands.}
\end{figure}
\end{listing}
which renders as
\begin{figure}
\centerline{\includegraphics[width=10cm]{example.png}}
\caption{\label{fig:example} This is an example of figure included using LaTeX commands.}
\end{figure}
Of course, this Figure can now be referenced:
\begin{listing}
Figure \ref{fig:example} shows a second filter with input $X_2$, output $Y_2$  and an impulse response denoted as $h_2(n)$
\end{listing}
Figure \ref{fig:example} shows a second filter with input $X_2$, output $Y_2$  and an impulse response denoted as $h_2(n)$

## figcaption

For Python users, we have added in passing a simple function in the `latex_envs.py` library.

This function can be imported classically, eg `from latex_envs.latex_envs import figcaption` (or `from jupyter_contrib_nbextensions.nbconvert_support.latex_envs import figcaption` if you installed from the jupyter_contrib repo).

Then, this function enables to specify a caption and a label for the next plot. In turn, when exporting to $\LaTeX$, the corresponding plot is converted to a nice figure environement with a label and a caption.

To enable large and wide figure in  $\LaTeX$ open notebook menu View->Cell Toolbar -> Edit metadata, open the cell metadata and set "widefigure" : true

```python
%matplotlib inline
import matplotlib.pyplot as plt
from latex_envs.latex_envs import figcaption
from numpy import pi, sin, cos,arange
figcaption("This is a nice sine wave", label="fig:mysin")
plt.plot(sin(2*pi*0.01*arange(100)))
plt.show()
figcaption("This is a nice cosine wave", label="fig:mycos")
plt.plot(cos(2*pi*0.01*arange(100)))
plt.show()

```

Sine wave is shown in fig \label{fig:mysin} and cosine in \label{fig:mycos}.

## Other features

- As shown in the examples, eg \ref{example:mixing} (or just below), **it is possible to mix LaTeX and markdown markup in environments**
- Support for **line-comments**: lines beginning with a % will be masked when rendering
- Support for **linebreaks**: `\par_`, where \_ denotes any space, tab, linefeed, cr, is replaced by a linebreak

- Environments can be nested. egg:
\begin{listing}
This is an example of nested environments, with equations inside\\
\begin{proof} Demo
% This is a comment
\begin{enumerate}
\item $$ \left\{ p_1, p_2, p_3 \ldots p_n \right\} $$
\item A **nested enumerate**
\item second item
\begin{enumerate}
\item $ \left\{ p_1, p_2, p_3 \ldots p_n \right\} $
\item And *another one*
\item second item
\begin{enumerate}
\item $$ \left\{ p_1, p_2, p_3 \ldots p_n \right\} $$
\item second item
\end{enumerate}
\end{enumerate}
\end{enumerate}
\end{proof}
\end{listing}
which results in

This is an example of nested environments, with equations inside\\
\begin{proof} Demo
% This is a comment
\begin{enumerate}
\item
\begin{equation}\label{eq:}
\left\{ p_1, p_2, p_3 \ldots p_n \right\}
\end{equation}
$$ \left\{ p_1, p_2, p_3 \ldots p_n \right\} $$
\item A **nested enumerate**
\item second item
\begin{enumerate}
\item $\left\{ p_1, p_2, p_3 \ldots p_n \right\}$
\item And *another one*
\item second item
\begin{enumerate}
\item $$ \left\{ p_1, p_2, p_3 \ldots p_n \right\} $$
\item second item
\end{enumerate}
\end{enumerate}
\end{enumerate}
\end{proof}

## User interface

### Buttons on main toolbar

On the main toolbar, the extension provides three buttons  The first one can be used to refresh the numerotation of equations and references in all the document. The second one fires the reading of the bibliography bibtex file and creates (or updates) the reference section. Finally the third one is a toogle button that opens or closes the configuration toolbar.

### Configuration toolbar

The configuration toolbar   enables to enter some configuration options for the extension.

First, the `LaTeX\_envs` title links to this documentation. Then, the bibliography text input can be used to indicate the name of the bibtex file. If this file is not found and the user creates the reference section, then this section will indicate that the file was not found. The `References` drop-down menu enables to choose the type of reference calls. The Equations input box enables to initiate numbering of equations at the given number (this may be useful for complex documents in several files/parts). The `Equations` drop-down menu let the user choose to number equation or to display their label instead. The two next buttons enable to toogle display of the LaTeX\_envs environments insertion menu or to toggle the displau of LaTeX labels. Finally The `Toogles` dropdown menu enable to toogle the state of several parameters. All these configuration options are then stored in the notebook's metadata (and restored on reload).

The `Toggles` dropdown menu

enables to toggle the state of several configuration options:

- display the `LaTeX_envs` insertion menu or not,
- show labels anchors,
- use $\LaTeX$ user own LaTeX defintions (loads `latexdefs.tex` file from current document directory),
- load user's environments configuration (file `user_envs.json` in `nbextensions/latex_envs` directory),
- select "report style" numbering of environments

### The `LaTeX_envs` insertion menu
The `LaTeX_envs` insertion menu

enables a quick insertion of LaTeX environments, some with a keyboard shorcut (this can be customized in `envsLatex.js`). Besides, selected text will be inserted in the environment.

# Conversion to LaTeX and HTML

The extension works in the live-notebook. Since it relies on a bunch of javascript, the notebook does not render as is in services such as `nbviewer` or `github` viewer. Similarly, `nbconvert` does not know of the LaTeX constructs which are used here and therefore does not fully convert notebooks using this extension.

Therefore, we provide specialized templates and exporters to achieve these conversions.

## Conversion to html
We provide a template `latex_envs.tpl` and an exporter class `LenvsHTMLExporter` (in library `latex_envs.py`). Using that class, conversion simply amounts to
```
jupyter nbconvert --to latex_envs.LenvsHTMLExporter FILE.ipynb
```
A shortcut is also provided
```
jupyter nbconvert --to html_with_lenvs FILE.ipynb
```
It should be noted that the rendering is done exactly in the same way as in the livenotebook. Actually, it is the very same javascript which is run in the html file. The javascript functions are available on the extension github as well as in the `jupyter_notebook_extensions` CDN, which means that the rendering of the html files requires an internet connection (this is also true for the rendering of equations with MathJax).

Another template `latex_envs_toc.tpl` is provided which enables to also
keep the toc2 features when exporting to html (*it even works if you do not have the `toc2` extension!*):
```bash
jupyter nbconvert --to html_with_toclenvs FILE.ipynb
```
**If you use the version included in the jupyter_notebook_extensions collection**, the entry-points (conversion shortcuts) are a little different: use instead

-
```
jupyter nbconvert --to html_lenvs FILE.ipynb
```
-
```
jupyter nbconvert --to html_toclenvs FILE.ipynb
```

## Conversion to LaTeX

We provide two templates `thmsInNb_article.tplx` and `thmsInNb_report.tplx` for article and report styles respectively. Anyway one can also use the standard article, report, book templates provided with nbconvert. Simply, we have improved some of the internals styles. More importantly, we provide an exporter class `LenvsLatexExporter` (also in library `latex_envs.py`). Using that class, conversion simply amounts to
```
jupyter nbconvert --to latex_envs.LenvsLatexExporter FILE.ipynb
```
A shortcut is also provided
```
jupyter nbconvert --to latex_with_lenvs FILE.ipynb
```
In addition, we provide several further options:

-  **removeHeaders**: Remove headers and footers, (default false)
-  **figcaptionProcess**: Process figcaptions, (default true)
- **tocrefRemove** Remove tocs and ref sections, + some cleaning, (default true),

These options can be specified on the command line as, eg,
```
jupyter nbconvert --to latex_with_lenvs --LenvsLatexExporter.removeHeaders=True -- LenvsLatexExporter.tocrefRemove=False FILE.ipynb
```

**If you use the version included in the jupyter_notebook_extensions collection**, the entry-points (conversion shortcuts) are a little different: use instead
```
jupyter nbconvert --to latex_lenvs FILE.ipynb
```

\begin{example}
As for an example, the present document has been converted using
```
jupyter nbconvert --to latex_with_lenvs --LenvsLatexExporter.removeHeaders=True latex_env_doc.ipynb
```

Then the resulting file (without header/footer) has been included in the main file `documentation.tex`, where some LaTeX definitions of environments are done (namely listings, colors, etc) and compiled using

- `xelatex -interaction=nonstopmode documentation`
- `bibTeX documentation`
- `xelatex -interaction=nonstopmode documentation`

The output can be consulted [here](documentation.pdf).
\end{example}

# Installation

The extension consists in a package that includes a javascript notebook extension. Since Jupyter 4.2, this is the recommended way to distribute nbextensions. The extension can be installed

- from the master version on the github repo (this will be always the most recent version)
- via pip for the version hosted on Pypi
- as part of the great [Jupyter-notebook-extensions](https://github.com/ipython-contrib/Jupyter-notebook-extensions) collection. Follow the instructions there for installing. Once this is done, you can open a tab at  `http://localhost:8888/nbextensions` to enable and configure the various extensions.

From the github repo or from Pypi,

- **step 1**: install the package
     - `pip3 install https://github.com/jfbercher/jupyter_latex_envs/archive/master.zip [--user][--upgrade]`
     -  <span style="color:blue"> or</span> `pip3 install jupyter_latex_envs [--user][--upgrade]`
     - <span style="color:blue"> or</span> clone the repo and install
   ```
   git clone https://github.com/jfbercher/jupyter_latex_envs.git
   python3 setup.py install
   ```

With Jupyter >= 4.2,

- **step 2**: install the notebook extension
```
jupyter nbextension install --py latex_envs [--user]
```

-  **step 3**: and enable it
```
jupyter nbextension enable latex_envs [--user] --py
```

For Jupyter versions before 4.2, the situation is more tricky since you will have to find the location of the source files (instructions from @jcb91 found [here](https://github.com/jcb91/jupyter_highlight_selected_word)): execute
```
python -c "import os.path as p; from jupyter_highlight_selected_word import __file__ as f, _jupyter_nbextension_paths as n; print(p.normpath(p.join(p.dirname(f), n()[0]['src'])))"
```
Then, issue
```
jupyter nbextension install <output source directory>
jupyter nbextension enable latex_envs/latex_envs
```
where `<output source directory>` is the output of the python command.

# Customization

## Configuration parameters

Some configuration parameters can be specified system-wide, using the `nbextension_configurator`. For that, open a browser at [http://localhost:8888/nbextensions/](http://localhost:8888/nbextensions/) -- the exact address may change eg if you use jupyterhub or if you use a non standard port. You will then be able to change default values for the boolean values
- LaTeX_envs menu (insert environments) present
- Label equation with numbers (otherwise with their \label{} key)
- Number environments as section.num
- Use customized environments as given in 'user_envs.json' (in the extension directory)
and enter a default filename for the bibtex file (in document directory).

All these values can also be changed per documents and these values are stored in the notebook's metadata.

## User environments configuration

Environments can be customized in the file `user_envs.json`, located in the `nbextensions/latex_envs` directory. It is even possible to add *new* environments. This file is read at startup (or when using the corresponding toggle option in the `Toggles` menu) and merged with the standard configuration. An example is provided as `example_user_envs.json`. For each (new/modified) environment, one has to  provide (i) the name of the environment (ii) its title (iii) the name of the associated counter for numbering it; eg
```
    "myenv": {
        "title": "MyEnv",
        "counterName": "example"
    },
```
Available counters are problem, exercise, example, property, theorem, lemma, corollary, proposition, definition, remark, and figure.

## Styling
The document title and the document author (as specified by `\title` and `\author` are formatted using the `maketitle` command according to the `.latex_maintitle` and `.latex_author` div styles.

Each environment is formatted according to the div style `.latex_environmentName`, e.g. `.latex_theorem`, `.latex_example`, etc. The titles of environments are formatted with respect to `.latex_title` and the optional parameter wrt `.latex_title_opt`.
Images are displayed using the style specified by `.latex_img` and thir caption using `.caption`.
Finally, enumerate environments are formatted according to the `.enum` style. Similarly, itemize environments are formatted using `.item` style.

These styles can be customized either in the `latex_envs.css` file, or better in a `custom.css` in the document directory.

# Usage and further examples

## First example (continued)

We continue the first example on fthe Fourier transform definition \ref{def:FT} in order to show that, of course, we can illustrate things using a simple code. Since the Fourier transform is an essential tool in signal processing, We put this in evidence using the `textboxa` environment -- which is defined here in the css, and that one should define in the LaTeX counterpart:
\begin{listing}
\begin{textboxa}
The Fourier transform is an extremely useful tool to have in your toolbox!
\end{textboxa}
\end{listing}

\begin{textboxa}
The Fourier transform is an extremely useful tool to have in your toolbox!
\end{textboxa}

The Fourier transform of a pure cosine is given by
$$
X[k] = \frac{1}{2} \left( \delta[k-k_0] + \delta[k-k_0] \right),
$$
modulo $N$. This is illustrated in the following simple script:

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from numpy.fft import fft
k0=4; N=128; n=np.arange(N); k=np.arange(N)
x=np.sin(2*np.pi*k0*n/N)
X=fft(x)
plt.stem(k,np.abs(X))
plt.xlim([0, 20])
plt.title("Fourier transform of a cosine")
_=plt.xlabel("Frequency index (k)")
```

## Second example

This example shows a series of environments, with different facets; **links, references, markdown or/and LaTeX formatting within environments**.  The listing of environments below is typed using the environment *listing*...

\begin{listing}
\begin{definition} \label{def:diffeq}
We call \textbf{difference equation} an equation of the form
$$
\label{eq:diffeq}
y[n]= \sum_{k=1}^{p} a_k y[n-k] + \sum_{i=0}^q b_i x[n-i]
$$
\end{definition}

\begin{property}
If all the $a_k$ in equation (\ref{eq:diffeq}) of definition \ref{def:diffeq} are zero, then the filter has a **finite impulse response**.
\end{property}

\begin{proof}
Let $\delta[n]$ denote the Dirac impulse. Take $x[n]=\delta[n]$ in (\ref{eq:diffeq}). This yields, by definition, the impulse response:
$$
\label{eq:fir}
h[n]= \sum_{i=0}^q b_i \delta[n-i],
$$
which has finite support.
\end{proof}

\begin{theorem}
The poles of a causal stable filter are located within the unit circle in the complex plane.
\end{theorem}

\begin{example} \label{ex:IIR1}
Consider $y[n]= a y[n-1] +  x[n]$. The pole of the transfer function is $z=a$. The impulse response $h[n]=a^n$ has infinite support.
\end{example}

In the following exercise, you will check that the filter is stable iff $a$<1.

\begin{exercise}\label{ex:exofilter}
Consider the filter defined in Example \ref{ex:IIR1}. Using the **function** `lfilter` of scipy, compute and plot the impulse response for several values of $a$.
\end{exercise}

\end{listing}

The lines above are rendered as follows (of course everything can be tailored in the stylesheet):
%
\begin{definition} \label{def:diffeq}
We call \textbf{difference equation} an equation of the form
\begin{equation}
\label{eq:diffeq}
y[n]= \sum_{k=1}^{p} a_k y[n-k] + \sum_{i=0}^q b_i x[n-i]
\end{equation}
\end{definition}
%
Properties of the filter are linked to the coefficients of the difference equation. For instance, an immediate property is
%
% this is a comment
\begin{property}
If all the $a_k$ in equation (\ref{eq:diffeq}) of definition \ref{def:diffeq} are zero, then the filter has a **finite impulse response**.
\end{property}
%
\begin{proof}
Let $\delta[n]$ denote the Dirac impulse. Take $x[n]=\delta[n]$ in (\ref{eq:diffeq}). This yields, by definition, the impulse response:
\begin{equation}
\label{eq:fir}
h[n]= \sum_{i=0}^q b_i \delta[n-i],
\end{equation}
which has finite support.
\end{proof}
%
\begin{theorem}
The poles of a causal stable filter are located within the unit circle in the complex plane.
\end{theorem}
%
\begin{example} \label{ex:IIR1}
Consider $y[n]= a y[n-1] +  x[n]$. The pole of the transfer function is $z=a$. The impulse response $h[n]=a^n$ has infinite support.
\end{example}

In the following exercise, you will check that the filter is stable iff $a$<1.
%
\begin{exercise}\label{ex:exofilter}
Consider the filter defined in Example \ref{ex:IIR1}. Using the **function** `lfilter` of scipy, compute and plot the impulse response for several values of $a$.
\end{exercise}

\begin{listing}
The solution of exercise \ref{ex:exofilter}, which uses a difference equation as in Definition \ref{def:diffeq}:
\end{listing}
The solution of exercise \ref{ex:exofilter}, which uses a difference equation as in Definition \ref{def:diffeq}:

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from scipy.signal import lfilter
d=np.zeros(100); d[0]=1 #dirac impulse
alist=[0.2, 0.8, 0.9, 0.95, 0.99, 0.999, 1.001, 1.01]
for a in alist:
    h=lfilter([1], [1, -a],d)
    _=plt.plot(h, label="a={}".format(a))
plt.ylim([0,1.5])
plt.xlabel('Time')
_=plt.legend()
```

## Third example:

This example shows that environments like itemize or enumerate are also available. As already indicated, this is useful for copying text from a TeX file. Following the same idea, text formating commands `\textit`, `\textbf`, `\underline`, etc are also available.

\begin{listing}
The following \textit{environments} are available:
\begin{itemize}
    \item \textbf{Theorems and likes}
    \begin{enumerate}
        \item theorem,
        \item lemma,
        \item corollary
        \item ...
    \end{enumerate}
    \item \textbf{exercises}
    \begin{enumerate}
        \item problem,
        \item example,
        \item exercise
    \end{enumerate}
\end{itemize}
\end{listing}

which gives...

The following \textit{environments} are available:
\begin{itemize}
\item \textbf{Theorems and likes}
\begin{enumerate}
\item theorem,
\item lemma,
\item corollary
\item ...
\end{enumerate}
\item \textbf{exercises}
\begin{enumerate}
\item problem,
\item example,
\item exercise
\end{enumerate}
\end{itemize}

# Disclaimer, sources and thanks

Originally, I used a piece of code from the nice online markdown editor `stackedit` [https://github.com/benweet/stackedit/issues/187](https://github.com/benweet/stackedit/issues/187), where the authors also considered the problem of incorporating LaTeX markup in their markdown.

I also studied and used examples and code from [https://github.com/ipython-contrib/IPython-notebook-extensions](https://github.com/ipython-contrib/IPython-notebook-extensions).

- This is done in the hope it can be useful. However there are many impovements possible, in the code and in the documentation.
**Contributions will be welcome and deeply appreciated.**

- If you have issues, please post an issue at
`https://github.com/jfbercher/jupyter_latex_envs/issues` [here](https://github.com/jfbercher/jupyter_latex_envs/issues).

**Self-Promotion --** Like `latex_envs`? Please star and follow the [repository](https://github.com/jfbercher/jupyter_latex_envs) on GitHub.

# References

(<a id="cit-PER-GRA:2007" href="#call-PER-GRA:2007">P\'erez and Granger, 2007</a>) P\'erez Fernando and Granger Brian E., ``_IPython: a System for Interactive Scientific Computing_'', Computing in Science and Engineering, vol. 9, number 3, pp. 21--29, May 2007.  [online](http://ipython.org)

(<a id="cit-mckinney2012python" href="#call-mckinney2012python">McKinney, 2012</a>) Wes McKinney, ``_Python for data analysis: Data wrangling with Pandas, NumPy, and IPython_'',  2012.

(<a id="cit-rossant2013learning" href="#call-rossant2013learning">Rossant, 2013</a>) Cyrille Rossant, ``_Learning IPython for interactive computing and data visualization_'',  2013.

(<a id="cit-shen2014interactive" href="#call-shen2014interactive">Shen, 2014</a>) Shen Helen, ``_Interactive notebooks: Sharing the code_'', Nature, vol. 515, number 7525, pp. 151--152,  2014.

```python
%%html
<style>
    .prompt{
        display: none;
    }

</style>
```

<style>
    .prompt{
        display: none;
    }

</style>