# Efficiency Analysis

## Objective and Prerequisites

How can mathematical optimization be used to measure the efficiency of an organization? Find out in this example, where you’ll learn how to formulate an Efficiency Analysis model as a linear programming problem using the Gurobi Python API and then generate an optimal solution with the Gurobi Optimizer.

This model is example 22 from the fifth edition of Model Building in Mathematical Programming by H. Paul Williams on pages 278-280 and 335-336.

This example is at the intermediate level, where we assume that you know Python and the Gurobi Python API and that you have some knowledge of building mathematical optimization models.

**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).

**Gurobi License**

In order to run this Jupyter Notebook properly, you must have a Gurobi license. If you do not have one, you can request an [evaluation license](https://www.gurobi.com/downloads/request-an-evaluation-license/?utm_source=3PW&utm_medium=OT&utm_campaign=WW-MU-MUI-OR-O_LEA-PR_NO-Q3_FY20_WW_JPME_Efficiency_Analysis_COM_EVAL_GitHub&utm_term=Efficiency%20Analysis&utm_content=C_JPM) as a *commercial user*, or download a [free license](https://www.gurobi.com/academia/academic-program-and-licenses/?utm_source=3PW&utm_medium=OT&utm_campaign=WW-MU-EDU-OR-O_LEA-PR_NO-Q3_FY20_WW_JPME_Efficiency_Analysis_COM_EVAL_GitHub&utm_term=Efficiency%20Analysis&utm_content=C_JPM) as an *academic user*.

## Background

 The Data Envelopment Analysis (DEA) is a nonparametric problem in operations research and economics whose solution is an estimation of production frontiers. It is used to empirically measure the productive efficiency of decision making units (DMUs). There are a number of linear programming formulations of the DEA problem. Fuller coverage of the subject can be found in Farrell (1957), Charnes et al. (1978) and Thanassoulis et al. (1987). The formulation given by H.P. Williams is described in Land (1991). This formulation is the dual model of a model  commonly used that relies on finding weighted ratios of outputs to inputs. We will use the formulation that is commonly used and can be found in Cooper et al. (2007).

The Data Envelopment Analysis has been used to evaluate the performance of many different kinds of entities engaged in many different activities, and in many different contexts in many different countries. Examples include the maintenance activities of U.S. Air Force bases in different geographic locations, or police forces in England and Wales as well as the performance of branch banks in Cyprus and Canada and the efficiency of universities in performing their education and research functions in the U.S., England and France.

The DEA approach is concerned with evaluations of *efficiency*. The most common measure of efficiency takes the form of a ratio like the following one:

$$
\text{efficiency} = \frac{\text{output}}{\text{input}}
$$

## Model Formulation

Assume there is a set of DMUs. Some common input and output items for each of these DMUs are selected as follows:
1. Numerical data are available for each input and output, with the data assumed to be positive, for all DMUs.
2. The items (inputs, outputs and choice of DMUs) should reflect an analyst's or a manager's interest in the components that will enter into the relative efficiency evaluations of the DMUs.
3. In principle, smaller input amounts are preferable and larger output amounts are preferable so the efficiency scores should reflect these principles.
4. The measurement units of the different inputs and outputs do not need to be congruent. Some may involve a number of persons, or areas of floor space, money expended, etc.

### Fractional problem formulation
The proposed measure of the efficiency of a target DMU $k$ is obtained as the maximum of a ratio of weighted outputs to weighted inputs subject to the condition that the similar ratios for every DMU be less than or equal to one.

### Sets and indices

$j,k \in \text{DMUS}$: Indices and set of DMUs, where $k$ represents the target DMU.

$i \in \text{Inputs}$: Index and set of inputs.

$r \in \text{Outputs}$: Index and set of outputs.

### Parameters

$\text{invalue}_{i,j} > 0$: Value of input $i$ for DMU $j$.

$\text{outvalue}_{r,j} > 0$: Value of output $r$ for DMU $j$.

### Decision Variables

$u_{r} \geq 0$: Weight of output $r$.

$v_{i} \geq 0$: Weight of input  $i$.

### Objective function

**Target DMU Efficiency**: Maximize efficiency at the target DMU $k$.

$$
\text{Maximize} \quad E_k =
\frac{\sum_{r \in \text{Outputs}} \text{outvalue}_{r,k}*u_{r}}{\sum_{i \in \text{Inputs}} \text{invalue}_{i,k}*v_{i}}
\tag{FP0}
$$

### Constraints

**Efficiency ratios**: The efficiency of a DMU is a number between $[0,1]$.

\begin{equation}
\frac{\sum_{r \in \text{Outputs}} \text{outvalue}_{r,j}*u_{r}}{\sum_{i \in \text{Inputs}} \text{invalue}_{i,j}*v_{i}}
 \leq 1 \quad \forall j \in \text{DMUS}
 \tag{FP1}
\end{equation}

### Linear programming problem formulation

This linear programming formulation can be found in the book by Cooper et al. (2007).

### Objective function

**Target DMU Efficiency**: Maximize efficiency at the target DMU $k$.

$$
\text{Maximize} \quad E_k = \sum_{r \in \text{Outputs}} \text{outvalue}_{r,k}*u_{r}
\tag{LP0}
$$

### Constraints

**Efficiency ratio**: The efficiency of a DMU is a number between $[0,1]$.

\begin{equation}
\sum_{r \in \text{Outputs}} \text{outvalue}_{r,j}*u_{r} -
\sum_{i \in \text{Inputs}} \text{invalue}_{i,k}*v_{i}
 \leq 0  \quad \forall j \in \text{DMUS}
\tag{LP1}
\end{equation}

**Normalization**: This constraint ensures that the denominator of the objective function of the fractional problem is equal to one.

\begin{equation}
\sum_{i \in \text{Inputs}} \text{invalue}_{i,k}*v_{i} = 1
\tag{LP2}
\end{equation}

It is easy to verify that the fractional problem and the linear programming problem are equivalent. Let's assume that the denominator of the efficiency ratio constraints of the fractional problem is positive for all DMUs, then we can obtain the constraints $LP1$ by multiplying both sides of the constraints $FP1$ by the denominator. Next, we set the denominator of $FP0$ eqaul to 1 and define constraint $LP2$, and then maximize the numerator, resulting in the objective function $LP0$.

### Definition of efficiency

1. $DMU_k$ is efficient if the optimal objective function value $E_{k}^{*} = 1$.
2. Otherwise, $DMU_k$ is inefficient.

## Problem Description

A car manufacturer wants to evaluate the efficiencies of different garages that have been granted a license to sell its cars. Each garage has a certain number of measurable ‘inputs’:
* Staff
* Showroom Space
* Population in category 1
* Population in category 2
* Enquiries Alpha model
* Enquiries Beta model

Each garage also has a certain number of measurable ‘outputs’:
* Number Sold of different brands of car
* annual Profit

The following table gives the inputs and outputs for each of the 28 franchised garages.

The goal is to identify efficient and inefficient garages and their input-output weights. In order to solve this problem, it is necessary to solve the LP model for each garage.

---
## Python Implementation

We import the Gurobi Python Module and other Python libraries.

### Helper Functions

* `solve_DEA` builds and solves the LP model.

```python
import numpy as np
import pandas as pd
from itertools import product

import gurobipy as gp
from gurobipy import GRB

# tested with Python 3.7.0 & Gurobi 9.1.0
```

```python
def solve_DEA(target, verbose=True):
    # input-output values for the garages
    inattr = ['staff', 'showRoom', 'Population1', 'Population2', 'alphaEnquiries', 'betaEnquiries']
    outattr = ['alphaSales', 'BetaSales', 'profit']

    dmus, inputs, outputs = gp.multidict({
        'Winchester': [{'staff': 7, 'showRoom': 8, 'Population1': 10, 'Population2': 12, 'alphaEnquiries': 8.5, 'betaEnquiries': 4}, {'alphaSales': 2, 'BetaSales': 0.6, 'profit': 1.5}],
        'Andover': [{'staff': 6, 'showRoom': 6, 'Population1': 20, 'Population2': 30, 'alphaEnquiries': 9, 'betaEnquiries': 4.5}, {'alphaSales': 2.3, 'BetaSales': 0.7, 'profit': 1.6}],
        'Basingstoke': [{'staff': 2, 'showRoom': 3, 'Population1': 40, 'Population2': 40, 'alphaEnquiries': 2, 'betaEnquiries': 1.5}, {'alphaSales': 0.8, 'BetaSales': 0.25, 'profit': 0.5}],
        'Poole': [{'staff': 14, 'showRoom': 9, 'Population1': 20, 'Population2': 25, 'alphaEnquiries': 10, 'betaEnquiries': 6}, {'alphaSales': 2.6, 'BetaSales': 0.86, 'profit': 1.9}],
        'Woking': [{'staff': 10, 'showRoom': 9, 'Population1': 10, 'Population2': 10, 'alphaEnquiries': 11, 'betaEnquiries': 5}, {'alphaSales': 2.4, 'BetaSales': 1, 'profit': 2}],
        'Newbury': [{'staff': 24, 'showRoom': 15, 'Population1': 15, 'Population2': 13, 'alphaEnquiries': 25, 'betaEnquiries': 1.9}, {'alphaSales': 8, 'BetaSales': 2.6, 'profit': 4.5}],
        'Portsmouth': [{'staff': 6, 'showRoom': 7, 'Population1': 50, 'Population2': 40, 'alphaEnquiries': 8.5, 'betaEnquiries': 3}, {'alphaSales': 2.5, 'BetaSales': 0.9, 'profit': 1.6}],
        'Alresford': [{'staff': 8, 'showRoom': 7.5, 'Population1': 5, 'Population2': 8, 'alphaEnquiries': 9, 'betaEnquiries': 4}, {'alphaSales': 2.1, 'BetaSales': 0.85, 'profit': 2}],
        'Salisbury': [{'staff': 5, 'showRoom': 5, 'Population1': 10, 'Population2': 10, 'alphaEnquiries': 5, 'betaEnquiries': 2.5}, {'alphaSales': 2, 'BetaSales': 0.65, 'profit': 0.9}],
        'Guildford': [{'staff': 8, 'showRoom': 10, 'Population1': 30, 'Population2': 35, 'alphaEnquiries': 9.5, 'betaEnquiries': 4.5}, {'alphaSales': 2.05, 'BetaSales': 0.75, 'profit': 1.7}],
        'Alton': [{'staff': 7, 'showRoom': 8, 'Population1': 7, 'Population2': 8, 'alphaEnquiries': 3, 'betaEnquiries': 2}, {'alphaSales': 1.9, 'BetaSales': 0.70, 'profit': 0.5}],
        'Weybridge': [{'staff': 5, 'showRoom': 6.5, 'Population1': 9, 'Population2': 12, 'alphaEnquiries': 8, 'betaEnquiries': 4.5}, {'alphaSales': 1.8, 'BetaSales': 0.63, 'profit': 1.4}],
        'Dorchester': [{'staff': 6, 'showRoom': 7.5, 'Population1': 10, 'Population2': 10, 'alphaEnquiries': 7.5, 'betaEnquiries': 4}, {'alphaSales': 1.5, 'BetaSales': 0.45, 'profit': 1.45}],
        'Bridport': [{'staff': 11, 'showRoom': 8, 'Population1': 8, 'Population2': 10, 'alphaEnquiries': 10, 'betaEnquiries': 6}, {'alphaSales': 2.2, 'BetaSales': 0.65, 'profit': 2.2}],
        'Weymouth': [{'staff': 4, 'showRoom': 5, 'Population1': 10, 'Population2': 10, 'alphaEnquiries': 7.5, 'betaEnquiries': 3.5}, {'alphaSales': 1.8, 'BetaSales': 0.62, 'profit': 1.6}],
        'Portland': [{'staff': 3, 'showRoom': 3.5, 'Population1': 3, 'Population2': 20, 'alphaEnquiries': 2, 'betaEnquiries': 1.5}, {'alphaSales': 0.9, 'BetaSales': 0.35, 'profit': 0.5}],
        'Chichester': [{'staff': 5, 'showRoom': 5.5, 'Population1': 8, 'Population2': 10, 'alphaEnquiries': 7, 'betaEnquiries': 3.5}, {'alphaSales': 1.2, 'BetaSales': 0.45, 'profit': 1.3}],
        'Petersfield': [{'staff': 21, 'showRoom': 12, 'Population1': 6, 'Population2': 6, 'alphaEnquiries': 15, 'betaEnquiries': 8}, {'alphaSales': 6, 'BetaSales': 0.25, 'profit': 2.9}],
        'Petworth': [{'staff': 6, 'showRoom': 5.5, 'Population1': 2, 'Population2': 2, 'alphaEnquiries': 8, 'betaEnquiries': 5}, {'alphaSales': 1.5, 'BetaSales': 0.55, 'profit': 1.55}],
        'Midhurst': [{'staff': 3, 'showRoom': 3.6, 'Population1': 3, 'Population2': 3, 'alphaEnquiries': 2.5, 'betaEnquiries': 1.5}, {'alphaSales': 0.8, 'BetaSales': 0.20, 'profit': 0.45}],
        'Reading': [{'staff': 30, 'showRoom': 29, 'Population1': 120, 'Population2': 80, 'alphaEnquiries': 35, 'betaEnquiries': 20}, {'alphaSales': 7, 'BetaSales': 2.5, 'profit': 8}],
        'Southampton': [{'staff': 25, 'showRoom': 16, 'Population1': 110, 'Population2': 80, 'alphaEnquiries': 27, 'betaEnquiries': 12}, {'alphaSales': 6.5, 'BetaSales': 3.5, 'profit': 5.4}],
        'Bournemouth': [{'staff': 19, 'showRoom': 10, 'Population1': 90, 'Population2': 22, 'alphaEnquiries': 25, 'betaEnquiries': 13}, {'alphaSales': 5.5, 'BetaSales': 3.1, 'profit': 4.5}],
        'Henley': [{'staff': 7, 'showRoom': 6, 'Population1': 5, 'Population2': 7, 'alphaEnquiries': 8.5, 'betaEnquiries': 4.5}, {'alphaSales': 1.2, 'BetaSales': 0.48, 'profit': 2}],
        'Maidenhead': [{'staff': 12, 'showRoom': 8, 'Population1': 7, 'Population2': 10, 'alphaEnquiries': 12, 'betaEnquiries': 7}, {'alphaSales': 4.5, 'BetaSales': 2, 'profit': 2.3}],
        'Fareham': [{'staff': 4, 'showRoom': 6, 'Population1': 1, 'Population2': 1, 'alphaEnquiries': 7.5, 'betaEnquiries': 3.5}, {'alphaSales': 1.1, 'BetaSales': 0.48, 'profit': 1.7}],
        'Romsey': [{'staff': 2, 'showRoom': 2.5, 'Population1': 1, 'Population2': 1, 'alphaEnquiries': 2.5, 'betaEnquiries': 1}, {'alphaSales': 0.4, 'BetaSales': 0.1, 'profit': 0.55}],
        'Ringwood': [{'staff': 2, 'showRoom': 3.5, 'Population1': 2, 'Population2': 2, 'alphaEnquiries': 1.9, 'betaEnquiries': 1.2}, {'alphaSales': 0.3, 'BetaSales': 0.09, 'profit': 0.4}]
    })

    ### Create LP model
    model = gp.Model('DEA')

    # Decision variables
    wout = model.addVars(outattr, name="outputWeight")
    win = model.addVars(inattr, name="inputWeight")

    # Constraints
    ratios = model.addConstrs( ( gp.quicksum(outputs[h][r]*wout[r] for r in outattr )
                                - gp.quicksum(inputs[h][i]*win[i] for i in inattr )
                                <= 0 for h in dmus ), name='ratios' )

    normalization = model.addConstr((gp.quicksum(inputs[target][i]*win[i] for i in inattr ) == 1 ),
                                    name='normalization')

    # Objective function

    model.setObjective( gp.quicksum(outputs[target][r]*wout[r] for r in outattr ), GRB.MAXIMIZE)

    # Run optimization engine
    if not verbose:
        model.params.OutputFlag = 0
    model.optimize()

    # Print results
    print(f"\nThe efficiency of target DMU {target} is {round(model.objVal,3)}")

    print("__________________________________________________________________")
    print(f"The weights for the inputs are:")
    for i in inattr:
        print(f"For {i}: {round(win[i].x,3)} ")

    print("__________________________________________________________________")
    print(f"The weights for the outputs are")
    for r in outattr:
        print(f"For {r} is: {round(wout[r].x,3)} ")
    print("__________________________________________________________________\n\n")

    return model.objVal
```

## Input Data
We define the list of garages.

```python
dmus = ['Winchester','Andover','Basingstoke', 'Poole', 'Woking','Newbury','Portsmouth','Alresford','Salisbury','Guildford','Alton','Weybridge', 'Dorchester', 'Bridport', 'Weymouth', 'Portland', 'Chichester', 'Petersfield', 'Petworth', 'Midhurst', 'Reading', 'Southampton', 'Bournemouth', 'Henley', 'Maidenhead', 'Fareham', 'Romsey', 'Ringwood']
```

---
## Output Report

We print out the efficiency score of each garage and its associated input and output weights.

```python
# Solving DEA model for each DMU

performance = {}
for h in dmus:
    performance[h] = solve_DEA(h, verbose=False)

```

    Using license file c:\gurobi\gurobi.lic

    The efficiency of target DMU Winchester is 0.835
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.012
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.002
    For alphaEnquiries: 0.095
    For betaEnquiries: 0.02
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.113
    For BetaSales is: 0.004
    For profit is: 0.404
    __________________________________________________________________

    The efficiency of target DMU Andover is 0.917
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.115
    For showRoom: 0.03
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.014
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.399
    For BetaSales is: 0.0
    For profit is: 0.0
    __________________________________________________________________

    The efficiency of target DMU Basingstoke is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.429
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.071
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 1.25
    For BetaSales is: 0.0
    For profit is: 0.0
    __________________________________________________________________

    The efficiency of target DMU Poole is 0.864
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.019
    For Population1: 0.0
    For Population2: 0.001
    For alphaEnquiries: 0.081
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.065
    For BetaSales is: 0.0
    For profit is: 0.366
    __________________________________________________________________

    The efficiency of target DMU Woking is 0.845
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.015
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.001
    For alphaEnquiries: 0.062
    For betaEnquiries: 0.032
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.0
    For BetaSales is: 0.218
    For profit is: 0.314
    __________________________________________________________________

    The efficiency of target DMU Newbury is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.0
    For betaEnquiries: 0.526
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.0
    For BetaSales is: 0.0
    For profit is: 0.222
    __________________________________________________________________

    The efficiency of target DMU Portsmouth is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.149
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.0
    For betaEnquiries: 0.036
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.4
    For BetaSales is: 0.0
    For profit is: 0.0
    __________________________________________________________________

    The efficiency of target DMU Alresford is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.002
    For alphaEnquiries: 0.098
    For betaEnquiries: 0.026
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.081
    For BetaSales is: 0.005
    For profit is: 0.413
    __________________________________________________________________

    The efficiency of target DMU Salisbury is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.087
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.113
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.5
    For BetaSales is: 0.0
    For profit is: 0.0
    __________________________________________________________________

    The efficiency of target DMU Guildford is 0.802
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.014
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.075
    For betaEnquiries: 0.036
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.062
    For BetaSales is: 0.0
    For profit is: 0.397
    __________________________________________________________________

    The efficiency of target DMU Alton is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.333
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.0
    For BetaSales is: 1.429
    For profit is: 0.0
    __________________________________________________________________

    The efficiency of target DMU Weybridge is 0.854
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.149
    For showRoom: 0.0
    For Population1: 0.028
    For Population2: 0.0
    For alphaEnquiries: 0.0
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.379
    For BetaSales is: 0.0
    For profit is: 0.122
    __________________________________________________________________

    The efficiency of target DMU Dorchester is 0.867
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.014
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.003
    For alphaEnquiries: 0.118
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.11
    For BetaSales is: 0.0
    For profit is: 0.484
    __________________________________________________________________

    The efficiency of target DMU Bridport is 0.982
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.002
    For Population1: 0.0
    For Population2: 0.002
    For alphaEnquiries: 0.096
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.06
    For BetaSales is: 0.0
    For profit is: 0.387
    __________________________________________________________________

    The efficiency of target DMU Weymouth is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.25
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.0
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.556
    For BetaSales is: 0.0
    For profit is: 0.0
    __________________________________________________________________

    The efficiency of target DMU Portland is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.0
    For Population1: 0.032
    For Population2: 0.0
    For alphaEnquiries: 0.452
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.0
    For BetaSales is: 0.0
    For profit is: 2.0
    __________________________________________________________________

    The efficiency of target DMU Chichester is 0.825
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.011
    For showRoom: 0.008
    For Population1: 0.0
    For Population2: 0.002
    For alphaEnquiries: 0.125
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.098
    For BetaSales is: 0.0
    For profit is: 0.545
    __________________________________________________________________

    The efficiency of target DMU Petersfield is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.019
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.04
    For betaEnquiries: 0.021
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.167
    For BetaSales is: 0.0
    For profit is: 0.0
    __________________________________________________________________

    The efficiency of target DMU Petworth is 0.988
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.156
    For Population1: 0.058
    For Population2: 0.013
    For alphaEnquiries: 0.0
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.141
    For BetaSales is: 0.0
    For profit is: 0.501
    __________________________________________________________________

    The efficiency of target DMU Midhurst is 0.889
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.018
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.011
    For alphaEnquiries: 0.366
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.294
    For BetaSales is: 0.047
    For profit is: 1.431
    __________________________________________________________________

    The efficiency of target DMU Reading is 0.984
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.001
    For showRoom: 0.006
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.023
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.019
    For BetaSales is: 0.0
    For profit is: 0.106
    __________________________________________________________________

    The efficiency of target DMU Southampton is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.005
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.028
    For betaEnquiries: 0.012
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.0
    For BetaSales is: 0.057
    For profit is: 0.149
    __________________________________________________________________

    The efficiency of target DMU Bournemouth is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.1
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.0
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.0
    For BetaSales is: 0.323
    For profit is: 0.0
    __________________________________________________________________

    The efficiency of target DMU Henley is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.161
    For Population1: 0.007
    For Population2: 0.0
    For alphaEnquiries: 0.0
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.0
    For BetaSales is: 0.0
    For profit is: 0.5
    __________________________________________________________________

    The efficiency of target DMU Maidenhead is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.122
    For Population1: 0.004
    For Population2: 0.0
    For alphaEnquiries: 0.0
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.0
    For BetaSales is: 0.5
    For profit is: 0.0
    __________________________________________________________________

    The efficiency of target DMU Fareham is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 1.0
    For alphaEnquiries: 0.0
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.0
    For BetaSales is: 2.083
    For profit is: 0.0
    __________________________________________________________________

    The efficiency of target DMU Romsey is 1.0
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.0
    For alphaEnquiries: 0.31
    For betaEnquiries: 0.224
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.0
    For BetaSales is: 0.0
    For profit is: 1.818
    __________________________________________________________________

    The efficiency of target DMU Ringwood is 0.908
    __________________________________________________________________
    The weights for the inputs are:
    For staff: 0.0
    For showRoom: 0.0
    For Population1: 0.0
    For Population2: 0.013
    For alphaEnquiries: 0.512
    For betaEnquiries: 0.0
    __________________________________________________________________
    The weights for the outputs are
    For alphaSales is: 0.299
    For BetaSales is: 0.0
    For profit is: 2.044
    __________________________________________________________________

---
## Analysis

We identify which garages are efficient and which ones are inefficient, and provide the efficiency scores for each garage.

```python
# Identifying efficient and inefficient DMUs

# Sorting garages in descending efficiency number
sorted_performance = {k: v for k, v in sorted(performance.items(), key=lambda item: item[1], reverse = True)}

efficient = []
inefficient = []

for h in sorted_performance.keys():
    if sorted_performance[h] >= 0.9999999:
        efficient.append(h)
    if sorted_performance[h] < 0.9999999:
        inefficient.append(h)

print('____________________________________________')
print(f"The efficient DMUs are:")
for eff in efficient:
    print(f"The performance value of DMU {eff} is: {round(performance[eff],3)}")

print('____________________________________________')
print(f"The inefficient DMUs are:")
for ine in inefficient:
    print(f"The performance value of DMU {ine} is: {round(performance[ine],3)}")

```

    ____________________________________________
    The efficient DMUs are:
    The performance value of DMU Newbury is: 1.0
    The performance value of DMU Alresford is: 1.0
    The performance value of DMU Salisbury is: 1.0
    The performance value of DMU Alton is: 1.0
    The performance value of DMU Weymouth is: 1.0
    The performance value of DMU Petersfield is: 1.0
    The performance value of DMU Southampton is: 1.0
    The performance value of DMU Bournemouth is: 1.0
    The performance value of DMU Maidenhead is: 1.0
    The performance value of DMU Fareham is: 1.0
    The performance value of DMU Romsey is: 1.0
    The performance value of DMU Basingstoke is: 1.0
    The performance value of DMU Portsmouth is: 1.0
    The performance value of DMU Portland is: 1.0
    The performance value of DMU Henley is: 1.0
    ____________________________________________
    The inefficient DMUs are:
    The performance value of DMU Petworth is: 0.988
    The performance value of DMU Reading is: 0.984
    The performance value of DMU Bridport is: 0.982
    The performance value of DMU Andover is: 0.917
    The performance value of DMU Ringwood is: 0.908
    The performance value of DMU Midhurst is: 0.889
    The performance value of DMU Dorchester is: 0.867
    The performance value of DMU Poole is: 0.864
    The performance value of DMU Weybridge is: 0.854
    The performance value of DMU Woking is: 0.845
    The performance value of DMU Winchester is: 0.835
    The performance value of DMU Chichester is: 0.825
    The performance value of DMU Guildford is: 0.802

## References

H. Paul Williams, Model Building in Mathematical Programming, fifth edition.

Cooper, W. W, L. M. Seiford, K. Tone. (2007) Data Envelopment Analysis: A Comprehensive Text with Models, Applications, References and DEA-Solver Software. Second edition. Springer-Verlag US.

Land, A. (1991) Data envelopment analysis, Chapter 5, in Operations Research in Management (eds S.C. Littlechild and M.F. Shutler), Prentice Hall, London.

Farrell, M.J. (1957) The measurement of productive efficiency. Journal of the Royal Statistical Society, Series A, 120, 253–290.

Charnes, A., Cooper, W.W. and Rhodes, E. (1978) Measuring the efficiency of decision making units. European Journal of Operational Research, 2, 429–444.

Thanassoulis, E., Dyson, R.G. and Foster, M.J. (1987) Relative efficiency assessments using data envelopment analysis: an application to data on rates departments. Journal of the Operational Research Society, 5, 397–411.

Copyright © 2020 Gurobi Optimization, LLC


```python
import numpy as np
import sympy
import math
import matplotlib.pyplot as plt
from pyodesys.symbolic import SymbolicSys
sympy.init_printing()
%matplotlib inline
```

```python
# 2 HNO2 -> H2O + NO + NO2; MassAction(EyringHS.fk('dH1', 'dS1'))
# 2 NO2 -> N2O4; MassAction(EyringHS.fk('dH2', 'dS2'))
#
# HNO2 H2O NO NO2 N2O4
def get_odesys(scaling=1, T_as_param=False, par_by_name=False, dep_by_name=False, **kwargs):
    names = 'HNO2 H2O NO NO2 N2O4'.split()
    param_names = 'dH1 dS1 dH2 dS2 T'.split()
    def rhs(t, y, p, backend=math):
        HNO2, H2O, NO, NO2, N2O4 = [y[k] for k in (names if dep_by_name else range(5))]
        dH1, dS1, dH2, dS2 = [p[k] for k in (param_names[:-1] if par_by_name else range(4))]
        R = 8.314
        if T_as_param:
            T = p['T' if par_by_name else 4]
        else:
            T = 300 + 10*backend.sin(0.2*math.pi*t - math.pi/2)
        kB_h = 2.08366e10
        k1 = kB_h*T*backend.exp(dS1/R - dH1/(R*T))/scaling  # bimolecular => scaling**-1
        k2 = kB_h*T*backend.exp(dS2/R - dH2/(R*T))/scaling  # bimolecular => scaling**-1
        r1 = k1*HNO2**2
        r2 = k2*NO2**2
        exprs = [-2*r1, r1, r1, r1 - 2*r2, r2]
        return dict(zip(names, exprs)) if dep_by_name else exprs

    return SymbolicSys.from_callback(
        rhs, 5, 4 + int(T_as_param), names=names,
        param_names=param_names[slice(None) if T_as_param else slice(0,-1)],
        dep_by_name=dep_by_name, par_by_name=par_by_name, **kwargs
    )
```

```python
def integrate_and_plot(system, init_y, p, axes=None):
    result = system.integrate(np.linspace(0, 60, 200), init_y, p, integrator='cvode', nsteps=5000)
    if axes is None:
        fig, axes = plt.subplots(1, 2, figsize=(10, 4))
    result.plot(ax=axes[0], indices=[0,2,4], title_info=True)
    result.plot(ax=axes[1], indices=[3])
    print({k: v for k, v in sorted(result.info.items()) if not k.startswith('internal')})
    return result
```

```python
def compare_autonomous(scaling):
    fig, all_axes = plt.subplots(3, 2, figsize=(10, 14))
    odesys = get_odesys(scaling)
    autsys = odesys.as_autonomous()
    assert all(o - a == 0 for o, a in zip(odesys.exprs, autsys.exprs[:-1]))
    assert autsys.exprs[-1] == 1
    init_y = [1*scaling, 55*scaling, 0, 0, 0]
    p = [85e3, 10, 70e3, 20]
    argso = odesys.pre_process(*odesys.to_arrays(0, init_y, p))
    argsa = autsys.pre_process(*autsys.to_arrays(0, init_y, p))
    for idx, (argo, arga) in enumerate(zip(argso, argsa)):
        assert np.allclose(argo, arga[:-1] if idx == 1 else arga)
    fo = odesys.f_cb(*argso)
    fa = autsys.f_cb(*argsa)
    assert np.allclose(fo, fa[:, :-1])
    assert np.allclose(fa[:, -1], 1)
    res1 = integrate_and_plot(odesys, init_y, p, axes=all_axes[0, :])
    res2 = integrate_and_plot(autsys, init_y, p, axes=all_axes[1, :])
    assert np.allclose(res1.yout, res2.yout, atol=1e-6)
    diff = res1.yout-res2.yout
    res1.plot(ax=all_axes[2, 0], indices=[0,2,4], y=diff/scaling)
    res1.plot(ax=all_axes[2, 1], indices=[3], y=diff/scaling)
```

```python
compare_autonomous(1)
```

```python
compare_autonomous(1000)
```

```python
from pyodesys import chained_parameter_variation
```

```python
odesys_T = get_odesys(T_as_param=True)
odesys_T.autonomous_interface and odesys_T.autonomous_exprs
```

```python
def integrate_and_plot_piecewise(N, axes=None):
    tout = np.linspace(0, 60, N)
    tcent = tout[:-1] + np.diff(tout)/2
    T = 300 + 10*np.sin(0.2*np.pi*tcent - np.pi/2)
    init_y = [1, 55, 0, 0, 0]
    p = [85e3, 10, 70e3, 20, 300]
    res = chained_parameter_variation(odesys_T, np.diff(tout), init_y, {4: T}, p,
                                      integrate_kwargs={'integrator': 'cvode'})
    if axes is None:
        fig, axes = plt.subplots(1, 2, figsize=(10, 4))
    res.plot(ax=axes[0], indices=[0,2,4], title_info=True)
    res.plot(ax=axes[1], indices=[3])
    axes[2].plot(tcent, T)
```

```python
fig, axes = plt.subplots(3, 3, figsize=(14, 14))
integrate_and_plot_piecewise(10, axes=axes[0])
integrate_and_plot_piecewise(30, axes=axes[1])
integrate_and_plot_piecewise(90, axes=axes[2])
```

```python
def plot_results(results):
    for axes, res in zip(plt.subplots(len(results), 2, figsize=(11, 11))[1], results):
        res.plot(ax=axes[0], indices=[0,2,4], title_info=True)
        res.plot(ax=axes[1], indices=[3], title_info=True)
```

```python
odesys_by_name_T = get_odesys(T_as_param=True, dep_by_name=True, par_by_name=True)
```

```python
tout = np.linspace(0, 60, 100)
tcent = tout[:-1] + np.diff(tout)/2
T = 300 + 10*np.sin(0.2*np.pi*tcent - np.pi/2)
H1vals = [80e3, 85e3, 90e3]
ps = dict(dH1=H1vals, dS1=10, dH2=70e3, dS2=20)
ic = dict(HNO2=1, H2O=55, NO=0, NO2=0, N2O4=0)
ikw = dict(integrator='cvode')
results = chained_parameter_variation(odesys_by_name_T, np.diff(tout), ic, dict(T=T), ps,
                                      integrate_kwargs=ikw)
```

```python
plot_results(results)
```

```python
odesys_by_name = get_odesys(dep_by_name=True, par_by_name=True)
plot_results(odesys_by_name.integrate(tout, ic, ps, **ikw))
```

```python

```


# Spherical hole problem

The spherial hole problem is considered in 24.1.2 in

[1] J.R. Barber, Elasticity, 2002, 2nd ed.

The domain defined as
$$
    \Omega = \{ x \in \mathbb{R^3} : 0 < a < |x| < A\}
$$
Let
$$
    \Gamma^r = \{ x \in \mathbb{R^3} : |x| = r\},
$$
then $\partial\Omega = \Gamma^a \cup \Gamma^A$.

Spherical coordinates are given by

$$
\begin{aligned}
    x &= R \cos(\theta) \sin(\beta), \\
    y &= R \sin(\theta) \sin(\beta), \\
    z &= R \cos(\beta),
\end{aligned}
$$
where $\theta \in [0,2\pi)$ and $\beta\in[0,\pi)$.

Sought is the solution to the problem
$$
\begin{aligned}
    \sigma_{RR} = 0, & \quad x \in \Gamma^a, \\
    \sigma_{R\theta} = 0, & \quad x \in \Gamma^a, \\
    \sigma_{R\beta} = 0, & \quad x \in \Gamma^a, \\
    u_r = g_r, & \quad x \in \Gamma^A, \\
    u_\theta = g_\theta, & \quad x \in \Gamma^A, \\
    u_\beta = g_\beta, & \quad x \in \Gamma^A,
\end{aligned}
$$
such that the equations of elasticity are satisfied.

Define necessary symbols

```python
from sympy import *
from util import lua_code, lua_code_cse
init_printing()
```

```python
R, t, b = symbols('R theta beta')
a, S, mu, nu = symbols('a S mu0 nu')
```

Solution ansatz [1]:

```python
A1, A2, A3, B1, B2 = symbols('A_1 A_2 A_3 B_1 B_2')
P2 = (3*cos(2*b) + 1) / 4
P1 = cos(b)
P0 = 1
phi = A1*R**2*P2 + A2*R**(-3)*P2 + A3*R**(-1)*P0
w = B1*R*P1 + B2*R**(-2)*P1
(phi, w)
```

Compute stresses (Table 19.2, Solutions A and B [1]):

```python
sRR = phi.diff(R,2) + R*cos(b)*w.diff(R, 2) - 2*(1-nu)*w.diff(R)*cos(b) + 2*nu/R * w.diff(b) * sin(b)
sRb = phi.diff(R).diff(b) / R - phi.diff(b) / R**2 + (1-2*nu)*w.diff(R)*sin(b) + cos(b)*w.diff(b).diff(R) - 2*(1-nu)*cos(b)/R * w.diff(b)
```

The solution is given by coefficients

```python
sol = [(A1, S*nu / (1+nu)),
        (A2, S*a**5 / (7-5*nu)),
        (A3, S*a**3 * (6-5*nu) / (2*(7-5*nu))),
        (B1, -S / (2*(1+nu))),
        (B2, -5*S*a**3 / (2*(7-5*nu)))]
```

Check boundary condition on inner sphere.

```python
(simplify(sRR.subs(sol).subs(R, a)),
 simplify(sRb.subs(sol).subs(R, a)))
```

Compute displacements (Table 19.2, Solutions A and B [1]):

```python
uR = (phi.diff(R) + R*cos(b)*w.diff(R) - (3-4*nu)*w*cos(b))/(2*mu)
ub = (phi.diff(b) / R + cos(b)*w.diff(b) + (3-4*nu)*w*sin(b))/(2*mu)
```

```python
uR.subs(sol)*2*mu
```

```python
ub.subs(sol)*2*mu
```

We set $g_r=u_r, g_\theta = 0, g_\beta = u_\beta$ on the outer sphere boundary.

## Manual displacement simplification

```python
uR_manual = S/(2*mu)*((R*(1-nu))/(2*(1+nu)) + (a**3*(10*nu - 13))/(4*R**2*(5*nu-7)) \
            + (3*a**5)/(4*R**4*(5*nu-7)) \
            + cos(2*b)*(R/2 + (5*a**3*(4*nu-5))/(4*R**2*(5*nu-7)) + 9*a**5/(4*R**4*(5*nu-7))))
uR_manual*2*mu
```

```python
ub_manual = -R*S/(4*mu)*sin(2*b)*((5*R**2*a**3*(1 - 2*nu) + 3*a**5)/(R**5*(7-5*nu)) + 1)
ub_manual*2*mu
```

Check.

```python
(simplify(uR.subs(sol) - uR_manual), simplify(ub.subs(sol) - ub_manual))
```

Generate Lua code:

```python
lua_code_cse([('uR', simplify(uR_manual)), ('ub', simplify(ub_manual))])
```

    local x0 = nu + 1
    local x1 = a^5
    local x2 = R^5
    local x3 = 5*nu - 7
    local x4 = x2*x3
    local x5 = 10*nu
    local x6 = R^2*a^3
    local x7 = 2*beta
    local x8 = S/(R^4*mu0*x3)
    local uR = (1.0/8.0)*x8*(x0*x6*(x5 - 13) + x0*(9*x1 + x2*(x5 - 14) + x6*(20*nu - 25))*math.cos(x7) + x1*(3*nu + 3) + 2*x4*(1 - nu))/x0
    local ub = -1.0/4.0*x8*(-3*x1 + x4 + x6*(x5 - 5))*math.sin(x7)

Compute Jacobian

```python
J = {}
J['uR_R'] = simplify(uR_manual.diff(R))
J['uR_b'] = simplify(uR_manual.diff(b))
J['ub_R'] = simplify(ub_manual.diff(R))
J['ub_b'] = simplify(ub_manual.diff(b))
lua_code_cse(J.items())
```

    local x0 = nu + 1
    local x1 = a^5
    local x2 = R^5
    local x3 = 5*nu
    local x4 = x3 - 7
    local x5 = x2*x4
    local x6 = 10*nu
    local x7 = R^2*a^3
    local x8 = 2*beta
    local x9 = math.cos(x8)
    local x10 = 20*nu
    local x11 = x7*(x10 - 25)
    local x12 = S/(mu0*x4)
    local x13 = (1.0/4.0)*x12
    local x14 = x13/x2
    local x15 = R^(-4)
    local x16 = math.sin(x8)
    local uR_R = -x14*(x0*x7*(x6 - 13) + x0*x9*(18*x1 + x11 - x5) + x1*(6*nu + 6) + x5*(nu - 1))/x0
    local uR_b = -x13*x15*x16*(9*x1 + x11 + x2*(x6 - 14))
    local ub_R = x14*x16*(-12*x1 + x10*x7 - x2*x3 + 7*x2 - 10*x7)
    local ub_b = -1.0/2.0*x12*x15*x9*(-3*x1 + x5 + x7*(x6 - 5))

```python

```


## The SIR model

The SIR model of epidemiology partitions the population into three compartments: susceptibles, S, who can catch the disease; infectives, I, who have already caught the disease and infect susceptibles; and removed individuals, R. Since the disease is assumed not to be fatal, the sum $N=S+I+R$ remains constant. The rate at which the susceptibles get infected is
$$
\lambda(t) = \frac{\beta I}{N}
$$
where the parameter $\beta$ is the probability of infection on contact. The infected individuals remove from the disease at a rate $\gamma$. Then, the ordinary differential equations of the SIR model are

\begin{align}
\dot S &= -\lambda(t)S \\
\dot I &= \lambda(t)I - \gamma I \\
\dot R &= \gamma I
\end{align}

This example integrates the above equations to obtain what is called the **epidemic curve**: a plot of the number of susceptibles and infectives as a function of time.

```python
%matplotlib inline
import numpy as np
import pyross
import matplotlib.pyplot as plt
#from matplotlib import rc; rc('text', usetex=True)
```

```python
M = 1                  # the SIR model has no age structure
Ni = 1000*np.ones(M)   # so there is only one age group
N = np.sum(Ni)         # and the total population is the size of this age group

beta  = 0.2            # infection rate
gIa   = 0.1            # removal rate of asymptomatic infectives
gIs   = 0.1            # removal rate of symptomatic infectives
alpha = 0              # fraction of asymptomatic infectives
fsa   = 1              # self-isolation of symtomatic infectives

Ia0 = np.array([0])     # the SIR model has only one kind of infective
Is0 = np.array([1])     # we take these to be symptomatic
R0  = np.array([0])     # and assume there are no removed individuals initially
S0  = N-(Ia0+Is0+R0)    # so that the initial susceptibles are obtained from S + Ia + Is + R = N

# there is no contact structure
def contactMatrix(t):
    return np.identity(M)

# duration of simulation and data file
Tf = 160;  Nt=160;

# instantiate model
parameters = {'alpha':alpha, 'beta':beta, 'gIa':gIa, 'gIs':gIs,'fsa':fsa}
model = pyross.deterministic.SIR(parameters, M, Ni)

# simulate model
#data = model.simulate(S0, Ia0, Is0, contactMatrix, Tf, Nt)
data = model.simulator(np.concatenate((S0, Ia0, Is0)), contactMatrix, Tf, Nt) # another possibility
```

```python
# plot the data and obtain the epidemic curve
S  = data['X'][:,0].flatten()
Ia = data['X'][:,1].flatten()
Is = data['X'][:,2].flatten()
t = data['t']

fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

plt.fill_between(t, 0, S/N, color="#348ABD", alpha=0.3)
plt.plot(t, S/N, '-', color="#348ABD", label='$S$', lw=4)

plt.fill_between(t, 0, Is/N, color='#A60628', alpha=0.3)
plt.plot(t, Is/N, '-', color='#A60628', label='$I$', lw=4)

R=N-S-Ia-Is; plt.fill_between(t, 0, R/N, color="dimgrey", alpha=0.3)
plt.plot(t, R/N, '-', color="dimgrey", label='$R$', lw=4)

plt.legend(fontsize=26); plt.grid()
plt.autoscale(enable=True, axis='x', tight=True)
plt.ylabel('Fraction of compartment value')
plt.xlabel('Days');
```

## Plot the same epidemic curve using utils method

```python
fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')
plt.rcParams.update({'font.size': 22})

data = model.simulate(S0, Ia0, Is0, contactMatrix, Tf, Nt)
#data = model.simulator(np.concatenate((S0, Ia0, Is0)), contactMatrix, Tf, Nt) # another possibility
pyross.utils.plotSIR(data)
```

## Plot the effective basic reproductive ratio $\mathcal R_0^{\text{eff}}(t)$ as a function of time

```python
fig = plt.figure(num=None, figsize=(10, 8), dpi=80, facecolor='w', edgecolor='k')

C=np.identity(M); contactMatrix = pyross.contactMatrix.SIR(0.25*C, 0.25*C, 0.25*C, 0.25*C)
r0de = contactMatrix.basicReproductiveRatio(data, state='dynamic')

plt.fill_between(t, 0, t*0+1, color="dimgrey", alpha=0.2); plt.ylabel('Basic reproductive ratio')
plt.plot(r0de, '*', color='#A60628')
plt.xlabel('Days'); plt.grid()
plt.autoscale(enable=True, axis='x', tight=True);
plt.yticks(np.arange(0, 3, step=0.5)); plt.ylim(0,2.2);
```


```python
import numpy as np
import matplotlib.pyplot as plt
import sympy as sp
```

```python
# fuggveny ami helyere be lehet helyettesiteni barmilyen masik fuggvenyt
def r(t):
    fr=3.0+0.5*np.sin(t)
    return(5*np.sin(fr*t))
```

```python
# legeneralja a hely-ido-t
# n a mintavetelek szama, h az intervallum a mintavetelek kozott
def generate_rt(n, h):
    i = 0.0
    timeStamps = np.empty((0))
    x = np.empty((0))
    while i <= n:
        timeStamps = np.append(timeStamps, i)
        x = np.append(x, r(i))
        i += h
    return x, timeStamps
```

```python
# derivalas kozelitessel, r argumentum a generate_rt() fuggveny elso visszateresi erteke
# t pedig a mintavetelezes idopontjai
def diff(r, t):
    if len(r) < 2: return False
    x = np.zeros_like(t)
    for i in range(len(r)):
        if i == 0: # elso elem
            x[i] = (r[i+1]-r[i])/(t[i+1]-t[i])
        elif i == len(r)-1: # utolso elem
            x[i] = (r[i]-r[i-1])/(t[i]-t[i-1])
        else: # minden mas esetben
            x[i] = (r[i+1]-r[i-1])/(t[i+1]-t[i-1])
    return x
```

```python
# Itt hozzuk letre a sebesseg, gyorsulas -ido grafikonokat a diff(r,t) segitsegevel
rt, tVec = generate_rt(10, 0.001) # rt: hely-ido, tVec: mintavetel idopontok
vt = diff(rt, tVec) # sebesseg-ido
at = diff(vt, tVec) # gyorsulas-ido
fig, axs = plt.subplots(3,1, figsize=(17,17)) # 3 diagram keszitese egymas ala, x es y tengely megnevezesevel
axs[0].set_xlabel('time')
axs[0].set_ylabel('position')
axs[1].set_xlabel('time')
axs[1].set_ylabel('velocity')
axs[2].set_xlabel('time')
axs[2].set_ylabel('accelaration')
axs[0].plot(tVec, rt, color='blue')
axs[1].plot(tVec, vt, color='red')
axs[2].plot(tVec, at, color='green')
axs[0].grid()
axs[1].grid()
axs[2].grid()
```

```python
fig2 = plt.figure(figsize=(17,17))
ax1 = fig2.add_subplot()
ax1.plot(tVec, rt, color='blue', label='r(t)')
ax1.plot(tVec, vt, color='red', label='v(t)')
ax1.plot(tVec, at, color='green', label='a(t)')
ax1.set_xlabel('t')
ax1.legend()
```


# Table of Contents

1  Python Pi Day 2017

2  Computing a lot of digits of <span class="MathJax" id="MathJax-Element-388-Frame" tabindex="0" style="position: relative;" data-mathml="<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>&#x03C0;</mi></math>" role="presentation"><nobr aria-hidden="true">π</nobr><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>π</mi></math>?

2.1  Two simple methods for finding the first digits of <span class="MathJax" id="MathJax-Element-391-Frame" tabindex="0" style="position: relative;" data-mathml="<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>&#x03C0;</mi></math>" role="presentation"><nobr aria-hidden="true">π</nobr><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>π</mi></math>

2.1.1  Fraction approximations, and <span class="MathJax" id="MathJax-Element-392-Frame" tabindex="0" style="position: relative;" data-mathml="<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>&#x03C0;</mi></math>" role="presentation"><nobr aria-hidden="true">π</nobr><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>π</mi></math> imported from the math module

2.1.2  A simple Monte-Carlo method

2.2  <span class="MathJax" id="MathJax-Element-397-Frame" tabindex="0" style="position: relative;" data-mathml="<math xmlns="http://www.w3.org/1998/Math/MathML"><mn>100</mn></math>" role="presentation"><nobr aria-hidden="true">100</nobr><math xmlns="http://www.w3.org/1998/Math/MathML"><mn>100</mn></math> first digits of <span class="MathJax" id="MathJax-Element-398-Frame" tabindex="0" style="position: relative;" data-mathml="<math xmlns="http://www.w3.org/1998/Math/MathML"><mi>&#x03C0;</mi></math>" role="presentation"><nobr aria-hidden="true">π</nobr><math xmlns="http://www.w3.org/1998/Math/MathML"><mi>π</mi></math>

2.3  Using mpmath

2.4  The Gauss–Legendre iterative algorithm

2.4.1  Using float numbers

2.4.2  Using decimal.Decimal to improve precision

2.5  Methods based on a convergent series

2.5.1  A Leibniz formula (easy):

2.5.2  Bailey-Borwein-Plouffe series (medium):

2.5.3  Bellard's formula (hard):

2.5.4  One Ramanujan's formula (hard):

2.5.5  Chudnovsky brothers' formula (hard):

2.6  Other methods

2.6.1  Trigonometric methods (hard)

2.6.1.1  High-precision arccot computation

2.6.1.2  Applying Machin's formula

2.6.1.3  Trying to solve my question!

2.6.1.4  Conclusion

2.6.2  (hard) Unbounded Spigot Algorithm

2.6.3  (hard) Borwein's algorithm

2.7  Examples and references

2.7.1  Links

2.7.2  Pie !

# Python Pi Day 2017
> This is heavily inspired by what I did two years ago, see this page [cs101/hackhaton/14_03/2015](http://perso.crans.org/besson/cs101/hackathon/14_03_2015) on my website.

Today is [Pi Day 2017](http://www.piday.org/), the day celebrating the number $\pi$.
For more details on this number, see [this Wikipedia page](https://en.wikipedia.org/wiki/Pi).

----

Let us use this occasion to showcase a few different approaches to compute the digits of the number $\pi$.
I will use the [Python](https://www.python.org/) programming language, and you are reading a [Jupyter notebook](https://www.jupyter.org/).

[](http://jupyter.org/)[](https://www.python.org/)

# Computing a lot of digits of $\pi$?

- **What to do ?** I will present and implement some methods that can be used to compute the first digits of the irrational number $\pi$.
- **How ?** One method is based on random numbers, but all the other are simple (or not so simple) iterative algorithm: the more steps you compute, the more digits you will have!
- **How to compare / assess the result ?** It is simple: the more digits you got, the better. We will also test the different methods implemented, and for the most efficient, see how fast it is to go up-to $140000$ digits.

The simple goal is to write a *small* function that computes digits of pi, as fast as possible, and find the 10 digits from position 140317 to 140327!
(that's the challenge I posted on Facebook)

----
## Two simple methods for finding the first digits of $\pi$
### Fraction approximations, and $\pi$ imported from the `math` module

Three approximations, using fractions, were known from a very long time (Aristote used $\frac{355}{113}$ !).
The first three approximations of pi are:

```python
print(" pi ~= 3.14 (two first digits).")
print(" pi ~= 22/7 = {} (two first digits).".format(22.0 / 7.0))
print(" pi ~= 355/113 = {} (six first digits).".format(355.0 / 113.0))
```

     pi ~= 3.14 (two first digits).
     pi ~= 22/7 = 3.142857142857143 (two first digits).
     pi ~= 355/113 = 3.1415929203539825 (six first digits).

This method is extremely limited, and will not give you more than 13 correct digits, as `math.pi` is stored as a *float* number (limited precision).

```python
def mathpi():
    from math import pi
    return pi

print("First method: using math.pi gives pi ~= {:.17f} (17 digits are displayed here).".format(mathpi()))
```

    First method: using math.pi gives pi ~= 3.14159265358979312 (17 digits are displayed here).

If we know the digits, we can directly print them:

```python
from decimal import Decimal
bigpi = Decimal('3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679')
print("The first 100 digits of pi are {}.".format(bigpi))
```

    The first 100 digits of pi are 3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679.

### A simple [Monte-Carlo method](https://en.wikipedia.org/wiki/Pi#Monte_Carlo_methods)
A simple Monte Carlo method for computing $\pi$ is to draw a circle inscribed in a square, and randomly place dots in the square.
The ratio of dots inside the circle to the total number of dots will approximately equal $\pi / 4$, which is also the ratio of the area of the circle by the area of the square:

In Python, such simulation can basically be implemented like this:

```python
from random import uniform

def montecarlo_pi(nbPoints=10000):
    """Returns a probabilist estimate of pi, as a float number."""
    nbInside = 0
    # we pick a certain number of points (nbPoints)
    for i in range(nbPoints):
        x = uniform(0, 1)
        y = uniform(0, 1)
        # (x, y) is now a random point in the square [0, 1] × [0, 1]
        if (x**2 + y**2) < 1:
            # This point (x, y) is inside the circle C(0, 1)
            nbInside += 1

    return 4 * float(nbInside) / floor(nbPoints)

```

```python
print("The simple Monte-Carlo method with {} random points gave pi = {}".format(10000, montecarlo_pi(10000)))
```

    The simple Monte-Carlo method with 10000 random points gave pi = 3.138

It is an interesting method, but it is just too limited for approximating digits of $\pi$.

The previous two methods are quite limited, and not efficient enough if you want more than 13 digits (resp. 4 digits for the Monte-Carlo method).

## $100$ first digits of $\pi$
$\pi \simeq 3.1415926535 ~ 8979323846 ~ 2643383279 ~ 5028841971 \\\\ 6939937510 ~ 5820974944 ~ 5923078164 ~ 9862803482 ~ 53421170679$ when computed to the first $100$ digits.

Can you compute up to $1000$ digits? Up to $10000$ digits? Up to $100000$ digits? **Up to 1 million digits?**

## Using [`mpmath`](http://mpmath.org/)
This is a simple and lazy method, using the [`mpmath`](http://mpmath.org/) module.
MPmath is a Python library for arbitrary-precision floating-point arithmetic (Multi-Precision), and it has a builtin highly-optimized algorithm to compute digits of $\pi$.

It works really fine up-to 1000000 digits (56 ms), from 1 million digits to be printed, printing them starts to get too time consuming (the IDE or the system might freeze).

```python
import mpmath
# from sympy import mpmath  # on older sympy versions
mp = mpmath.mp
```

We can [arbitrarily set the precision](http://docs.sympy.org/dev/modules/mpmath/basics.html#setting-the-precision), with the constant `mp.dps` (digit numbers).

```python
mp.dps = 1000  # number of digits
my_pi = mp.pi  # Gives pi to a thousand places
print("A lazy method using the mpmath module:\npi is approximatly {} (with {} digits).".format(my_pi, mp.dps))
```

    A lazy method using the mpmath module:
    pi is approximatly 3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588174881520920962829254091715364367892590360011330530548820466521384146951941511609433057270365759591953092186117381932611793105118548074462379962749567351885752724891227938183011949129833673362440656643086021394946395224737190702179860943702770539217176293176752384674818467669405132000568127145263560827785771342757789609173637178721468440901224953430146549585371050792279689258923542019956112129021960864034418159813629774771309960518707211349999998372978049951059731732816096318595024459455346908302642522308253344685035261931188171010003137838752886587533208381420617177669147303598253490428755468731159562863882353787593751957781857780532171226806613001927876611195909216420198 (with 1000 digits).

Let save it for further comparison of simpler methods.

```python
mp.dps = 100000  # number of digits
len(str(mp.pi))
mpmath_pi = Decimal(str(mp.pi))
```

    100001

We can solve the initial challenge easily:

```python
mp.dps = 140330
print(str(mp.pi)[2:][140317:140317+10])
```

    9341076406

And it will most probably be the quickest method presented here:

```python
%timeit mp.dps=140330;print(str(mp.pi)[2:][140317:140317+10])
```

    9341076406
    9341076406
    9341076406
    9341076406
    1 loop, best of 3: 236 ms per loop

Asking for $10$ times more digits take about $100$ more of time (that's a bad news).

```python
%timeit mp.dps=1403230;print(str(mp.pi)[2:][1403217:1403217+10])
```

    7966916520
    7966916520
    7966916520
    7966916520
    The slowest run took 6.34 times longer than the fastest. This could mean that an intermediate result is being cached.
    1 loop, best of 3: 20.4 s per loop

## The Gauss–Legendre iterative algorithm
> More details can be found on [this page](https://en.wikipedia.org/wiki/Gauss%E2%80%93Legendre_algorithm).

The first method given here is explained in detail.
This algorithm is called the [Gauss-Legendre method](https://en.wikipedia.org/wiki/Gauss%E2%80%93Legendre_algorithm), and for example it was used to compute the first $206 158 430 000$ decimal digits of $\pi$ on September 18th to 20th, $1999$.

It is a very simply **iterative algorithm** (ie. step by step, you update the variables, as long as you want):

1. First, start with 4 variables $a_0, b_0, t_0, p_0$, and their initial values are $a_0 = 1, b_0 = 1/\sqrt{2}, t_0 = 1/4, p_0 = 1$.

2. Then, update the variables (or create 4 new ones $a_{n+1}, b_{n+1}, t_{n+1}, p_{n+1}$) by repeating the following instructions (in this order) until the difference of $a_{n}$ and  $b_{n}$, is within the desired accuracy:
   - $a_{n+1} = \frac{a_n + b_n}{2}$,
   - $b_{n+1} = \sqrt{a_n \times b_n}$,
   - $t_{n+1} = t_n - p_n (a_n - a_{n+1})^2$,
   - $p_{n+1} = 2 p_n$.

3. Finally, the desired approximation of $\pi$ is: $$\pi \simeq \frac{(a_{n+1} + b_{n+1})^2}{4 t_{n+1}}.$$

### Using float numbers
The first three iterations give (approximations given up to and including the first incorrect digit):

    3.140 …
    3.14159264 …
    3.1415926535897932382 …

The algorithm has **second-order convergent nature**, which essentially means that the number of correct digits doubles with each step of the algorithm.
Try to see how far it can go in less than 120 seconds (print the approximation of $\pi$ after every step, and stop/kill the program after 2 minutes).

> This method is based on [computing the limits of the arithmetic–geometric mean](https://en.wikipedia.org/wiki/Arithmetic%E2%80%93geometric_mean) of some well-chosen number ([see on Wikipédia for more mathematical details](https://en.wikipedia.org/wiki/Gauss%E2%80%93Legendre_algorithm#Mathematical_background)).

```python
import math

def gauss_legendre_1(max_step):
    """Float number implementation of the Gauss-Legendre algorithm, for max_step steps."""
    a = 1.
    b = 1./math.sqrt(2)
    t = 1./4.0
    p = 1.
    for i in range(max_step):
        at = (a + b) / 2.0
        bt = math.sqrt(a*b)
        tt = t - p*(a-at)**2
        pt = 2.0 * p
        a, b, t, p = at, bt, tt, pt

    my_pi = ((a+b)**2)/(4.0*t)
    return my_pi
```

```python
my_pi = gauss_legendre_1(4)
my_pi
print("pi is approximately: {:.15f} (as a float number, precision is limited).".format(my_pi))
accuracy = 100*abs(math.pi - my_pi)/math.pi
print("Accuracy % with math.pi: {:.4g}".format(accuracy))
accuracy = 100*abs(float(mpmath_pi) - my_pi)/float(mpmath_pi)
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    3.141592653589794

    pi is approximately: 3.141592653589794 (as a float number, precision is limited).
    Accuracy % with math.pi: 2.827e-14
    Accuracy % with mpmath_pi: 2.827e-14

```python
my_pi = gauss_legendre_1(40)
my_pi
print("pi is approximately: {:.15f} (as a float number, precision is limited).".format(my_pi))
accuracy = 100*abs(math.pi - my_pi)/math.pi
print("Accuracy % with math.pi: {:.4g}".format(accuracy))
accuracy = 100*abs(float(mpmath_pi) - my_pi)/float(mpmath_pi)
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    3.141592653589794

    pi is approximately: 3.141592653589794 (as a float number, precision is limited).
    Accuracy % with math.pi: 2.827e-14
    Accuracy % with mpmath_pi: 2.827e-14

This first implementation of the Gauss-Legendre algorithm is limited to a precision of 13 or 14 digits. But it converges quickly ! (4 steps here).

### Using `decimal.Decimal` to improve precision
The limitation of this first algorithm came from using simple *float* numbers.
Let us now use [`Decimal`](https://docs.python.org/3/library/decimal.html) numbers to keep as many digits after the decimal as we need.

```python
from decimal import Decimal, getcontext
```

```python
def gauss_legendre_2(max_step):
    """Decimal number implementation of the Gauss-Legendre algorithm, for max_step steps."""
    # trick to improve precision
    getcontext().prec = 3 + 2**(max_step + 2)

    cst_2 = Decimal(2.0)
    cst_4 = Decimal(4.0)
    a = Decimal(1.0)
    b = Decimal(0.5).sqrt()
    t = Decimal(0.25)
    p = Decimal(1.0)

    for i in range(max_step):
        new_a = (a+b)/cst_2
        new_b = (a*b).sqrt()
        new_t = Decimal(t - p*(a - new_a)**2)
        new_p = cst_2*p

        a, b, t, p = new_a, new_b, new_t, new_p

    my_pi = Decimal(((a+b)**2)/(cst_4*t))
    return my_pi
```

```python
my_pi = gauss_legendre_2(5)
print("pi is approximately: {}.".format(my_pi.to_eng_string()[:2**(5+1)]))

accuracy = 100*abs(Decimal(math.pi) - my_pi)/Decimal(math.pi)
print("Accuracy % with math.pi: {:.4g}".format(accuracy))
accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    pi is approximately: 3.14159265358979323846264338327950288419716939937510582097494459.
    Accuracy % with math.pi: 3.898e-15
    Accuracy % with mpmath_pi: 7.659e-83

The second implementation of the Gauss-Legendre algorithm is now working better (when we adapt the precision). And it converges quickly ! (8 steps give a precision upto the 697th digits).

How much did we lost in term of performance by using decimal numbers? About a factor $1000$, but that's normal as the second solution stores a lot of digits. They don't even compute the same response.

```python
%timeit gauss_legendre_1(8)
%timeit gauss_legendre_2(8)
```

    The slowest run took 6.47 times longer than the fastest. This could mean that an intermediate result is being cached.
    100000 loops, best of 3: 4.06 µs per loop
    100 loops, best of 3: 3.9 ms per loop

## Methods based on a convergent series
For the following formulae, you can try to write a program that computes the partial sum of the series, up to a certain number of term ($N \geq 1$).
Basically, the bigger the $N$, the more digits you get (but the longer the program will run).

Some methods might be really efficient, only needing a few number of steps (a small $N$) for computing many digits.
Try to implement and compare at least two of these methods.
You can use the function `sum` (or `math.fsum`) to compute the sum, or a simple `while`/`for` loop.

All these partial sums are written up to an integer $N \geq 1$.

### [A Leibniz formula](https://en.wikipedia.org/wiki/Leibniz_formula_for_pi) (*easy*):
It has a number of digits sub-linear in the number $N$ of terms in the sum: we need $10$ times more terms to win just one digit.
$$\pi \simeq 4\sum_{n=0}^{N} \cfrac {(-1)^n}{2n+1}. $$

```python
def leibniz(max_step):
    """ Computing an approximation of pi with Leibniz series."""
    my_pi = Decimal(0)
    for k in range(max_step):
        my_pi += Decimal((-1)**k) / Decimal(2*k+1)
    return Decimal(4) * my_pi
```

```python
getcontext().prec = 20  # trick to improve precision
my_pi = leibniz(1000)
my_pi

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Decimal('3.1405926538397929257')

    Accuracy % with mpmath_pi: 0.03183

```python
getcontext().prec = 20  # trick to improve precision
my_pi = leibniz(10000)
my_pi

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Decimal('3.1414926535900432389')

    Accuracy % with mpmath_pi: 0.003183

This first formula is very inefficient!

### [Bailey-Borwein-Plouffe series](https://en.wikipedia.org/wiki/Bailey%E2%80%93Borwein%E2%80%93Plouffe_formula) (*medium*):
It also has a number of digits linear in the number $N$ of terms in the sum.
$$\pi \simeq \sum_{n = 1}^{N} \left( \frac{1}{16^{n}} \left( \frac{4}{8n+1} - \frac{2}{8n+4} - \frac{1}{8n+5} - \frac{1}{8n+6} \right) \right). $$

```python
def bbp(max_step):
    """ Computing an approximation of pi with Bailey-Borwein-Plouffe series."""
    my_pi = Decimal(0)
    for k in range(max_step):
        my_pi += (Decimal(1)/(16**k))*((Decimal(4)/(8*k+1))-(Decimal(2)/(8*k+4))-(Decimal(1)/(8*k+5))-(Decimal(1)/(8*k+6)))
    return my_pi
```

```python
getcontext().prec = 20  # trick to improve precision
my_pi = bbp(10)
my_pi

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Decimal('3.1415926535897911465')

    Accuracy % with mpmath_pi: 6.659e-14

That's pretty impressive, in only $10$ steps!
But that algorithm is highly dependent on the precision we ask, and on the number of terms in the sum.

```python
getcontext().prec = 200  # trick to improve precision
my_pi = bbp(200)
my_pi

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Decimal('3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303817')

    Accuracy % with mpmath_pi: 8.417e-198

```python
getcontext().prec = 500  # trick to improve precision
my_pi = bbp(500)
my_pi

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Decimal('3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119494')

    Accuracy % with mpmath_pi: 8.600e-498

It is, of course, slower than the optimized algorithm from `mpmath`.
And it does not scale well with the precision:

```python
getcontext().prec = 10 + 1000  # trick to improve precision
%timeit bbp(1000)

getcontext().prec = 10 + 2000  # trick to improve precision
%timeit bbp(2000)
```

    10 loops, best of 3: 95 ms per loop
    1 loop, best of 3: 661 ms per loop

### [Bellard's formula](https://en.wikipedia.org/wiki/Bellard%27s_formula) (*hard*):
It is a more efficient formula.
$$\pi \simeq \frac1{2^6} \sum_{n=0}^N \frac{(-1)^n}{2^{10n}} \, \left(-\frac{2^5}{4n+1} - \frac1{4n+3} + \frac{2^8}{10n+1} - \frac{2^6}{10n+3} - \frac{2^2}{10n+5} - \frac{2^2}{10n+7} + \frac1{10n+9} \right). $$

```python
def bellard(max_step):
    """ Computing an approximation of pi with Bellard series."""
    my_pi = Decimal(0)
    for k in range(max_step):
        my_pi += (Decimal(-1)**k/(1024**k))*( Decimal(256)/(10*k+1) + Decimal(1)/(10*k+9) - Decimal(64)/(10*k+3) - Decimal(32)/(4*k+1) - Decimal(4)/(10*k+5) - Decimal(4)/(10*k+7) -Decimal(1)/(4*k+3))
    return my_pi * Decimal(1.0/(2**6))
```

```python
getcontext().prec = 40  # trick to improve precision
my_pi = bellard(10)
my_pi

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Decimal('3.141592653589793238462643383279490036578')

    Accuracy % with mpmath_pi: 4.090e-31

That's pretty impressive, in only $10$ steps!
But that algorithm is again highly dependent on the precision we ask, and on the number of terms in the sum.

```python
getcontext().prec = 800  # trick to improve precision
my_pi = bellard(200)
my_pi

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Decimal('3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005611526979981607242018469874601593584399987639992795109821175268982496078980620087198769557504210588066903133631260918929384314932145237223424648852944318026933908108945450734428224399103245946394')

    Accuracy % with mpmath_pi: 2.220e-604

It is, of course, slower than the optimized algorithm from `mpmath`.
And it does not scale well with the precision:

```python
getcontext().prec = 10 + 1000  # trick to improve precision
%timeit bellard(1000)

getcontext().prec = 10 + 2000  # trick to improve precision
%timeit bellard(2000)
```

    1 loop, best of 3: 378 ms per loop
    1 loop, best of 3: 2.91 s per loop

It is also slower than BBP formula.

### One [Ramanujan's formula](https://en.wikipedia.org/wiki/Approximations_of_%CF%80#Efficient_methods) (*hard*):
It is efficient but harder to compute easily with high precision, due to the factorial.
But hopefully, the function `math.factorial` works with Python *integers*, of arbitrary size, so this won't be an issue.

$$\frac{1}{\pi} \simeq \frac{2\sqrt{2}}{9801} \sum_{n=0}^N \frac{(4n)!(1103+26390n)}{(n!)^4 396^{4n}}. $$

*Remark:* This method and the next one compute approximation of $\frac{1}{\pi}$, not $\pi$.

This method has a quadratic precision (the number of correct digits is of the order $\mathcal{O}(N^2)$.

```python
from math import factorial

def ramanujan(max_step):
    """ Computing an approximation of pi with a Ramanujan's formula."""
    my_pi = Decimal(0)
    d_1103 = Decimal(1103)
    d_26390 = Decimal(26390)
    d_396 = Decimal(396)
    for k in range(max_step):
        my_pi += ((Decimal(factorial(4 * k))) * (d_1103 + d_26390 * Decimal(k))) / ( (Decimal(factorial(k)))**4 * (d_396**(4*k)))
    my_pi = my_pi * 2 * Decimal(2).sqrt() / Decimal(9801)
    my_pi = my_pi**(-1)
    return my_pi
```

```python
getcontext().prec = 40  # trick to improve precision
my_pi = ramanujan(4)
my_pi

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Decimal('3.141592653589793238462643383279555273157')

    Accuracy % with mpmath_pi: 1.668e-30

```python
getcontext().prec = 400  # trick to improve precision
my_pi = ramanujan(40)
my_pi

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Decimal('3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117067982148086513282306647093844609550582231725359408128481117450284102701938521105559644622948954930381964428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273724587006606315588249728368707403112841783926380916831222457885852997711270901024127742895829505936')

    Accuracy % with mpmath_pi: 2.382e-318

```python
getcontext().prec = 2000  # trick to improve precision
my_pi = ramanujan(200)
my_pi

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Decimal('3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337867831652712019091456485669234603486104543266482133936072602491412737245870066063155881748815209209628292540917153643678925903600113305305488204665213841469519415116094330572703657595919530921861173819326117931051185480744623799627495673518857527248912279381830119491298336733624406566430860213949463952247371907021798609437027705392171762931767523846748184676694051320005681271452635608277857713427577896091736371787214684409012249534301465495853710507922796892589235420199561121290219608640344181598136297747713099605187072113499999983729780499510597317328160963185950244594553469083026425223082533446850352619311881710100031378387528865875332083814206171776691473035982534904287554687311595628638823537875937519577818577805321712268066130019278766111959092164201989380952572010654858632788659361533818279682303019520353018529689957736225994138912497217752834791315155748572424541506959508295331168617278558890750983817546374649393192550604009277016711390098488240128583616035637076601047101819429555961989467678374494482553797747268471040475346462080466842590694912933136770289891521047521620569660240580381501935112533824300355876402474964732639141992726042699227967823547816360093417216412199245863150302861829745557067498385054945885869269956909272107975093029553211653449872027559602364806654991198818347977535663698074265425278625518184175746728909777727940092593679577412880114072520662452344498309284389026310583057432459663517241665671357564259409181627161036619398380266804323123256475817186916514676524019320976885611753635255011894984447618075599914209997043044425803288826839697424724950586712681051712136647565220069328838660790206792014356556715545181106432983263815336472301873472006826126207435983354628153181952126467499415321188698738323941189429')

    Accuracy % with mpmath_pi: 6.658e-1596

$1595$ correct digits with $200$ terms, that's quite good!!

```python
getcontext().prec = 10 + 2000  # trick to improve precision
%timeit ramanujan(200)

getcontext().prec = 10 + 5000  # trick to improve precision
%timeit ramanujan(400)
```

    10 loops, best of 3: 50.1 ms per loop
    1 loop, best of 3: 478 ms per loop

Let's try to answer my initial question, using this naive implementation.

```python
%%time
getcontext().prec = 140350  # trick to improve precision
i = 140317
my_pi = ramanujan(10000)
print(str(my_pi)[2:][i:i+10])

mp.dps=140330
print(str(mp.pi)[2:][i:i+10])
```

... It was too slow!

### [Chudnovsky brothers' formula](https://en.wikipedia.org/wiki/Chudnovsky_algorithm) (*hard*):
$$\frac{1}{\pi} \simeq 12 \sum_{n=0}^N \frac {(-1)^{n}(6n)!(545140134n+13591409)}{(3n)!(n!)^{3}640320^{{3n+3/2}}}. $$
In 2015, the best method is still the Chudnovsky brothers's formula.

> Be careful when you use these formulae, *check carefully* the constants you wrote (545140134 will work well, 545140135 might not work as well!).

> This formula is used as the logo of the [French agrégation of Mathematics](https://en.wikipedia.org/wiki/Agr%C3%A9gation) [website `agreg.org`](http://agreg.org/) :
>

```python
from math import factorial

def chudnovsky(max_step):
    """ Computing an approximation of pi with Bellard series."""
    my_pi = Decimal(0)
    for k in range(max_step):
        my_pi += (Decimal(-1)**k)*(Decimal(factorial(6*k))/((factorial(k)**3)*(factorial(3*k)))* (13591409+545140134*k)/(640320**(3*k)))
    my_pi = my_pi * Decimal(10005).sqrt()/4270934400
    my_pi = my_pi**(-1)
    return my_pi
```

It is very efficient, as Ramanujan's formula.

```python
getcontext().prec = 3000  # trick to improve precision
my_pi = chudnovsky(200)
my_pi

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Decimal('3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861045432664821339360726024914127372458700660631558817488152092096282925409171536436789259036001133053054882046652138414695194151160943305727036575959195309218611738193261179310511854807446237996274956735188575272489122793818301194912983367336244065664308602139494639522473719070217986094370277053921717629317675238467481846766940513200056812714526356082778577134275778960917363717872146844090122495343014654958537105079227968925892354201995611212902196086403441815981362977477130996051870721134999999837297804995105973173281609631859502445945534690830264252230825334468503526193118817101000313783875288658753320838142061717766914730359825349042875546873115956286388235378759375195778185778053217122680661300192787661119590921642019893809525720106548586327886593615338182796823030195203530185296899577362259941389124972177528347913151557485724245415069595082953311686172785588907509838175463746493931925506040092770167113900984882401285836160356370766010471018194295559619894676783744944825537977472684710404753464620804668425906949129331367702898915210475216205696602405803815019351125338243003558764024749647326391419927260426992279678235478163600934172164121992458631503028618297455570674983850549458858692699569092721079750930295532116534498720275596023648066549911988183479775356636980742654252786255181841757467289097777279380008164706001614524919217321721477235014144197356854816136115735255213347574184946843852332390739414333454776241686251898356948556209921922218427255025425688767179049460165346680498862723279178608578438382796797668145410095388378636095068006422512520511739298489608412848862694560424196528502221066118630674427862203919494504712371378696095636437191728746776465757396241389086583264599581339047802759009946576407895126946839835259570982582262052248940772671947826848260147699090264013639443745530506820349625245174939965143142980919065925093722169646151570985838741059788595977297549893016175392846813826868386894277415599185592524595395943104997252468084598727364469584865383673622262609912460805124388439045124413654976278079771569143599770012961608944169486855584840635342207222582848864815845602850601684273945226746767889525213852254995466672782398645659611635488623057745649803559363456817432411251507606947945109659609402522887971089314566913686722874894056010150330861792868092087476091782493858900971490967598526136554978189312978482168299894872265880485756401427047755513237964145152374623436454285844479526586782105114135473573952311342716610213596953623144295248493718711014576540359027993440374200731057853906219838744780847852710405603214833731504646600861733152271806431614075966417958570002328166126409832551776784733662072536424251339850953855478119486626262729703543651855423452347092')

    Accuracy % with mpmath_pi: 1.191e-2835

It gets $2834$ correct numbers in $200$ steps!
It is more efficient that Ramanujan's formula, but slower to compute.

```python
getcontext().prec = 6000  # trick to improve precision
my_pi = chudnovsky(400)

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Accuracy % with mpmath_pi: 3.947e-5672

```python
getcontext().prec = 3000  # trick to improve precision
%timeit chudnovsky(200)

getcontext().prec = 6000  # trick to improve precision
%timeit chudnovsky(400)
```

    1 loop, best of 3: 216 ms per loop
    1 loop, best of 3: 1.93 s per loop

About $2$ seconds to find correctly the first $5671$ digits? That's slow! But hey, it's Python (dynamic typing etc).

----
## Other methods

### Trigonometric methods (*hard*)
Some methods are based on the $\mathrm{arccot}$ or $\arctan$ functions, and use the appropriate Taylor series to approximate these functions.
The best example is [Machin's formula](http://en.literateprograms.org/Pi_with_Machin%27s_formula_%28Python%29):
$$\pi = 16 \;\mathrm{arccot}(5) - 4 \;\mathrm{arccot}(239).$$

And we use the Taylor series to approximate $\mathrm{arccot}(x)$:
$$\mathrm{arccot}(x) = \frac{1}{x} - \frac{1}{3x^3} + \frac{1}{5x^5} - \frac{1}{7x^7} + \dots = \sum_{n=0}^{+\infty} \frac{(-1)^n}{(2n+1) x^{2n+1}} .$$

This method is also explained here with some details.
In order to obtain $n$ digits, we will use *fixed-point* arithmetic to compute $\pi \times 10^n$ as a Python `long` integer.

#### [High-precision arccot computation](http://en.literateprograms.org/Pi_with_Machin%27s_formula_%28Python%29#High-precision_arccot_computation)
To calculate $\mathrm{arccot}$ of an argument $x$, we start by dividing the number $1$ (represented by $10^n$, which we provide as the argument `unity`) by $x$ to obtain the first term.

We then repeatedly divide by $x^2$ and a counter value that runs over $3$, $5$, $7$ etc, to obtain each next term.
The summation is stopped at the first zero `term`, which in this *fixed-point* representation corresponds to a real value less than $10^{-n}$:

```python
def arccot(x, unity):
    xpower = unity / x
    sum = xpower
    n = 3
    sign = -1
    while True:
        xpower = xpower / (x*x)
        term = xpower / n
        if term == 0:
            break  # we are done
        sum += sign * term
        sign = -sign
        n += 2
    return sum
```

Adapting it to use Decimal numbers is easy:

```python
def arccot(x, unity):
    """Compute arccot(x) with a certain level of precision."""
    x = Decimal(x)
    unity = Decimal(unity)
    mysum = xpower = unity / x
    n = 3
    sign = -1
    while True:
        xpower = xpower / (x*x)
        term = xpower / n
        if not term:
            break
        mysum += sign * term
        sign = -sign  # we alternate the sign
        n += 2
    return mysum
```

#### Applying Machin's formula
Finally, the main function uses Machin's formula to compute $\pi$ using the necessary level of precision, by using this high precision $\mathrm{arccot}$ function:
$$\pi = 16 \;\mathrm{arccot}(5) - 4 \;\mathrm{arccot}(239).$$

```python
def machin(digits):
    unity = 10**(digits + 10)
    pi = 4 * (4*arccot(5, unity) - arccot(239, unity))
    return pi / unity
```

To avoid rounding errors in the result, we use 10 guard digits internally during the calculation.
We may now reproduce the historical result obtained by [Machin in 1706](https://en.wikipedia.org/wiki/John_Machin).

```python
def machin(digits):
    """Compute pi with Machin's formula, with precision at least digits."""
    unity = 10**(digits + 10)
    my_pi = Decimal(4) * (Decimal(4)*arccot(5, unity) - arccot(239, unity))
    return my_pi / Decimal(unity)
```

```python
getcontext().prec = 10000  # trick to improve precision
my_pi = machin(100)

accuracy = 100*abs(mpmath_pi - my_pi)/mpmath_pi
print("Accuracy % with mpmath_pi: {:.4g}".format(accuracy))
```

    Accuracy % with mpmath_pi: 1.501e-9996

So we got the first $9995$ digits correctly... in $45$ seconds.
That will still be too slow to have the digits at position $130317$.

```python
getcontext().prec = 5000  # trick to improve precision
%timeit machin(50)

getcontext().prec = 10000  # trick to improve precision
%timeit machin(100)
```

    1 loop, best of 3: 22.7 s per loop
    1 loop, best of 3: 44.5 s per loop

The program can be used to compute tens of thousands of digits in just a few seconds on a modern computer.
Typical implementation will be in highly efficient compiled language; like C or maybe Julia.

Many [Machin-like formulas](https://en.wikipedia.org/wiki/Machin-like_formula) also exist, like:
$$\pi = 4\arctan\left(\frac{1}{2}\right) + 4 \arctan\left(\frac{1}{3}\right).$$

#### Trying to solve my question!
The important parameter to tune is not the precision given to `machin()` but the `Decimal.prec` precision.

```python
%%time
i = 14031
getcontext().prec = i + 20  # trick to improve precision
mp.dps = i + 20
print(str(mp.pi)[2:][i:i+10])

my_pi = machin(11)
print(str(my_pi)[2:][i:i+10])
```

    7126218283
    7126218283
    CPU times: user 1min 3s, sys: 12 ms, total: 1min 3s
    Wall time: 1min 3s

```python
%%time
i = 140317
getcontext().prec = i + 20  # trick to improve precision
mp.dps = i + 20
print(str(mp.pi)[2:][i:i+10])

my_pi = machin(50)
print(str(my_pi)[2:][i:i+10])
```

    9341076406
    9341076406
    CPU times: user 10min 40s, sys: 224 ms, total: 10min 40s
    Wall time: 10min 41s

It was too slow too! But at least it worked!

My manual implementation of Machin's formula took about $10$ minutes, on an old laptop with $1$ core running Python 3.5, to find the $10$ digits of $\pi$ at index $140317$.

#### Conclusion
$\implies$ To the question "What are the $10$ digits of $\pi$ at index $140317..140326$", the answer is $9341076406$.

For more, see http://fredrikj.net/blog/2011/03/100-mpmath-one-liners-for-pi/.

### (*hard*) [Unbounded Spigot Algorithm](http://www.cs.ox.ac.uk/people/jeremy.gibbons/publications/spigot.pdf)
This algorithm is quite efficient, but not easy to explain. Go check on-line if you want.

See this page (http://codepad.org/3yDnw0n9) for a 1-line C program that uses a simpler Spigot algorithm for computing the first 15000 digits

A nice method, with a generator yielding the next digit each time, comes from http://stackoverflow.com/a/9005163.
It uses only integer, so no need to do any `Decimal` trick here.

```python
def next_pi_digit(max_step):
    q, r, t, k, m, x = 1, 0, 1, 1, 3, 3
    for j in range(max_step):
        if 4 * q + r - t < m * t:
            yield m
            # More details on Python generators can be found here http://stackoverflow.com/a/231855
            q, r, t, k, m, x = 10*q, 10*(r-m*t), t, k, (10*(3*q+r))//t - 10*m, x
        else:
            q, r, t, k, m, x = q*k, (2*q+r)*x, t*x, k+1, (q*(7*k+2)+r*x)//(t*x), x+2
```

```python
def generator_pi(max_step):
    big_str = ''.join(str(d) for d in next_pi_digit(max_step))
    return Decimal(big_str[0] + '.' + big_str[1:])
```

It does not use `Decimal` numbers.

```python
getcontext().prec = 50  # trick to improve precision
generator_pi(1000)
```

    Decimal('3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337')

```python
getcontext().prec = 5000  # trick to improve precision
generator_pi(1000)
```

    Decimal('3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081284811174502841027019385211055596446229489549303819644288109756659334461284756482337')

### (*hard*) [Borwein's algorithm](https://en.wikipedia.org/wiki/Borwein%27s_algorithm#Nonic_convergence)
It has several versions, one with a cubic convergence (each new step multiplies by $3$ the number of digits), one with a nonic convergence (of order $9$) etc.
They are not so easy to explain either.
Please check on-line, here [en.WikiPedia.org/wiki/Borwein's_algorithm](https://en.wikipedia.org/wiki/Borwein%27s_algorithm).

The cubic method is similar to the Gauss-Legendre algorithm:

1. Start with $a_0 = 1/3$, and $s_0 = \frac{\sqrt{3}-1}{2}$,
2. And then iterate, as much as you want, by defining $r_{k+1} = \frac{3}{1+2(1-s_k^3)^{1/3}}$, and updating $s_{k+1} = \frac{r_{k+1}-1}{2}$ and $a_{k+1} = r_{k+1}^2 a_k - 3^k (r_{k+1}^2 - 1)$.

Then the numbers $a_k$ will converge to $\frac{1}{\pi}$.

----
## Examples and references
### Links
- [en.WikiPedia.org/wiki/Pi#Modern_quest_for_more_digits](https://en.wikipedia.org/wiki/Pi#Modern_quest_for_more_digits),
- [www.JoyOfPi.com/pi.html](http://www.joyofpi.com/pi.html) and [www.JoyOfPi.com/pilinks.html](http://www.joyofpi.com/pilinks.html),
- [www.EveAndersson.com/pi/digits/](http://www.eveandersson.com/pi/digits/) has great interactive tools,
- more crazy stuff [MathWorld.Wolfram.com/PiDigits.html](http://mathworld.wolfram.com/PiDigits.html), or [MathWorld.Wolfram.com/Pi.html](http://mathworld.wolfram.com/Pi.html),
- [one idea with Fibonacci numbers](http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibpi.html#section2),
- [and this incredibly long list of digits](http://piworld.calico.jp/estart.html) at [PiWorld.calico.jp/estart.html](http://piworld.calico.jp/estart.html).
- http://bellard.org/pi/ by Francois Bellard (and http://bellard.org/pi/pi_n2/pi_n2.html)

> That's it for today! Happy Pi Day!

----
### Pie !

----