# Tutorial: Small Angle X-ray Scattering

Small Angle X-ray Scattering (SAXS) is a reciprocal space technique that can be used to investigate magnetic structures on mesoscopic length scales. It uses the effect of magnetic circular dichroism, in which left and right circularly polarised light are absorbed differently in the presence of a magnetic field parallel to the propagation direction of the light. Due to this effect, only magnetisation parallel the beam direction is able to scatter X-rays. The wavelengths of the X-rays can be tuned to the absorption edges of the different elements present in the sample allowing for element-specific magnetisation to be measured. However, in calculating SAXS from micromagnetic simulations we can use the approximation that the cross sections are non element specific.

The differential scattering cross section can be given by
\begin{equation}
\frac{d\Sigma}{d\Omega} \propto \left\vert \widetilde{M_z} \right\vert^2
\end{equation}
where $\widetilde{M_z}$ is the Fourier transform of the $z$ component of the magnetisation Ref. \[[van der Laan](https://onlinelibrary.wiley.com/iucr/doi/10.1107/S0909049598005019)\].

## SAXS Reference frame

For `mag2exp` the SAXS experimental reference frame is defined with the beam propagating in the $z$ direction.

## The micromagnetic simulation

A micromagnetic simulation can be set up using Ubermag to obtain a 3-dimensional magnetic structure with periodic boundary conditions in the $xy$ plane.

```python
%matplotlib inline
import oommfc as oc
import discretisedfield as df
import micromagneticmodel as mm
import numpy as np
import ubermagutil.units as uu
np.random.seed(1)

region = df.Region(p1=(-300e-9, -150e-9, 0), p2=(300e-9, 300e-9, 5e-9))
mesh = df.Mesh(region=region, cell=(5e-9, 5e-9, 5e-9), bc='xy')

system = mm.System(name='Box2')

system.energy = (mm.Exchange(A=1.6e-11)
                 + mm.DMI(D=4e-3, crystalclass='T')
                 + mm.Zeeman(H=(0, 0, 2e5)))

Ms = 1.1e6

def m_fun(pos):
    return 2 * np.random.rand(3) - 1

# create system with above geometry and initial magnetisation
system.m = df.Field(mesh, dim=3, value=m_fun, norm=Ms)
system.m.plane('z').mpl()
```

```python
# NBVAL_IGNORE_OUTPUT
# minimize the energy
md = oc.MinDriver()
md.drive(system)

# Plot relaxed configuration: vectors in z-plane
system.m.plane('z').mpl()
```

Now we have a magnetisation texture we can compute the SAXS scattering cross sections.

## Computing SAXS Cross-sections

We can use the `mag2exp` package to calculate the cross section
\begin{equation}
\frac{d\Sigma}{d\Omega} \propto \left\vert \widetilde{M_z} \right\vert^2 .
\end{equation}

```python
import mag2exp
```

```python
cross_section = mag2exp.x_ray.saxs(system.m)
```

As the cross section is a `discretisedfield` object the built-in plotting functions can be used to view them.

NOTE: The values of the axis in Fourier space are frequency NOT angular frequency so DO NOT include a factor of $2\pi$. i.e. $|{\bf k}| = \frac{1}{\lambda} \neq \frac{2 \pi}{\lambda}$, where $\bf k$ is the wave vector and $\lambda$ is the wavelength.

```python
cross_section.mpl.scalar(cmap='gray', interpolation='spline16',
                         colorbar_label=r'Intensity')
```

Due to the high intensity of some areas a linear colour map make some features more difficult to see. We can use `matplotlib.color` to change the colour bar to a logarithmic scale. This
reveals the higher order low intensity diffraction peaks.

```python
import matplotlib.colors as colors
cross_section.mpl.scalar(cmap='gray',
                         colorbar_label=r'Intensity', norm=colors.LogNorm(vmin=1e40,vmax=cross_section.real.array.max()))
```

It is also possible to just plot a selected region of the cross section by

```python
saxs_region = df.Region(p1=(-40e6, -40e6, 0), p2=(40e6, 40e6, 0.5))
cross_section[saxs_region].mpl.scalar(cmap='gray', interpolation='spline16',
                                      colorbar_label=r'Intensity')
```


```python
import unyt
import sympy
```

```python
# Using unyt
from unyt import cm, m, ft, yard
print(3.*cm + 4.*m - 5.*ft + 6.*yard)
```

    799.24 cm

```python
from unyt import km, mile, hr
velocities = [20., 25., 35., 70]*mile/hr
print(velocities.to('km/hr'))
```

    [ 32.18688  40.2336   56.32704 112.65408] km/hr

```python
from unyt import kg, m
3*kg + 4*m        # gives a unit error
```

```python
from unyt import g, cm
import numpy as np
print(np.log10(1e-23*g/cm**3))   # This is allowed
```

    -23.0

```python
# This works
from unyt import degree, radian
import numpy as np
print( np.sin(np.pi/4*radian) )
print( np.sin(45.*degree) )
```

    0.7071067811865475
    0.7071067811865475

```python
# Nice text for figures
import matplotlib.pyplot as plt
from unyt import g, cm
print( (g/cm**3).units.latex_representation() )
plt.text( 1,1 , '.' )
plt.text( 0.5,0.5 , r'$%s$'%(g/cm**3).units.latex_repr, fontsize=24 );
```

```python
from unyt import m, cm
>>> m**2/cm.simplify()
m**2/cm
```

    m**2/cm

```python

```


```python
%%capture

# Comment this out if you don't want to install pennylane from this notebook
!pip install pennylane
```

```python
import pennylane as qml
from pennylane import numpy as np
```

# Computing quantum gradients on any device

In the last two notebooks we saw that PennyLane allows you to seamlessly "slot" quantum computations into automatic differentiation frameworks. While we used the Numpy/Autograd framework, you can also interface PennyLane with PyTorch, Tensorflow and Jax.

If quantum computations are *simulated* by coding up matrix-vector multiplications in a specific coding language,  they just become differentiable computations. This is the trick that the `default.qubit` device uses if possible: it knows how to simulate quantum circuits in Autograd, TensorFlow etc. But what if we use a device that is not implemented in an automatic differentiation fashion? And what if the device uses quantum hardware?

In PennyLane, any quantum device, whether a hardware device or a simulator, can be trained using the [parameter-shift rule](https://pennylane.ai/qml/glossary/parameter_shift.html) to compute quantum gradients. Indeed, the parameter-shift rule is ideally suited to hardware devices, as it does
not require any knowledge about the internal workings of the device; it is sufficient to treat the device as a 'black box', and to query it with different input values in order to determine the gradient.

## 1. The parameter-shift rule

The parameter-shift rule states that, given a variational quantum circuit $U(\boldsymbol
\theta)$ composed of parametrized Pauli rotations, and some measured observable $\mathcal{M}$, the
derivative of the expectation value

\begin{align}\langle \mathcal{M} \rangle (\boldsymbol\theta) =
    \langle 0 \vert U(\boldsymbol\theta)^\dagger \mathcal{M} U(\boldsymbol\theta) \vert 0\rangle\end{align}

with respect to the input circuit parameters $\boldsymbol{\theta}$ is given by

\begin{align}\nabla_{\theta_i}\langle \mathcal{M} \rangle(\boldsymbol\theta)
      =  \frac{1}{2}
            \left[
                \langle \mathcal{M} \rangle\left(\boldsymbol\theta + \frac{\pi}{2}\hat{\mathbf{e}}_i\right)
              - \langle \mathcal{M} \rangle\left(\boldsymbol\theta - \frac{\pi}{2}\hat{\mathbf{e}}_i\right)
            \right].\end{align}

Thus, the gradient of the expectation value can be calculated by evaluating the same variational quantum circuit, but with shifted parameter values (hence the name, parameter-shift rule!).

## 2. Hand-coded parameter-shift rule

Let's first have a go implementing the parameter-shift rule manually. We use the quantum model from the previous notebook.

```python
dev = qml.device("default.qubit", wires=2)
```

```python
@qml.qnode(dev, diff_method='parameter-shift')
def quantum_model(x, w):
    qml.templates.AngleEmbedding(x, wires=[0, 1])
    qml.templates.BasicEntanglerLayers(w, wires=[0, 1])
    return qml.expval(qml.PauliZ(wires=0))

x = np.array([0.1, 0.2], requires_grad=False)
w = np.array([[-2.1, 1.2], [-1.4, -3.9], [0.5, 0.2]])

quantum_model(x, w)
```

    tensor(0.012023, requires_grad=True)

```python
print(quantum_model.draw())
```

     0: ──RX(0.1)──RX(-2.1)──╭C──RX(-1.4)──╭C──RX(0.5)──╭C──┤ ⟨Z⟩
     1: ──RX(0.2)──RX(1.2)───╰X──RX(-3.9)──╰X──RX(0.2)──╰X──┤

Now that we have defined our variational circuit QNode, we can construct
a function that computes the gradient of the $i\text{th}$ parameter
using the parameter-shift rule. Since the model also depends on input $x$, the gradient will be computed for this fixed $x$ only.

```python
def parameter_shift_term(qnode, params, x, i, j):

    shifted = params.copy()

    shifted[i, j] += np.pi/2
    forward = qnode(x, shifted)  # forward evaluation

    shifted[i, j] -= np.pi
    backward = qnode(x, shifted) # backward evaluation

    return 0.5 * (forward - backward)

# gradient with respect to the first parameter
print(parameter_shift_term(quantum_model, w, x, 0, 0))
```

    0.16954137293040533

In order to compute the gradient with respect to *all* parameters, we need
to loop over the indices ``i`` and ``j``:

```python
def parameter_shift(qnode, params, x):
    gradients = np.zeros_like((params))

    for i in range(len(gradients)):
        for j in range(len(gradients[0])):
            gradients[i, j] = parameter_shift_term(qnode, w, x, i, j)

    return gradients

print(parameter_shift(quantum_model, w, x))
```

    [[1.69541373e-01 0.00000000e+00]
     [6.97079563e-02 1.11022302e-16]
     [1.69541373e-01 0.00000000e+00]]

## 3. Built-in parameter shift differentiation

We can compare this to PennyLane's *built-in* parameter-shift feature by using
the `qml.grad` function. Remember, when we defined the
QNode, we specified that we wanted it to be differentiable using the parameter-shift
method (``diff_method="parameter-shift"``).

```python
grad_function = qml.grad(quantum_model, argnum=1)

print(grad_function(x, w))
```

    [[1.69541373e-01 0.00000000e+00]
     [6.97079563e-02 1.11022302e-16]
     [1.69541373e-01 0.00000000e+00]]

If you count the number of quantum evaluations, you will notice that we had to evaluate the circuit roughly
``2*len(params)`` number of times in order to compute the quantum gradient with respect to all
parameters.

```python
before = dev.num_executions
grad_function = qml.grad(quantum_model, argnum=1)
grad_function(x, w)
after = dev.num_executions
print("Number of evaluations per gradient:", after-before)
```

    Number of evaluations per gradient: 13

While reasonably fast for a small number of parameters, as the number of parameters in
our quantum circuit grows, so does both

1. the circuit depth (and thus the time taken to evaluate each expectation value or 'forward' pass), and

2. the number of parameter-shift evaluations required.

Both of these factors increase the time taken to compute the gradient with
respect to all parameters.

It is therefore crucial to develop efficient pipelines for the evaluation of gradients in quantum machine learning. If you want to learn more about this, check out the [Amazon-Braket demo](https://pennylane.ai/qml/demos/braket-parallel-gradients.html), which explains how PennyLane and AWS have teamed up to paralellise the evaluation of quantum gradients.

#  TASKS

1. Create a new `default.qubit` device where you can set the `diff_method` explicitely to `diff_method = 'backprop'`. This will use automatic differentiation tricks to compute gradients much more efficiently for simulations (as implicitely used in the previous notebooks). Find out how often the device is evaluated when you compute a gradient using the `qml.grad` function.

2. In each step of gradient descent we need to compute the gradient of a quantum computation. Write a function `num_evals(n_params, n_steps)` that takes the number of parameters as well as the number of steps and returns the number of circuit evaluations needed for gradient descent training with a parameter shift rule. Let's say you need as many training steps as you have parameters or `n_steps=n_params`. Plot the number of circuit evaluations over the number of parameters. What scaling behaviour do you see? Compare this to a scaling in which each step only takes a single evaluation.

```python

```


# Modelling techniques in portfolio optimization using second-order cone programming (SOCP) in the NAG Library

# Correct Rendering of this notebook

This notebook makes use of the `latex_envs` Jupyter extension for equations and references.  If the LaTeX is not rendering properly in your local installation of Jupyter , it may be because you have not installed this extension.  Details at https://jupyter-contrib-nbextensions.readthedocs.io/en/latest/nbextensions/latex_envs/README.html

The notebook is also not rendered well by GitHub so if you are reading it from there, you may prefer the [pdf version instead](./static/portfolio_optimization_using_socp.pdf).

# Installing the NAG library and running this notebook

This notebook depends on the NAG library for Python to run. Please read the instructions in the [Readme.md](https://github.com/numericalalgorithmsgroup/NAGPythonExamples/blob/master/local_optimization/Readme.md#install) file to download, install and obtain a licence for the library.

Instruction on how to run the notebook can be found [here](https://github.com/numericalalgorithmsgroup/NAGPythonExamples/blob/master/local_optimization/Readme.md#jupyter).

# Note for the users of the NAG Library Mark $27.1$ onwards

At Mark $27.1$ of the NAG Library, NAG introduced two new additions to help users easily define a Quadratically Constrained Quadratic Programming (QCQP) problem. All the models in this notebook then can be solved in a much simpler way without the need of a reformulation or any extra effort. It's recommended that the users of the NAG Library Mark $27.1$ or newer should look at the [notebook on QCQP instead](./portfolio_optimization_qcqp.ipynb).

# Introduction

Second-order cone programming (SOCP) is convex optimization which extends linear programming (LP) with second-order (Lorentz or the ice cream) cones. Search region of the solution is the intersection of an affine linear manifold with the Cartesian product of second-order cones. SOCP appears in a broad range of applications from engineering, control theory and quantitative finance to quadratic programming and robust optimization. It has become an important tool for financial optimization due to its powerful nature. Interior point methods (IPM) are the most popular approaches to solve SOCP problems due to their theoretical polynomial complexity and practical performance.

NAG introduces at Mark $27$ an interior point method for large-scale SOCP problem in the standard form:
\begin{equation}\label{SOCP}
\begin{array}{ll}
\underset{x\in\Re^n}{\mbox{minimize}} & c^Tx\\[0.6ex]
\mbox{subject to} & l_A\leq Ax\leq u_A,\\[0.6ex]
     & l_x\leq x\leq u_x,\\[0.6ex]
     & x\in{\cal K},
\end{array}
\end{equation}
where $A\in\Re^{m\times n}$, $l_A, u_A\in\Re^m$, $c, l_x, u_x\in\Re^n$ are the problem data, and $\cal K={\cal K}^{n_1}\times\cdots\times{\cal K}^{n_r}\times\Re^{n_l}$ where ${\cal K}^{n_i}$ is either a quadratic cone or a rotated quadratic cone defined as follows:

\begin{itemize}
\item Quadratic cone:
\begin{equation}\label{SOC}
{\cal K}_q^{n_i}:=\left\lbrace x=\left(x_1,\ldots,x_{n_i}\right)\in\Re^{n_i}~:~x_1^2\geq\sum_{j=2}^{n_i} x_j^2, ~x_1\geq0\right\rbrace.
\end{equation}
\item Rotated quadratic cone:
\begin{equation}\label{RSOC}
{\cal K}_r^{n_i}=\left\lbrace x=\left(x_1,x_2,\ldots,x_{n_i}\right)\in\Re^{n_i}~:~2x_1x_2\geq\sum_{j=3}^{n_i} x_j^2,\quad x_1\geq0,\quad x_2\geq0\right\rbrace.
\end{equation}
\end{itemize}
SOCP is widely used in portfolio optimization due to its flexibility and versatility to handle a large variety of problems with different kinds of constraints, which can be transformed into SOCPs that are equivalent to (\ref{SOCP}), see \cite{AG03, LVBL98} for more details. In the rest of this article we demonstrate how to use SOCP solver in the NAG Library for Python to build and solve models with various practical constraints for portfolio optimization.

# Data preparation

We consider daily prices for the 30 stocks in the DJIA from March 2018 to March 2019. In practice, the estimation of the mean return $r$ and covariance $V$ is often a non-trivial task. In this notebook, we estimate those entities using simple sample estimates.

```python
# Import necessary libraries
import pickle as pkl
import numpy as np
import matplotlib.pyplot as plt
```

```python
# Load stock price data from stock_price.plk
# Stock_price: dict = ['close_price': [data], 'date_index': [data]]
stock_price = stock_price = pkl.load(open('./data/stock_price.pkl', 'rb'))
close_price = stock_price['close_price']
date_index = stock_price['date_index']
```

```python
# Size of data, m: number of observations, n: number of stocks
m = len(date_index)
n = len(close_price)
```

```python
# Extract stock closing prices to a numpy array
data = np.zeros(shape=(m, n))
i = 0
for stock in close_price:
    data[:,i] = close_price[stock]
    plt.plot(np.arange(m), data[:,i])
    i += 1
# Plot closing prices
plt.xlabel('Time (days)')
plt.ylabel('Closing price ($)')
plt.show()
```

For each stock $i$, we first estimate the $j$th daily relative return as $$relative~return_{i,j} = \frac{closing~price_{i,j+1}-closing~price_{i,j}}{closing~price_{i,j}}.$$

```python
# Relative return
rel_rtn = np.zeros(shape=(m-1, n))
for j in range(m-1):
    rel_rtn[j,:] = np.divide(data[j+1,:] - data[j,:], data[j,:])
# Plot relative return
for i in range(n):
    plt.plot(np.arange(m-1),rel_rtn[:,i])
plt.xlabel('Time (days)')
plt.ylabel('Relative return')
plt.show()
```

Simply take arithmetic mean of each column of relative return to get mean return $r$ for each stock, followed by estimating covariance $V$ using numpy.

```python
# Mean return
r = np.zeros(n)
r = rel_rtn.sum(axis=0)
r = r / (m-1)
# Covariance matrix
V = np.cov(rel_rtn.T)
```

# Classic Mean-Variance Model
## Efficient Frontier

One of the major goals of portfolio management is to achieve a certain level of return under a specific risk measurement. Here we demonstrate how to use NAG Library to build efficient frontier by solving classical Markowitz model with long-only constraint (meaning, buy to hold and and short selling is not allowed):
\begin{equation}\label{MV_model}
\begin{array}{ll}
\underset{x\in\Re^n}{\mbox{minimize}} & -r^Tx+\mu x^TVx\\[0.6ex]
\mbox{subject to} & e^Tx = 1,\\[0.6ex]
     & x\geq0,
\end{array}
\end{equation}
where $e\in\Re^n$ is vector of all ones and $\mu$ is a scalar controling trade-off between return and risk. Note one could build the efficient frontier by varying $\mu$ from $0$ to a certain value.

```python
# Import the NAG Library
from naginterfaces.base import utils
from naginterfaces.library import opt
from naginterfaces.library import lapackeig
# Import necessary math libraries
import math as mt
import warnings as wn
```

To solve Problem (\ref{MV_model}) via SOCP, we need to transform it to the standard formulation (\ref{SOCP}) and feed the NAG SOCP solver with data $A$, $l_A$, $u_A$, $c$, $l_x$, $u_x$ and $\cal K$. This modelling process is essential to the usage of SOCP. Getting familiar with these reformulation techniques would unleash the maximum power of SOCP.

To manage the data that the solver requires, one could create and maintain a dictionary structure.

```python
def model_init(n):
    """
    Initialize a dict to store the data that is used to feed NAG socp solver
    """
    model = {
#        Number of variables
         'n': n,
#        Number of constraints
         'm': 0,
#        Coefficient in objective
         'c': np.full(n, 0.0, float),
#        Bound constraints on variables
         'blx': np.full(n, -1.e20, float),
         'bux': np.full(n, 1.e20, float),
#        Coefficient in linear constraints and their bounds
         'linear': {'bl': np.empty(0, float),
                    'bu': np.empty(0, float),
                    'irowa': np.empty(0, int),
                    'icola': np.empty(0, int),
                    'a': np.empty(0, float)},
#        Cone constraint type and variables group
         'cone' : {'type': [],
                   'group': []}
         }
    return model
```

Once the data in the model has been completed, we could feed the data to the NAG SOCP solver by the following function.

```python
def set_nag(model):
    """
    Use data in model to feed NAG optimization suite to define problm
    """

#   Creat problem handle
    handle = opt.handle_init(model['n'])

#   Set objective function
    opt.handle_set_linobj(handle, model['c'])

#   Set box constraints
    opt.handle_set_simplebounds(handle, model['blx'], model['bux'])

#   Set linear constraints
    opt.handle_set_linconstr(handle, model['linear']['bl'], model['linear']['bu'],
            model['linear']['irowa'], model['linear']['icola'], model['linear']['a'])

#   Set cone constraints
    i = 0
    while i<len(model['cone']['type']):
        opt.handle_set_group(handle,model['cone']['type'][i],
                             0, model['cone']['group'][i])
        i += 1

#   Set options
    for option in [
            'Print Options = NO',
            'Print Level = 1',
            'Print File = -1',
            'SOCP Scaling = A'
    ]:
        opt.handle_opt_set(handle, option)

    return handle
```

Now Let's focus on how to get the data in the model ready. In order to add the quadratic objective to the model, we need the following transformation. Note that by introducing variable $t$,
\begin{equation}
\begin{array}{ll}
\underset{x\in\Re^n}{\mbox{minimize}} & -r^Tx+\mu x^TVx
\end{array}
\end{equation}
is equivalent to
\begin{equation}\label{e_1}
\begin{array}{ll}
\underset{x\in\Re^n}{\mbox{minimize}} & -r^Tx+t\\
\mbox{subject to} & \mu x^TVx\leq t
\end{array}
\end{equation}
when $V$ is positive semidefinite. Now the objective in (\ref{e_1}) is linear which fits into the standard model (\ref{SOCP}). By factorizing $V=F^TF$, one can rewrite the quadratic inequality in (\ref{e_1}) to
$$\label{e_2}
\mu\|Fx\|^2\leq t,
$$
where $\|\cdot\|$ is the Euclidean norm. Note that by introducing $y=Fx$ and $s=\frac{1}{2\mu}$, (\ref{e_2}) can be rewritten as
$$
\|y\|^2\leq 2st,
$$
which has exactly the same form of cone constraint (\ref{RSOC}). Therefore, the final SOCP formulation of problem (\ref{MV_model}) is
\begin{equation}\label{MV_model_trans}
\begin{array}{ll}
\underset{x\in\Re^n, y\in\Re^n, s\in\Re, t\in\Re}{\mbox{minimize}} & -r^Tx+t\\[0.6ex]
\mbox{subject to} & e^Tx = 1,\\[0.6ex]
     & Fx - y = 0,\\
     & s=\frac{1}{2\mu},\\
     & x\geq0,\\
     & (s,t,y)\in{\cal K}^{n+2}_r.
\end{array}
\end{equation}
Factorization of $V$ can be done using the NAG Library as follows.

```python
def factorize(V):
    """
    For any given positive semidefinite matrix V, factorize it V = F'*F where
    F is kxn matrix that is returned
    """
#   Size of V
    n = V.shape[0]

#   Note one could use sparse factorization if V is input as sparse matrix
    U, lamda = lapackeig.dsyevd('V', 'L', V)

#   Find positive eigenvalues and corresponding eigenvectors
    i = 0
    k = 0
    F = []

    while i<len(lamda):
        if lamda[i] > 0:
            F = np.append(F, mt.sqrt(lamda[i])*U[:,i])
            k += 1
        i += 1

    F = F.reshape((k, n))

    return F
```

The following code adds a general quadratic objective
\begin{equation}
\begin{array}{ll}
\underset{x\in\Re^n}{\mbox{minimize}} & \frac{1}{2}x^TVx + q^Tx
\end{array}
\end{equation}
to the model.

```python
def add_qobj(model, F, q=None):
    """
    Add quadratic objective defined as: 1/2 * x'Vx + q'x
    transformed to second order cone by adding artificial variables

    Parameters
    ----------
    model: a dict with structure:
        {
         'n': int,
         'm': int,
         'c': float numpy array,
         'blx': float numpy array,
         'bux': float numpy array,
         'linear': {'bl': float numpy array,
                    'bu': float numpy array,
                    'irowa': int numpy array,
                    'icola': int numpy array,
                    'a': float numpy array},
         'cone' : {'type': character list,
                   'group': nested list of int numpy arrays}
        }
    F: float 2d numpy array
        kxn dense matrix that V = F'*F where k is the rank of V
    q: float 1d numpy array
        n vector

    Returns
    -------
    model: modified structure of model

    Note: imput will not be checked
    """

#   Get the dimension of F (kxn)
    k, n = F.shape

#   Default q
    if q is None:
        q = np.zeros(n)

#   Up-to-date problem size
    m_up = model['m']
    n_up = model['n']

#   Then k + 2 more variables need to be added together with
#   k + 1 linear constraints and a rotated cone contraint
#   Enlarge the model
    model['n'] = model['n'] + k + 2
    model['m'] = model['m'] + k + 1

#   Initialize c in the objective
#   The order of variable is [x, t, y, s]
    model['c'][0:n] = q
    model['c'] = np.append(model['c'], np.zeros(k+2))
    model['c'][n_up] = 1.0

#   Enlarge bounds on x, add inf bounds on the new added k+2 variables
    model['blx'] = np.append(model['blx'], np.full(k+2, -1.e20, dtype=float))
    model['bux'] = np.append(model['bux'], np.full(k+2, 1.e20, dtype=float))

#   Enlarge linear constraints
#   Get the aparsity pattern of F
    row, col = np.nonzero(F)
    val = F[row, col]

#   Convert to 1-based and move row down by m
    row = row + 1 + m_up
    col = col + 1
#   Add coefficient of y, t and s to the existing linear coefficient A
#   The result is
#                  [A, 0,  0, 0;
#                   F, 0, -I, 0;
#                   0, 0,  0, 1]
    row = np.append(row, np.arange(m_up+1, m_up+k+1+1))
    col = np.append(col, np.arange(n_up+2, n_up+k+1+1+1))
    val = np.append(val, np.append(np.full(k, -1.0, dtype=float), 1.0))
    model['linear']['irowa'] = np.append(model['linear']['irowa'], row)
    model['linear']['icola'] = np.append(model['linear']['icola'], col)
    model['linear']['a'] = np.append(model['linear']['a'], val)

    model['linear']['bl'] = np.append(model['linear']['bl'],
                                      np.append(np.zeros(k), 1.0))
    model['linear']['bu'] = np.append(model['linear']['bu'],
                                      np.append(np.zeros(k), 1.0))

#   Enlarge cone constraints
    model['cone']['type'].extend('R')
    group = np.zeros(k+2, dtype=int)
    group[0] = n_up + 1
    group[1] = n_up + 1 + k + 1
    group[2:] = np.arange(n_up+2, n_up+k+1+1)
    model['cone']['group'].append(group)

    return model
```

Note that in the above function, we require the input to be the factor of V instead of V because of two reasons:
\begin{itemize}
\item In some cases, this factorization is already avaliable or easy to compute from the user, for example, when user is using factor-based expected returns, risk and correlations, he should already have $V = B*CF*B^T + \mbox{Diag}(rv)$, then a factorization is a decomposition of a much smaller matrix CF (factor covariance).
\item In many cases, V is not changing during modifications of models, for example, when adding $1/2 * \mu * x^TVx + q^Tx$ with different $\mu$, users do not need to factorize V every time they change $\mu$.
\end{itemize}
Once the objective of (\ref{MV_model}) has been added, we could use the following function to add the long-only constraint$$e^Tx=1~\mbox{and}~x\geq0.$$

```python
def add_longonlycon(model, n, b=None):
    """
    Add long-only constraint to model
    If no b (benchmark) presents, add sum(x) = 1, x >= 0
    If b presents, add sum(x) = 0, x + b >= 0
    """
#   Up-to-date problem size
    m = model['m']

#   No of constraints increased by 1
    model['m'] = model['m'] + 1

#   Bound constraint: x >=0 or x >= -b
    if b is not None:
        model['blx'][0:n] = -b
    else:
        model['blx'][0:n] = np.zeros(n)

#   Linear constraint: e'x = 1 or e'x = 0
    if b is not None:
        model['linear']['bl'] = np.append(model['linear']['bl'],
                                          np.full(1, 0.0, dtype=float))
        model['linear']['bu'] = np.append(model['linear']['bu'],
                                          np.full(1, 0.0, dtype=float))
    else:
        model['linear']['bl'] = np.append(model['linear']['bl'],
                                          np.full(1, 1.0, dtype=float))
        model['linear']['bu'] = np.append(model['linear']['bu'],
                                          np.full(1, 1.0, dtype=float))

    model['linear']['irowa'] = np.append(model['linear']['irowa'],
                                         np.full(n, m+1, dtype=int))
    model['linear']['icola'] = np.append(model['linear']['icola'],
                                         np.arange(1, n+1))
    model['linear']['a'] = np.append(model['linear']['a'],
                                     np.full(n, 1.0, dtype=float))

    return model
```

By using the functions above, we can easily build the efficient frontier as follows.

```python
def ef_lo_basic(n, r, V, step=None):
    """
    Basic model to build efficient frontier with long-only constraint
    by solving:
        min -r'*x + mu * x'Vx
        s.t. e'x = 1, x >= 0
    Parameters
    ----------
    n: number of assets
    r: expected return
    V: covariance matrix
    step: define smoothness of the curve of efficient frontier,
          mu would be generated from [0, 2000] with step in default

    Output:
    -------
    risk: a vector of risk sqrt(x'Vx) with repect to certain mu
    rtn: a vector of expected return r'x with repect to certain mu
    """

#   Set optional argument
    if step is None:
        step = 2001

#   Factorize V for just one time, use the factorization in the rest of the code
#   Eigenvalue decomposition on dense V = U*Diag(lamda)*U' to get V = F'*F
    F = factorize(V)

    risk = np.empty(0, float)
    rtn = np.empty(0, float)

    for mu in np.linspace(0.0, 2000.0, step):
#       Initialize a data structure for build the model
        model = model_init(n)

#       Quadratic objective function
        muf = F * mt.sqrt(2.0*mu)

        model = add_qobj(model, muf, -r)

#       Add long-only constraint
        model = add_longonlycon(model, n)

#       Now use model to feed NAG socp solver
        handle = set_nag(model)

#       Call socp interior point solver
#       Mute warnings and do not count results from warnings
        wn.simplefilter('error', utils.NagAlgorithmicWarning)
        try:
            slt = opt.handle_solve_socp_ipm(handle)

#           Compute risk and return from the portfolio
            risk = np.append(risk, mt.sqrt(slt.x[0:n].dot(V.dot(slt.x[0:n]))))
            rtn = np.append(rtn, r.dot(slt.x[0:n]))
        except utils.NagAlgorithmicWarning:
            pass

#       Destroy the handle:
        opt.handle_free(handle)

    return risk, rtn
```

```python
# Build efficient frontier and plot the result
ab_risk, ab_rtn = ef_lo_basic(n, r, V, 500)
plt.plot(ab_risk*100.0, ab_rtn*100.0)
plt.ylabel('Total Expected Return (%)')
plt.xlabel('Absolute Risk (%)')
plt.show()
```

## Maximizing the Sharpe ratio

The Sharpe ratio is defined as the ratio of return of portfolio and standard deviation of the portfolio's excess return. It is usually used to measure the efficiency of a portfolio. Find the most efficient portfolio is equivalent to solve the following optimization problem.
\begin{equation}\label{sr_model}
\begin{array}{ll}
\underset{x\in\Re^n}{\mbox{minimize}} & \frac{\sqrt{x^TVx}}{r^Tx}\\[0.6ex]
\mbox{subject to} & e^Tx = 1,\\[0.6ex]
     & x\geq0.
\end{array}
\end{equation}
By replacing $x$ with $\frac{y}{\lambda}, \lambda\gt0$, model (\ref{sr_model}) is equivalent to
\begin{equation}\label{sr_model_eq}
\begin{array}{ll}
\underset{y\in\Re^n, \lambda\in\Re}{\mbox{minimize}} & y^TVy\\[0.6ex]
\mbox{subject to} & e^Ty = \lambda,\\[0.6ex]
     & r^Ty=1, \\
     & y\geq0, \\
     & \lambda\geq0.
\end{array}
\end{equation}
Problem (\ref{sr_model_eq}) is similar to problem (\ref{MV_model}) in the sense that they both have a quadratic objective function and linear constraints. We could reuse most of the functions above since the reformulation is almost the same except for the definition of linear constraints. For that purpose, we need to following function.

```python
def add_sr_lincon(model, r, n):
    """
    Add linear constraints for Sharpe ratio problem
    e'y = lamda, y >= 0, r'y = 1, lamda >= 0
    Enlarge model by 1 more variable lamda
    Return: model and index of lambda in the final result, need it to
            reconstruct the original solution
    """
#   Up-to-date problem size
    m_up = model['m']
    n_up = model['n']

#   Add one more var and two more linear constraints
    model['n'] = model['n'] + 1
    model['m'] = model['m'] + 2

#   Enlarge c by one parameter 0.0
    model['c'] = np.append(model['c'], 0.0)

#   Bounds constraints on y
    model['blx'][0:n] = np.zeros(n)

#   Set bound constraints on lamda
    model['blx'] = np.append(model['blx'], 0.0)
    model['bux'] = np.append(model['bux'], 1.e20)

#   Add e'y = lamda
    row = np.full(n+1, m_up+1, dtype=int)
    col = np.append(np.arange(1, n+1), n_up+1)
    val = np.append(np.full(n, 1.0, dtype=float), -1.0)

#   Add r'y = 1
    row = np.append(row, np.full(n, m_up+2, dtype=int))
    col = np.append(col, np.arange(1, n+1))
    val = np.append(val, r)

#   Update model
    model['linear']['irowa'] = np.append(model['linear']['irowa'], row)
    model['linear']['icola'] = np.append(model['linear']['icola'], col)
    model['linear']['a'] = np.append(model['linear']['a'], val)

#   Bounds on linear constraints
    model['linear']['bl'] = np.append(model['linear']['bl'],
                                      np.append(np.zeros(1), 1.0))
    model['linear']['bu'] = np.append(model['linear']['bu'],
                                      np.append(np.zeros(1), 1.0))

    return model, n_up
```

Now we can call the NAG SOCP solver as follows.

```python
def sr_lo_basic(n, r, V):
    """
    Basic model to calculate efficient portfolio that maximize the Sharpe ratio
    min y'Vy
    s.t. e'y = lamda, y >= 0, r'y = 1, lamda >= 0
    Return efficient portfolio y/lamda and corresponding risk and return
    """
#   Factorize V for just one time, use the factorization in the rest of the code
#   Eigenvalue decomposition on dense V = U*Diag(lamda)*U' to get V = F'*F
    F = factorize(V)

#   Initialize a data structure for build the model
    model = model_init(n)

#   Quadratic objective function
    muf = F * mt.sqrt(2.0)

    model = add_qobj(model, muf)

#   Add linear constraints
    model, lamda_idx = add_sr_lincon(model, r, n)

#   Now use model to feed NAG socp solver
    handle = set_nag(model)

#   Call socp interior point solver
    slt = opt.handle_solve_socp_ipm(handle)

    sr_risk = mt.sqrt(slt.x[0:n].dot(V.dot(slt.x[0:n])))/slt.x[lamda_idx]
    sr_rtn = r.dot(slt.x[0:n])/slt.x[lamda_idx]

    return sr_risk, sr_rtn, slt.x[0:n]/slt.x[lamda_idx]
```

```python
# Compute the most efficient portfolio and plot result.
sr_risk, sr_rtn, sr_x = sr_lo_basic(n, r, V)
plt.plot(ab_risk*100.0, ab_rtn*100.0, label='Efficient frontier')
plt.plot([sr_risk*100], [sr_rtn*100], 'rs',
         label='Portfolio with maximum Sharpe ratio')
plt.plot([sr_risk*100, 0.0], [sr_rtn*100, 0.0], 'r-', label='Capital market line')
plt.axis([min(ab_risk*100), max(ab_risk*100), min(ab_rtn*100), max(ab_rtn*100)])
plt.ylabel('Total Expected Return (%)')
plt.xlabel('Absolute Risk (%)')
plt.legend()
plt.show()
```

# Portfolio optimization with tracking-error constraint

To avoid taking unnecessary risk when beating a benchmark, the investors commonly impose a limit on the volatility of the deviation of the active portfolio from the benchmark, which is also known as tracking-error volatility (TEV) \cite{J03}. The model to build efficient frontier in excess-return space is
\begin{equation}\label{er_tev}
\begin{array}{ll}
\underset{x\in\Re^n}{\mbox{maximize}} & r^Tx\\
\mbox{subject to} & e^Tx = 0,\\
     & x^TVx\leq tev,
\end{array}
\end{equation}
where $tev$ is a limit on the track-error. Roll \cite{R92} noted that problem (\ref{er_tev}) is totally independent of the benchmark and leads to the unpalatable result that the active portfolio has systematically higher risk than the benchmark and is not optimal. Therefore, in this section we solve a more advanced model by taking absolute risk into account as follows.
\begin{equation}\label{tev_model}
\begin{array}{ll}
\underset{x\in\Re^n}{\mbox{minimize}} & -r^Tx+\mu (x+b)^TV(x+b)\\
\mbox{subject to} & e^Tx = 0,\\
     & x^TVx\leq tev,\\
     & x+b\geq0,
\end{array}
\end{equation}
where $b$ is a benchmark portfolio. In this demonstration, it is generated synthetically. Note here we use the same covariance matrix $V$ for tev and absolute risk measurement for demonstration purpose. In practice one could use different covariance matrices from different markets.

```python
# Generate a benchmark portfolio from efficient portfolio that
# maximizes the Sharpe ratio
# Perturb x
b = sr_x + 1.e-1
# Normalize b
b = b/sum(b)

# Compute risk and return at the benchmark
b_risk = mt.sqrt(b.dot(V.dot(b)))
b_rtn = r.dot(b)
```

Note that same as in problem (\ref{MV_model}), the objective function in (\ref{tev_model}) is quadratic, so we can use $add\_qobj()$ to add it to the model. But problem (\ref{tev_model}) has a quadratic constraint, which makes it a quadratically constrained quadratic programming (QCQP). Following a similar procedure to the transformation of constraint in (\ref{e_1}), we can write a function that can be reused repeatedly to add general quadratic constraints.

```python
def add_qcon(model, F, q=None, r=None):
    """
    Add quadratic contraint defined as: 1/2 * x'Vx + q'x + r <= 0,
    which is equivalent to t + q'x + r = 0, 1/2 * x'Vx <= t,
    transformed to second order cone by adding artificial variables

    Parameters
    ----------
    model: a dict with structure:
        {
         'n': int,
         'm': int,
         'c': float numpy array,
         'blx': float numpy array,
         'bux': float numpy array,
         'linear': {'bl': float numpy array,
                    'bu': float numpy array,
                    'irowa': int numpy array,
                    'icola': int numpy array,
                    'a': float numpy array},
         'cone' : {'type': character list,
                   'group': nested list of int numpy arrays}
        }
    F: float 2d numpy array
        kxn dense matrix that V = F'*F where k is the rank of V

    q: float 1d numpy array
        n vector

    r: float scalar

    Returns
    -------
    model: modified structure of model

    Note: imput will not be checked
    """
#   Default parameter
    if r is None:
        r = 0.0

#   Get the dimension of F (kxn)
    k, n = F.shape

#   Up-to-date problem size
    m_up = model['m']
    n_up = model['n']

#   Then k + 2 more variables need to be added together with
#   k + 2 linear constraints and a rotated cone contraint
#   Enlarge the model
    model['n'] = model['n'] + k + 2
    model['m'] = model['m'] + k + 2

#   All the added auxiliary variables do not take part in obj
#   So their coeffients in obj are all zeros.
    model['c'] = np.append(model['c'], np.zeros(k+2))

#   Enlarge bounds on x, add inf bounds on the new added k+2 variables
    model['blx'] = np.append(model['blx'], np.full(k+2, -1.e20, dtype=float))
    model['bux'] = np.append(model['bux'], np.full(k+2, 1.e20, dtype=float))

#   Enlarge linear constraints
    row, col = np.nonzero(F)
    val = F[row, col]

#   Convert to 1-based and move row down by m_up
#   Add Fx = y and s = 1
#   [x,t,y,s]
    row = row + 1 + m_up
    col = col + 1
    row = np.append(np.append(row, np.arange(m_up+1, m_up+k+1+1)), m_up+k+1+1)
    col = np.append(np.append(col, np.arange(n_up+2, n_up+k+1+1+1)), n_up+1)
    val = np.append(np.append(val, np.append(np.full(k, -1.0,
                                                     dtype=float), 1.0)), 1.0)
    model['linear']['irowa'] = np.append(model['linear']['irowa'], row)
    model['linear']['icola'] = np.append(model['linear']['icola'], col)
    model['linear']['a'] = np.append(model['linear']['a'], val)

    model['linear']['bl'] = np.append(np.append(model['linear']['bl'],
                                                np.append(np.zeros(k), 1.0)), -r)
    model['linear']['bu'] = np.append(np.append(model['linear']['bu'],
                                                np.append(np.zeros(k), 1.0)), -r)

#   Add t + q'x + r = 0
    if q is not None:
        model['linear']['irowa'] = np.append(model['linear']['irowa'],
                                             np.full(n, m_up+k+2, dtype=int))
        model['linear']['icola'] = np.append(model['linear']['icola'],
                                             np.arange(1, n+1))
        model['linear']['a'] = np.append(model['linear']['a'], q)

#   Enlarge cone constraints
    model['cone']['type'].extend('R')
    group = np.zeros(k+2, dtype=int)
    group[0] = n_up + 1
    group[1] = n_up + 1 + k + 1
    group[2:] = np.arange(n_up+2, n_up+k+1+1)
    model['cone']['group'].append(group)

    return model
```

By using the function above, we can easily build the efficient frotier with TEV constraint as follows.

```python
def tev_lo(n, r, V, b, tev, step=None):
    """
    TEV contrained portforlio optimization with absolute risk taken into
    consideration by solving:
    min -r'y + mu*(b+y)'V(b+y)
    s.t. sum(y) = 0, y+b >=o, y'Vy <= tev
    """
#   Set optional argument
    if step is None:
        step = 2001

#   Factorize V for just one time, use the factorization in the rest of the code
#   Eigenvalue decomposition on dense V = U*Diag(lamda)*U' to get V = F'*F
    F = factorize(V)

    risk = np.empty(0, float)
    rtn = np.empty(0, float)

    for mu in np.linspace(0.0, 2000.0, step):
#       Initialize a data structure for build the model
        model = model_init(n)

#       Add long-only constraint
        model = add_longonlycon(model, n, b)

#       Quadratic objective function
        muf = F * mt.sqrt(2.0*mu)
        mur = 2.0*mu*V.dot(b) - r

        model = add_qobj(model, muf, mur)

#       Add Quadratic constraint y'Vy <= tev
        F_hf = F * mt.sqrt(2.0)
        model = add_qcon(model, F_hf, r=-tev)

#       Now use model to feed NAG socp solver
        handle = set_nag(model)

#       Call socp interior point solver
#       Mute warnings and do not count results from warnings
        wn.simplefilter('error', utils.NagAlgorithmicWarning)
        try:
            slt = opt.handle_solve_socp_ipm(handle)

#           Compute risk and return from the portfolio
            risk = np.append(risk, mt.sqrt((slt.x[0:n]+b).dot(V.dot(slt.x[0:n]+b))))
            rtn = np.append(rtn, r.dot(slt.x[0:n]+b))
        except utils.NagAlgorithmicWarning:
            pass

#       Destroy the handle:
        opt.handle_free(handle)

    return risk, rtn
```

```python
# Set limit on tracking-error
tev = 0.000002
# Solve the model
tev_risk, tev_rtn = tev_lo(n, r, V, b, tev, step=500)
# Plot the result
plt.figure(figsize=(7.5, 5.5))
plt.plot(ab_risk*100.0, ab_rtn*100.0, label='Classic efficient frontier')
plt.plot([sr_risk*100], [sr_rtn*100], 'rs',
         label='Portfolio with maximum Sharpe ratio')
plt.plot([sr_risk*100, 0.0], [sr_rtn*100, 0.0], 'r-', label='Capital market line')
plt.plot(b_risk*100, b_rtn*100, 'r*', label='Benchmark portfolio')
plt.plot(tev_risk*100.0, tev_rtn*100.0, 'seagreen',
         label='Efficient frontier with tev constraint')

plt.axis([min(ab_risk*100), max(ab_risk*100), min(tev_rtn*100), max(ab_rtn*100)])
plt.ylabel('Total Expected Return (%)')
plt.xlabel('Absolute Risk (%)')
plt.legend()
plt.show()
```

# Conclusion

In this notebook, we demonstrated how to use NAG Library to solve various models in portfolio optimization. One could take some of the functions mentioned above and start to build their own model immediately. It is worth pointing out that the versatility of SOCP is not just limited to the models mentioned here. It covers a lot more problems and constraints. For example, DeMiguel et al. \cite{DGNU09} discussed portfolio optimization with norm constraint, which can be easily transformed into an SOCP problem. We refer readers to the NAG Library documentation \cite{NAGDOC} on SOCP solver and \cite{AG03, LVBL98} for more details.

# References

[<a id="cit-AG03" href="#call-AG03">1</a>] Alizadeh Farid and Goldfarb Donald, ``_Second-order cone programming_'', Mathematical programming, vol. 95, number 1, pp. 3--51,  2003.

[<a id="cit-LVBL98" href="#call-LVBL98">2</a>] Lobo Miguel Sousa, Vandenberghe Lieven, Boyd Stephen <em>et al.</em>, ``_Applications of second-order cone programming_'', Linear algebra and its applications, vol. 284, number 1-3, pp. 193--228,  1998.

[<a id="cit-J03" href="#call-J03">3</a>] Jorion Philippe, ``_Portfolio optimization with tracking-error constraints_'', Financial Analysts Journal, vol. 59, number 5, pp. 70--82,  2003.

[<a id="cit-R92" href="#call-R92">4</a>] Roll Richard, ``_A mean/variance analysis of tracking error_'', The Journal of Portfolio Management, vol. 18, number 4, pp. 13--22,  1992.

[<a id="cit-DGNU09" href="#call-DGNU09">5</a>] DeMiguel Victor, Garlappi Lorenzo, Nogales Francisco J <em>et al.</em>, ``_A generalized approach to portfolio optimization: Improving performance by constraining portfolio norms_'', Management science, vol. 55, number 5, pp. 798--812,  2009.

[<a id="cit-NAGDOC" href="#call-NAGDOC">6</a>] Numerical Algorithms Group, ``_NAG documentation_'',  2019.  [online](https://www.nag.com/numeric/fl/nagdoc_latest/html/frontmatter/manconts.html)


```python
%matplotlib inline
```

# 592B, Class 5.2 (02/22).  The Fourier Transform in Action

```python
import numpy as np
import matplotlib.pyplot as plt
import librosa
from scipy import fftpack #new

from ipywidgets import interactive
from IPython.display import Audio, display
```

## Review questions

Remember the Fourier series of the square wave you computed? Grab that code and define the square wave approximation as a Fourier series for $N=6$ as you did in Problem Set 2, and plot your square wave approximation.

Draw the expected spectrum by hand.

---

Last time we ended up with the Fourier series defined as $T \rightarrow \infty$, expressed as a double integral:

\begin{equation}
g(t) = \int_{-\infty}^{\infty} \int_{-\infty}^{\infty}e^{-2\pi ift}g(t)dt e^{2\pi ift}df
\end{equation}

and then we said you could rewrite it as $G(f)$, where $G(f)$ is defined as the Fourier transform of a function $g(t)$:

\begin{equation}
G(f) = \int_{-\infty}^{\infty}e^{-2\pi ift} g(t)dt
\end{equation}

and the inverse Fourier transform $g(t)$ of a function $G(f)$ is:

\begin{equation}
g(t) = \int_{-\infty}^{\infty}e^{2\pi ift} G(f)df
\end{equation}

Show that $g(t)$ rewritten in terms of $G(f)$ is the same as the $g(t)$ written out as a double integral.

---

Today we'll be having some fun with Fourier transforms, after all the hard work of mathematically deriving Fourier series and transforms. We'll be getting acquainted with [`scipy.fftpack`](https://docs.scipy.org/doc/scipy/reference/tutorial/fftpack.html). A good portion of this notebook is inspired by [this fantastic chapter of **Elegant SciPy**](https://www.safaribooksonline.com/library/view/elegant-scipy/9781491922927/ch04.html).

## The Fourier Transform in action: a first look

Remember this function?

$$y(t) = \cos \left(\frac{9\pi}{2}t\right ) $$

We worked with it when we were studying aliasing. Let's sample it at 20 Hz, for a duration of 20 seconds.

```python
fs = 20 # Sampling rate of 20 Hz
t_start = 0; t_stop = 20
ns = int((t_stop - t_start) * fs)
#t = np.linspace(t_start,t_stop,ns)
t = np.linspace(t_start,t_stop,ns, endpoint=False)
x = np.cos(9/2*np.pi*t)

fig, ax = plt.subplots()
ax.plot(t, x)
ax.set_xlabel('Time [s]')
ax.set_ylabel('Signal amplitude')
plt.xlim(0,10)
plt.title("A familiar signal")
```

Now let's take the Fourier Transform of the signal!

```python
X = fftpack.fft(x)
freqs = fftpack.fftfreq(len(x)) * fs

fig, ax = plt.subplots()

ax.stem(freqs, np.abs(X))
ax.set_xlabel('Frequency in Hertz [Hz]')
ax.set_ylabel('Magnitude')
ax.set_xlim(-fs/2, fs/2)
#ax.set_xlim(-3.5, 3.5)
ax.set_ylim(-5, 220)
ax.set_ylabel('Fourier transform of signal')
```

We end up with non-zero values at only -2.25 and 2.25 Hz. At the moment, let's not worry about why we also get negative frequencies.

---
***Discussion: why set the x-axis limits as [-fs/2,fs/2]?***

***Discussion: does it make sense that we're getting a peak at 2.25 Hz?***

---

***In-class exercise: what happens to the computed spectrum if you delete `endpoint=False` in the np.linspace command? Why? ***

---

***In-class exercise: repeat what we did above for the signal sampled at 1 Hz. What happens and why?***

---

### Again, a bit more slowly...

Let's walk through this step-by-step. Now let's define a complex wave with two components.

```python
fs = 100
t_stop = 10
t = np.linspace(0,t_stop, t_stop*fs, endpoint=False)
f1 = 5; f2= 2
x = 0.1 * np.sin(2 * np.pi * f1 * t) + 1* np.sin(2 * np.pi * f2 * t) + 50
```

Let's take the Fourier Transform and inspect the values we get.

```python
X = fftpack.fft(x)

print(X[0:5])
print(len(X))

plt.stem(X)
```

Aha! We are getting **complex** values!

What we actually plot in the spectrum turns out to be the size of a complex number, its **modulus**, defined as:

$$Mod(a+bi) = \sqrt{a^2+b^2}$$

In python, we can work with complex numbers as follows:

```python
c = complex(3,4) # syntax: complex(a,b) for defining a+bi
print(c)
print(c.real) # the real component, a
print(c.imag) # the imaginary component, b
print(np.abs(c)) # the modulus
```

    (3+4j)
    3.0
    4.0
    5.0

So let's try plotting the Fourier transform again, this time plotting the modulus.

```python
print(np.abs(X[0:5]))
plt.stem(np.abs(X))
```

But there are still some problems. First the x-axis is in terms of points. We have 1000 points, so we get an x-axis going up to 1000. How do we get this in terms of frequency? We need to set the x-axis to go from [$-f_{Nyquist}, f_{Nyquist}$], $\Delta f_s$ apart. This is where `fftpack.fftfreq` comes in.

***Discussion: What do you notice about the array `freqs`? How long is it? What numbers does it range over? How far are the numbers apart?***

```python
freqs = fftpack.fftfreq(len(x)) * fs
print(freqs)

#print(fftpack.fftfreq(len(x)))

```

    [  0.    0.1   0.2   0.3   0.4   0.5   0.6   0.7   0.8   0.9   1.    1.1
       1.2   1.3   1.4   1.5   1.6   1.7   1.8   1.9   2.    2.1   2.2   2.3
       2.4   2.5   2.6   2.7   2.8   2.9   3.    3.1   3.2   3.3   3.4   3.5
       3.6   3.7   3.8   3.9   4.    4.1   4.2   4.3   4.4   4.5   4.6   4.7
       4.8   4.9   5.    5.1   5.2   5.3   5.4   5.5   5.6   5.7   5.8   5.9
       6.    6.1   6.2   6.3   6.4   6.5   6.6   6.7   6.8   6.9   7.    7.1
       7.2   7.3   7.4   7.5   7.6   7.7   7.8   7.9   8.    8.1   8.2   8.3
       8.4   8.5   8.6   8.7   8.8   8.9   9.    9.1   9.2   9.3   9.4   9.5
       9.6   9.7   9.8   9.9  10.   10.1  10.2  10.3  10.4  10.5  10.6  10.7
      10.8  10.9  11.   11.1  11.2  11.3  11.4  11.5  11.6  11.7  11.8  11.9
      12.   12.1  12.2  12.3  12.4  12.5  12.6  12.7  12.8  12.9  13.   13.1
      13.2  13.3  13.4  13.5  13.6  13.7  13.8  13.9  14.   14.1  14.2  14.3
      14.4  14.5  14.6  14.7  14.8  14.9  15.   15.1  15.2  15.3  15.4  15.5
      15.6  15.7  15.8  15.9  16.   16.1  16.2  16.3  16.4  16.5  16.6  16.7
      16.8  16.9  17.   17.1  17.2  17.3  17.4  17.5  17.6  17.7  17.8  17.9
      18.   18.1  18.2  18.3  18.4  18.5  18.6  18.7  18.8  18.9  19.   19.1
      19.2  19.3  19.4  19.5  19.6  19.7  19.8  19.9  20.   20.1  20.2  20.3
      20.4  20.5  20.6  20.7  20.8  20.9  21.   21.1  21.2  21.3  21.4  21.5
      21.6  21.7  21.8  21.9  22.   22.1  22.2  22.3  22.4  22.5  22.6  22.7
      22.8  22.9  23.   23.1  23.2  23.3  23.4  23.5  23.6  23.7  23.8  23.9
      24.   24.1  24.2  24.3  24.4  24.5  24.6  24.7  24.8  24.9  25.   25.1
      25.2  25.3  25.4  25.5  25.6  25.7  25.8  25.9  26.   26.1  26.2  26.3
      26.4  26.5  26.6  26.7  26.8  26.9  27.   27.1  27.2  27.3  27.4  27.5
      27.6  27.7  27.8  27.9  28.   28.1  28.2  28.3  28.4  28.5  28.6  28.7
      28.8  28.9  29.   29.1  29.2  29.3  29.4  29.5  29.6  29.7  29.8  29.9
      30.   30.1  30.2  30.3  30.4  30.5  30.6  30.7  30.8  30.9  31.   31.1
      31.2  31.3  31.4  31.5  31.6  31.7  31.8  31.9  32.   32.1  32.2  32.3
      32.4  32.5  32.6  32.7  32.8  32.9  33.   33.1  33.2  33.3  33.4  33.5
      33.6  33.7  33.8  33.9  34.   34.1  34.2  34.3  34.4  34.5  34.6  34.7
      34.8  34.9  35.   35.1  35.2  35.3  35.4  35.5  35.6  35.7  35.8  35.9
      36.   36.1  36.2  36.3  36.4  36.5  36.6  36.7  36.8  36.9  37.   37.1
      37.2  37.3  37.4  37.5  37.6  37.7  37.8  37.9  38.   38.1  38.2  38.3
      38.4  38.5  38.6  38.7  38.8  38.9  39.   39.1  39.2  39.3  39.4  39.5
      39.6  39.7  39.8  39.9  40.   40.1  40.2  40.3  40.4  40.5  40.6  40.7
      40.8  40.9  41.   41.1  41.2  41.3  41.4  41.5  41.6  41.7  41.8  41.9
      42.   42.1  42.2  42.3  42.4  42.5  42.6  42.7  42.8  42.9  43.   43.1
      43.2  43.3  43.4  43.5  43.6  43.7  43.8  43.9  44.   44.1  44.2  44.3
      44.4  44.5  44.6  44.7  44.8  44.9  45.   45.1  45.2  45.3  45.4  45.5
      45.6  45.7  45.8  45.9  46.   46.1  46.2  46.3  46.4  46.5  46.6  46.7
      46.8  46.9  47.   47.1  47.2  47.3  47.4  47.5  47.6  47.7  47.8  47.9
      48.   48.1  48.2  48.3  48.4  48.5  48.6  48.7  48.8  48.9  49.   49.1
      49.2  49.3  49.4  49.5  49.6  49.7  49.8  49.9 -50.  -49.9 -49.8 -49.7
     -49.6 -49.5 -49.4 -49.3 -49.2 -49.1 -49.  -48.9 -48.8 -48.7 -48.6 -48.5
     -48.4 -48.3 -48.2 -48.1 -48.  -47.9 -47.8 -47.7 -47.6 -47.5 -47.4 -47.3
     -47.2 -47.1 -47.  -46.9 -46.8 -46.7 -46.6 -46.5 -46.4 -46.3 -46.2 -46.1
     -46.  -45.9 -45.8 -45.7 -45.6 -45.5 -45.4 -45.3 -45.2 -45.1 -45.  -44.9
     -44.8 -44.7 -44.6 -44.5 -44.4 -44.3 -44.2 -44.1 -44.  -43.9 -43.8 -43.7
     -43.6 -43.5 -43.4 -43.3 -43.2 -43.1 -43.  -42.9 -42.8 -42.7 -42.6 -42.5
     -42.4 -42.3 -42.2 -42.1 -42.  -41.9 -41.8 -41.7 -41.6 -41.5 -41.4 -41.3
     -41.2 -41.1 -41.  -40.9 -40.8 -40.7 -40.6 -40.5 -40.4 -40.3 -40.2 -40.1
     -40.  -39.9 -39.8 -39.7 -39.6 -39.5 -39.4 -39.3 -39.2 -39.1 -39.  -38.9
     -38.8 -38.7 -38.6 -38.5 -38.4 -38.3 -38.2 -38.1 -38.  -37.9 -37.8 -37.7
     -37.6 -37.5 -37.4 -37.3 -37.2 -37.1 -37.  -36.9 -36.8 -36.7 -36.6 -36.5
     -36.4 -36.3 -36.2 -36.1 -36.  -35.9 -35.8 -35.7 -35.6 -35.5 -35.4 -35.3
     -35.2 -35.1 -35.  -34.9 -34.8 -34.7 -34.6 -34.5 -34.4 -34.3 -34.2 -34.1
     -34.  -33.9 -33.8 -33.7 -33.6 -33.5 -33.4 -33.3 -33.2 -33.1 -33.  -32.9
     -32.8 -32.7 -32.6 -32.5 -32.4 -32.3 -32.2 -32.1 -32.  -31.9 -31.8 -31.7
     -31.6 -31.5 -31.4 -31.3 -31.2 -31.1 -31.  -30.9 -30.8 -30.7 -30.6 -30.5
     -30.4 -30.3 -30.2 -30.1 -30.  -29.9 -29.8 -29.7 -29.6 -29.5 -29.4 -29.3
     -29.2 -29.1 -29.  -28.9 -28.8 -28.7 -28.6 -28.5 -28.4 -28.3 -28.2 -28.1
     -28.  -27.9 -27.8 -27.7 -27.6 -27.5 -27.4 -27.3 -27.2 -27.1 -27.  -26.9
     -26.8 -26.7 -26.6 -26.5 -26.4 -26.3 -26.2 -26.1 -26.  -25.9 -25.8 -25.7
     -25.6 -25.5 -25.4 -25.3 -25.2 -25.1 -25.  -24.9 -24.8 -24.7 -24.6 -24.5
     -24.4 -24.3 -24.2 -24.1 -24.  -23.9 -23.8 -23.7 -23.6 -23.5 -23.4 -23.3
     -23.2 -23.1 -23.  -22.9 -22.8 -22.7 -22.6 -22.5 -22.4 -22.3 -22.2 -22.1
     -22.  -21.9 -21.8 -21.7 -21.6 -21.5 -21.4 -21.3 -21.2 -21.1 -21.  -20.9
     -20.8 -20.7 -20.6 -20.5 -20.4 -20.3 -20.2 -20.1 -20.  -19.9 -19.8 -19.7
     -19.6 -19.5 -19.4 -19.3 -19.2 -19.1 -19.  -18.9 -18.8 -18.7 -18.6 -18.5
     -18.4 -18.3 -18.2 -18.1 -18.  -17.9 -17.8 -17.7 -17.6 -17.5 -17.4 -17.3
     -17.2 -17.1 -17.  -16.9 -16.8 -16.7 -16.6 -16.5 -16.4 -16.3 -16.2 -16.1
     -16.  -15.9 -15.8 -15.7 -15.6 -15.5 -15.4 -15.3 -15.2 -15.1 -15.  -14.9
     -14.8 -14.7 -14.6 -14.5 -14.4 -14.3 -14.2 -14.1 -14.  -13.9 -13.8 -13.7
     -13.6 -13.5 -13.4 -13.3 -13.2 -13.1 -13.  -12.9 -12.8 -12.7 -12.6 -12.5
     -12.4 -12.3 -12.2 -12.1 -12.  -11.9 -11.8 -11.7 -11.6 -11.5 -11.4 -11.3
     -11.2 -11.1 -11.  -10.9 -10.8 -10.7 -10.6 -10.5 -10.4 -10.3 -10.2 -10.1
     -10.   -9.9  -9.8  -9.7  -9.6  -9.5  -9.4  -9.3  -9.2  -9.1  -9.   -8.9
      -8.8  -8.7  -8.6  -8.5  -8.4  -8.3  -8.2  -8.1  -8.   -7.9  -7.8  -7.7
      -7.6  -7.5  -7.4  -7.3  -7.2  -7.1  -7.   -6.9  -6.8  -6.7  -6.6  -6.5
      -6.4  -6.3  -6.2  -6.1  -6.   -5.9  -5.8  -5.7  -5.6  -5.5  -5.4  -5.3
      -5.2  -5.1  -5.   -4.9  -4.8  -4.7  -4.6  -4.5  -4.4  -4.3  -4.2  -4.1
      -4.   -3.9  -3.8  -3.7  -3.6  -3.5  -3.4  -3.3  -3.2  -3.1  -3.   -2.9
      -2.8  -2.7  -2.6  -2.5  -2.4  -2.3  -2.2  -2.1  -2.   -1.9  -1.8  -1.7
      -1.6  -1.5  -1.4  -1.3  -1.2  -1.1  -1.   -0.9  -0.8  -0.7  -0.6  -0.5
      -0.4  -0.3  -0.2  -0.1]

OK, now we're set!

```python
fig, ax = plt.subplots()

ax.stem(freqs, np.abs(X))
ax.set_xlabel('Frequency in Hertz [Hz]')
ax.set_ylabel('Magnitude')
#ax.set_xlim(-fs/2, fs/2)
ax.set_xlim(-10, 10)
ax.set_ylim(-5, 300)
ax.set_ylabel('Fourier transform of signal')

```

***In-class exercise: Fourier transform of a square wave. Compute a spectrum for the square wave you defined in the Review Questions.***

---

## From spectrum to spectrogram

Let's have some fun with [Japanese vowels](http://www.phonetics.ucla.edu/course/chapter9/japanese/japanese.html) from soundfiles collected by Peter Ladefoged. First we'll use `librosa` to load an audio recording of [ma]. And just for kicks, let's listen to it resampled at different sampling rates.

```python
y, sr = librosa.load("ma.aiff", sr = None)
print(type(y)) # Note that it is a numpy array
print("The sampling rate is: ", sr, "Hz")

y_8k = librosa.resample(y, sr, 8000)
y_2k = librosa.resample(y, sr, 2000)
y_500 = librosa.resample(y, sr, 500)

display(Audio(data=y,rate = sr))
display(Audio(data=y_8k,rate = 8000))
display(Audio(data=y_2k,rate = 2000))
display(Audio(data=y_500,rate = 500))

```

---
***In-class exercise: compute the spectrum of [a] in the recording.***

1. Plot the waveform, and use the waveform to determine what part of the recording has a steady-state [a].
2. Now that you've determined that, work with just that portion of the signal for computing the spectrum.
3. Try computing the spectrum for the [a] sampled at different sampling rates. How does this affect what you see in the resulting spectrum, if at all?
4. Do your computed spectra look as expected, given what you know about formants? You might try computing spectra for the other vowel samples at the webpage, too, e.g., [o], [i], etc.

---

Now let's work towards computing the spectrogram for the audio file. First let's plot the waveform of the audio file.

***Discussion: do you notice a technical problem with the recording revealed from plotting the waveform?***

```python
N = y.shape[0]
L = N / sr

print(f'Audio length: {L:.2f} seconds')

f, ax = plt.subplots()
ax.plot(np.arange(N) / sr, y)
ax.set_xlabel('Time [s]')
ax.set_ylabel('Amplitude [unknown]');
```

We can compute a spectrogram using [spectral analysis tools provided by `scipy.signal`](https://github.com/scipy/scipy/blob/master/scipy/signal/spectral.py), namely, `scipy.signal.spectrogram`.

```python
help(signal.spectrogram)
```

We can use the example code from the help file to try to compute the spectrogram for [ma].

***Discussion: Does the spectrogram look as you expect? Fiddle with the settings of the plot as suits you. Also try using the code from [Elegant SciPy](https://www.safaribooksonline.com/library/view/elegant-scipy/9781491922927/ch04.html) used to produce figure 4-4. How does that look? Compare your computed spectrograms to the ones you get when you analyze the [ma] file in Praat.***

```python
f,t, Sxx = signal.spectrogram(y,sr)
plt.pcolormesh(t, f, Sxx)
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')
```

To better understand how scipy.signal.spectrogram works, let's look at how it is defined. We can do this [using the `inspect` module](https://stackoverflow.com/questions/427453/how-can-i-get-the-source-code-of-a-python-function#427533). We can also look at [the code on github](https://github.com/scipy/scipy/blob/master/scipy/signal/spectral.py), which is a little prettier.

```python
import inspect
lines = inspect.getsourcelines(signal.spectrogram)
print("".join(lines[0]))
```

Well that's not that helpful. Looks like we need to take a look at `signal._spectral_helper`, which we can look at in the github code.

```python
# This doesn't work!
#lines = inspect.getsourcelines(signal.spectral_helper)
#print("".join(lines[0]))
```

Now let's use the code from [Elegant Scipy](https://www.safaribooksonline.com/library/view/elegant-scipy/9781491922927/ch04.html) used to produce Figure 4.3, which breaks down spectrogram computation step by step.

```python
from skimage import util

M = 1024

slices = util.view_as_windows(y, window_shape=(M,), step=100)
print(f'Audio shape: {y.shape}, Sliced audio shape: {slices.shape}')

win = np.hanning(M + 1)[:-1]
slices = slices * win

slices = slices.T
print('Shape of `slices`:', slices.shape)

spectrum = np.fft.fft(slices, axis=0)[:M // 2 + 1:-1]
spectrum = np.abs(spectrum)
```

```python
f, ax = plt.subplots(figsize=(4.8, 2.4))

S = np.abs(spectrum)
S = 20 * np.log10(S / np.max(S))

ax.imshow(S, origin='lower', cmap='viridis',
          extent=(0, L, 0, sr / 2 / 1000))
ax.axis('tight')
ax.set_ylabel('Frequency [kHz]')
ax.set_xlabel('Time [s]');
```

## Working through DFT, FFT

Finally, let's work through the [Elegant Scipy](https://www.safaribooksonline.com/library/view/elegant-scipy/9781491922927/ch04.html) material on DFT and FFT together. This is the section that starts after Figure 4.4.


```python

```

Давайте вернёмся к данным выживаемости пациентов с лейкоцитарной лимфомой из видео про критерий знаков:

49, 58, 75, 110, 112, 132, 151, 276, 281, 362^*49,58,75,110,112,132,151,276,281,362
∗

Измерено остаточное время жизни с момента начала наблюдения (в неделях); звёздочка обозначает цензурирование сверху — исследование длилось 7 лет, и остаточное время жизни одного пациента, который дожил до конца наблюдения, неизвестно.

Поскольку цензурировано только одно наблюдение, для проверки гипотезы H0:medX=200 на этих данных можно использовать критерий знаковых рангов — можно считать, что время дожития последнего пациента в точности равно 362, на ранг этого наблюдения это никак не повлияет.

Критерием знаковых рангов проверьте эту гипотезу против двусторонней альтернативы, введите достигаемый уровень значимости, округлённый до четырёх знаков после десятичной точки.

```python
import numpy as np
import pandas as pd

import scipy
from statsmodels.stats.weightstats import *
from statsmodels.stats.proportion import proportion_confint
```

    The history saving thread hit an unexpected error (DatabaseError('database disk image is malformed',)).History will not be written to the database.

```python
data = [49,58,75,110,112,132,151,276,281,362]
m0 = 200
stats.wilcoxon(np.array(data) - m0)
```

    WilcoxonResult(statistic=17.0, pvalue=0.2845026979112075)

```python
f1 = [22, 22, 15, 13, 19, 19, 18, 20, 21, 13, 13, 15]
f2 = [17,18,18,15,12,4,14,15,10]
f1 = np.array(f1)
f2 = np.array(f2)
```

```python
stats.mannwhitneyu(f1, f2, alternative='greater')
```

    MannwhitneyuResult(statistic=81.0, pvalue=0.02900499272087373)

```python

```

С помощью бутстрепа постройте 95% доверительный интервал для разности средних температур воздуха при запусках, когда уплотнительные кольца повреждались, и запусках, когда повреждений не было. Чему равна его ближайшая к нулю граница? Округлите до четырёх знаков после запятой.

Чтобы получить в точности такой же доверительный интервал, как у нас:

установите random seed = 0 перед первым вызовом функции get_bootstrap_samples, один раз
сделайте по 1000 псевдовыборок из каждой выборки.

```python
challenger = pd.read_table('challenger.txt', sep = '\t', header = 0)
challenger.head()
```

      Unnamed: 0
      Temperature
      Incident

      0
      Apr12.81
      18.9
      0

      1
      Nov12.81
      21.1
      1

      2
      Mar22.82
      20.6
      0

      3
      Nov11.82
      20.0
      0

      4
      Apr04.83
      19.4
      0

```python
no_damage=challenger[challenger.Incident == 0].Temperature.values
damage=challenger[challenger.Incident == 1].Temperature.values
print no_damage, '\n',  damage
```

    [18.9 20.6 20.  19.4 22.2 22.8 21.1 25.6 19.4 19.4 23.9 21.1 27.2 24.4
     26.1 24.4]
    [21.1 13.9 17.2 21.1 11.7 23.9 14.4]

```python
def get_bootstrap_samples(data, n_samples):
    indices = np.random.randint(0, len(data), (n_samples, len(data)))
    samples = data[indices]
    return samples

def stat_intervals(stat, alpha):
    boundaries = np.percentile(stat, [100 * alpha / 2., 100 * (1 - alpha / 2.)])
    return boundaries

```

```python
np.random.seed(0)
```

```python
damage_scores = map(np.mean, get_bootstrap_samples(damage, 1000))
no_damage_scores = map(np.mean, get_bootstrap_samples(no_damage, 1000))

print "95% confidence interval for the damage mean:",  stat_intervals(damage_scores, 0.05)
print "95% confidence interval for the no damage mean:",  stat_intervals(no_damage_scores, 0.05)

```

    95% confidence interval for the damage mean: [14.61428571 20.71607143]
    95% confidence interval for the no damage mean: [21.14359375 23.55046875]

```python
delta_median_scores = map(lambda x: x[1] - x[0], zip(damage_scores, no_damage_scores))

```

```python
print "95% confidence interval for the difference between medians", \
    stat_intervals(delta_median_scores, 0.05)
```

    95% confidence interval for the difference between medians [1.45040179 8.06457589]

```python
np.array(n1)[[2,0]]
```

    array([22, 11])

```python
from itertools import permutations
from sympy.utilities.iterables import multiset_permutations
n1 = [11, 22, 22]
n2 = [44, 555]
```

```python
# x = set([tuple(sorted(x)) for x in permutations(n1 + n2, len(n1))])
# x
from itertools import combinations
x = list(combinations(range(len(n1)+len(n2)), len(n1)))
x
```

    [(0, 1, 2),
     (0, 1, 3),
     (0, 1, 4),
     (0, 2, 3),
     (0, 2, 4),
     (0, 3, 4),
     (1, 2, 3),
     (1, 2, 4),
     (1, 3, 4),
     (2, 3, 4)]

```python
get_random_combinations([11, 22, 22], [22, 77], 1000)
```

```python
import random
```

```python
def get_random_combinations(sample1, sample2, max_combinations):

    n1 = len(sample1)
    n2 = len(sample2)
    s = np.array(sample1 + sample2)
    x = list(combinations(range(n1+n2), n1))
    ind_1 = random.choice(x,)
    ind_2 = [set(range(n1+n2)) - set(i) for i in ind_1]
    return [s[i1] for i1 in ind_1], [s[i2] for i2 in ind_2]
```

```python
def permutation_zero_distr_1sample(sample, mean, max_permutations = None):
    centered_sample = map(lambda x: x - mean, sample)
    if max_permutations:
        signs_array = set([tuple(x) for x in 2 * np.random.randint(2, size = (max_permutations,
                                                                              len(sample))) - 1 ])
    else:
        signs_array =  itertools.product([-1, 1], repeat = len(sample))
    distr = [sum(centered_sample * np.array(signs)) for signs in signs_array]
    return distr
```

```python
def permutation_t_stat_ind(sample1, sample2):
    return np.mean(sample1) - np.mean(sample2)

def get_random_combinations(n1, n2, max_combinations):
    index = range(n1 + n2)
    indices = set([tuple(index)])
    for i in range(max_combinations - 1):
        np.random.shuffle(index)
        indices.add(tuple(index))
    return [(index[:n1], index[n1:]) for index in indices]

def permutation_test(sample1, sample2, mean, max_permutations = None, alternative = 'two-sided'):
    if alternative not in ('two-sided', 'less', 'greater'):
        raise ValueError("alternative not recognized\n"
                         "should be 'two-sided', 'less' or 'greater'")

    t_stat = permutation_t_stat_ind(sample1, sample2)

#     zero_distr = get_random_combinations(sample, mean, max_permutations)
    zero_distr = get_random_combinations(len(sample1), len(sample2), max_permutations)

    if alternative == 'two-sided':
        return sum([1. if abs(x) >= abs(t_stat) else 0. for x in zero_distr]) / len(zero_distr)

    if alternative == 'less':
        return sum([1. if x <= t_stat else 0. for x in zero_distr]) / len(zero_distr)

    if alternative == 'greater':
        return sum([1. if x >= t_stat else 0. for x in zero_distr]) / len(zero_distr)
```

```python
print "p-value: %f" % permutation_test(damage, no_damage, 0.,
                               max_permutations = 50000)
```

```python

```


# Neuromatch Academy: Week 2, Day 5, Tutorial 2
# Learning to Act: Multi-Armed Bandits

# Tutorial Objectives

In this tutorial you will use 'bandits' to understand the fundementals of how a policy interacts with the learning algorithm in reinforcement learning.

* You will understand the fundemental tradeoff between exploration and exploitation in a policy.
* You will understand how the learning rate interacts with exploration to find the best availible action.

# Setup

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

import ipywidgets as widgets
```

```python
#@title Figure Settings
%matplotlib inline
fig_w, fig_h = (8, 6)
plt.rcParams.update({'figure.figsize': (fig_w, fig_h)})
%config InlineBackend.figure_format = 'retina'
```

```python
#@title Helper Functions
np.set_printoptions(precision=3)

def plot_choices(q, epsilon, choice_fn, n_steps=1000, rng_seed=1):
  np.random.seed(rng_seed)
  counts = np.zeros_like(q)
  for t in range(n_steps):
    action = choice_fn(q, epsilon)
    counts[action] += 1

  fig, ax = plt.subplots()
  ax.bar(range(len(q)), counts/n_steps)
  ax.set(ylabel='% chosen', xlabel='action', ylim=(0,1), xticks=range(len(q)))

def plot_multi_armed_bandit_results(results):
  fig, (ax1, ax2, ax3) = plt.subplots(ncols=3, figsize=(20, 4))
  ax1.plot(results['rewards'])
  ax1.set(title=f"Total Reward: {np.sum(results['rewards']):.2f}",
          xlabel='step', ylabel='reward')
  ax2.plot(results['qs'])
  ax2.set(xlabel='step', ylabel='value')
  ax2.legend(range(len(results['mu'])))
  ax3.plot(results['mu'], label='latent')
  ax3.plot(results['qs'][-1], label='learned')
  ax3.set(xlabel='action', ylabel='value')
  ax3.legend()

def plot_parameter_performance(labels, fixed, trial_rewards, trial_optimal):
  fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(16, 6))

  ax1.plot(np.mean(trial_rewards, axis=1).T)
  ax1.set(title=f'Average Reward ({fixed})', xlabel='step', ylabel='reward')
  ax1.legend(labels)

  ax2.plot(np.mean(trial_optimal, axis=1).T)
  ax2.set(title=f'Performance ({fixed})', xlabel='step', ylabel='% optimal')
  ax2.legend(labels)
```

# Multi-Arm Bandits

Consider the following learning problem. You are faced repeatedly with a choice among $k$ different options, or actions. After each choice you receive a reward signal in the form of a numerical value, where the larger value is the better. Your objective is to maximize the expected total reward over some time period, for example, over 1000 action selections, or time steps.

This is the original form of the k-armed bandit problem. This name derives from the colloquial name for a slot machine, the "one-armed bandit", because it has the one lever to pull, and it is often rigged to take more money than it pays out over time. The multi-armed bandit extension is to imagine, for instance, that you are faced with multiple slot machines that you can play, but only one at a time. Which machine should you play, i.e. which arm should you pull, which action should you take, at any given time to maximize your total payout.

While there are many different levels of sophistication and assumptions in how the rewards are determined, for simplicity sake we will assume that each action results in a reward drawn from a fixed Gaussian distribution with unknown mean and unit variance. This problem setting is referred to as the *environment*, and goal is to find the arm with the highest mean value.

We will solve this *optimization problem* with an *agent*, in this case an algorithm that takes in rewards and returns actions.

# Choosing an Action

The first thing our agent needs to be able to do is choose which arm to pull. The strategy for choosing actions based on our expectations is called a *policy* (often denoted $\pi$). We could have a random policy -- just pick an arm at random each time -- though this doesn't seem likely to be capable of optimizing our reward. We want some intentionality, and to do that we need a way of describing our beliefs about the arms' reward potential. We can do this with an action-value function

\begin{align}
q(a) = \mathbb{E} [r_{t} | a_{t} = a]
\end{align}

where the value $q$ for taking action $a \in A$ at time $t$ is equal to the expected value of the reward $r_t$ given that we took action $a$ at that time. In practice, this is often represented as an array of values, where each action's value is a different element in the array.

Great, now that we have a way to describe our beliefs about the values each action should return, let's come up with a policy.

An obvious choice would be to take the action with the highest expected value. This is referred to as the *greedy* policy

\begin{align}
a_{t} = \text{argmax}_{a} \; q_{t} (a)
\end{align}

where our choice action is the one that maximizes the current value function.

So far so good, but it can't be this easy. And, in fact, the greedy policy does have a fatal flaw, in that it easily gets trapped in local maxima. It never explores to see what it hasn't seen before if one option is already better than the others. This leads us to a fundamental challenge in coming up with effective polices.

## The Exploitation-Exploration Dilemma

If we never try anything new, if we always stick to the safe bet, we don't know what we are missing. Sometimes we aren't missing much of anything, and regret not sticking with our preferred choice, yet other times we stumble upon something new that was way better than we thought.

This is the exploitation-exploration dilemma: do you go with you best choice now, or risk the less certain option with the hope of finding something better. Too much exploration, however, means you may end up with a sub-optimal reward once it's time to stop.

In order to avoid getting stuck in local minima while also maximizing reward, effective policies need some way to balance between these two aims.

A simple extension to our greedy policy is to add some randomness. For instance, a coin flip -- heads I take the best choice now, tails I pick one at random. This is referred to as the $\epsilon$-greedy policy:

\begin{align}
P (a_{t} = a) =
        \begin{cases}
        1 - \epsilon    & \quad \text{if } a_{t} = \text{argmax}_{a} \; q_{t} (a) \\
        \epsilon/N        & \quad \text{else}
        \end{cases}
\end{align}

which is to say that the probability of selecting the greedy choice is equal to 1 - $\epsilon$ for $\epsilon \in [0,1]$ otherwise we select the action at random.

Despite its relative simplicity, the epsilon-greedy policy is quite effective, which leads to its general popularity.

## Exercise: Implement Epsilon-Greedy

In this exercise you will implement the epsilon-greedy algorithm for deciding which action to take from a set of possible actions given their value function and a probability $\epsilon$ of simply chosing one at random.

TIP: You may find [`np.random.random`](https://numpy.org/doc/stable/reference/random/generated/numpy.random.random.html), [`np.random.choice`](https://numpy.org/doc/stable/reference/random/generated/numpy.random.choice.html), and [`np.argmax`](https://numpy.org/doc/stable/reference/generated/numpy.argmax.html) useful here.

```python
def epsilon_greedy(q, epsilon):
  """Epsilon-greedy policy: selects the maximum value action with probabilty
  (1-epsilon) and selects randomly with epsilon probability.

  Args:
    q (ndarray): an array of action values
    epsilon (float): probability of selecting an action randomly

  Returns:
    int: the chosen action
  """
  #####################################################################
  ## TODO for students: implement the epsilon greedy decision algorithm
  #####################################################################
  # comment this out when you've filled
  raise NotImplementedError("Student excercise: implement the epsilon greedy decision algorithm")
  # write a boolean expression that determines if we should take the best action
  be_greedy = ...
  if be_greedy:
    # write an expression for selecting the best action from the action values
    action = ...
  else:
    # write an expression for selecting a random action
    action = ...

  return action

# Uncomment once the epsilon_greedy function is complete
# q = [-2, 5, 0, 1]
# epsilon = 0.1
# plot_choices(q, epsilon, epsilon_greedy)
```

```python
# to_remove solution
def epsilon_greedy(q, epsilon):
  """Epsilon-greedy policy: selects the maximum value action with probabilty
  (1-epsilon) and selects randomly with epsilon probability.

  Args:
    q (ndarray): an array of action values
    epsilon (float): probability of selecting an action randomly

  Returns:
    int: the chosen action
  """
  be_greedy = np.random.random() > epsilon
  if be_greedy:
    action = np.argmax(q)
  else:
    action = np.random.choice(len(q))

  return action

q = [-2, 5, 0, 1]
epsilon = 0.1
with plt.xkcd():
  plot_choices(q, epsilon, epsilon_greedy)
```

This is what we should expect, that the action with the largest value (action 1) is selected about (1-$\epsilon$) of the time, or 90% for $\epsilon = 0.1$, and the remaining 10% is split evenly amongst the other options. Use the demo below to explore how changing $\epsilon$ affects the distribution of selected actions.

```python
#@title Interactive Demo: Changing Epsilon
@widgets.interact(epsilon=widgets.FloatSlider(0.1, min=0.0, max=1.0))
def explore_epilson_values(epsilon=0.1):
  q = [-2, 5, 0, 1]
  plot_choices(q, epsilon, epsilon_greedy, rng_seed=None)
```

# Learning from Rewards

Now that we have a policy for deciding what to do, how do we learn from our actions?

One way to do this is just keep a record of every result we ever got and use the averages for each action. If we have a potentially very long running episode, the computational cost of keeping all these values and recomputing the mean over and over again isn't ideal. Instead we can use a streaming mean calculation, which looks like this:

\begin{align}
q_{t}(a) \leftarrow q_{t}(a) + \frac{1}{n_t} (r_{t} - q_{t}(a))
\end{align}

where our action-value function $q_t(a)$ is the mean of the rewards seen so far, $n_t$ is the number of actions taken at time $t$, and $r_t$ is the reward just received for taking action $a$.

This still requires us to remember how many actions we've taken, so let's generalize this a bit further and replace the action total with a general parameter $\alpha$, which we will call the learning rate

\begin{align}
q_{t}(a) \leftarrow q_{t}(a) + \alpha (r_{t} - q_{t}(a)).
\end{align}

## Exercise: Updating Action Values

In this exercise you will implement the action-value update rule above. The function will take in the action-value function represented as an array `q`, the action taken, the reward received, and the learning rate, `alpha`. The function will return the updated value for the selection action.

```python
def update_action_value(q, action, reward, alpha):
  """ Compute the updated action value given the learning rate and observed
  reward.

  Args:
    q (ndarray): an array of action values
    action (int): the action taken
    reward (float): the reward received for taking the action
    alpha (float): the learning rate

  Returns:
    float: the updated value for the selected action
  """
  #####################################################
  ## TODO for students: compute the action value update
  #####################################################
  # comment this out when you've filled
  raise NotImplementedError("Student excercise: compute the action value update")
  # write an expression for the updated action value
  value = ...
  return value

# Uncomment once the update_action_value function is complete
# q = [-2, 5, 0, 1]
# action = 2
# print(f"Original q({action}) value = {q[action]}")
# q[action] = update_action_value(q, 2, 10, 0.01)
# print(f"Updated q({action}) value = {q[action]}")
```

```python
# to_remove solution
def update_action_value(q, action, reward, alpha):
  """ Compute the updated action value given the learning rate and observed
  reward.

  Args:
    q (ndarray): an array of action values
    action (int): the action taken
    reward (float): the reward received for taking the action
    alpha (float): the learning rate

  Returns:
    float: the updated value for the selected action
  """
  value = q[action] + alpha * (reward - q[action])
  return value

q = [-2, 5, 0, 1]
action = 2
print(f"Original q({action}) value = {q[action]}")
q[action] = update_action_value(q, 2, 10, 0.01)
print(f"Updated q({action}) value = {q[action]}")
```

    Original q(2) value = 0
    Updated q(2) value = 0.1

# Solving Multi-Armed Bandits

Now that we have both a policy and a learning rule, we can combine these to solve our original multi-armed bandit task. Recall that we have some number of arms that give rewards drawn from Gaussian distributions with unknown mean and unit variance, and our goal is to find the arm with the highest mean.

First, let's see how we will simulate this environment by reading through the annotated code below.

```python
def multi_armed_bandit(n_arms, epsilon, alpha, n_steps):
  """ A Gaussian multi-armed bandit using an epsilon-greedy policy. For each
  action, rewards are randomly sampled from normal distribution, with a mean
  associated with that arm and unit variance.

  Args:
    n_arms (int): number of arms or actions
    epsilon (float): probability of selecting an action randomly
    alpha (float): the learning rate
    n_steps (int): number of steps to evaluate

  Returns:
    dict: a dictionary containing the action values, actions, and rewards from
    the evaluation along with the true arm parameters mu and the optimality of
    the chosen actions.
  """
  # Gaussian bandit parameters
  mu = np.random.normal(size=n_arms)

  # evaluation and reporting state
  q = np.zeros(n_arms)
  qs = np.zeros((n_steps, n_arms))
  rewards = np.zeros(n_steps)
  actions = np.zeros(n_steps)
  optimal = np.zeros(n_steps)

  # run the bandit
  for t in range(n_steps):
    # choose an action
    action = epsilon_greedy(q, epsilon)
    actions[t] = action

    # copmute rewards for all actions
    all_rewards = np.random.normal(mu)
    # observe the reward for the chosen action
    reward = all_rewards[action]
    rewards[t] = reward
    # was it the best possible choice?
    optimal_action = np.argmax(all_rewards)
    optimal[t] = action == optimal_action

    # update the action value
    q[action] = update_action_value(q, action, reward, alpha)
    qs[t] = q

  results = {
      'qs': qs,
      'actions': actions,
      'rewards': rewards,
      'mu': mu,
      'optimal': optimal
  }

  return results
```

We can use our multi-armed bandit method to evaluate how our epsilon-greedy policy and learning rule perform at solving the task. First we will set our environment to have 10 arms and our agent parameters to $\epsilon=0.1$ and $\alpha=0.01$. In order to get a good sense of the agent's performance, we will run the episode for 1000 steps.

```python
# set for reproducibility, comment out / change seed value for different results
np.random.seed(1)

n_arms = 10
epsilon = 0.1
alpha = 0.01
n_steps = 1000

results = multi_armed_bandit(n_arms, epsilon, alpha, n_steps)

fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(16, 6))
ax1.plot(results['rewards'])
ax1.set(title=f'Observed Reward ($\epsilon$={epsilon}, $\\alpha$={alpha})',
        xlabel='step', ylabel='reward')
ax2.plot(results['qs'])
ax2.set(title=f'Action Values ($\epsilon$={epsilon}, $\\alpha$={alpha})',
        xlabel='step', ylabel='value')
ax2.legend(range(n_arms));
```

Alright, we got some rewards that are kind of all over the place, but the agent seemed to settle in on the first arm as the preferred choice of action relatively quickly. Let's see how well we did at recovering the true means of the Gaussian random variables behind the arms.

```python
fig, ax = plt.subplots()
ax.plot(results['mu'], label='latent')
ax.plot(results['qs'][-1], label='learned')
ax.set(title=f'$\epsilon$={epsilon}, $\\alpha$={alpha}',
       xlabel='action', ylabel='value')
ax.legend();
```

Well, we seem to have found a very good estimate for action 0, but most of the others are not great. In fact, we can see the effect of the local maxima trap at work -- the greedy part of our algorithm locked onto action 0, which is actually the 2nd best choice to action 6. Since these are the means of Gaussian random variables, we can see that the overlap between the two would be quite high, so even if we did explore action 6, we may draw a sample that is still lower than our estimate for action 0.

However, this was just one choice of parameters. Perhaps there is a better combination. Use the widget below to explore how varying the values of epsilon and alpha, and even the number of actions, changes the behavior of our agent.

```python
@widgets.interact_manual(n_arms=widgets.IntSlider(10, min=2, max=15),
                         epsilon=widgets.FloatSlider(0.1, min=0.0, max=1.0),
                         alpha=widgets.FloatLogSlider(0.01, min=-3, max=0))
def explore_bandit_parameters(n_arms=10, epsilon=0.1, alpha=0.001):
  results = multi_armed_bandit(n_arms, epsilon, alpha, 1000)
  plot_multi_armed_bandit_results(results)
```

    interactive(children=(IntSlider(value=10, description='n_arms', max=15, min=2), FloatSlider(value=0.1, descrip…

While we can see how changing the epsilon and alpha values impact the agent's behavior, this doesn't give as a great sense of which combination is optimal. Due to the stochastic nature of both our rewards and our policy, a single trial run isn't sufficient to give us this information. Let's run mulitple trials and compare the average performance.

First we will look at differet values for $\epsilon \in [0.0, 0.1, 0.2]$ to a fixed $\alpha=0.1$. We will run 200 trials as a nice balance between speed and accuracy.

```python
# set for reproducibility, comment out / change seed value for different results
np.random.seed(1)

epsilons = [0.0, 0.1, 0.2]
alpha = 0.1
n_trials = 200
trial_rewards = np.zeros((len(epsilons), n_trials, n_steps))
trial_optimal = np.zeros((len(epsilons), n_trials, n_steps))
for i, epsilon in enumerate(epsilons):
  for n in range(n_trials):
    results = multi_armed_bandit(n_arms, epsilon, alpha, n_steps)
    trial_rewards[i, n] = results['rewards']
    trial_optimal[i, n] = results['optimal']

labels = [f'$\epsilon$={e}' for e in epsilons]
fixed = f'$\\alpha$={alpha}'
plot_parameter_performance(labels, fixed, trial_rewards, trial_optimal)
```

On the left we have plotted the average reward over time, and we see that while $\epsilon=0$ (the greedy policy) does well initially, $\epsilon=0.1$ starts to do slightly better in the long run, while $\epsilon=0.2$ does the worst. Looking on the right, we see the percentage of times the optimal action (the best possible choice at time $t$) was taken, and here again we see a similar pattern of $\epsilon=0.1$ starting out a bit slower but eventually having a slight edge in the longer run.

We can also do the same for the learning rates. We will evaluate $\alpha \in [0.01, 0.1, 1.0]$ to a fixed $\epsilon=0.1$.

```python
# set for reproducibility, comment out / change seed value for different results
np.random.seed(1)

epsilon = 0.1
alphas = [0.01, 0.1, 1.0]
n_trials = 200
trial_rewards = np.zeros((len(epsilons), n_trials, n_steps))
trial_optimal = np.zeros((len(epsilons), n_trials, n_steps))
for i, alpha in enumerate(alphas):
  for n in range(n_trials):
    results = multi_armed_bandit(n_arms, epsilon, alpha, n_steps)
    trial_rewards[i, n] = results['rewards']
    trial_optimal[i, n] = results['optimal']

labels = [f'$\\alpha$={a}' for a in alphas]
fixed = f'$\epsilon$={epsilon}'
plot_parameter_performance(labels, fixed, trial_rewards, trial_optimal)
```

Again we see a balance between an effective learning rate. $\alpha=0.01$ is too weak to quickly incorporate good values, while $\alpha=1$ is too strong likely resulting in high variance in values due to the Gaussian nature of the rewards.


```python
%%html
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
<style>#notebook-container{font-size: 13pt;font-family:'Open Sans', sans-serif;} div.text_cell{max-width: 104ex;}</style>
```

<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet">
<style>#notebook-container{font-size: 13pt;font-family:'Open Sans', sans-serif;} div.text_cell{max-width: 104ex;}</style>

```python
%pylab inline
```

    Populating the interactive namespace from numpy and matplotlib

# Palindromes with an even number of digits

A palindrome is a number that has the same sequence of digits from left to center, and from right to the center. E.g.:

* 11
* 1221
* 123321
* 212212
* ...

The easiest example is if there are $2$ digits. We can write this as $10k + k$, where $k \in \mathbb{N}$ and $0 < k < 10$. This gives:

$$\tag{1} 10k+k = k(10+1) = 11k$$

which is divisible by $11$.

**Theorem 1**

For any $k,n\in \mathbb{N}_0$ where $0<k<10$, then $10^{n+1}k+10^nk$ is divisible by $11$.

**Proof (induction)**

Let $n=0$, this gives:

$$\tag{2}10^1k + 10^0k = 10k + 1k = 11k$$

which is indeed divisible by $11$.

Assume that it is true for $P(n)$. If we fill in $P(n+1)$:

$$\begin{align} & 10^{(n+1)+1}k+10^{n+1}k \\ &= 10^{n+2}k + 10^{n+1}k \\ &= 10(10^{n+1}k+10^nk) \\ &=11\cdot10^{n+1}k\end{align}$$

which is divisible by $11$.

**Proof (direct)**

We can factor $10^{n+1}k+10^nk$ into $10^n(10k + k)$ or $10^n(k(10+1))$ which is $11k\cdot10^n$. So $10^{n+1}k+10^nk$ is divisible by $11$ for any $k\in\mathbb{R}$ and $n\in\mathbb{N}_0$.

**Theorem 2**

Every palindrome with an even number of digits is a multiple of $11$.

**Proof**

If we start with the simplest palindrome $10^{n+1}k+10^nk$ which is $2$ digits. We want to expand this by adding any equal digit to the left and right side. To do this we multiply the original palindrome by $10$ to shift all the values one decimal place to the left. Then we add our new digit $m\in\mathbb{N}$, $0<m<10$ to get:

$$\begin{align} & 10^{n+3}m+10^{n+2}k+10^{n+1}k+10^nm \\ &= 10^{n+3} m+\underbrace{10^{n+2}k+10^{n+1}}_{11k \text{ by theorem 1}}k+10^nm \\ &= 10^{n+3}m+11k+10^nm \\ &= 10^n(10^3m + m) + 11k \\ &= 10^n(1001m)+11k \\ &= 10^n(11\cdot91m)+11k \\ &= 11(10^n\cdot91m+k) \end{align}$$

which is divisible by $11$.

Notebooks/Palindromes with even numbers.ipynb


# Universidade Federal do Amazonas
** Departamento de Engenharia de Produção **

** - Projeto Super 2021 - Curso Python 2.0 **

** Aula 2 - Computação científica - Pacote Sympy **
**  Prof. William Costa **

```python

```

### Sympy é um biblioteca criada para trabalhar com matemática simbólica **

** Tais como: **

*   Matrizes;
*   Cálculo de derivadas, integrais;

*   Utiliza solvers;

** Para mais informações acesse: [https://docs.sympy.org/latest/index.html](https://)
**

####- SÍMBOLOS

*   Função symbols(string_de_símbolo)
*   Função init_printing( )

*   Função subs(simbolos, valores)
*   Função simplify(equação)

** Para mais informações acesse: [https://docs.sympy.org/latest/tutorial/simplification.html](https://)

```python
# Importar  a biblioteca sympy
import sympy as sp
x = sp.symbols('x')
x**3
```

```python
sp.init_printing()

#Criando  uma listda com  simbolos y e x
lista_simbolos = ['y','x']
y,x = sp.symbols(lista_simbolos)

# criando uma funçao f(x)
f_x_y =x**2 + y**2 + x*y
f_x_y
```

```python
#unçao subs- troca os simbolos por valores
resposta = f_x_y.subs(x,1).subs(y,2)
resposta
```

```python
# Função simplify - ela smplifica uma expressao
f_xy = (x+y)**2 +y**2 +2*x*y +y**2
f_xy
#sp.simplify(f_xy)
```

```python
sp.simplify(f_xy)
```

Matrix

```python
# função .Matrix([])
import sympy as sp
sp.init_printing()
```

```python
# Criar matrix
sp.Matrix([[1,2,3],[4,5,6]])
A = sp.Matrix([[1,2,3],[4,5,6]])
A
```

$\displaystyle \left[\begin{matrix}1 & 2 & 3\\4 & 5 & 6\end{matrix}\right]$

```python
# Shape é uma propriedade da matrix - serve indicar qtde de linhas e colunas
A = sp.Matrix([[1,2,3],[4,5,6]])
A.shape
```

```python
# Transposta da matrix - converte linhas em colunas
B = sp.Matrix([[1,2,3]])
B
```

$\displaystyle \left[\begin{matrix}1 & 2 & 3\end{matrix}\right]$

```python
B = sp.Matrix([[1,2,3]]).T
B
```

$\displaystyle \left[\begin{matrix}1\\2\\3\end{matrix}\right]$

```python
# O que vcs acham que vai dar de resultado?
B = sp.Matrix([[1,2,3]])
#B
C = sp.Matrix([[1],[2],[3]])
C

```

$\displaystyle \left[\begin{matrix}1\\2\\3\end{matrix}\right]$

```python
# Matrix zeros -
I = sp.zeros(3,9)
I
```

$\displaystyle \left[\begin{matrix}0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\end{matrix}\right]$

```python
# Sistemas Lineares
x1,x2,x3 = sp.symbols(['x1','x2','x3'])
x3

X = sp.Matrix([x1,x2,x3])
X
```

$\displaystyle \left[\begin{matrix}x_{1}\\x_{2}\\x_{3}\end{matrix}\right]$

```python

# Criei uma matrix de incognitas x
x1,x2,x3 = sp.symbols(['x1','x2','x3'])
X = sp.Matrix([x1,x2,x3])
X
# Criar  uma matrix 3x3
E = sp.Matrix([[1,2,3],[4,5,6],[7,8,9]])

# Criar um vetor de resultado R
R = sp.Matrix([11,22,33])

# E * x = R
# E * x - R = 0
E * X - R
```

$\displaystyle \left[\begin{matrix}x_{1} + 2 x_{2} + 3 x_{3} - 11\\4 x_{1} + 5 x_{2} + 6 x_{3} - 22\\7 x_{1} + 8 x_{2} + 9 x_{3} - 33\end{matrix}\right]$

## Funções do cálculo

*   Derivada: .diff(funcao,variavel)
*   Integral definida: .integrate(funcao,(variavel,inicio,fim))

*   Integral indefinida: .integrate(funcao,variavel)

```python
import sympy as sp
sp.init_printing()
import math as mt

# criar uma expressão numero pi (3.1416)
a = sp.pi*2 + 198*sp.pi
a
```

```python
a = mt.pi*2 + 198*mt.pi
a
```

```python
# Criando uma variavel ou expressão
x = sp.symbols('x')
f_x = sp.sin(x)
f_x
```

```python
# Derivar a funçao f(x)
df_dx = sp.diff(f_x,x)
df_dx
```

```python
# Calcular a derivada segunda da função f(x) = seno(x)
f_x = sp.sin(x)
df2dx2 = sp.diff(f_x,x,x)
df2dx2
```

### - Vamos verificar e prof. acertou ?
https://pt.symbolab.com/solver/derivative-calculator/%5Cfrac%7Bd%7D%7Bdx%7D%5Cleft(%5Csin%5E%7B2%7D%5Cleft(x%5Cright)%5Cright)

```python
# Derivada parciais - quando a fç apresenta duas variáveis independentes f(x,y)
funcao_y_x = ['y','x']
y,x = sp.symbols(['y','x'])

f_y_x = x**2 + y**2 - 100
f_y_x

```

```python
f_y_x = x**2 + y**2 -100

# Derivar f(x,y) em funçao de y
dfdy =sp.diff(f_y_x,y)
dfdy
```

```python
f_y_x = x**2 + y**2 -100

# Derivar f(x,y) em funçao de x
dfdx =sp.diff(f_y_x,x)
dfdx
```

### - Integral
** sp.integrate(nome_da_funcao,variavel_para_integrar)  **

**  sp.integrate(nome_da_funcao,(variavel_para_integrar,LInferior,LSuperior))**

```python
x = sp.symbols('x')
sp.init_printing()

fx = x**2
fx

# Integrando f(x) em fç de x
int_fx = sp.integrate(fx,x)
int_fx
```

```python
# Criando a constante C de integração na função f(x)
int_fx
int_fx +=C1   # int_fx = int_fx + C1
int_fx
```

```python
# Integral definida sp.integrate(nome_funcao,(variavel,Inicio,Fim))
fx
int_fx
int_fx2 = sp.integrate(fx,(x,0,2))
int_fx2

```

```python
# Integral definida sp.integrate(nome_funcao,(variavel,Inicio,Fim))
C2 = sp.symbols('C2')
fx
int_fx
int_fx2 = sp.integrate(fx,(x,0,2))
int_fx2

int_fx2 +=C2
int_fx2
```

** Semana 3 (10/09) – Módulo I – Python computação científica : Numpy –

– Array

– Funções de criação de Array;

– Solver para sistemas lineares

– Exercícios

 **

- Dica de livro:

[https://penseallen.github.io/PensePython2e/](https://)

```python

```

```python

```