https://github.com/pysal/mgwr/pull/56

```python
import sys
sys.path.append("C:/Users/msachde1/Downloads/Research/Development/mgwr")
```

```python
import warnings
warnings.filterwarnings("ignore")
import pandas as pd
import numpy as np

from mgwr.gwr import GWR
from spglm.family import Gaussian, Binomial, Poisson
from mgwr.gwr import MGWR
from mgwr.sel_bw import Sel_BW
import multiprocessing as mp
pool = mp.Pool()
from scipy import linalg
import numpy.linalg as la
from scipy import sparse as sp
from scipy.sparse import linalg as spla
from spreg.utils import spdot, spmultiply
from scipy import special
import libpysal as ps
import seaborn as sns
import matplotlib.pyplot as plt
from copy import deepcopy
import copy
from collections import namedtuple
import spglm
```

#### Fundamental equation

By simple algebraic manipulation, the probability that Y=1 is:

\begin{align}
p = 1 / (1 + exp (-{\beta} & _k x _{k,i})  )            \\
\end{align}

### Approaches tried:

1. Changing XB to : `1 / (1 + np.exp (-1*np.sum(np.multiply(X,params),axis=1)))` - these are the predicted probabilities ~(0,1)

2. Changing XB as above and writing a function to create temp_y as a binary variable using condition `1 if BXi > 0 else 0.`

3. Derived manipulations to temp_y as in iwls for Logistic regression as below:

    `v = np.sum(np.multiply(X,params),axis=1)`

    `mu = 1/(1+(np.exp(-v)))`

    `z = v + (1/(mu * (1-mu)) * (y-mu))`  -- this becomes the temp_y

    Then a simple linear regression can be run as z as the temp dependent variable

4. Taken from GAM logistic model literature:

    `y=exp(b0+b1*x1+...+bm*xm)/{1+exp(b0+b1*x1+...+bm*xm)}`

    Applying the logistic link function to the probability p (ranging between 0  and 1):

    `p' = log {p/(1-p)}`

    By applying the logistic link function, we can now rewrite the model as:

    `p' = b0 + b1*X1  + ... + bm*Xm`

    Finally, we substitute the simple single-parameter additive terms to derive the generalized additive logistic model:

    `p' = b0 + f1(X1) + ... + fm(Xm)`

    (http://www.statsoft.com/textbook/generalized-additive-models#gam)

    This is the current approach in the latest commit:

    `XB = 1 / (1 + np.exp (-1*(np.multiply(X,params))))`

    XB is now the probability and is normally distributed

    Run MGWR (Gaussian) on this as the dependent variable for the partial models.

### Data

#### Clearwater data - downloaded from link: https://sgsup.asu.edu/sparc/multiscale-gwr

```python
data_p = pd.read_csv("C:/Users/msachde1/Downloads/logistic_mgwr_data/landslides.csv")
```

```python
data_p.head()
```

      UserID
      X
      Y
      Elev
      Slope
      SinAspct
      CosAspct
      AbsSouth
      Landslid
      DistStrm

      0
      1
      616168.5625
      5201076.5
      1450.475
      27.44172
      0.409126
      -0.912478
      24.1499
      1
      8.506

      1
      2
      624923.8125
      5201008.5
      1567.476
      21.88343
      -0.919245
      -0.393685
      66.8160
      1
      15.561

      2
      3
      615672.0000
      5199187.5
      1515.065
      38.81030
      -0.535024
      -0.844837
      32.3455
      1
      41.238

      3
      4
      615209.3125
      5199112.0
      1459.827
      26.71631
      -0.828548
      -0.559918
      55.9499
      1
      17.539

      4
      5
      616354.6875
      5198945.5
      1379.442
      27.55271
      -0.872281
      -0.489005
      60.7248
      1
      35.023

#### Helper functions - hardcoded here for simplicity in the notebook workflow
Please note: A separate bw_func_b will not be required when changes will be made in the repository

```python
kernel='bisquare'
fixed=False
spherical=False
search_method='golden_section'
criterion='AICc'
interval=None
tol=1e-06
max_iter=500
X_glob=[]
```

```python
def gwr_func(y, X, bw,family=Gaussian(),offset=None):
    return GWR(coords, y, X, bw, family,offset,kernel=kernel,
               fixed=fixed, constant=False,
               spherical=spherical, hat_matrix=False).fit(
                   lite=True, pool=pool)

def gwr_func_g(y, X, bw):
    return GWR(coords, y, X, bw, family=Gaussian(),offset=None,kernel=kernel,
               fixed=fixed, constant=False,
               spherical=spherical, hat_matrix=False).fit(
                   lite=True, pool=pool)

def bw_func_b(coords,y, X):
    selector = Sel_BW(coords,y, X,family=Binomial(),offset=None, X_glob=[],
                      kernel=kernel, fixed=fixed,
                      constant=False, spherical=spherical)
    return selector

def bw_func_p(coords,y, X):
    selector = Sel_BW(coords,y, X,family=Poisson(),offset=off, X_glob=[],
                      kernel=kernel, fixed=fixed,
                      constant=False, spherical=spherical)
    return selector

def bw_func(coords,y,X):
    selector = Sel_BW(coords,y,X,X_glob=[],
                      kernel=kernel, fixed=fixed,
                      constant=False, spherical=spherical)
    return selector

def sel_func(bw_func, bw_min=None, bw_max=None):
    return bw_func.search(
        search_method=search_method, criterion=criterion,
        bw_min=bw_min, bw_max=bw_max, interval=interval, tol=tol,
        max_iter=max_iter, pool=pool, verbose=False)
```

### GWR Binomial model with independent variable, x = slope

```python
coords = list(zip(data_p['X'],data_p['Y']))
y = np.array(data_p['Landslid']).reshape((-1,1))
elev = np.array(data_p['Elev']).reshape((-1,1))
slope = np.array(data_p['Slope']).reshape((-1,1))
SinAspct = np.array(data_p['SinAspct']).reshape(-1,1)
CosAspct = np.array(data_p['CosAspct']).reshape(-1,1)
X = np.hstack([elev,slope,SinAspct,CosAspct])
x = SinAspct

X_std = (X-X.mean(axis=0))/X.std(axis=0)
x_std = (x-x.mean(axis=0))/x.std(axis=0)
y_std = (y-y.mean(axis=0))/y.std(axis=0)
```

```python
bw_gwbr=Sel_BW(coords,y,x_std,family=Binomial(),constant=False).search()
```

```python
gwbr_model=GWR(coords,y,x_std,bw=bw_gwbr,family=Binomial(),constant=False).fit()
```

```python
bw_gwbr
```

    100.0

```python
predy = 1/(1+np.exp(-1*np.sum(gwbr_model.X * gwbr_model.params, axis=1).reshape(-1, 1)))
```

```python
sns.distplot(predy)
```

```python
(predy==gwbr_model.predy).all()
```

    True

```python
sns.distplot(gwbr_model.y)
```

#### Multi_bw changes

```python
def multi_bw(init,coords,y, X, n, k, family=Gaussian(),offset=None, tol=1e-06, max_iter=20, multi_bw_min=[None], multi_bw_max=[None],rss_score=True,bws_same_times=3,
                     verbose=True):

    if multi_bw_min==[None]:
        multi_bw_min = multi_bw_min*X.shape[1]

    if multi_bw_max==[None]:
        multi_bw_max = multi_bw_max*X.shape[1]

    if isinstance(family,spglm.family.Poisson):
        bw = sel_func(bw_func_p(coords,y,X))
        optim_model=gwr_func(y,X,bw,family=Poisson(),offset=offset)
        err = optim_model.resid_response.reshape((-1, 1))
        param = optim_model.params
        #This change for the Poisson model follows from equation (1) above
        XB = offset*np.exp(np.multiply(param, X))

    elif isinstance(family,spglm.family.Binomial):
        bw = sel_func(bw_func_b(coords,y,X))
        optim_model=gwr_func(y,X,bw,family=Binomial())
        err = optim_model.resid_response.reshape((-1, 1))
        param = optim_model.params
        XB = 1/(1+np.exp(-1*np.multiply(optim_model.params,X)))
        print("first family: "+str(optim_model.family))

    else:
        bw=sel_func(bw_func(coords,y,X))
        optim_model=gwr_func(y,X,bw)
        err = optim_model.resid_response.reshape((-1, 1))
        param = optim_model.params
        XB = np.multiply(param, X)

    bw_gwr = bw
    XB=XB

    if rss_score:
        rss = np.sum((err)**2)
    iters = 0
    scores = []
    delta = 1e6
    BWs = []
    bw_stable_counter = np.ones(k)
    bws = np.empty(k)

    try:
        from tqdm.auto import tqdm  #if they have it, let users have a progress bar
    except ImportError:

        def tqdm(x, desc=''):  #otherwise, just passthrough the range
            return x

    for iters in tqdm(range(1, max_iter + 1), desc='Backfitting'):
        new_XB = np.zeros_like(X)
        neww_XB = np.zeros_like(X)

        params = np.zeros_like(X)

        for j in range(k):
            temp_y = XB[:, j].reshape((-1, 1))
            temp_y = temp_y + err

            temp_X = X[:, j].reshape((-1, 1))

            #The step below will not be necessary once the bw_func is changed in the repo to accept family and offset as attributes
            if isinstance(family,spglm.family.Poisson):

                bw_class = bw_func_p(coords,temp_y, temp_X)

            else:
                bw_class = bw_func(coords,temp_y, temp_X)
                print(bw_class.family)

            if np.all(bw_stable_counter == bws_same_times):
                #If in backfitting, all bws not changing in bws_same_times (default 3) iterations
                bw = bws[j]
            else:
                bw = sel_func(bw_class, multi_bw_min[j], multi_bw_max[j])
                if bw == bws[j]:
                    bw_stable_counter[j] += 1
                else:
                    bw_stable_counter = np.ones(k)

            optim_model = gwr_func_g(temp_y, temp_X, bw)
            print(optim_model.family)

            err = optim_model.resid_response.reshape((-1, 1))
            param = optim_model.params.reshape((-1, ))

            new_XB[:,j]=optim_model.predy.reshape(-1)
            params[:, j] = param
            bws[j] = bw

        num = np.sum((new_XB - XB)**2) / n
        print("num = "+str(num))
        den = np.sum(np.sum(new_XB, axis=1)**2)
        score = (num / den)**0.5
        print(score)
        XB = new_XB

        if rss_score:
            print("here")
            predy = 1/(1+np.exp(-1*np.sum(X * params, axis=1).reshape(-1, 1)))
            new_rss = np.sum((y - predy)**2)
            score = np.abs((new_rss - rss) / new_rss)
            rss = new_rss
        scores.append(deepcopy(score))
        delta = score
        print(delta)
        BWs.append(deepcopy(bws))

        if verbose:
            print("Current iteration:", iters, ",SOC:", np.round(score, 7))
            print("Bandwidths:", ', '.join([str(bw) for bw in bws]))

        if delta < tol:
            break

    print("iters = "+str(iters))
    opt_bws = BWs[-1]
    print("opt_bws = "+str(opt_bws))
    print(bw_gwr)
    return (opt_bws, np.array(BWs), np.array(scores), params, err, bw_gwr)
```

```python
mgwbr = multi_bw(init=None,coords=coords,y=y, X=x_std, n=239, k=x.shape[1], family=Binomial())
```

    first family: <spglm.family.Binomial object at 0x0000017B8F302588>
    <spglm.family.Gaussian object at 0x0000017B8DA45940>
    <spglm.family.Gaussian object at 0x0000017B8F341828>
    num = 0.2261860620412264
    0.1493438159109546
    here
    0.019261197296944955
    Current iteration: 1 ,SOC: 0.0192612
    Bandwidths: 82.0
    <spglm.family.Gaussian object at 0x0000017B8DA45940>
    <spglm.family.Gaussian object at 0x0000017B8F2BD4A8>
    num = 0.0
    0.0
    here
    0.0
    Current iteration: 2 ,SOC: 0.0
    Bandwidths: 82.0
    iters = 2
    opt_bws = [82.]
    100.0

```python
param = mgwbr[3]
```

```python
predy = 1/(1+np.exp(-1*np.sum(x_std * param, axis=1).reshape(-1, 1)))
```

```python
sns.distplot(predy)
```


# Simulating readout noise on the Rigetti Quantum Virtual Machine
© Copyright 2018, Rigetti Computing.
$$
\newcommand{ket}[1]{\left|{#1}\right\rangle}
\newcommand{bra}[1]{\left\langle {#1}\right|}
\newcommand{tr}[1]{\mathrm{Tr}\,\left[ {#1}\right]}
\newcommand{expect}[1]{\left\langle {#1} \right \rangle}
$$

## Theoretical Overview

Qubit-Readout can be corrupted in a variety of ways. The two most relevant error mechanisms on the Rigetti QPU right now are:

1. Transmission line noise that makes a 0-state look like a 1-state or vice versa. We call this **classical readout bit-flip error**. This type of readout noise can be reduced by tailoring optimal readout pulses and using superconducting, quantum limited amplifiers to amplify the readout signal before it is corrupted by classical noise at the higher temperature stages of our cryostats.
2. T1 qubit decay during readout (our readout operations can take more than a µsecond unless they have been specially optimized), which leads to readout signals that initially behave like 1-states but then collapse to something resembling a 0-state. We will call this **T1-readout error**. This type of readout error can be reduced by achieving shorter readout pulses relative to the T1 time, i.e., one can try to reduce the readout pulse length, or increase the T1 time or both.

## Qubit measurements

This section provides the necessary theoretical foundation for accurately modeling noisy quantum measurements on superconducting quantum processors. It relies on some of the abstractions (density matrices, Kraus maps) introduced in our notebook on [gate noise models](GateNoiseModels.ipynb).

The most general type of measurement performed on a single qubit at a single time can be characterized by some set $\mathcal{O}$ of measurement outcomes, e.g., in the simplest case $\mathcal{O} = \{0, 1\}$, and some unnormalized quantum channels (see notebook on gate noise models) that encapsulate
1. the probability of that outcome
2. how the qubit state is affected conditional on the measurement outcome.

Here the _outcome_ is understood as classical information that has been extracted from the quantum system.

### Projective, ideal measurement
The simplest case that is usually taught in introductory quantum mechanics and quantum information courses are Born's rule and the projection postulate which state that there exist a complete set of orthogonal projection operators
$$
P_{\mathcal{O}} := \{\Pi_x \text{ Projector }\mid x \in \mathcal{O}\},
$$
i.e., one for each measurement outcome. Any projection operator must satisfy $\Pi_x^\dagger = \Pi_x = \Pi_x^2$ and for an _orthogonal_ set of projectors any two members satisfy
$$
\Pi_x\Pi_y = \delta_{xy} \Pi_x = \begin{cases} 0 & \text{ if } x \ne y \\ \Pi_x & \text{ if } x = y \end{cases}
$$
and for a _complete_ set we additionally demand that $\sum_{x\in\mathcal{O}} \Pi_x = 1$.
Following our introduction to gate noise, we write quantum states as density matrices as this is more general and in closer correspondence with classical probability theory.

With these the probability of outcome $x$ is given by $p(x) = \tr{\Pi_x \rho \Pi_x} = \tr{\Pi_x^2 \rho} = \tr{\Pi_x \rho}$ and the post measurement state is
$$
\rho_x = \frac{1}{p(x)} \Pi_x \rho \Pi_x,
$$
which is the projection postulate applied to mixed states.

If we were a sloppy quantum programmer and accidentally erased the measurement outcome then our best guess for the post measurement state would be given by something that looks an awful lot like a Kraus map:
$$
\rho_{\text{post measurement}} = \sum_{x\in\mathcal{O}} p(x) \rho_x = \sum_{x\in\mathcal{O}} \Pi_x \rho \Pi_x.
$$
The completeness of the projector set ensures that the trace of the post measurement is still 1 and the Kraus map form of this expression ensures that $\rho_{\text{post measurement}}$ is a positive (semi-)definite operator.

### Classical readout bit-flip error

Consider now the ideal measurement as above, but where the outcome $x$ is transmitted across a noisy classical channel that produces a final outcome $x'\in \mathcal{O}' = \{0', 1'\}$ according to some conditional probabilities $p(x'|x)$ that can be recorded in the _assignment probability matrix_
$$
P_{x'|x} = \begin{pmatrix}
p(0 | 0) & p(0 | 1) \\
p(1 | 0) & p(1 | 1)
\end{pmatrix}
$$
Note that this matrix has only two independent parameters as each column must be a valid probability distribution, i.e. all elements are non-negative and each column sums to 1.

This matrix allows us to obtain the probabilities $\mathbf{p}' := (p(x'=0), p(x'=1))^T$ from the original outcome probabilities $\mathbf{p} := (p(x=0), p(x=1))^T$  via $\mathbf{p}' = P_{x'|x}\mathbf{p}$.
The difference relative to the ideal case above is that now an outcome $x' = 0$ does not necessarily imply that the post measurement state is truly $\Pi_{0} \rho \Pi_{0} / p(x=0)$. Instead, the post measurement state given a noisy outcome $x'$ must be
\begin{align}
\rho_{x'} & = \sum_{x\in \mathcal{O}} p(x|x') \rho_x \\
          & = \sum_{x\in \mathcal{O}} p(x'|x)\frac{p(x)}{p(x')} \rho_x \\
          & = \frac{1}{p(x')}\sum_{x\in \mathcal{O}} p(x'|x) \Pi_x \rho \Pi_x
\end{align}
where
\begin{align}
p(x') & = \sum_{x\in\mathcal{O}} p(x'|x) p(x)  \\
& = \tr{\sum_{x\in \mathcal{O}} p(x'|x) \Pi_x \rho \Pi_x} \\
& = \tr{\rho \sum_{x\in \mathcal{O}} p(x'|x)\Pi_x} \\
& = \tr{\rho E_x'}.
\end{align}
where we have exploited the cyclical property of the trace $\tr{ABC}=\tr{BCA}$ and the projection property $\Pi_x^2 = \Pi_x$. This has allowed us to derive the noisy outcome probabilities from a set of positive operators
$$
E_{x'} := \sum_{x\in \mathcal{O}} p(x'|x)\Pi_x \ge 0
$$
that must sum to 1:
$$
\sum_{x'\in\mathcal{O}'} E_x' = \sum_{x\in\mathcal{O}}\underbrace{\left[\sum_{x'\in\mathcal{O}'} p(x'|x)\right]}_{1}\Pi_x = \sum_{x\in\mathcal{O}}\Pi_x = 1.
$$
The above result is a type of generalized **Bayes' theorem** that is extremely useful for this type of (slightly) generalized measurement and the family of operators $\{E_{x'}| x' \in \mathcal{O}'\}$ whose expectations give the probabilities is called a **positive operator valued measure** (POVM). These operators are not generally orthogonal nor valid projection operators but they naturally arise in this scenario. This is not yet the most general type of measurement, but it will get us pretty far.

### How to model $T_1$ error
T1 type errors fall outside our framework so far as they involve a scenario in which the _quantum state itself_ is corrupted during the measurement process in a way that potentially erases the pre-measurement information as opposed to a loss of purely classical information. The most appropriate framework for describing this is given by that of measurement instruments, but for the practical purpose of arriving at a relatively simple description, we propose describing this by a T1 damping Kraus map followed by the noisy readout process as described above.

### Further reading
Chapter 3 of John Preskill's lecture notes http://www.theory.caltech.edu/people/preskill/ph229/notes/chap3.pdf

## How do I get started?

1. Come up with a good guess for your readout noise parameters $p(0|0)$ and $p(1|1)$, the off-diagonals then follow from the normalization of $P_{x'|x}$. If your assignment fidelity $F$ is given, and you assume that the classical bit flip noise is roughly symmetric, then a good approximation is to set $p(0|0)=p(1|1)=F$.
2. For your QUIL program `p`, and a qubit index `q` call:
    ```
    p.define_noisy_readout(q, p00, p11)
    ```
    where you should replace `p00` and `p11` with the assumed probabilities.

### Estimate $P_{x'|x}$ yourself!
You can also run some simple experiments to estimate the assignment probability matrix directly from a QPU.

**Scroll down for some examples!**

```python
from __future__ import print_function, division
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

from pyquil.quil import Program, MEASURE, Pragma
from pyquil.api.qvm import QVMConnection
from pyquil.gates import I, X, RX, H, CNOT
from pyquil.noise import (estimate_bitstring_probs, correct_bitstring_probs,
                          bitstring_probs_to_z_moments, estimate_assignment_probs)

DARK_TEAL = '#48737F'
FUSCHIA = '#D6619E'
BEIGE = '#EAE8C6'

cxn = QVMConnection()
```

## Example 1: Rabi sequence with noisy readout

```python
%%time

# number of angles
num_theta = 101

# number of program executions
trials = 200

thetas = np.linspace(0, 2*np.pi, num_theta)

p00s = [1., 0.95, 0.9, 0.8]

results_rabi = np.zeros((num_theta, len(p00s)))

for jj, theta in enumerate(thetas):
    for kk, p00 in enumerate(p00s):
        cxn.random_seed = hash((jj, kk))
        p = Program(RX(theta)(0))
        # assume symmetric noise p11 = p00
        p.define_noisy_readout(0, p00=p00, p11=p00)
        p.measure(0, 0)
        res = cxn.run(p, [0], trials=trials)
        results_rabi[jj, kk] = np.sum(res)

```

    CPU times: user 1.2 s, sys: 73.6 ms, total: 1.27 s
    Wall time: 3.97 s

```python
plt.figure(figsize=(14, 6))
for jj, (p00, c) in enumerate(zip(p00s, [DARK_TEAL, FUSCHIA, "k", "gray"])):
    plt.plot(thetas, results_rabi[:, jj]/trials, c=c, label=r"$p(0|0)=p(1|1)={:g}$".format(p00))
plt.legend(loc="best")
plt.xlim(*thetas[[0,-1]])
plt.ylim(-.1, 1.1)
plt.grid(alpha=.5)
plt.xlabel(r"RX angle $\theta$ [radian]", size=16)
plt.ylabel(r"Excited state fraction $n_1/n_{\rm trials}$", size=16)
plt.title("Effect of classical readout noise on Rabi contrast.", size=18)
```

## Example 2: Estimate the assignment probabilities

### Estimate assignment probabilities for a perfect quantum computer

```python
estimate_assignment_probs(0, 1000, cxn, Program())
```

    array([[ 1.,  0.],
           [ 0.,  1.]])

### Re-Estimate assignment probabilities for an imperfect quantum computer

```python
cxn.seed = None
header0 = Program().define_noisy_readout(0, .85, .95)
header1 = Program().define_noisy_readout(1, .8, .9)
header2 = Program().define_noisy_readout(2, .9, .85)

ap0 = estimate_assignment_probs(0, 100000, cxn, header0)
ap1 = estimate_assignment_probs(1, 100000, cxn, header1)
ap2 = estimate_assignment_probs(2, 100000, cxn, header2)
```

```python
print(ap0, ap1, ap2, sep="\n")
```

    [[ 0.84967  0.04941]
     [ 0.15033  0.95059]]
    [[ 0.80058  0.09993]
     [ 0.19942  0.90007]]
    [[ 0.90048  0.14988]
     [ 0.09952  0.85012]]

## Example 3: Use `pyquil.noise.correct_bitstring_probs` to correct for noisy readout

### 3a) Correcting the Rabi signal from above

```python
ap_last = np.array([[p00s[-1], 1 - p00s[-1]],
                    [1 - p00s[-1], p00s[-1]]])
corrected_last_result = [correct_bitstring_probs([1-p, p], [ap_last])[1] for p in results_rabi[:, -1] / trials]
```

```python
plt.figure(figsize=(14, 6))
for jj, (p00, c) in enumerate(zip(p00s, [DARK_TEAL, FUSCHIA, "k", "gray"])):
    if jj not in [0, 3]:
        continue
    plt.plot(thetas, results_rabi[:, jj]/trials, c=c, label=r"$p(0|0)=p(1|1)={:g}$".format(p00), alpha=.3)
plt.plot(thetas, corrected_last_result, c="red", label=r"Corrected $p(0|0)=p(1|1)={:g}$".format(p00s[-1]))
plt.legend(loc="best")
plt.xlim(*thetas[[0,-1]])
plt.ylim(-.1, 1.1)
plt.grid(alpha=.5)
plt.xlabel(r"RX angle $\theta$ [radian]", size=16)
plt.ylabel(r"Excited state fraction $n_1/n_{\rm trials}$", size=16)
plt.title("Corrected contrast", size=18)
```

**We find that the corrected signal is fairly noisy (and sometimes exceeds the allowed interval $[0,1]$) due to the overall very small number of samples $n=200$.**

### 3b) In this example we will create a GHZ state $\frac{1}{\sqrt{2}}\left[\left|000\right\rangle + \left|111\right\rangle \right]$ and measure its outcome probabilities with and without the above noise model. We will then see how the Pauli-Z moments that indicate the qubit correlations are corrupted (and corrected) using our API.

```python
ghz_prog = Program(H(0), CNOT(0, 1), CNOT(1, 2),
                   MEASURE(0, 0), MEASURE(1, 1), MEASURE(2, 2))
print(ghz_prog)
results = cxn.run(ghz_prog, [0, 1, 2], trials=10000)
```

    H 0
    CNOT 0 1
    CNOT 1 2
    MEASURE 0 [0]
    MEASURE 1 [1]
    MEASURE 2 [2]

```python
header = header0 + header1 + header2
noisy_ghz = header + ghz_prog
print(noisy_ghz)
noisy_results = cxn.run(noisy_ghz, [0, 1, 2], trials=10000)
```

    PRAGMA READOUT-POVM 0 "(0.85 0.050000000000000044 0.15000000000000002 0.95)"
    PRAGMA READOUT-POVM 1 "(0.8 0.09999999999999998 0.19999999999999996 0.9)"
    PRAGMA READOUT-POVM 2 "(0.9 0.15000000000000002 0.09999999999999998 0.85)"
    H 0
    CNOT 0 1
    CNOT 1 2
    MEASURE 0 [0]
    MEASURE 1 [1]
    MEASURE 2 [2]

### Uncorrupted probability for $\left|000\right\rangle$ and $\left|111\right\rangle$

```python
probs = estimate_bitstring_probs(results)
probs[0, 0, 0], probs[1, 1, 1]
```

    (0.50419999999999998, 0.49580000000000002)

As expected the outcomes `000` and `111` each have roughly probability $1/2$.

### Corrupted probability for $\left|011\right\rangle$ and $\left|100\right\rangle$

```python
noisy_probs = estimate_bitstring_probs(noisy_results)
noisy_probs[0, 0, 0], noisy_probs[1, 1, 1]
```

    (0.30869999999999997, 0.3644)

The noise-corrupted outcome probabilities deviate significantly from their ideal values!

### Corrected probability for $\left|011\right\rangle$ and $\left|100\right\rangle$

```python
corrected_probs = correct_bitstring_probs(noisy_probs, [ap0, ap1, ap2])
corrected_probs[0, 0, 0], corrected_probs[1, 1, 1]
```

    (0.50397601453064977, 0.49866843912900716)

The corrected outcome probabilities are much closer to the ideal value.

### Estimate $\langle Z_0^{j} Z_1^{k} Z_2^{\ell}\rangle$ for $jkl=100, 010, 001$ from non-noisy data
*We expect these to all be very small*

```python
zmoments = bitstring_probs_to_z_moments(probs)
zmoments[1, 0, 0], zmoments[0, 1, 0], zmoments[0, 0, 1]
```

    (0.0083999999999999631, 0.0083999999999999631, 0.0083999999999999631)

### Estimate $\langle Z_0^{j} Z_1^{k} Z_2^{\ell}\rangle$ for $jkl=110, 011, 101$ from non-noisy data
*We expect these to all be close to 1.*

```python
zmoments[1, 1, 0], zmoments[0, 1, 1], zmoments[1, 0, 1]
```

    (1.0, 1.0, 1.0)

### Estimate $\langle Z_0^{j} Z_1^{k} Z_2^{\ell}\rangle$ for $jkl=100, 010, 001$ from noise-corrected data

```python
zmoments_corr = bitstring_probs_to_z_moments(corrected_probs)
zmoments_corr[1, 0, 0], zmoments_corr[0, 1, 0], zmoments_corr[0, 0, 1]
```

    (0.0071476770049732075, -0.0078641261685578612, 0.0088462563282706852)

### Estimate $\langle Z_0^{j} Z_1^{k} Z_2^{\ell}\rangle$ for $jkl=110, 011, 101$ from noise-corrected data

```python
zmoments_corr[1, 1, 0], zmoments_corr[0, 1, 1], zmoments_corr[1, 0, 1]
```

    (0.99477496902638118, 1.0008376440216553, 1.0149652015905912)

##### Overall the correction can restore the contrast in our multi-qubit observables, though we also see that the correction can lead to slightly non-physical expectations. This effect is reduced the more samples we take.

```python

```


# Calculate events in Circular Restricted Three Body Problem

```python
!pip install fastrk orbipy
```

    Requirement already satisfied: fastrk in c:\users\stasb\pycharmprojects\fastrk (0.0.2)
    Requirement already satisfied: orbipy in c:\users\stasb\anaconda3\envs\p38\lib\site-packages (0.2.5)
    Collecting numpy~=1.19.2
      Downloading numpy-1.19.5-cp38-cp38-win_amd64.whl (13.3 MB)
    Collecting numba~=0.51.2
      Using cached numba-0.51.2-cp38-cp38-win_amd64.whl (2.2 MB)
    Requirement already satisfied: sympy~=1.6.2 in c:\users\stasb\appdata\roaming\python\python38\site-packages\sympy-1.6.2-py3.8.egg (from fastrk) (1.6.2)
    Requirement already satisfied: matplotlib>=3.0.0 in c:\users\stasb\appdata\roaming\python\python38\site-packages (from orbipy) (3.4.1)
    Requirement already satisfied: scipy>=1.1.0 in c:\users\stasb\anaconda3\envs\p38\lib\site-packages (from orbipy) (1.6.3)
    Requirement already satisfied: pandas>=0.23.4 in c:\users\stasb\appdata\roaming\python\python38\site-packages (from orbipy) (1.1.4)
    Collecting llvmlite<0.35,>=0.34.0.dev0
      Using cached llvmlite-0.34.0-cp38-cp38-win_amd64.whl (15.9 MB)
    Requirement already satisfied: setuptools in c:\users\stasb\anaconda3\envs\p38\lib\site-packages (from numba~=0.51.2->fastrk) (49.6.0.post20200814)
    Requirement already satisfied: mpmath>=0.19 in c:\users\stasb\appdata\roaming\python\python38\site-packages\mpmath-1.1.0-py3.8.egg (from sympy~=1.6.2->fastrk) (1.1.0)
    Requirement already satisfied: python-dateutil>=2.7 in c:\users\stasb\appdata\roaming\python\python38\site-packages\python_dateutil-2.8.1-py3.8.egg (from matplotlib>=3.0.0->orbipy) (2.8.1)
    Requirement already satisfied: pyparsing>=2.2.1 in c:\users\stasb\appdata\roaming\python\python38\site-packages\pyparsing-3.0.0b1-py3.8.egg (from matplotlib>=3.0.0->orbipy) (3.0.0b1)
    Requirement already satisfied: pillow>=6.2.0 in c:\users\stasb\appdata\roaming\python\python38\site-packages\pillow-8.0.1-py3.8-win-amd64.egg (from matplotlib>=3.0.0->orbipy) (8.0.1)
    Requirement already satisfied: cycler>=0.10 in c:\users\stasb\appdata\roaming\python\python38\site-packages\cycler-0.10.0-py3.8.egg (from matplotlib>=3.0.0->orbipy) (0.10.0)
    Requirement already satisfied: kiwisolver>=1.0.1 in c:\users\stasb\appdata\roaming\python\python38\site-packages\kiwisolver-1.3.1-py3.8-win-amd64.egg (from matplotlib>=3.0.0->orbipy) (1.3.1)
    Requirement already satisfied: pytz>=2017.2 in c:\users\stasb\anaconda3\envs\p38\lib\site-packages (from pandas>=0.23.4->orbipy) (2021.1)
    Requirement already satisfied: six>=1.5 in c:\users\stasb\appdata\roaming\python\python38\site-packages\six-1.15.0-py3.8.egg (from python-dateutil>=2.7->matplotlib>=3.0.0->orbipy) (1.15.0)
    Installing collected packages: numpy, llvmlite, numba
      Attempting uninstall: numpy
        Found existing installation: numpy 1.20.2
        Uninstalling numpy-1.20.2:
          Successfully uninstalled numpy-1.20.2

    WARNING: Keyring is skipped due to an exception: entry_points() got an unexpected keyword argument 'group'
    ERROR: Could not install packages due to an EnvironmentError: [WinError 5] Access is denied: 'C:\\Users\\stasb\\AppData\\Roaming\\Python\\Python38\\site-packages\\~umpy\\.libs\\libopenblas.GK7GX5KEQ4F6UYO3P26ULGBQYHGQO7J4.gfortran-win_amd64.dll'
    Consider using the `--user` option or check the permissions.

```python
import numpy as np
import pandas as pd
from scipy.integrate import solve_ivp
from fastrk import BT8713M, RKCodeGen, EventsCodeGen
from model_crtbp import crtbp
from timeit import timeit
from numba import njit
import orbipy as op
```

    C:\Users\stasb\Anaconda3\envs\p38\lib\site-packages\numpy\_distributor_init.py:30: UserWarning: loaded more than 1 DLL from .libs:
    C:\Users\stasb\Anaconda3\envs\p38\lib\site-packages\numpy\.libs\libopenblas.NOIJJG62EMASZI6NYURL6JBKM4EVBGM7.gfortran-win_amd64.dll
    C:\Users\stasb\Anaconda3\envs\p38\lib\site-packages\numpy\.libs\libopenblas.WCDJNK7YVMPZQ2ME2ZZHJJRJ3JIKNDB7.gfortran-win_amd64.dll
      warnings.warn("loaded more than 1 DLL from .libs:"

```python
# Orbipy uses scipy.integrate.ode
model = op.crtbp3_model()

# Events

ev_y = op.eventY(0., terminal=False)
ev_z = op.eventZ(0., terminal=False)
ev_vx = op.eventVX(0., terminal=False)
ev_vy = op.eventVY(0., terminal=False)
ev_vz = op.eventVZ(0., terminal=False)

events = [ev_y, ev_z, ev_vx, ev_vy, ev_vz]
```

```python
# generate code and import rk_prop function
# new module rk_bt8713m.py will be created (at first call) or loaded
# CRTBP is autonomous system i.e. equations doesn't have explicit time dependency

rk_module = RKCodeGen(BT8713M, autonomous=True).save_and_import()
rk_prop = rk_module.rk_prop
rk_prop_ev = rk_module.rk_prop_ev

# after first run code above can be replaced by
# from rk_8713M import rk_prop, rk_prop_ev

```

```python
# generate code and import call_event function;
# new module __evcache__/ev_<hash>.py will be created (at first call) or loaded;
# call_event(t, s, values, idx) function calls specific event function by its index in events list

call_event = EventsCodeGen(events).save_and_import()
```

```python
# Let's define Cauchy's problem:
# initial time and state for halo orbit

t0 = 0.
t1 = 3*np.pi
s0 = np.zeros(6)
s0[[0, 2, 4]] = 9.949942666080747733e-01, 4.732924802139452415e-03, -1.973768492871211949e-02
mc = np.array([3.001348389698916e-06])
```

```python
# integration parameters, same as Orbipy default
params = {'max_step': np.inf,
          'rtol': 1e-12,
          'atol': 1e-12,
          }
```

```python
# event detector object
dtr = op.event_detector(model, events)

df, evdf = dtr.prop(s0, t0, t1)
```

```python
values = np.array([e.value for e in events])
terminals = np.array([e.terminal for e in events])
directions = np.array([e.direction for e in events])
counts = np.array([e.count for e in events])
accurates = np.array([e.accurate for e in events])

trj, evarr = rk_prop_ev(crtbp, s0, 0, t1, *params.values(),
                        values, terminals, directions, counts, accurates,
                        call_event, 1e-12, 1e-12, 100, mc)
```

```python
evdf1 = pd.DataFrame(evarr, columns=['e', 'cnt', 't', 'x', 'y', 'z', 'vx', 'vy', 'vz'])
```

```python
# check event times
evdf.t - evdf1.t
```

    0    -4.922285e-12
    1     4.164447e-12
    2     7.951861e-12
    3     3.029155e-11
    4     4.951595e-13
    5     5.851453e-11
    6     1.755840e-11
    7     1.680007e-10
    8    -6.343224e-10
    9     1.618141e-10
    10    4.295067e-10
    11    1.630771e-10
    12   -7.911112e-10
    13    6.959928e-09
    14    2.087988e-09
    15    1.687998e-08
    16    8.396579e-09
    17    5.646641e-08
    18   -2.277712e-07
    19    5.820032e-08
    20    1.568987e-07
    21    5.598841e-08
    22   -2.966316e-07
    23    2.560417e-06
    24    7.688840e-07
    25    6.216301e-06
    26    3.096731e-06
    27    2.080954e-05
    28   -8.392425e-05
    29    2.144755e-05
    Name: t, dtype: float64

```python
evdf[evdf.e == 0].y - evdf1[evdf1.e == 0].y
```

    2     2.089539e-17
    7    -1.923244e-18
    12   -3.282024e-16
    17   -1.551711e-13
    22    1.787718e-16
    27   -2.841581e-13
    Name: y, dtype: float64

```python
pltr = op.plotter.from_model(model)

ax = pltr.plot_proj(evdf, color='b', ls='', marker='+', ms=10)
pltr.plot_proj(evdf1, color='r', ls='', marker='.', ax=ax);
```

```python
loops = 1000
str0 = "dtr.prop(s0, t0, t1)"
str1 = """rk_prop_ev(crtbp, s0, 0, t1, *params.values(),
                     values, terminals, directions, counts, accurates,
                     call_event, 1e-12, 1e-12, 100, mc)"""

r0 = timeit(str0, number=loops, globals=globals())
r1 = timeit(str1, number=loops, globals=globals())

print(f"orbipy DOP853 time {r0:.2f}")
print(f"fastrk DOP8713M time {r1:.2f}")
print(f"speedup x {r0/r1:.2f}")

```

    orbipy DOP853 time 12.63
    fastrk DOP8713M time 2.11
    speedup x 6.00

```python
@njit
def event_0(t, s, *args):
    return s[1]

@njit
def event_1(t, s, *args):
    return s[2]

@njit
def event_2(t, s, *args):
    return s[3]

@njit
def event_3(t, s, *args):
    return s[4]

@njit
def event_4(t, s, *args):
    return s[5]

# use solve_ivp to calculate events
sol = solve_ivp(crtbp, (t0, t1), s0,
                method='DOP853', **params,
                args=(mc,),
                events=[event_0, event_1, event_2, event_3, event_4])
```

```python
loops = 100
str0 = """solve_ivp(crtbp, (t0, t1), s0,
                method='DOP853', **params,
                args=(mc,),
                events=[event_0, event_1, event_2, event_3, event_4])"""
str1 = """rk_prop_ev(crtbp, s0, 0, t1, *params.values(),
                     values, terminals, directions, counts, accurates,
                     call_event, 1e-12, 1e-12, 100, mc)"""

r0 = timeit(str0, number=loops, globals=globals())
r1 = timeit(str1, number=loops, globals=globals())

print(f"solve_ivp DOP853 time {r0:.2f}")
print(f"fastrk DOP8713M time {r1:.2f}")
print(f"speedup x {r0/r1:.2f}")
```

    solve_ivp DOP853 time 3.80
    fastrk DOP8713M time 0.22
    speedup x 17.65

```python
dfs = []
for i, evarr in enumerate(sol.y_events):
    df = pd.DataFrame(evarr, columns=['x','y','z','vx','vy','vz'])
    df['e'] = i
    df['t'] = sol.t_events[i]
    dfs.append(df)

evdf2 = pd.concat(dfs, axis=0)
```

```python
# solve ivp calculated additional events at initial time
# let's drop it and sort by time
evdf3 = evdf2[evdf2.t > 1e-19].sort_values(by='t').reset_index(drop=True)

# check event times
evdf1.t - evdf3.t
```

    0    -7.969181e-13
    1    -4.184764e-12
    2     5.084377e-12
    3     5.291101e-12
    4    -4.860801e-11
    5    -6.075274e-11
    6    -7.313705e-12
    7     1.243031e-09
    8    -4.274163e-10
    9    -1.897766e-10
    10   -4.538290e-10
    11   -1.785923e-10
    12    8.427596e-10
    13   -7.195746e-10
    14   -8.946062e-09
    15   -1.796951e-08
    16   -8.984693e-09
    17    3.321368e-07
    18   -1.491594e-07
    19   -6.215807e-08
    20   -1.675911e-07
    21   -5.982199e-08
    22    3.168344e-07
    23   -2.611800e-07
    24   -3.294949e-06
    25   -6.639791e-06
    26   -3.307739e-06
    27    1.223962e-04
    28   -5.498337e-05
    29   -2.290884e-05
    Name: t, dtype: float64

```python
evdf[evdf.e == 0].y.to_numpy() - evdf3[evdf3.e == 0].y.to_numpy()
```

    array([-8.60585474e-19,  2.32595247e-18,  9.75781955e-19, -1.55185442e-13,
            8.34835673e-18, -2.84128339e-13])

```python
ax = pltr.plot_proj(evdf, color='b', ls='', marker='+', ms=10)
pltr.plot_proj(evdf3, color='r', ls='', marker='.', ax=ax);
```


Simulación matemática 2018

Lázaro Alonso

Email:   `alonsosilva@iteso.mx, lazarus.alon@gmail.com`

< [Clasificación Binaria](Clase7_ClasificacionBinaria.ipynb) | [Guía](Clase0_GuiaSimulacionM.ipynb) | [Mapa logístico](Clase9_MapaLogistico.ipynb) >
___

#  Introducción a ecuaciones diferenciales

> Las primeras ecuaciones diferenciales se encuentran históricamente con la invención del cálculo por Newton y Leibniz. En el Capítulo 2 de su trabajo "Methodus fluxionum et Serierum Infinitarum", (Newton, 1671), Newton describe ecuaciones del tipo

$$\frac{dy(x)}{dx}=f(x,y(x)).$$

**Referencia**
- https://en.wikipedia.org/wiki/Differential_equation
- https://www.mathsisfun.com/calculus/differential-equations.html
- http://www.sympy.org
- http://matplotlib.org
- http://www.numpy.org
- http://ipywidgets.readthedocs.io/en/latest/index.html

### Nociones básicas

#### Definición
Una ecuación diferencial es una ecuació que involucra una función y una o más de sus derivadas.

Por ejemplo

una ecuación de la función $y(x)$ y su derivada $\frac{dy(x)}{dx}$.

### Solución

Decimos que hemos resuelto la ecuación diferencial si descubrimos la función $y(x)$ (o conjunto de funciones $y(x)$).

**Ejemplo** Estudiar la ecuación diferencial:

$$\frac{dx}{dt}=a x(t).$$

___
**Cuando una ecuación puede ser resuelta, hay varios trucos para intentar resolverla. En muchos casos, no es posible o es muy difícil encontrar la solución analítica. Por eso, en el curso examinaremos la forma de encontrar solución numérica.**

### ¿Porqué son útiles las ecuaciones diferenciales?

Antes qué nada, conceptualmente, **¿qué significa la derivada $\frac{dx}{dt}$?**

Nuestro mundo, y particularmente los fenómenos que estudiamos en ingeniería, es cambiante (evoluciona) en el tiempo. De modo que las descripciones (modelos) de como cambian las cosas en el tiempo terminan como una ecuación diferencial.

### Ejemplos

**1. Biología (crecimiento poblacional de conejos)**

Mientras más conejos tengamos, más bebés conejo obtendremos (los conejos tienen una grandiosa habilidad de reproducción). Luego, los bebés conejo crecen y tienen bebés a la vez. La población crece muy muy rápido.

Partes importantes:

- Población en el tiempo $t$: $N(t)$.
- Tasa de crecimiento: $r$.
- Tasa de cambio de la población: $\frac{dN}{dt}$.

Imaginemos algunos valores:

- La población actual (en el tiempo $t=0$) es $N(0)=1000$ conejos.
- La tasa de crecimiento es de $0.01$ conejos por semana por cada conejo actualmente.

Entonces la tasa de cambio de la población $\left.\frac{dN}{dt}\right|_{t=0}=0.01\times 1000$.

Sin embargo, esto sólo es cierto en el tiempo específico $t=0$, y esto no significa que la población crece de manera constante.

Recordemos que: mientras más conejos, más conejos nuevos se obtienen.

De manera que es mejor decir que la tasa de cambio (en cualquier instante de tiempo $t$) es la tasa de crecimiento $r$ veces la población $N(t)$ en ese instante:

$$\frac{dN}{dt}=rN,$$

y eso es una ecuación diferencial, porque es una ecuación de la función $N(t)$ y su derivada.

**El poder de las matemáticas... con esa simple expresión decimos que "la tasa de cambio de la población en el tiempo equivale a la tasa de crecimiento veces la población".**

Las ecuaciones diferenciales pueden describir como cambia la población, como se dispersa el calor, como un material radioactivo se desintegra y mucho más. Son una forma natural de describir cambios o movimiento en el universo.

### ¿Qué hacemos con la ecuación diferencial?

En principio, las ecuaciones diferenciales son magníficas para expresar (modelar) muchos fenómenos. Sin embargo, son difíciles de usar tal cual están.

De manera que intentamos **resolverlas** encontrando la(s) funciones que satisfagan la ecuación, es decir, quitando la derivada, de manera que podamos hacer cálculos, gráficas, predecir, y todo lo demás.

**2. Finanzas (interés continuamente compuesto)**

El valor del dinero cambia en el tiempo. Esto se expresa por medio de tasas de interés. Normalmente, el interés se puede calcular en tiempo fijados como años, meses, etcétera, y esto se añade al capital inicial y se reinvierte.

Esto se llama interés compuesto.

Pero cuando se se compone continuamente (en todo tiempo), entonces a cada instante, el interés se añade al proporcionalmente a la inversión (o préstamo).

Mientras más inversión (o préstamo) más interés gana.

Usando $t$ para el tiempo, $r$ para la tasa de interés y $V(t)$ para el valor en el instante $t$ de la inversión:

$$\frac{dV}{dt}=rV.$$

Notar que es la misma ecuación que tenemos para los conejos, solo con diferentes letras. Entonces, las matemáticas muestran que esos dos fenómenos se comportan de la misma manera.

Ya dijimos que como ecuación, es difícil usar esta información. Pero tranquilos, se puede resolver (por separación de variables) y la solución es:

$$V(t) = P e^{rt},$$

donde $P$ es el principal (capital inicial).

De forma que un préstamo continuamente compuesto de $1,000 por dos años y una tasa de interés del 10% se vuelve:

$$V = 1000 × e^{2\times0.1}$$
$$V = 1000 × 1.22140...$$
$$V = $1,221.40$$

**3. Mecánica Clásica**

Un resorte tiene una masa amarrada:

- la masa es jalada hacia abajo por la acción de gravedad,
- cuando el resorte se estira, su tensión se incrementa,
- la masa se detienne,
- la tensión del resorte la jala de nuevo hacia arriba,
- luego baja, luego sube, luego baja, etcétera...

# Modelo de ahorro

> **¿Tiene el dinero el mismo valor a lo largo del tiempo?** La respuesta es *no*. Todos lo hemos vivido.

> Dos situaciones básicas:
1. Inflación: ¿Cuánto dinero necesitabas para comprar unas papas y un refresco hace 10 años? ¿Cuánto necesitas hoy?
2. Interés: no es lo mismo tener \$10000 MXN disponibles hoy a recibir \$10000 MXN en un año, pues los primeros pueden ser invertidos en un negocio o una cuenta bancaria para generar *interés*. Por lo tanto los \$10000 MXN disponibles hoy valen más que los \$10000 MXN que se recibirán en un año.

Referencia:
- Vidaurri Aguirre, Héctor Manuel. *Ingeniería económica básica*, ISBN: 978-607-519-017-4. (Disponible en biblioteca)

___
## Interés
Nos centraremos en como cambia el valor del dinero en el tiempo debido al **interés**. Existen dos tipos:

### Capitalización por interés simple
Este tipo de interés se calcula única y exclusivamente sobre la cantidad original que se invirtió. Como consecuencia, el interés generado no forma parte del dinero que se invierte, es decir, los intereses no ganan intereses.

Suponga que se tiene un capital inicial $C_0$ y se invierte a un plazo de $k$ periodos (pueden ser meses, trimestres, semestres, años...) a una tasa de **interés simple** por periodo $i$. Al final del primer periodo, el capital $C_1$ que se obtiene es:

$$C_1=C_0+iC_0=C_0(1+i).$$

De la misma manera, como el interés solo se calcula sobre el capital inicial, al final del segundo periodo, el capital $C_2$ que se obtiene es:

$$C_2=C_1+iC_0=C_0+iC_0+iC_0=C_0(1+2i).$$

Así, al final del $k-$ésimo periodo, el capital $C_k$ que se obtiene es:

$$C_k=C_{k-1}+iC_0=C_0+kiC_0=C_0(1+ki).$$

> **Ejemplo.** Suponga que se tiene un capital de \$10000 MXN, el cual se pone en un fondo de inversión que paga una tasa de interés simple del 0.8% mensual.

> Si se tiene una meta de ahorro de \$11000 MXN sin inversiones adicionales, ¿cuántos meses se debería dejar invertido el dinero?

```python
import numpy as np
```

```python
def Nper(c0, ck, interes):
    nper = (ck/c0 - 1 )/interes
    if (nper - int(nper)) == 0:
        return nper
    else:
        return int(nper) + 1
```

```python
Nper(10000, 11000, .008 )
```

    13

> **Actividad.**
> - ¿Qué pasa si el interés no es del 0.8% mensual sino del 1% mensual?
> - ¿Qué pasa si la meta no son \$11000 MXN si no \$12000 MXN?

```python
# Solución

```

> **Una gráfica que nos permite ilustrar la situación anterior se puede realizar de la siguiente manera.**

```python
import matplotlib.pyplot as plt
# Para que se muestren las gráficas en la misma ventana
%matplotlib inline
# Librería para widgets de jupyter
from ipywidgets import *
```

```python
def interes_simple(c0, meta, i):
    # Despejamos k
    k = Nper(c0, meta, i)
    # Vector de periodos
    kk = np.arange(0, k + 1, 1)
    # Vector de capitales por periodo
    cc = c0 * (1 + kk * i)
    # Gráfico
    plt.figure(num=1); plt.clf()            # Figura 1, borrar lo que contenga
    plt.plot(kk, cc,'ro',linewidth=3.0)      # Se grafica la evolución de los capitales
    plt.plot(kk, meta*np.ones(k+1),'--k')    # Se grafica la meta
    plt.xlabel('k')                         # Etiqueta eje x
    plt.ylabel('C_k')                       # Etiqueta eje y
    plt.grid(False)                          # Malla en la gráfica
    plt.show()                              # Mostrar la figura
```

```python
interact_manual(interes_simple, c0 = fixed(10000), meta = (10000,12000,100), i = fixed(0.008));
```

Failed to display Jupyter Widget of type interactive.

  If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean
  that the widgets JavaScript is still loading. If this message persists, it
  likely means that the widgets JavaScript library is either not installed or
  not enabled. See the Jupyter
  Widgets Documentation for setup instructions.

  If you're reading this message in another frontend (for example, a static
  rendering on GitHub or NBViewer),
  it may mean that your frontend doesn't currently support widgets.

Como se esperaba, el capital en el $k-$ésimo periodo $C_k=C_0(1+ki)$ crece linealmente con $k$.

### Capitalización por interés compuesto
El capital que genera el interés simple permanece constante todo el tiempo de duración de la inversión. En cambio, el que produce el interés compuesto en un periodo se convierte en capital en el siguiente periodo. Esto es, el interés generado al final de un periodo se reinvierte para el siguiente periodo para también producir interés.

Suponga que se tiene un capital inicial $C_0$, y se va a ceder el uso de este capital por un periodo de tiempo determinado a una tasa de interés $i$. El capital que se obtiene al final del primer periodo $C_1$ se puede calcular por

$$C_1=C_0(1+i).$$

Si la anterior suma se vuelve a ceder a la misma tasa de interés, al final del periodo dos el capital $C_2$ es

$$C_2=C_1(1+i)=C_0(1+i)^2.$$

Si se repite el anterior proceso $k$ veces, el capital al final del $k-$ésimo periodo $C_k$ es

$$C_k=C_{k-1}(1+i)=C_0(1+i)^k.$$

**Referencia**:
- https://es.wikipedia.org/wiki/Inter%C3%A9s_compuesto.

> **Ejemplo.** Suponga que se tiene un capital de \$10000 MXN, el cual se pone en un fondo de inversión que paga una tasa de interés del 0.8% mensual.

> Si se tiene una meta de ahorro de \$11000 MXN sin inversiones adicionales, ¿cuántos meses se debería dejar invertido el dinero?

> Muestre una gráfica que ilustre la situación.

```python
def NperComp(c0, ck, interes):
    nper = np.log(ck/c0)/np.log(1 + interes)
    if (nper - int(nper)) == 0:
        return nper
    else:
        return int(nper) + 1
```

```python
def interes_compuesto(c0, meta, i):
    # Despejamos k
    k = NperComp(c0, meta, i)
    # Vector de periodos
    kk = np.arange(0, k + 1, 1)
    # Vector de capitales por periodo
    cc = c0 * (1 + i) ** kk
    # Gráfico
    plt.figure(num=1); plt.clf()            # Figura 1, borrar lo que contenga
    plt.plot(kk, cc,'ro',linewidth=3.0)      # Se grafica la evolución de los capitales
    plt.plot(kk, meta*np.ones(k + 1),'--k')    # Se grafica la meta
    plt.xlabel('k')                         # Etiqueta eje x
    plt.ylabel('C_k')                       # Etiqueta eje y
    plt.grid(False)                          # Malla en la gráfica
    plt.show()                              # Mostrar la figura

```

```python
interact_manual(interes_compuesto, c0=fixed(10000), meta=(10000,12000,100), i=fixed(0.008));
```

Failed to display Jupyter Widget of type interactive.

  If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean
  that the widgets JavaScript is still loading. If this message persists, it
  likely means that the widgets JavaScript library is either not installed or
  not enabled. See the Jupyter
  Widgets Documentation for setup instructions.

  If you're reading this message in another frontend (for example, a static
  rendering on GitHub or NBViewer),
  it may mean that your frontend doesn't currently support widgets.

El capital en el $k-$ésimo periodo $C_k=C_0(1+i)^k$ crece de manera exponencial con $k$.

> **Actividad.**
> - Modificar el código anterior para dejar fija la meta de ahorro y variar la tasa de interés compuesta.

```python
# Respuesta

```

### Capitalización continua de intereses
La capitalización continua se considera un tipo de capitalización compuesta, en la que a cada instante de tiempo $t$ se se capitalizan los intereses. Es decir, la frecuencia de capitalización es infinita (o, equivalentemente, el periodo de capitalización tiende a cero).

Suponga que se tiene un capital inicial $C_0$, y que el capital acumulado en el tiempo $t$ es $C(t)$. Queremos saber cuanto será el capital pasado un periodo de tiempo $\Delta t$, dado que la tasa de interés efectiva para este periodo de tiempo es $i$. De acuerdo a lo anterior tenemos

$$C(t+\Delta t)=C(t)(1+i)=C(t)(1+r\Delta t),$$

donde $r=\frac{i}{\Delta t}$ es la tasa de interés instantánea. Manipulando la anterior expresión, obtenemos

$$\frac{C(t+\Delta t)-C(t)}{\Delta t}=r\; C(t).$$

Haciendo $\Delta t\to 0$, obtenemos la siguiente ecuación diferencial

$$\frac{d C(t)}{dt}=r\; C(t),$$

sujeta a la condición inicial (monto o capital inicial) $C(0)=C_0$.

La anterior, es una ecuación diferencial lineal de primer orden, para la cual se puede calcular la *solución analítica*.

```python
# Librería de cálculo simbólico
import sympy as sym

# Para imprimir en formato TeX
from sympy import init_printing; init_printing(use_latex='mathjax')

# Símbolos t(para el tiempo) y r(para el interés instantáneo)
#t, r = sym.symbols('t r')
# Otra forma de hacer lo anterior
sym.var('t r')

C = sym.Function('C')
```

```python
eqn = sym.Eq(sym.Derivative(C(t),t) - r*C(t), 0) # Ecuación diferencial
eqn
```

$$- r C{\left (t \right )} + \frac{d}{d t} C{\left (t \right )} = 0$$

```python
sym.dsolve(eqn, C(t)) # Resolver
```

$$C{\left (t \right )} = C_{1} e^{r t}$$

con $C_1=C_0$.

___
¿Cómo podemos calcular la *solución numérica*?

> **Ejemplo.** Suponga que se tiene un capital de \$10000 MXN, el cual se pone en un fondo de inversión que paga una tasa de interés del 0.8% mensual.

> Si se tiene una meta de ahorro de \$11000 MXN sin inversiones adicionales, ¿cuánto tiempo se debe dejar invertido el dinero?

> Muestre una gráfica que ilustre la situación.

```python
# Librerías para integración numérica
from scipy.integrate import odeint

# Modelo de capitalización continua
def cap_continua(C, t, r):
    return r * C
```

```python
def interes_continuo(C_0, meta, r):
    # Despejamos t
    t = np.log(meta/C_0)/r
    # Vector de periodos
    tt = np.linspace(0,t,100)
    # Vector de capitales por periodo
    CC = odeint(cap_continua, C_0, tt, args = (r,))
    # Gráfico
    plt.figure(num=1); plt.clf()                # Figura 1, borrar lo que contenga
    plt.plot(tt, CC,'-',linewidth=3.0)          # Se grafica la evolución de los capitales
    plt.plot(tt,meta*np.ones(len(tt)),'--k')    # Se grafica la meta
    plt.xlabel('t')                             # Etiqueta eje x
    plt.ylabel('C(t)')                          # Etiqueta eje y
    plt.grid(False)                              # Malla en la gráfica
    plt.show()                                  # Mostrar la figura
    print("El tiempo que se debe dejar invertido el dinero para llegar a la meta de ", meta," es ", t, " meses.", sep="")

interact_manual(interes_continuo, C_0=fixed(10000), meta=(10000,20000,100), r=fixed(np.log(1+.008)));
```

Failed to display Jupyter Widget of type interactive.

  If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean
  that the widgets JavaScript is still loading. If this message persists, it
  likely means that the widgets JavaScript library is either not installed or
  not enabled. See the Jupyter
  Widgets Documentation for setup instructions.

  If you're reading this message in another frontend (for example, a static
  rendering on GitHub or NBViewer),
  it may mean that your frontend doesn't currently support widgets.

> **Actividad.**
> - Averiguar tasas de interés reales en algún banco y proyectar un ahorro mensual para que al terminar su carrera tengan \$50000 MXN en su cuenta.

```python
# Respuesta

```

___

< [Clasificación Binaria](Clase7_ClasificacionBinaria.ipynb) | [Guía](Clase0_GuiaSimulacionM.ipynb) | [Mapa logístico](Clase9_MapaLogistico.ipynb) >

Created with Jupyter by Lázaro Alonso.
  Copyright:  Public Domain como en [CC](https://creativecommons.org/licenses/by/2.0/) (Exepto donde se indique lo contrario)


## 3章

\begin{eqnarray}
y
 =
  \begin{cases}
    0 & ( b + w_1x_1 + w_2x_2 \leqq \theta ) \\
    1 & ( b + w_1x_1 + w_2x_2 \gt \theta )
  \end{cases}
\end{eqnarray}

上記式は以下に変換

\begin{equation}
y = h( b + w_1x_1 + w_2x_2)
\end{equation}

\begin{eqnarray}
h(x)
 =
  \begin{cases}
    0 & ( x \leqq \theta ) \\
    1 & ( x \gt \theta )
  \end{cases}
\end{eqnarray}

h は活性化関数と呼ばれる
この関数は閾値を境に出力が切り替わるためステップ関数や階段関数と呼ばれる

\begin{eqnarray}
h(x)
 =
  \begin{cases}
    0 & ( x \leqq \theta ) \\
    1 & ( x \gt \theta )
  \end{cases}
\end{eqnarray}

### シグモイド関数
\begin{eqnarray}
h(x)
 =
    \frac {1}{1 + exp(-x)}
  \end{eqnarray}

```python
import numpy as np
import matplotlib.pylab as plt
def step(x):
    y = x > 0
    return np.array(x > 0, dtype=np.int)
def sigmoid(x):
    return 1 / (1 + np.exp(-x))
def relu(x):
    return np.maximum(0, x)

x = np.arange(-5,5,0.1)

y1= step(x)
y2 = sigmoid(x)
y3 = relu(x)
plt.ylim(-0.1,5)
plt.plot(x, y1, label="step")
plt.plot(x, y2, linestyle="--", label="sigmoid")
plt.plot(x, y3, linestyle="-", label="relu")

plt.show()

```

### 行列の積

ようわからん。あとでまた学習

### MINST データセット

```python
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image

def img_show(img):
    pil_img = Image.fromarray(np.uint8(img))
    pil_img.show()

# (訓練画像、訓練ラベル)、（テスト画像、テストラベル）
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False, one_hot_label=False)

print(x_train.shape)
print(t_train.shape)
print(x_test.shape)
print(t_test.shape)

img = x_train[0]
label = t_train[0]
print(label)  # 5

print(img.shape)  # (784,)
img = img.reshape(28, 28)  # 形状を元の画像サイズに変形
print(img.shape)  # (28, 28)

img_show(img)
```

    (60000, 784)
    (60000,)
    (10000, 784)
    (10000,)
    5
    (784,)
    (28, 28)

```python
# 推論処理
# MINST データとサンプルの学習済みの重みパラメータを使用してニューラルネットワークによる推論処理を行う
# 結果として、学習済みのニューラネットワークの認識精度を表示
from ch03.neuralnet_mnist import get_data, init_network,predict
import numpy as np
x, t = get_data()
network = init_network()

batch_size = 100
accuracy_cnt = 0

print(len(x))

for i in range(0, len(x), batch_size):
    x_batch = x[i:i+batch_size]
    y_batch = predict(network, x_batch)
    p = np.argmax(y_batch, axis=1)
    accuracy_cnt += np.sum(p==t[i:i+batch_size])

line = "Accuracy:{0}".format(str(float(accuracy_cnt/ len(x))))
print(line)

```

    10000
    Accuracy:0.9352

```python

```


# 概率与统计

```python
import numpy as np
import scipy
import sympy as sym
import matplotlib
sym.init_printing()

print("NumPy version:", np.__version__)
print("SciPy version:", scipy.__version__)
print("SymPy version:", sym.__version__)
print("Matplotlib version:", matplotlib.__version__)
```

    NumPy version: 1.13.1
    SciPy version: 0.19.1
    SymPy version: 1.1.1
    Matplotlib version: 2.0.2

## 随机事件
在一定条件下，并不总是出现相同结果的现象称为**随机现象**。
特点：随机现象的结果至少有两个；
至于那一个出现，事先并不知道。
- 抛硬币
- 掷骰子
- 一天内进入某超市的顾客数
- 一顾客在超市排队等候付款的时间
- 一台电视机从开始使用道发生第一次故障的时间

认识一个随机现象首要的罗列出它的一切发生的基本结果。这里的基本结果称为**样本点**，
随机现象一切可能样本点的全体称为这个随机现象的**样本空间**，常记为Ω。
“抛一枚硬币”的样本空间 Ω＝{正面，反面} ；
“掷一颗骰子”的样本空间 Ω＝{1， 2， 3， 4， 5， 6}；
“一天内进入某超市的顾客数”的样本空间 Ω＝{n： n ≥０}
“一顾客在超市排队等候付款的时间” 的样本空间 Ω＝{t： t≥０}
“一台电视机从开始使用道发生第一次故障的时间”的样本空间
Ω＝{t： t≥０}

**定义**：随机现象的某些样本点组成的集合称为**随机事件**，简称事件，常用大写字母A、 B、 C等
表示。
[例子]掷一个骰子时，“出现奇数点”是一个事件，它由1点、 3点和5点共三个样本点组成，若
记这个事件为A，则有A＝ { 1， 3， 5}。

**定义**：一个随机事件A发生可能性的大小称为这个事件的概率，用P(A)表示。
概率是一个介于0到1之间的数。概率越大，事件发生的可能性就越大；概率越小，事件发生
的可能性也就越小。特别，不可能事件的概率为0，必然事件的概率为1。 P(φ)＝ 0, P(Ω)＝1。

## 概率的统计定义
- 与事件A有关的随机现象是可以大量重复事件的；

- 若在n次重复试验中，事件A发生kn次，则事件A发生的频率为：

    - fn(A)= kn/n＝事件A发生的次数/重复试验的次数

    - 频率fn(A)能反应事件A发生的可能性大小。

- 频率fn(A)将会随着试验次数不断增加而趋于稳定，这个频率的稳定值就是事件A的概率。
在实际中人们无法把一个试验无限次重复下去，只能用重复试验次数n较大时的频率去
近似概率。

| 试验者 | 抛的次数n| 出现正面次数k| 正面出现频率k/n|
|---：|----：|----：|----：|
| 德·摩根 |2048 |1061 |0.5180|
| 蒲丰    | 4040| 2048| 0.5069|
| 皮尔逊   |12000| 6019| 0.5016|
| 皮儿孙   |24000| 12012| 0.5005|
| 微尼     |  30000| 14994| 0.4998|

## 随机变量
定义：表示随机现象的结果的变量称为**随机变量**。
常用大写字母X， Y， Z等表示随机变量，他们的取值用相应的小写字母x， y， z表示。
假如一个随机变量仅取数轴上有限个点或可列个点，则称此随机变量为**离散型随机变量**。
假如一个随机变量的所有可能取值充满数轴上的一个区间（a， b），则称此变量为**连续型随机变量**。

### 例子
- 设X是一只铸件上的瑕疵数，则X是一个离散随机变量，它可以取0， 1， 2， ….等值。可用随机变量X的取值来表示事件，如“X＝0”表示事件“铸件上无瑕疵”。

- 一台电视机的寿命X是在0到正无穷大区间内取值的连续随机变量，“X＝0”表示事件“一台电视机在开箱时就发生故障”，“X＞40000”表示“电视机寿命超过40000小时”。

## 随机变量的分布

随机变量的取值是随机的，但内在还是有规律的，这个规律可以用分布来描述。分布包括
如下两方面内容：
- X可能取哪些值，或在哪个区间内取值。
- X取这些值的概率各是多少，或X在任一区间上取值的概率是多少？

### 例子

掷两颗骰子， 6点出现的次数

|X |0 |1 |2 |
|P |25/36 |10/36 |1/36|

连续性随机变量X的分布可用概率密度函数p(x)表示，也可记作f(x)。
下面以产品质量特性x（如机械加工轴的直径）为例来说明p（x）的由来。
假定我们一个接一个地测量产品的某个质量特性值X，把测量得到的x值一个接一个地放在
数轴上。当累计到很多x值时，就形成一定的图形，为了使这个图形稳定，把纵轴改为单位长
度上的频率，由于频率的稳定性，随着被测量质量特性值x的数量愈多，这个图形就愈稳定，
其外形显现出一条光滑曲线。这条曲线就是概率密度曲线，相应的函数表达式f（x）称为概率
密度函数，它就是一种表示质量特性X随机取值内在统计规律性的函数

## 均值、方差与标准差

随机变量的分布有几个重要的特征数，用来表示分布的集中位置（中心位置）分散度大小。

### 均值
用来表示分布的中心位置，用$\mu(X)$表示。对于绝大多数的随机变量，在均值附近出现的
机会较多。计算公式为：

$$
E(X)=\sum_i x_i \cdot p_i \\
E(X)=\int_a^b x \cdot p(x) dx
$$

### 方差与标准差
方差用来表示分布的散步大小，用$\sigma^2$表示,方差小意味着分布较集中。方差的计算公式为：

$$
\sigma^2(X)=\sum_i (x_i - E(X))^2 p_i \\
\sigma^2(X)=\int_a^b (x - E(X))^2 \cdot p(x) dx
$$

## 正态分布

$$
p(x)=\frac{1}{\sqrt{2\pi}\sigma} e^{-\frac{(x-\mu)^2}{2\sigma^2}}
$$

```python
import sympy.stats as stats
coin = stats.Die('coin',2)
P=stats.P
E=stats.E
```

```python
stats.density(coin).dict
```

```python
import numpy as np
import matplotlib.pyplot as plt
from pyecharts import Bar
def mydice(number):
    rd = np.random.random(number)*2+1
    return rd.astype(int)

#np.random.seed(19680801)
x=mydice(10000)
tag=[]
sta=[]
for itr in range(len(set(x))):
    tag.append("%d"%(itr+1))
    sta.append(len(x[x==(itr+1)]))
bar = Bar("统计图", "硬币")
bar.add("点数", tag, sta)
#bar.show_config()
bar.render("echarts/coindicechart.html")
bar
```

```python
E(coin)
```

```python
dice1, dice2 = stats.Die('dice1',6),stats.Die('dice2',6)
```

```python
import numpy as np
import matplotlib.pyplot as plt
from pyecharts import Bar
def mydice(number):
    rd = np.random.random(number)*6+1
    return rd.astype(int)

#np.random.seed(19680801)
x=mydice(1000)
tag=[]
sta=[]
for itr in range(len(set(x))):
    tag.append("%d"%(itr+1))
    sta.append(len(x[x==(itr+1)]))
bar = Bar("统计图", "骰子")
bar.add("点数", tag, sta)
#bar.show_config()
bar.render("echarts/stadicechart.html")
bar
```

```python
stats.density(dice1).dict
```

```python
import numpy as np
import matplotlib.pyplot as plt
from pyecharts import Bar
def mydice(number):
    rd = np.random.random(number)*6+1
    return rd.astype(int)

#np.random.seed(19680801)
x1=mydice(1000)
x2=mydice(1000)
x=x1+x2
tag=[]
sta=[]
lx=len(x)
for itr in range(len(set(x))):
    tag.append("%d"%(itr+1))
    sta.append(len(x[x==(itr+1)])/lx)
bar = Bar("统计图", "骰子")
bar.add("点数", tag, sta)
#bar.show_config()
bar.render("echarts/stadice2chart.html")
bar
```

```python
stats.density(dice1+dice2).dict
```

```python
stats.density(dice1*dice2).dict
```

```python
import numpy as np
import matplotlib.pyplot as plt
from pyecharts import Bar
def mydice(number):
    rd = np.random.random(number)*6+1
    return rd.astype(int)

#np.random.seed(19680801)
x1=mydice(1000)
x2=mydice(1000)
x=x1*x2
tag=[]
sta=[]
lx=len(x)
for itr in range(len(set(x))):
    tag.append("%d"%(itr+1))
    sta.append(len(x[x==(itr+1)])/lx)
bar = Bar("统计图", "骰子")
bar.add("点数", tag, sta)
#bar.show_config()
bar.render("echarts/stadice2chart.html")
bar
```

```python
E(dice1+dice2)
```

```python
E(dice1*dice2)
```

```python
stats.density(dice1+dice2>6)
```

```python
x, y = sym.symbols('x y')
normal = stats.Normal('N', 2, 3)
sym.plot(stats.P(normal<x),(x,-10,10))
sym.plot(stats.P(normal<x).diff(x),(x,-10,10))
```

```python
rand1=np.random.normal(loc=1, scale=1, size=[1000])
rand2=np.random.normal(loc=1, scale=1, size=[1000])
```

```python
np.mean(rand1)
```

```python
np.std(rand1)
```

```python

```

```python

```

```python
np.corrcoef(rand1, rand2)
```

    array([[ 1.       , -0.0180464],
           [-0.0180464,  1.       ]])

```python
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
mpl.style.use('seaborn-darkgrid')
```

```python
fig = plt.figure()
ax=fig.add_subplot(2,2,1)
ax.scatter(rand1, rand1)
ax=fig.add_subplot(2,2,2)
ax.scatter(rand1, rand2)
ax=fig.add_subplot(2,2,3)
ax.scatter(rand2, rand1)
ax=fig.add_subplot(2,2,4)
ax.scatter(rand2, rand2)
```

# 协方差

$$
E((X-E(X))(Y-E(Y)))
$$

```python
import numpy as np
```

```python
rand1=np.random.normal(loc=1, scale=3, size=[1000])
rand2=np.random.normal(loc=1, scale=3, size=[1000])
```

```python
def Conv(dt1, dt2):
    return np.mean((dt1-np.mean(dt1))*(dt2-np.mean(dt2)))
```

```python
Conv(rand1, rand2)
```

    -0.14984037611140225

```python
Conv(rand1, rand2)/np.std(rand1)/np.std(rand2)
```

    -0.016631410183790416

```python
np.corrcoef(rand1, rand2)
```

    array([[ 1.        , -0.01663141],
           [-0.01663141,  1.        ]])

## 信息熵

第一，假设存在一个随机变量，可以问一下自己当我们观测到该随机变量的一个样本时，我们可以接受到多少信息量呢？毫无疑问，当我们被告知一个极不可能发生的事情发生了，那我们就接收到了更多的信息；而当我们观测到一个非常常见的事情发生了，那么我们就接收到了相对较少的信息量。因此信息的量度应该依赖于概率分布$p(x)$，所以说熵$h(x)$的定义应该是概率的单调函数。

第二，假设两个随机变量和是相互独立的，那么分别观测两个变量得到的信息量应该和同时观测两个变量的信息量是相同的，即：$h(x+y)=h(x)+h(y)$。而从概率上来讲，两个独立随机变量就意味着$p(x, y)=p(x)p(y)$，所以此处可以得出结论熵的定义应该是概率的函数。因此一个随机变量的熵可以使用如下定义：
$$
h(x)=-log(p(x))
$$

此处的负号仅仅是用来保证熵（即信息量）是正数或者为零。而函数基的选择是任意的（信息论中基常常选择为2，因此信息的单位为比特bits；而机器学习中基常常选择为自然常数，因此单位常常被称为nats）。

最后，我们用熵来评价整个随机变量平均的信息量，而平均最好的量度就是随机变量的期望，即熵的定义如下：
$$
H(x)=-\sum p(x)log(x)
$$

### 高斯分布是最大熵分布

建立泛函
$$
F(p(x))=\int_{-\infty}^{\infty} [-p(x)log(p(x))+\lambda_0 p(x)+\lambda_1 g(x) p(x)]dx
$$

$$
p(x)=e^{(-1+\lambda_0+\lambda_1 g(x))}
$$

$$
g(x)=(x-\mu)^2
$$

```python
x,n1,n2,mu,sigma = sym.symbols("x \lambda_0 \lambda_1 \mu \sigma")
p=sym.exp(-1+n1+n2*(x-mu)**2)
g=(x-mu)**2
```

```python
a = sym.Integral(p*g, (x, -sym.oo,sym.oo))
sym.Eq(a, sigma**2)
```

```python
b = sym.Integral(p, (x, -sym.oo,sym.oo))
sym.Eq(a, mu)
```

```python
sym.simplify(a.doit())
```

$$\begin{cases} \frac{i \sqrt{\pi}}{2 \lambda_1^{\frac{3}{2}}} e^{\lambda_0 - 1} & \text{for}\: \left(\left|{\operatorname{periodic_{argument}}{\left (e^{i \pi} \operatorname{polar\_lift}{\left (\lambda_1 \right )},\infty \right )}}\right| \leq \frac{\pi}{2} \wedge \left|{\operatorname{periodic_{argument}}{\left (\operatorname{polar\_lift}^{2}{\left (\lambda_1 \right )} \operatorname{polar\_lift}^{2}{\left (\mu \right )},\infty \right )}}\right| < \pi \wedge \left|{\operatorname{periodic_{argument}}{\left (e^{2 i \pi} \operatorname{polar\_lift}^{2}{\left (\lambda_1 \right )} \operatorname{polar\_lift}^{2}{\left (\mu \right )},\infty \right )}}\right| < \pi\right) \vee \left|{\operatorname{periodic_{argument}}{\left (e^{i \pi} \operatorname{polar\_lift}{\left (\lambda_1 \right )},\infty \right )}}\right| < \frac{\pi}{2} \\\int_{-\infty}^{\infty} \left(\mu - x\right)^{2} e^{\lambda_0 + \lambda_1 \left(\mu - x\right)^{2} - 1}\, dx & \text{otherwise} \end{cases}$$

```python
sym.simplify(b.doit())
```

$$\begin{cases} - \frac{i \sqrt{\pi}}{\sqrt{\lambda_1}} e^{\lambda_0 - 1} & \text{for}\: \left(\left|{\operatorname{periodic_{argument}}{\left (e^{i \pi} \operatorname{polar\_lift}{\left (\lambda_1 \right )},\infty \right )}}\right| \leq \frac{\pi}{2} \wedge \left|{\operatorname{periodic_{argument}}{\left (\operatorname{polar\_lift}^{2}{\left (\lambda_1 \right )} \operatorname{polar\_lift}^{2}{\left (\mu \right )},\infty \right )}}\right| < \pi \wedge \left|{\operatorname{periodic_{argument}}{\left (e^{2 i \pi} \operatorname{polar\_lift}^{2}{\left (\lambda_1 \right )} \operatorname{polar\_lift}^{2}{\left (\mu \right )},\infty \right )}}\right| < \pi\right) \vee \left|{\operatorname{periodic_{argument}}{\left (e^{i \pi} \operatorname{polar\_lift}{\left (\lambda_1 \right )},\infty \right )}}\right| < \frac{\pi}{2} \\\int_{-\infty}^{\infty} e^{\lambda_0 + \lambda_1 \left(\mu - x\right)^{2} - 1}\, dx & \text{otherwise} \end{cases}$$

```python
eq1=-(sym.pi/(4*n2**3))*sym.exp(2*n1-2)-sigma**4
eq2=-(sym.pi/(n2))*sym.exp(2*n1-2)-1
sym.solve((eq1, eq2),n1, n2)
```

```python
## 积分收敛
$$
\lambda_1<0
$$
```

math_slides/Chapter 2 - Probability and statistics.ipynb