# Messtechnik HS2021 - Tutorial 3

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

# Fourier transformation
def compute_fft(t,X):
    """ Manually compute the FT of a simple signal"""
    t = np.atleast_1d(t)
    X = np.atleast_1d(X)
    # Construct grid of points for spectrum
    N = np.size(t)
    grid = np.arange(N)
    dt = (max(t) - min(t))/(N - 1)
    # Frequency axis
    omega = 4*np.pi/N*grid
    # Shift zero-frequency component to center of spectrum
    omega = omega - 0.5*max(omega)
    # Scale by Nyquist criterion
    omega = omega/(2*dt)
    # Calculate Fourier transform operator
    FToperator = np.exp(-1j*np.outer(omega.T,t))
    # Transform signal
    Y = FToperator@X
    return omega,Y

# Inverse Fourier transformation
def compute_ifft(t,omega,X):
    """ Manually compute the FT of a simple signal"""
    t = np.atleast_1d(t)
    omega = np.atleast_1d(omega)
    X = np.atleast_1d(X)
    # Calculate Fourier transform operator
    FToperator = np.exp(+1j*np.outer(t,omega.T))
    # Transform signal
    Y = FToperator@X
    return Y

```

# Aufgabe 1: Lineare Antwort
----------------------

Gegeben ist einen Input EM-Puls $ x(t) $ der durch einen Kabel zum Spektrometer geschickt wird. Am Ende des Kabels wird der Output Puls $ y(t) $ neu gemessen.
Die Input und Output Pulsen sind in der Numpy-formattierte Datei `pulses.npz` gespeichert.

1) Plotten Sie die gemessene Pulsen. Welche Unterschieden sehen Sie?
2) Berechnen und vergleichen Sie die Pulsen im Frequenzbereich. Benutzen Sie dafür die vorgegebene Funktion `compute_fft`.
3) Berechnen Sie die Antwortsfunktion des Kabels. Benutzen Sie dafür die vorgegebene Funktion `compute_ifft`.
4) Wie würden Sie die gewünschte Puls-Form im Spektrometer erreichen?

```python
# Load external data file
data = np.load('pulses.npz')
# Extract the variables
t = data['t']
pulse_input = data['input']
pulse_output = data['output']

plt.figure(figsize=[8,3])
plt.subplot(121)
plt.plot(t,pulse_input)
plt.xlabel('t [μs]')
plt.ylabel('x(t)')
plt.title('Input')

plt.subplot(122)
plt.plot(t,pulse_output)
plt.xlabel('t [μs]')
plt.ylabel('y(t)')
plt.title('Output')

plt.tight_layout()
plt.show()
```

```python
# Compute the spectra (via Fourier transform)
freq,spec_input = compute_fft(t,pulse_input)
_,spec_output = compute_fft(t,pulse_output)

# Angular frequencies -> Linear frequencies
freq = freq/2/np.pi

# Normalize the spectra to their maximum
spec_input = spec_input/max(spec_input)
spec_output = spec_output/max(spec_output)

plt.figure(figsize=[8,3])
plt.subplot(121)
plt.plot(freq,spec_input.real)
plt.xlim([-4,4])
plt.xlabel(r'$\nu$ [MHz]')
plt.ylabel(r'X($\nu$)')
plt.title('Input')

plt.subplot(122)
plt.plot(freq,spec_output.real)
plt.xlim([-4,4])
plt.xlabel(r'$\nu$ [MHz]')
plt.ylabel(r'Y($\nu$)')
plt.title('Output')

plt.tight_layout()
plt.show()
```

```python
# Calculate the impulse reponse function
transferfcn_spec = spec_output/spec_input
transferfcn = compute_ifft(t,freq*2*np.pi,transferfcn_spec).real

plt.figure(figsize=[8,3])
plt.subplot(121)
plt.plot(freq,transferfcn_spec.real)
plt.xlim([-4,4])
plt.xlabel(r'$\nu$ [MHz]')
plt.ylabel(r'H($\nu$)')

plt.subplot(122)
plt.plot(t,transferfcn)
plt.xlabel('t [μs]')
plt.ylabel('h(t)')

plt.tight_layout()
plt.show()
```

```python

# Compute the compensated input pulse
pulse_input_new = compute_ifft(t,freq*2*np.pi,np.round(spec_input,2)/transferfcn_spec)
_,spec_input_new = compute_fft(t,pulse_input_new)

# Calculate the new output pulse
pulse_output_new = compute_ifft(t,freq*2*np.pi,spec_input_new*transferfcn_spec)
_,spec_output_new = compute_fft(t,pulse_output_new)

# Normalize for clearer plots
pulse_input_new = pulse_input_new/max(pulse_input_new)
pulse_input = pulse_input/max(pulse_input)

pulse_output = pulse_output/max(pulse_output)
pulse_output_new = pulse_output_new/max(pulse_output_new)

spec_input = spec_input/max(spec_input)
spec_input_new = spec_input_new/max(spec_input_new)

spec_output = spec_output/max(spec_output)
spec_output_new = spec_output_new/max(spec_output_new)

plt.figure(figsize=[12,7])
plt.subplot(421)
plt.plot(t,pulse_input)
plt.xlabel('t [μs]')
plt.ylabel('x(t)')
plt.title('Input (original)')
plt.subplot(422)
plt.plot(t,pulse_input_new.real,'tab:red')
plt.xlabel('t [μs]')
plt.ylabel('x(t)')
plt.title('Input (compensated)')

plt.subplot(423)
plt.plot(t,pulse_output)
plt.xlabel('t [μs]')
plt.ylabel('y(t)')
plt.title('Output (original)')
plt.subplot(424)
plt.plot(t,pulse_output_new.real,'tab:red')
plt.xlabel('t [μs]')
plt.ylabel('y(t)')
plt.title('Output (compensated)')

plt.subplot(425)
plt.plot(freq,spec_input.real)
plt.xlim([-4,4])
plt.xlabel(r'$\nu$ [MHz]')
plt.ylabel(r'X($\nu$)')
plt.title('Input (original)')
plt.subplot(426)
plt.plot(freq,spec_input_new.real,'tab:red')
plt.xlim([-4,4])
plt.xlabel(r'$\nu$ [MHz]')
plt.ylabel(r'X($\nu$)')
plt.title('Input (compensated)')

plt.subplot(427)
plt.plot(freq,spec_output.real)
plt.xlim([-4,4])
plt.xlabel(r'$\nu$ [MHz]')
plt.ylabel(r'Y($\nu$)')
plt.title('Output (original)')
plt.subplot(428)
plt.plot(freq,spec_output_new.real,'tab:red')
plt.xlim([-4,4])
plt.xlabel(r'$\nu$ [MHz]')
plt.ylabel(r'Y($\nu$)')
plt.title('Output (compensated)')

plt.tight_layout()
plt.show()
```

## Aufgabe 2: Faltung
--------------------

Berechnen Sie numerisch die Faltung einer Rechteckfunktion (box function) mit einer identischen Rechteckfunktion (a) sowie die Faltung einer Rechteckfunktion mit einer Gauss-Funktion (b):

Benutzen Sie dazu die Numpy-Funktion ``convolve``. Definieren Sie alle Ausgangfunktionen zwischen $ t = -10 $ und $ t=10 $ mit 2001 Punkten. Konstruieren Sie eine Rechteckfunktion die eine Breite von 18.0 hat und eine Gauss-Funktion mit einer Standardabweichung $ \sigma = 1 $. Die Zeitachse der Faltung soll explizit berechnet werden.\\
\textit{Hinweis:} Um den Vektor $ t $ zu kreieren können Sie den Befehl ``t = np.linspace(-10,10,2001)`` verwenden. Für die Gaussfunktion brauchen sie die foglende Definition:

\begin{equation}
f(x,\mu,\sigma) = \frac{1}{\sigma \sqrt{2\pi}}\exp\left(-\frac{(x - \mu)^2}{2\sigma^2} \right)
\end{equation}

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

# Define the axis
N_pts = 2001 # odd number in order to have explicitly a zero point in the spectrum
t_lim = 10
t_box = 18
t = np.linspace(-t_lim,t_lim,N_pts)

# Generate box-function (rechteck-funktion)
boxfcn = np.zeros_like(t)
boxfcn[(t>=-t_box/2) & (t<=t_box/2)] = 1

# Numerical convolution:
box_box = np.convolve(boxfcn,boxfcn,'full')
box_box = box_box/max(box_box) # normalize to maximum

# Construct new axis for result (has more points)
n_conv = np.size(box_box) # length of the convolution: equal to m+n-1 (see numpy.convolve help)
t_conv = np.linspace(-2*t_lim,2*t_lim,n_conv)

plt.figure(figsize=[14,4])
plt.subplot(121)
plt.plot(t,boxfcn,'tab:blue',label='Input')
plt.plot(t_conv,box_box,'tab:red',label='Convolution')
plt.legend(loc='upper right',frameon=False)
plt.xlabel('t')

# Generate Gaussian function
mu = 0 # center around zero
sigma = 1 # width parameter
gaussfcn = 1/(sigma*np.sqrt(2*np.pi))*np.exp( - (t - mu)**2 / (2*sigma**2))

# Convolution of the box and the Gaussian functions
box_gauss = np.convolve(boxfcn,gaussfcn)
box_gauss = box_gauss/max(box_gauss)
gaussfcn = gaussfcn/max(gaussfcn)

plt.subplot(122)
plt.plot(t,gaussfcn,'tab:blue',label='Input')
plt.plot(t_conv,box_gauss,'tab:red',label='Convolution')
plt.legend(loc='upper right',frameon=False)
plt.xlabel('t');
```

## Aufgabe 3: Fourier-Transformation
--------------------

Berechnen Sie numerisch die Fourier Transformierten der folgenden Funktionen:

(a) Rechteckfunktion mit der Breite $ \Delta t = 25.0 $, zentriert um $ t=0 $.

(b) Rechteckfunktion mit der Breite $ \Delta t = 5.0 $, zentriert um $ t = 0 $.

(c) Rechteckfunktion mit der Breite $ \Delta t = 25.0 $, zentriert um $ t=0 $, multipliziert mit einer harmonischen Funktion $ \cos(2\pi t) $, mit $ \nu = 1.0 $.

(d) Rechteckfunktion mit der Breite $ \Delta t = 5.0 $, zentriert um $ t=25 $.

*Hinweis:* Verwenden Sie für Ihre Berechnungen die folgende Zeitachse:
``t = np.linspace(-50,50,4096); # Zeitachse der Fourier Transformation``

```python
# Since we need different box-functions with different parameters
# it is useful to define a function to do it
def boxfcn(t,width,shift):
    box = np.zeros_like(t)
    lower_side = -width/2 + shift
    upper_side = width/2 + shift
    box[(t>=lower_side) & (t<=upper_side)] = 1
    return box

# Define time axis
t = np.linspace(-50,50,4096)

# (a)
#---------------------------------------------
fun = boxfcn(t,25,0)
omega,Y = compute_fft(t,fun)

plt.figure(figsize=[8,16])
plt.subplot(421)
plt.plot(t,fun,'k')
plt.xlabel('time t')

plt.subplot(422)
plt.plot(omega/(2*np.pi),np.real(Y))
plt.plot(omega/(2*np.pi),np.imag(Y))
plt.xlim([-3,3])
plt.xlabel('frequency $\\nu$')
plt.legend(['real','imag'])

# (b)
#---------------------------------------------
fun = boxfcn(t,5,0)
omega,Y = compute_fft(t,fun)

plt.subplot(423)
plt.plot(t,fun,'k')
plt.xlabel('time t')

plt.subplot(424)
plt.plot(omega/(2*np.pi),np.real(Y))
plt.plot(omega/(2*np.pi),np.imag(Y))
plt.xlim([-3,3])
plt.xlabel('frequency $\\nu$')
plt.legend(['real','imag'])

# (c)
#---------------------------------------------
nu = 1
fun = boxfcn(t,25,0)*np.cos(2*np.pi*nu*t)
omega,Y = compute_fft(t,fun)

plt.subplot(425)
plt.plot(t,fun,'k')
plt.xlabel('time t')

plt.subplot(426)
plt.plot(omega/(2*np.pi),np.real(Y))
plt.plot(omega/(2*np.pi),np.imag(Y))
plt.xlim([-3,3])
plt.xlabel('frequency $\\nu$')
plt.legend(['real','imag'])

# (d)
#---------------------------------------------
fun = boxfcn(t,5,25)
omega,Y = compute_fft(t,fun)

plt.subplot(427)
plt.plot(t,fun,'k')
plt.xlabel('time t')

plt.subplot(428)
plt.plot(omega/(2*np.pi),np.real(Y))
plt.plot(omega/(2*np.pi),np.imag(Y))
plt.xlim([-1,1])
plt.xlabel('frequency $\\nu$')
plt.legend(['real','imag']);
```

```python

```


# Foundations for Machine Learning - Linear Algebra

We've briefly touched on the three aspects of machine learning (ML) defined by Tom Mitchell in his book _Machine Learning_ (1997) in past lectures:

1. Task (T)
2. Performance Measure (P)
3. Experience (E)

Our goal in machine learning is to construct a formal (i.e. mathematical) framework for these three ideas, and this will constitute a machine learning approach. Of course, the definition above is very broad, and says nothing about whether we find the machine learning algorithm figuratively _useful_ or _informative_ in some way. For example, just because we write a formal description of a P and calculate its value under T and E, doesn't mean the algorithm has to obtain the *best* value of P. Instead, just _improving_ according to P over the course of computing the algorithm could technically be called learning. To start devling into these ideas more, we need an example at least useful enough in the sense that it can help us gain some understanding about how one generally constructs T, P, and E, and one that also performs something we might consider _useful_ or _informative_. Alternatively, some algorithms are mathematically complex, and require a bit of study before we begin to understand how they work. Instead of diving directly into a machine learning approach, for this assignment we will be getting familiar with some mathematical concepts and the python libraries that we will be using to implement those mathematical concepts. Once we have these tools in hand, we will be prepared to begin exploring how these three elements T, P, and E are constructed, and how one might use them to solve engineering problems.

## Classification Tasks and Vectors

Generally, a classification task involves sorting experiences amongst a discrete set of options. One might, for example, think about the last week and separate each day into the classes of "good days" and "bad days". Or maybe one might listen to a song to determine which genre it belongs (pop, rock, r-n-b, reggae, etc.). Typically, you will use already-classified examples from the past to motivate your selection process and help you decide how to classify a new example. Being familiar with many rock songs might help one identify key features that are common to rock songs, and new songs which have these same features may be classified as rock then as well. However, different features might be used to identify pop songs, and if a song shares some features of both rock and pop then you will be forced to make a difficult decision. Songs which clearly have many features of the rock genre and few features of the pop genre might be desribed as more similar or _less distant_ to other songs in the rock genre, but also less similar or _more distant_ to songs in the pop genre. Notice that we are using the terms _similarity_ and _distance_ where one is generally taken to be roughly the inverse of the other. We often use the term _similarity_ to describe relationships between songs, and hence put similar songs into the same genre. However, due the inverse relationship distance has to similarity, it is also reasonable to say that we instead put songs that are less distant into the same genre. Using distance, it's easier to say if something does _not_ belong to a category. For example, if we say that a pop song doesn't sound like it belongs in the rock genre, we are saying that the song shares few features with other rock songs. That is, the pop song is distant from other rock songs. This concept is important because you have probably already studied and used some so-called _distance metrics_ in other math and CS courses, and we will be using these mathematical tools to describe relationships between experiences in machine learning tasks. For example, a common _distance metric_ is the Euclidean distance, defined between two vectors (or points) $\boldsymbol{x}$ and $\boldsymbol{y}$ to be: $\sqrt{\sum_{i=1}^{n}(x_{i}-y_{i})^2}$ where $\boldsymbol{x}$ and $\boldsymbol{y}$ are both vectors of length $n$.

Vectors play an important role in machine learning. We need a formal way to deliver experiences to a machine learning algorithm, and vectors provide a general mathematical way to do this, and they also help us understand how this mode of delivery shapes the learning and performance of the algorithms that we develop.

As a quick exercise, let's construct a couple of vectors and calculate the Euclidean distance between them:

```python
# Prep the environment
import numpy as np

from sympy import *
init_printing(use_latex=True)

# New stuff!!
import scipy.spatial.distance as ssd

import matplotlib.pyplot as plt
%matplotlib inline
```

```python
x = np.array([1,1])
print(x)
y = np.array([0.5,1.5])
print(y)
```

    [1 1]
    [ 0.5  1.5]

Notice that we have chosen to use two vectors ($\boldsymbol{x}$ and $\boldsymbol{y}$) with a length of $n=2$ for this example.
We create each by constructing a python list of the vector elements and the passing the list to numpy's `array()` function. The reason why we don't use standard python lists should become apparent very soon.

Now, we can calculate their distance...

```python
# Direct calculation with numpy
np.sqrt(np.sum(pow(x-y,2.0)))
```

Here we have utilized a nice property of numpy where arrays of the _same size/dimensions_ can have an operator or function apply to each of the *corresponding* elements in the arrays. For comparison, the $x-y$ operation would not be computable between two python lists (try it and you will see that it's _not supported_ under python). However, numpy recognizes that you would like to perform a subtraction operation for the two vectors _and_ that they have the same dimensions. Therefore, it knows to go through the corresponding elements in the vectors and subtract them one at a time, similar to if you had written a for-loop to iterate over the elements. It's a wonderful thing...

The end result of the subtraction operation is then another _temporary_ vector (let's call it $r$) of the same size ($n=2$) where $r_1 = x_1 - y_1$ and $r_2 = x_2 - y_2$. In a more python-like description, we would say `r[0] = x[0] - y[0]` and `r[1] = x[1] - y[1]` since numpy arrays are 0-indexed just like other python data structures.

Keep in mind that most mathematical descriptions that you see in these examples, as well as in textbooks, will also be 1-indexed. You will sometimes need to take care to remember that the data structures are 0-indexed instead.

Additionally, the `pow()` function is also applied over the $r$ array, and then the `sum()` function understands that it should sum over the values in the array provided to produce a single _scalar_ value which is finally passed to the `sqrt()` function for evaluation of the square-root.

We'll get back to vector data manipulation soon, but before we move on, let's look at a utility function from the scipy.spatial.distance package which can make this even easier:

## Distance and Similarity Calculations

```python
# Using the scipy library spatial.distance
ssd.euclidean(x,y)
```

There are many functions in numpy and/or scipy which provide useful routines for common mathematical calculations like distances. You can see the list of distance functions [here](https://docs.scipy.org/doc/scipy/reference/spatial.distance.html).

Let's explore vectors and distance calculations a little more...

First note that the Euclidean distance is a special case of the so-called Minkowski distance which has an additional parameter, $p$:

${\left( \sum_{i=1}^{n} {\lvert x_i - y_i \rvert}^p \right)}^{\frac{1}{p}} = {\lVert \boldsymbol{x} - \boldsymbol{y} \rVert}_p$

If we set $p=2$, we obtain the Euclidean distance:

${\left( \sum_{i=1}^{n} {\vert x_i - y_i \rvert}^2 \right)}^{\frac{1}{2}} = \sqrt{\sum_{i=1}^{n}(x_{i}-y_{i})^2} = {\lVert \boldsymbol{x} - \boldsymbol{y} \rVert}_2$

The notation used here to denote the Minkowski distance is worth remembering.

Another common distance metric used in machine learning is the Manhattan distance, which is also a special case of the Minkowski distance when $p=1$:

${\left( \sum_{i=1}^{n} {\vert x_i - y_i \rvert}^1 \right)}^{\frac{1}{1}} = \sum_{i=1}^{n}\lvert x_{i}-y_{i} \rvert = {\lVert \boldsymbol{x} - \boldsymbol{y} \rVert}_1$

You can calculate these distances using the `minkowski()` function:

```python
print('Euclidean: %f'%ssd.minkowski(x,y,2))
print('Manhattan: %f'%ssd.minkowski(x,y,1))
```

    Euclidean: 0.707107
    Manhattan: 1.000000

Another commonly used metric is the _cosine_ similarity. Note that this is not a distance function, but a similarity function. For the metrics above, similar vectors have _low_ distance from one another and dissimilar vectors have _high_ distance from one another. However, under the cosine similarity metric, similar items will have _high_ similarity and dissimilar items will have _low_ similarity.

Cosine similarity is defined as follows:
$\cos{\theta} = \frac{\boldsymbol{x} \cdot \boldsymbol{y}}{{\lVert \boldsymbol{x} \rVert}_2 {\lVert \boldsymbol{y} \rVert}_2}$

where

${\lVert \boldsymbol{x} \lVert}_p = {\left( \sum_{i=1}^{n} {\lvert x_i \rvert}^p \right)}^\frac{1}{p}$ (so-called: p-norm)

and

$\boldsymbol{x} \cdot \boldsymbol{y} = \sum_{i=1}^{n} x_i y_i$ (so-called: dot-product)

and when $p=2$, we refer to this as the L2-norm of $\boldsymbol{x}$ (or somethimes simply the _length_ of the vector described by $\boldsymbol{x}$).

Let's calculate the cosine similarity between $\boldsymbol{x}$ and $\boldsymbol{y}$:

```python
np.sum(x*y)/(np.sqrt(np.dot(x,x))*np.sqrt(np.dot(y,y)))
```

An alternative way to formulate the L2-norm is to take the square-root of the dot product between a vector and itself:

${{\lVert \boldsymbol{x} \rVert}_2} = \sqrt{\boldsymbol{x} \cdot \boldsymbol{x}}$

The value, $\theta$, represents the angle between the two vectors measured in _radians_. However, instead of solving for $\theta$, we usually are only interested in $\cos{\theta}$ since this value will be in the range $[-1,1]$ where vectors with high similarity will have a $\cos{\theta}$ close to 1, _orthogonal_ vectors will have a $\cos{\theta}$ close to 0, and vectors which nearly lie on the same line but point in _opposite_ directions will have a $\cos{\theta}$ of near -1.

Due to this property, another common distance metric is the _cosine distance_ which is defined to be $1-\cos{\theta}$, or:

$1 - \frac{\boldsymbol{x} \cdot \boldsymbol{y}}{{\lVert \boldsymbol{x} \rVert}_2 {\lVert \boldsymbol{y} \rVert}_2}$

This formulation allows for vectors which point in similar directions to have a distance of _zero_, while _orthgonal_ vectors have a distance of _one_, and vectors pointing in opposite directions will have a distance of _two_ (this is however, the maximum distance between two vectors using cosine distance).

Also, the dot product, $\boldsymbol{x} \cdot \boldsymbol{y}$, alone has similar properties to cosine similarity so it is sometimes preferred due to the ease of computation using numpy's `dot()` function. We may investigate different ways to transform distance metrics into similarity metrics and vice-versa in future assignments.

Let's calculate cosine similarity and distance now...

```python
print('Cosine similarity: %f'%(np.dot(x,y) /
                               (np.sqrt(np.dot(x,x)) * np.sqrt(np.dot(y,y)))))
print('Cosine distance: %f'%ssd.cosine(x,y))
```

    Cosine similarity: 0.894427
    Cosine distance: 0.105573

As mentioned above, we can rewrite cosine similarity as follows:

$\boldsymbol{x} \cdot \boldsymbol{y} = {{\lVert \boldsymbol{x} \rVert}_2 {\lVert \boldsymbol{y} \rVert}_2} \cos {\theta}$

This means that the dot product ($\boldsymbol{x} \cdot \boldsymbol{y}$) is an _unscaled_ or _unnormalized_ version of the cosine similarity metric. You can compute it in two different ways:

```python
# The dot product
print(np.sum(x*y))
print(np.dot(x,y))
```

    2.0
    2.0

The `dot()` function actually is very useful for working with matrices and tensors as well, and will come in handy later. For now, if you wanted to learn a little more about its capabilities, you could just ask for help in the notebook. This will look up the basic documentation provided for the function:

```python
help(np.dot)
```

    Help on built-in function dot in module numpy.core.multiarray:

    dot(...)
        dot(a, b, out=None)

        Dot product of two arrays.

        For 2-D arrays it is equivalent to matrix multiplication, and for 1-D
        arrays to inner product of vectors (without complex conjugation). For
        N dimensions it is a sum product over the last axis of `a` and
        the second-to-last of `b`::

            dot(a, b)[i,j,k,m] = sum(a[i,j,:] * b[k,:,m])

        Parameters
        ----------
        a : array_like
            First argument.
        b : array_like
            Second argument.
        out : ndarray, optional
            Output argument. This must have the exact kind that would be returned
            if it was not used. In particular, it must have the right type, must be
            C-contiguous, and its dtype must be the dtype that would be returned
            for `dot(a,b)`. This is a performance feature. Therefore, if these
            conditions are not met, an exception is raised, instead of attempting
            to be flexible.

        Returns
        -------
        output : ndarray
            Returns the dot product of `a` and `b`.  If `a` and `b` are both
            scalars or both 1-D arrays then a scalar is returned; otherwise
            an array is returned.
            If `out` is given, then it is returned.

        Raises
        ------
        ValueError
            If the last dimension of `a` is not the same size as
            the second-to-last dimension of `b`.

        See Also
        --------
        vdot : Complex-conjugating dot product.
        tensordot : Sum products over arbitrary axes.
        einsum : Einstein summation convention.
        matmul : '@' operator as method with out parameter.

        Examples
        --------
        >>> np.dot(3, 4)
        12

        Neither argument is complex-conjugated:

        >>> np.dot([2j, 3j], [2j, 3j])
        (-13+0j)

        For 2-D arrays it is the matrix product:

        >>> a = [[1, 0], [0, 1]]
        >>> b = [[4, 1], [2, 2]]
        >>> np.dot(a, b)
        array([[4, 1],
               [2, 2]])

        >>> a = np.arange(3*4*5*6).reshape((3,4,5,6))
        >>> b = np.arange(3*4*5*6)[::-1].reshape((5,4,6,3))
        >>> np.dot(a, b)[2,3,2,1,2,2]
        499128
        >>> sum(a[2,3,2,:] * b[1,2,:,2])
        499128

## Matrices and Vectors

Now that we have worked with vectors a little, we will find ourselves needing to work with many of them at the same time. For that, we usually stack our vectors into a matrix so that we can utilize them for matrix-vector and matrix-matrix multiplication operations.

Let's make a new vector, $\boldsymbol{z}$, and then combine each of the 2-element vectors $\boldsymbol{x},\boldsymbol{y},\boldsymbol{z}$ into a 3-row matrix.

The result (we will call it: data) will be a 3x2 matrix.

```python
# Make another vector
z = np.array([1.5,0.25])

# Combine all vectors -by row- to form a matrix
data = np.array([x,y,z])
data
```

    array([[ 1.  ,  1.  ],
           [ 0.5 ,  1.5 ],
           [ 1.5 ,  0.25]])

If you prefer, you can use sympy to print matrices and vectors for \LaTeX style rendering. Note that you will get column-wise vectors by default. You can always see the transpose instead using the `T` member.

```python
# Note that Matrix() was imported from sympy
Matrix(data)
```

```python
# Same for vectors - but you will get a column-matrix
Matrix(x)
```

```python
# Maybe you wante the transpose - use T?
Matrix(x).T
```

## Pairwise Distance Matrices

A common kind of matrix used in machine learning is the *pairwise distance* matrix. This matrix consists of distance calculations for $m$ different vectors obtained from an $m$ x $n$ data matrix.

Our matrix was 3x2, which means we have $m=3$ vectors, each of dimension $n=2$. What we want to do is compare each of the 3 vectors to itself and the other two. After doing this for all 3 vectors, we will have a 3x3 matrix which contains the distance from vector $i$ to $j$ where $i=1...n$ and $j=1...n$.

The `pdist()` function from sypy.spatial is useful for making this calculation, but to get the full $m$ x $m$ to display, we then convert the result into a square matrix using the `squareform()` function.

Here are some examples using the data we created above for a few distance metrics:

```python
ssd.squareform(ssd.pdist(data,metric='euclidean'))
```

    array([[ 0.        ,  0.70710678,  0.90138782],
           [ 0.70710678,  0.        ,  1.60078106],
           [ 0.90138782,  1.60078106,  0.        ]])

```python
ssd.squareform(ssd.pdist(data,metric='minkowski',p=2))
```

    array([[ 0.        ,  0.70710678,  0.90138782],
           [ 0.70710678,  0.        ,  1.60078106],
           [ 0.90138782,  1.60078106,  0.        ]])

```python
ssd.squareform(ssd.pdist(data,metric='minkowski',p=1))
```

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

```python
ssd.squareform(ssd.pdist(data,metric='cosine'))
```

    array([[ 0.        ,  0.10557281,  0.18626653],
           [ 0.10557281,  0.        ,  0.53211228],
           [ 0.18626653,  0.53211228,  0.        ]])

```python
# Just to make it pretty...
Matrix(ssd.squareform(ssd.pdist(data,metric='cosine')))
```

# Matrix-Vector Operations

We will be using common linear algebra operations often for neural network computations.

One common operations are the matrix-vector product. I will show how to print the expression we are wanting to compute using sympy, but most of the time, I will _not_ ask you to do this for your assignments. Sympy, by default, will simplify expressions automatically, so it takes some work to turn off the simplication engine in order to use it for printing.

Nevertheless, let's try to compute a matrix-vector product using the numpy `dot()` function...

```python
# Let's use sympy to print a pretty version of the operation..
Eq(Mul(Matrix(data),Matrix(x),evaluate=False),
   Matrix(np.dot(data,x)),evaluate=False)
```

```python
# Most of the time, you will simply use:
np.dot(data,x)
```

    array([ 2.  ,  2.  ,  1.75])

```python
# And a simple pretty-print of the results...
Matrix(np.dot(data,x))
```

Note how numpy knows that `data` is a 3x2 array and `x` is a 2-dimensional vector. This means that it treats the vector as a 2x1 matrix for this operation. The result is therefore a 3x1, which is expressed more clearly in the pretty-printed version.

This property of numpy which allows it match vector, matrix, and other arrays of arbitrary dimension (tensors - we will see them later) by correspondingly sized dimensions will be useful.

Note what happens if we reverse the order of `data` and `x`:

```python
np.dot(x,data)
```

While numpy is smart enough to match corresponding dimensions when requested, you still have to formulate your computation in the order that makes sense for the array and matrix you are using.

In the case above, since numpy sees the `x` vector as a column vector, it has size 2x1, and trying to multply a 2x1 by a 3x2 doesn't work (the "inner" numbers need to match in the order of calculations).

So, just remember, numpy is *great*, but _order matters_...

## Matrix-Matrix operations

Numpy is also good for performing matrix-matrix operations (as well as vector-tensor, matrix-tensor, and tensor-tensor... we will do those later).

For now, we will calculate `data` times its transpose. That's a 3x2 times a 2x3, so the result is a 3x3. Again, I will use sympy to show the math, but most of the time the `dot()` function is what you will be using...

```python
Eq(Mul(Matrix(data),Matrix(data).T,evaluate=False),
   Matrix(np.dot(data,data.T)),evaluate=False)
```

```python
# Let's use numpy!
Matrix(np.dot(data,data.T))
```

Notice, this is equivalent to calculating the pairwise dot-product between all three vectors!

With a little more work, we can calculate the pairwise cosine distances like we did using the `pdist()` function and the `metric=cosine` argument, but using only pure numpy...

```python
def mynorm(a_vector):
    return np.sqrt(np.dot(a_vector,a_vector))

A = np.round(
    1.0 - (np.dot(data,data.T) / np.dot(
    np.reshape(np.apply_along_axis(mynorm,1,data),[3,1]),
    np.reshape(np.apply_along_axis(mynorm,1,data),[1,3]))),12)
Matrix(A)
```

There are a lot of new functions being used here. Some are fairly straight-forward to understand, such as `round()` which simply rounds numbers to the specified number of digits following the decimal point (in this case, 12). Also, the `dot()` and `T` operations you have already seen.

However, another useful tool is the `apply_along_axis()` function. This function allows us to call a function and apply it to individual vectors (or even sub-matrices if we are using tensors) in a matrix. In the case of the cosine distance, we need to calculate the vector norms in the denominator of the equation. First, we create a function, `mynorm` which takes a vector as input, and calculates the norm (a scalar) for that vector. We use this as the first argument to apply_along_axis since this is the function we need to apply to the vectors in `data`. Next, we know we need to apply the function to the vectors in each _row_ and the first dimension of a matrix specifies the number of _rows_ in that matrix. Thus, if we want to apply the function to each row, we select the first dimension (0) as the second argument. Finally, we supply the matrix containing the vectors we are performing the operation on: `data`. The end result is a 3-element vector which contains the _lengths_ (vector norms) of each of the 3 vectors in the matrix.

The `reshape()` function then comes in handy for forming the 3 elements into first a 3x1 matrix, and then a 1x3 matrix (one matrix for each call). By multiplying the two matrices together with `dot()` we have calculated all pairs of vector norms (a 3x3 matrix - the denominators in the cosine distance equation).

We then use `dot()` to calculate the dot products for all vectors and divide it by the pairwise norm matrix before we finally subtract the result from 1, and pass it to `round()` to eliminate some numerical round-off error accumulation. (Feel free to remove the `round()` function to observe the left-over values along the diagonal, the values of which are very near, but not quite, zero.

## Matrix Decompositions

Finally, let's perform some matrix factorizations (or often matrix decomposition). This is similar to finding the factors of a number, like how you can get $12=4*3$ or $12=2*6$. Each pair is a factorization of the number $12$.

For matrix factorization, we want a combination of matrices that can be multiplied together to obtain a given matrix, $\boldsymbol{A}$.

We will explore how to use these operations in neural networks and machine learning in the future. For now, just learn how to compute them.

Let's start with *Eigen decomposition*, where we want to factor a square and (in most cases) symmetric matrix $\boldsymbol{A}$ (which we computer above) into:

$\boldsymbol{A} = \boldsymbol{Q} \boldsymbol{\Lambda} \boldsymbol{Q}^\intercal$

```python
L,Q = np.linalg.eig(A)

Mul(Matrix(Q),Matrix(np.diag(L)),Matrix(Q).T,evaluate=False)
```

The result is a 3x3 matrix of eigen vectors, $\boldsymbol{Q}$, and a _diagonal_ matrix of eigen values, $\boldsymbol{\Lambda}$ (which I have assigned to L for simplicity).

You can obtain the original matrix, $A$, (within a little numerical rounding error) by multiplying the parts together as prescribed above:

```python
Matrix(np.round(np.dot(np.dot(Q,np.diag(L)),Q.T),12))
```

Another common decomposition is the *singular value decomposition*, which can be applied to $m$ x $n$ (i.e. rectangular) matrices:

$\boldsymbol{A} = \boldsymbol{U} \boldsymbol{\Sigma} \boldsymbol{V}^\intercal$

```python
U,S,V = np.linalg.svd(A,full_matrices=True)

Mul(Matrix(U),Matrix(np.diag(S)),Matrix(V),evaluate=False)
```

The result is a 3x3 matrix of left-singular vectors, $\boldsymbol{U}$, and a _diagonal_ matrix of singular values, $\boldsymbol{\Sigma}$ (which I have assigned to S for simplicity), and a 3x3 matrix of right-singular vectors, $\boldsymbol{V}$. (However, note that V is already in transposed form when returned by numpy.)

You can obtain the original matrix, $\boldsymbol{A}$, (within a little numerical rounding error) by multiplying the parts together as prescribed above:

```python
Matrix(np.round(np.dot(np.dot(U,np.diag(S)),V),12))
```

```python

```


```python
import symbolic_modern_robotics as smr
import sympy as s
s.init_printing()
```

```python
# Example 6.1
from IPython.display import Image
Image('exam_6.1_fig.PNG', width=500)
```

```python
# 1-1. Find Forward Kinematics, Space PoE
l1,l2,g = s.symbols('l1,l2,g')
m1,m2 = s.symbols('m1,m2')
theta_1,theta_2,thetadot_1,thetadot_2,thetaddot_1,thetaddot_2 = s.symbols('theta_1,theta_2,thetadot_1,thetadot_2,thetaddot_1,thetaddot_2', positive=True)

thetalist = s.Matrix([theta_1,theta_2])
dthetalist = s.Matrix([thetadot_1,thetadot_2])
ddthetalist = s.Matrix([thetaddot_1,thetaddot_2])

M = s.Matrix([[1, 0, 0, l1+l2],
              [0, 1, 0, 0],
              [0, 0, 1, 0],
              [0, 0, 0, 1]])

M01 = s.Matrix([[1, 0, 0, l1],
                [0, 1, 0, 0],
                [0, 0, 1, 0],
                [0, 0, 0, 1]])

M12 = s.Matrix([[1, 0, 0, l2],
                [0, 1, 0, 0],
                [0, 0, 1, 0],
                [0, 0, 0, 1]])

Mlist = [M01, M12]

Slist = s.Matrix([[0, 0, 1, 0, 0, 0],
                  [0, 0, 1, 0, -l1, 0]]).T

Blist = s.Matrix([[0, 0, 1, 0, l1+l2, 0],
                  [0, 0, 1, 0, l2, 0]]).T

# Check Forward Kinematics
T01 = smr.MatrixExp6(smr.VecTose3(Slist[:, 0]*thetalist[0]))
T12 = smr.MatrixExp6(smr.VecTose3(Slist[:, 1]*thetalist[1]))
ForKinSpace = T01@T12@M
T01 = smr.MatrixExp6(smr.VecTose3(Blist[:, 0]*thetalist[0]))
T12 = smr.MatrixExp6(smr.VecTose3(Blist[:, 1]*thetalist[1]))
ForKinBody = M@T01@T12

G01 = s.diag(0,0,0, m1, m1, m1)
G12 = s.diag(0,0,0, m2, m2, m2)
Glist = [G01, G12]

g_vec = s.Matrix([0, -g, 0])
Ftip = s.Matrix([0,0,0,0,0,0])

tau, V, Vd = smr.InverseDynamics(thetalist, dthetalist, ddthetalist, g_vec, Ftip, Mlist, Glist, Slist)
tau
```

```python
V
```

```python
Vd
```

```python
s.simplify(T01@T12@M)
```

```python
T01 = smr.MatrixExp6(smr.VecTose3(Blist[:, 0]*thetalist[0]))
T12 = smr.MatrixExp6(smr.VecTose3(Blist[:, 1]*thetalist[1]))
s.simplify(M@T01@T12)
```

```python
smr.get_EoM_from_T(tau, ddthetalist, g)
```

```python

```

```python

```


```python
import cirq
```

We have already seen that quantum circuits can be used to transfer information efficiently.
Now, we will see for the first time how we can use a quantum circuit to solve a problem in a more efficient way than it is possible with a classical probabilistic Turing machine. While the problem we will introduce below seems and also is a bit artificial, it demonstrates some essential concepts.

Reading Ch. 6, rspa.1992.0167

Before we state the problem and show the algorithm we want to briefly describe a process called phase kick-back, which quite nicely demonstrates the power of working with states that are superposed

To demonstrate the basic idea behind the phase kick-back we imagine a two qubit system, where the first qubit is im some arbitray superposition $a_0|0\rangle +a_1|1\rangle $ and the second qubit is in the following state $\frac{1}{\sqrt{2}}(|0\rangle -|1\rangle) = H|1\rangle$. Hence the system is in the follwoing state
\begin{equation}
|\psi\rangle = \left(a_0|0\rangle +a_1|1\rangle\right)\frac{1}{\sqrt{2}}(|0\rangle -|1\rangle)
\end{equation}
If we now look at the effect of a general unitary transformation $cU_{f(x)}$ where the first gate controls the transformation, i.e. $cU=|0\rangle\langle0| \otimes U_{f(0)} + |1\rangle\langle1| \otimes U_{f(1)}$, with $f:\{0,1\}\rightarrow \{0,1\}$ and $U_{f(x)}|y\rangle = |y\oplus f(x)\rangle $. It is now a simple exercise to apply $cU_{f(x)}$ to the initial state and we find
\begin{equation}
|\psi\rangle = \left((-1)^{f(0)}a_0|0\rangle +(-1)^{f(1)}a_1|1\rangle\right)\frac{1}{\sqrt{2}}(|0\rangle -|1\rangle)
\end{equation}
Surprisingly, we have, in fact not modified the target state but the control state, and its phase is changed by $\pi f(x)$, hence the name phase kick-back

We are now going to formulate the Deutsch problem and from there it should become clear, how one can use the phase kick-back idea to solve it

\textbf{The Deutsch Problem}
\begin{problem}
Let $f: \{0,1\} \rightarrow \{0,1\}$ be an unkown function.
Determine $f(0) \oplus f(1)$
\end{problem}

It is evident that with a classical computer, this problem can be solved using two queries.
The Deutsch algorithm can solve this problem using a quantum circuit using a \textbf{single} query

This can be done using the phase kick-back.
Imagine we strart out in the state $|\psi\rangle = \frac{1}{2}\left(|0\rangle +1\rangle\right)(|0\rangle -|1\rangle)$ If we now apply $cU_{f(x)}$ we find
\begin{equation}
|\psi_1\rangle =cU|\psi\rangle = \frac{(-1)^{f(0)}}{2}\left(|0\rangle +(-1)^{f(1)\oplus f(0)}|1\rangle\right)(|0\rangle -|1\rangle)
\end{equation}
One can now see that the state in the first bracket is the outcome of applying the Hadamard gate to $|0\rangle$ or ($|1\rangle$), when $f(1)\oplus f(0) =0$ ($f(1)\oplus f(0)=1$). Using that the Hadmard gate is self inverse, we find
\begin{equation}
H|\psi_1\rangle = \frac{(-1)^{f(0)}}{\sqrt{2}}|0 (1)\rangle (|0\rangle -|1\rangle)
\end{equation}
telling us the value of $f(0) \oplus f(1)$

Before we implement the Deutsch cuircuit we have to construct a circuit, that implements $cU_{f(x)}$

```python
def f_cirquit(q0,q1, f0, f1):
    if f0:
        yield [cirq.X(q0),cirq.CNOT(q0,q1),cirq.X(q0)]
    if f1:
        yield cirq.CNOT(q0,q1)
```

```python
def deutsch_circuit(f0, f1):
    c = cirq.Circuit()
    q0,q1 = cirq.LineQubit.range(2)
    #create intial states
    c.append([cirq.H(q0),cirq.X(q1),cirq.H(q1)])
    #apply cU_f
    c.append(f_cirquit(q0,q1, f0, f1))
    #apply final H gate
    c.append(cirq.H(q0))
    #measure first qubit
    c.append(cirq.measure(q0))
    return c
```

```python
s=cirq.Simulator()
c = deutsch_circuit(1,0)
print(c)
```

    0: ───H───X───@───X───H───M───
                  │
    1: ───X───H───X───────────────

```python
x = s.simulate(c)
```

```python
x
```

    measurements: 0=1
    output vector: -0.707|10⟩ + 0.707|11⟩

```python
for f in [(0,0),(0,1),(1,0),(1,1)]:
    s=cirq.Simulator()
    c = deutsch_circuit(*f)
    x = s.simulate(c)
    assert (f[0] + f[1])%2 == x.measurements['0'][0]
```


# Population growth
## Stepping beyond the constant rate logistic growth

    Author: Fábio Hipólito
    Contact: fabio.hipolito@gmail.com
    github: https://github.com/f-hipolito

## The motivation and purpose

This notebook was written to refresh my knowledge on population dynamics
(primarily focused on population growth) and how to apply first order
differential equations to characterize to such problems.

The interest in this class of problems is tightly connected with 2019-2020
Corona Virus Disease (CoViD19) outbreak and the spread of information and
statistical analysis of dubious quality.
Given the impact of this infection, many people are trying to make sense of
the propagation of the disease with either over-simplistic models, such as a
simple logistic model, or with overly complex ML based models.

It is my understanding that before addressing this problem from an ML
perspective we can, and should, analyze it with fairly simple models
containing few parameters that can be mapped to our understanding of
reality, _e.g._ incubation times, policy change dates, etc.

As discussed (and demonstrated) below, the logistic function is the solution
for the population growth problem, if and if only the *environment is
constant*.
This assumption is incompatible with propagation of CoViD19, as every country
in the world is implementing some sort mitigation policies to reduce the
growth rate of the infection.
In the present notebook no claim is made regarding the latter, clear and
solid information on this can be found at the WHO website, namely the impact
of policy on the propagation of infections in
[geral](https://www.who.int/infection-prevention/en/), and
[CoViD19](https://www.who.int/emergencies/diseases/novel-coronavirus-2019/technical-guidance/infection-prevention-and-control).

In this notebook I delve into some generalizations of a logistic differential
equation to overcome the key limitations of this model, namely the
assumption of constant environment and infinite (mean) lifetimes.
We compare the merits and limitations of the methods by applying them to the
characterization of the propagation of CoViD19.
Using the multi-steady state we identify the 'transition' dates were it is
possible to observe clear change in the growth rate constants.

Furthermore, we extend our model beyond the simple differential equation for
_infected_ cases by considering a system of coupled differential equations
for the *Susceptible*, *Infected*, *Recovered* and *Deceased*, *i.e.* the a
generalization of the **SIR** model.

### Notes to the reader

__Mathematics:__ I will try to provide clear and easy to follow presentation of
the mathematics behind population dynamics, for those with basic knowledge of
differential calculus. For those unfamiliar with differential calculus, I
recommend referring to:
1. [*Advanced Mathematical Methods for Engineering and Science Students* by Stephenson and Radmore](https://doi.org/10.1017/CBO9781139168120)
2. any edition of [*Mathematical Methods for Physicists* by Arfken, Weber and Harris](
https://doi.org/10.1016/C2009-0-30629-7).

__Notation:__
* $N(t)$ population at time $t$
* $N_t$ total population
* $n \equiv n(t) \doteq N(t)/N_t$ population density, note that the population
    density is dimensionless $[n] = \mathbb{1}$.
* $dn/dt$ stands for the population rate of change, with dimension
    $[ dn/dt ] = \mathbb{1}/T$, where $T$ stands for Time
* $\omega(t)$ and $\omega_i$ are the general and ith growth rates, with
    dimension $[ dn/dt ] = \mathbb{1}/T$.
* $\mathrm{d}$ stands for days, unless otherwise stated all time scales and
    growth rates are in units of $\mathrm{d}$ and $\mathrm{d}^{-1}$,
    respectively.
* $f_X(t)$ probability density function (PDF) for a _continuous_ variable $t$
    distributed according to $X-$distribution.
* $F_X(t)$ cumulative distribution function (CDF) corresponding to $f_X(t)$

__Real number rounding:__
Defaults to rounding to 3 significant digits, please refer to the attached
notebooks for figures with machine precision.

__Data sources:__
We collected our data from the _European Centre for Disease Prevention and
Control_ [(ECDC)](https://www.ecdc.europa.eu/en/publications-data/download-todays-data-geographic-distribution-covid-19-cases-worldwide), refer to README.md file for
further details.
Latest data collection 2020-04-25.

## 1. Generalization(s) of the Logistic differential equation

A step-by-step generalization of the logistic differential equation,
$dn/dt = \omega n(1-n)$, and the respective solutions are provided below.
Here, we offer a brief presentation of the key arguments supporting this model
and present the solution that is used to characterize the propagation of
CoViD19 in a few countries.

The manifestation of infection is not immediate due to finite (distribution of)
incubation time(s).
Consequently, any change to the environment, _i.e._ the growth rate,
will necessarily be _delayed_ and also _smoothen_ by the distribution of
incubation times.
Consider the following growth rate with time dependence governed by a
cumulative distribution function for the incubation times

where the rate transitions smoothly between rates
$\omega_i = \{ 2, 0.5, 1.5\} \, \mathrm{d}^{-1}$ at times
$t_i = \{ 10, 35 \} \, \mathrm{d}$.

This approach is useful to characterize growth in environments where the growth
rate evolves, smoothly, between different _constant_ or _steady state_ values.
While at first glance this model might appear a toy model for a mathematician,
an inspection of number of cases of CoViD19 in South Korea reveals that multi
steady state logistic models can be useful.
In the log plot depicted below, we plot number of cases in South Korea and three
logistic regressions fitted to a subset of the original dataset.

To begin, this plot clearly shows why it is important to generalize the
logistic model accommodate multiple rates, as no single rate logistic model
will ever represent this data accurately.
More subtly, it also offers the possibility to infer that the transition occurs

Assuming that the transition between _constant_ rates is governed by the CDF
for the incubation times, we require a characterization of the incubation times
to define our model.
This task has already been completed recently in
[*Euro Surveill. 2020;25(5):pii=2000062*](https://doi.org/10.2807/1560-7917.ES.2020.25.5.2000062)
where the distribution of incubation times is shown to be characterizable by
multiple distributions, namely Weibull, Gamma and LogNormal distributions.
For the purpose of this work, we consider consider the Gamma distribution
incubation times with $\mu_{inc} = 6.5\; \mathrm{d}$ and the standard
deviation $\sigma_{inc} = 2.6 \; \mathrm{d}$.
_Note:_ recently published results
[*Ann Intern Med. 2020;172(9):577-582*](https://doi.org/10.7326/M20-0504)
set the shape and scale parameters at $\alpha = 5.81$ and
$\beta = 0.948 \, \mathrm{d}^{-1}$, with mean
$\mu_{inc} = \alpha/\beta = 6.13 \, \mathrm{d}$ and standard deviation
$\sigma_{inc} = \sqrt{ \alpha/\beta^2 } = 2.54 \, \mathrm{d}$ are in line
the previous reference.

At the present moment we do not account for recovered or deceased cases, our
analysis is strictly limited to the growth of infected people.
The impact of finite recovery and death times is under consideration and
could be introduced soon.

### A _very_ short introduction to the $m+1$ _steady state_ solution

Without loss of generality, we introduce the $m+1$ _steady state_ logistic
function
$$n_m(t) = \sigma\big( \Omega_m(t) \big)
= 1\big/\Big[ 1 + e^{-\Omega_m(t)} \Big] \, ,$$
where the time dependence is entirely contained within the argument of the
logistic/sigmoid function.
We adopt the following representation for argument of the logistic function
representing the $m-$ transitions between _steady states_
$$ \Omega_m(t) = \omega_0(t-t_0)
+\sum_{i=1}^m \big( \omega_i -\omega_{i-1} \big) I_X(t -t_i) \; , $$
where $\omega_0$ is the initial rate, $t_0$ is the global integration constant,
$m$ represents the number of transitions, $\omega_i$ and $t_i$ are the
rate and transition times for each $i^\mathrm{th}$-steady state after the
initial steady state.
Finally, the function $I_X(t -t_i)$ is the result integral of CDF of
distribution $X$ representing the transition at time $t-t_i$.
It should be evident that for $t_{i\neq0} \to \infty $ the general solution
reduces to the solution of logistic differential equation.

For the a set of Gamma distributed incubation times, the integral of the
respective CDF reads
$$ I_\Gamma(t;\alpha,\beta) = \frac{1}{\Gamma(\alpha)}\bigg[
\bigg( t-\frac{\alpha}{\beta} \bigg)\gamma(\alpha,\beta t)
+\beta^{\alpha-1} t^\alpha e^{-\beta t} \bigg] \, , $$
where $\alpha$ and $\beta$ are shape and rate parameters for the Gamma
distribution; $\gamma(\alpha,\beta t)$ and $\Gamma(\alpha)$ are the
lower incomplete and complete Gamma functions.

Below we show that the $m+1$ steady state model can be used to accurately
characterize the propagation of the infection in several countries.
While this simple model is useful and permits the identification of the _steady
state_ rates and respective transition times, it is built under the assumption
of endless lifetime for the infected individuals.
As patients recover or die the pool of infected individuals reduces, leading to
an inevitable break down of the model.
The constraints associated with this assumption are discussed below in the
analysis of the data from South Korea.

The natural next step for any model is the inclusion of a finite lifetime, or
alternatively a dissipation mechanism.
It is worth noting that tentative figures for mean infected time $\tau_a = 21 \, \mathrm{d}$
[DOI:10.1016/S1473-3099(20)30287-5](https://doi.org/10.1016/S1473-3099(20)30287-5)
have been published in scientific literature.

### General time dependent rate and decay times

As discussed above any model built upon the assumption of boundless lifetime for
the infection will inevitably fail in the long run.
In this section we generalize our model to accommodate mechanisms that represent
the finite lifetime of the infection and also a more elaborate model for the
time dependent rates.

To begin, we introduce a new representation for population, $n(t)$, in which we
the population is split onto three non-intersecting groups, defined by
$N(t)/ N_t = s(t) +i(t) +r(t) = 1$, where $s(t)$, $i(t)$, $r(t)$ are the
*susceptible*, _infected_ and _removed_ portions of the population,
respectively.
Note that the _removed_ group contains both recovered and deceased cases.
This representation is the basis of the so-called _SIR_ models for population
dynamics with constant total population population, _i.e._ $d N/dt = 0$.
It follows immediately that $ds(t)/dt +di(t)/dt +dr(t)/dt = 0$.

Once again, the best approach to characterize the dynamics of the population,
is to start by identifying the set of coupled differential equation and then
integrating them over the time domain.
It is natural to expect that the rate of change of susceptible population
$ds/dt$ will proportional to the infected cases and reduce with vanishing
$s(t)$.
Conversely, the rate of change for the infected will evolve in the opposite
direction, with respect to the interplay of $s$ and and $i$, but also decay as
time passes by.

This is formally represented by **SIR differential equations** that read
$$ \begin{align}
\frac{ \partial s(t) }{\partial t } &= -\omega i(t) s(t) \, ,\\
\frac{ \partial i(t) }{\partial t } &=  \omega i(t) s(t) -\nu_a i(t) \, ,\\
\frac{ \partial r(t) }{\partial t } &=  \nu_a i(t)\, ,
\end{align}$$
where $\omega,\nu_a \in \mathbb{R}$ are the infection growth and decay rates
with arbitrary dependence on $t$.

Considering a constant rate $d \omega/dt =0$ and $\nu_a=0$ the third equation
vanishes and the second differential equation reduces to our initial
$d i /dt = \omega i ( 1-i)$, where $n(t)=i(t)$.

Furthermore, in this model we assume that surviving cases present in the
*removed* group are __permanently immunized__ and thus no longer susceptible.
While it might be tempting to immediately consider the more general case, it is
worth keeping in mind that, while the personal impact of re-infection is huge,
the impact on the differential equations will be small.
The small magnitude of the impact arises from the fact that the susceptible
term only impacts the evolution of the disease when the infection has covered
a very large portion of the population.
Hence, introducing this parameter could just add a redundant parameter to our
model that only makes over-fitting more likely.

__Note__ that the infection growth rate $\omega$ is not restricted to a
constant.
In fact, we can just as easily generalize to time dependent function as in
the $m+1$ steady state model discussed in the context of the logistic equation
by setting $ \omega \equiv \Omega_m(t) $ as defined in $\S 2$).

All coupled differential equations are integrated numerically using the open
source suite [DifferentialEquations.jl](https://docs.sciml.ai/stable/)
provided by https://sciml.ai/.
The parameter estimation is performed with
[DiffEqParamEstim.jl](https://docs.sciml.ai/latest/analysis/parameter_estimation/).
The software packages are written in Julia and are available for Julia, Python
and R.

#### Leaky immunization and deaths

The permanent immunization constraint can be lifted by separating the removed
cases into two non-intersecting groups, the _recovered_ and the *deceased*,
which we label here as **SIRD** model.
The elements of the former _leak_ back to the susceptible group with a finite
decay rate, $\mu$, while the later simply accumulates the growing number of
deceased people
$$ \begin{align}
\frac{ \partial s(t) }{\partial t } &= -\omega i(t) s(t) +\mu r(t) \, ,\\
\frac{ \partial i(t) }{\partial t } &=
    \omega i(t) s(t) -(\nu_a +\nu_d) i(t) \, ,\\
\frac{ \partial r(t) }{\partial t } &=  \nu_a i(t) -\mu r(t) \, ,\\
\frac{ \partial d(t) }{\partial t } &=  \nu_d i(t) \, .
\end{align}$$
Note that $r(t)$ stands for the recovered only, $d(t)$ for the deceased,
$\nu_d$ the rate deaths from the infection and $\mu$ the rate
at which recovered people become susceptible once again.
As in the previous case, the generalization to time-dependent infection growth
rates is obtained straightforwardly by setting $ \omega \equiv \Omega_m(t) $
as defined in $\S 2$).

## Analysis of CoViD19 cases

The below we apply this model to characterize the spread of CoViD19 in several
different countries, using data from the *JHU CSSE* public records on CoViD19.
In a previous version, we used public data from *ECDC*.
Please refer to the [README.md](README.md) for further details and copyright.

For the analysis at hand, the preprocessing of the dataset for each country
reduces to:
1. extract the following data fields for each country:
    1. date;
    2. number of daily infections, recoveries and deaths;
    3. total population;
2. make a cumulative sum of each time series;
3. normalize data to the total population;
4. convert dates to time in units of days and set initial time to date of first
    entry in the date field for the relevant country;

The present moment we use the model to characterize the propagation of CoViD19
in three countries, namely: [KOREA.ipynb](Korea.ipynb),
[Denmark.ipynb](Denmark.ipynb) and [Portugal.ipynb](Portugal.ipynb).

### South Korea

#### Basic models: logistic and generalizations

As show in the previous figure the growth of infections in South Korea is
clearly incompatible with a simple logistic regression model.
To clarify beyond any reasonable doubt, we show in the following the best
nonlinear least square fit for a logistic model to the number of infections

where a simple visual inspection shows that this model fails to reproduce real
data.

As discussed earlier, the logistic model can be shown to fit properly the data
by truncating the dataset, _i.e._ restricting the analysis to segments of
periods of time when the _environment_ is kept constant.
Below we address the impact of introducing additional parameters, as well as
the effect of truncating the dataset.

Starting from the most simple model, the green, blue and purle curves show
the logistic regressions applied to truncated subsets of data.
In each subset, the traditional logistic regression represents the
propagation of the infection rather well. For instance, in the first interval
it indicates a growth rate $\omega_0 = 0.159 \, \mathrm{d}^{-1} $, while
simultaneously predicting that infection would spread to half population in
day $t\sim 111$, ie 2020-05-01!.
While the estimate for the rate in the initial phase is likely to be correct,
the model clearly fails beyond day $t\sim 45$, and thus urging us to consider
a model with at least two rates.

The red curve represents the best fit considering a truncated two steady
state model, parametrized with
$\omega_i \simeq \{ 0.258, 0.0115 \} \, \mathrm{d}^{-1}$ and
$t_i \simeq \{ 19.6,  35.0 \} \, \mathrm{d}$.
It can be seen that this model follows data closely until the transition
occurring circa day $t\sim 75$, but fails to reproduce accurately data beyond
this transition.

By introducing additional steady states we obtain the orange and pink curves,
which allow for a more "accurate" representation of the total number
of cases of CoViD19 in South Korea.
The three steady-state model is parametrized as follows
* $\omega_i^{(3)} \simeq \{ 0.261, \; 0.012, \; 0.00183 \} \, \mathrm{d}^{-1}$,
* $t_i^{(3)} \simeq \{ 19.7, \; 34.9, \; 66.4 \} \, \mathrm{d}$

while the parameters for the four steady-state model read
* $\omega_i^{(4)} \simeq \{ 0.260, \; 0.0118, \; 0.00142, \; 0.00371 \}
\, \mathrm{d}^{-1}$,
* $t_i^{(4)} \simeq \{ 19.7, \; 35.0, \; 66.7, \; 114.0 \} \, \mathrm{d}$.

The evaluation of standard error metrics read
* $ \sigma_\mathrm{error}^{\omega_i}
= \{ 0.00552,\; 0.000183, \; 7.54\times 10^{-5}, \; 0.000384 \}$;
* $ \sigma_\mathrm{error}^{t_i} = \{ 0.209, \; 0.0951, \; 0.367, \; 1.99 \}$;
* $ \delta_\mathrm{error}^{\omega_i}
= \{ 0.0109, \; 0.000363, \; 0.000149, \; 0.000759 \}$;
* $ \delta_\mathrm{error}^{t_i} = \{ 0.414, \; 0.188, \; 0.726, \; 3.93 \}$.
The difference between the coefficients found with the three and four
steady-state model lies within the confidence intervals for all common
parameters.

While the significance of $t_0$ is lost in this model, _i.e._ it no longer
represent the time at which half population mark is reached as in the
case of logistic model, the remaining times should represent the dates,
namely 2020-02-26 and 2020-03-29, of around which social behavior should
have changed significantly in South Korea.
The latter remains to be demonstrated and requires comparison with
changes in policy in South Korea.

#### Limitations of the generalized Logistic models

##### Finite infection mean lifetime

Among other flaws, the logistic model is built under the assumption that
individuals remain infected for arbitrarily long periods of time.
In reality, people recovered or eventually die from this disease, which
leads to finite *active* lifetime during which a person can infect others.
Reports indicate that the active lifetime $\tau_a \sim 21 \, \mathrm{d}$
[DOI:10.1016/S1473-3099(20)30287-5](https://doi.org/10.1016/S1473-3099(20)30287-5).

The initial phase the growth of the logistic function, valid only in for
$n\ll 1/2$, exhibits a characteristic $p$-multiple time scale
$\tau_p = \ln(p)/\omega$ (see $\S 2$,).
In this regime, we can estimate the time required for doubling the number of
infected people, *i.e.* $\tau_2^{(i)} = \ln(2) /\omega_i $.
Therefore, to ensure that the propagation is _"sustainable"_, the doubling
time has to be smaller than the mean active lifetime, *i.e.*
$ \tau_2 \ll \tau_a$, which in turn defines a limit to "validity" of the
logistic model.

From our nonlinear regression on the Korean dataset, we find
$\tau_2^{(i)} = \{ 2.66, \; 57.8, \; 379.0 \} \, \mathrm{d}$.
On the bright side, the propagation of CoViD19 is growing at a rate slower
(the doubling time estimate) than the recovery/death mean lifetime since
the first transition date (2020-02-26).

On the other hand, despite the nice close fit to the data, this simple model
fails to realistically characterize the propagation of the disease since
the first transition date.

__Note:__
It is important to note that nonlinear fits using multiple parameters are
known for being notoriously problematic, as it was eloquently put by
_John von Neumann_

    "...with four parameters I can fit an elephant, and with five I can make
    him wiggle his trunk."

[Nature volume 427, page297(2004)](https://www.nature.com/articles/427297a).

I am confident that all models discussed here have been properly fit, _i.e._
the optimization process converges and outputs a set of meaningful parameters
for the specified model.
The convergence nonlinear fit is straightforward for all cases, but it is
important to state that the results shown above are dependent on the initial
guess, that is provided to the fitting routine. please refer to the relevant
notebook (Korea.ipynb) for full details.

Nonetheless, it is important to keep in mind that an incorrect model can
*fit* accurately data, therefore *fit quality* estimates should always be
taken into consideration care.

#### SIR-like models (WIP)

To address the finite _active_ mean lifetime of an infected individual, we
also analyze data using two models, which can accommodate time-dependent
growth rates for the infection. Please refer to the notebook
[SIR_KOREA.ipynb](SIR_Korea.ipynb).

The first model is the most simple incarnation of the SIR model, with the
population split into three non-overlapping groups, the *susceptible* $s(t)$,
the *infected* $i(t)$ and the *removed* $r(t)$, as defined in the
introduction.

In the attached notebook, we define the differential equations, using the
$m-$steady state model for the infection growth rate, and estimate the
parameters $\omega_i$, $t_i$ and $\nu$ using different optimization
algorithms.
The numerical integration of the differential equations relies on
[DifferentialEquations.jl](https://docs.sciml.ai/stable/)
while the optimization process uses
[DiffEqParamEstim.jl](https://docs.sciml.ai/latest/analysis/parameter_estimation/)
and [Optim.jl](https://julianlsolvers.github.io/Optim.jl/stable/#).

Based on the results from the previous section, we jump straight to a *four
steady-state* model ($m=3$) and compare the results obtained via two
optimization algorithms, namely *Nelder-Mead* (dashed red) and *BFGS* (solid
black).
In both cases we use the standard $L_2$ loss function.

Below we compare the respective results with data for the total cases
$1-s(t)$, infected $i(t)$ and removed $r(t)$.

While at first glance the results from both methods are apparently similar,
and have a similar minimum,
$L_2^\mathrm{NM}   = 9.79e\times 10^{-8}$ and
$L_2^\mathrm{BFGS} = 5.88\times 10^{-8}$, it is important to highlight
several differences.
First, the former is considerably more sensitive to the initial guess.
Second, the algorithms converge to solutions that are considerably
different for several parameters, namely $\omega_2$, $t_2$, $t_3$ and
$\nu_a$.

$ \omega_i^\mathrm{NM} = \{ 0.249, \, 0.0254, \, 0.0162, \, 0.0317 \}
\, \mathrm{d}^{-1} \; ;\quad
t_i^\mathrm{NM} = \{ 35.2, \, 65.3, \, 111.0 \} \, \mathrm{d}$

$ \nu_a^\mathrm{NM} = 0.0427 \, \mathrm{d}^{-1} \; \Leftrightarrow \;
\tau_a^\mathrm{NM} = 23.4 \, \mathrm{d}$

$ \omega_i^\mathrm{BFGS} = \{ 0.244, \, 0.0238, \, 0.000582, \, 0.0229 \}
\, \mathrm{d}^{-1} \; ;\quad
t_i^\mathrm{BFGS} = \{ 34.9, \, 66.4, \, 105.0 \} \, \mathrm{d}$

$ \nu_a^\mathrm{BFGS} = 0.0331 \, \mathrm{d}^{-1} \; \Leftrightarrow \;
\tau_a^\mathrm{BFGS} = 30.2 \, \mathrm{d}$

Furthermore, preliminary tests with *Gradient Descent* algorithm return
results that converge towards the solution of the *BFGS* algorithm, but at a
much slower rate.

While at this moment we do not have enough information to decide which
algorithm provides the best set of parameters, we can extract relevant
information.
First, this model is capable of reproducing the general trend for
the total cases, the infected and removed cases.

Second, both algorithms indicate that the infection growth rate for
steady-state 4, *i.e.* $\omega_4$, is approximately one order of magnitude
larger than that estimated via the generalized logistic model.
Even a quick inspection of the logistic model show make it evident that
it will likely underestimate the growth rate at the difference between
the total number of cases and the number of active cases grows.
Hence, the figures provided by the SIR model are more likely to be the
realistic figures.

#### Further refining:  SIRD models (WIP)

The natural next step is to separate the recovered cases from the deaths.
This can be easily achieved by redefining the third differential equation and
introducing a new one for the fraction of population that whose death is
attributed to CoViD19.
Therefore, at this moment we divide the population into four groups
susceptible $s(t)$, infected $i(t)$, recovered $r(t)$ and deceased $d(t)$.

As in the case of the SIR model, we compare the results total cases $1-s(t)$,
infected $i(t)$, recovered $r(t)$ and deceased $d(t)$, obtained via multiple
methods with data.

While one could claim some degree of compatibility between the results
obtained via the *Nelder-Mead* (dashed-red) and *BFGS* (solid black)
algorithms for the *SIR* model, the same cannot be said for the *SIRD* model.

Note that both algorithms converge without any numerical issue, but it is
important to note that the former leads to solutions that are not compatible
with reality, with the negative decay rate for deaths $\nu_d$ being the prime
example.

Thus far, we have only considered **non-constrained** optimization, which
could be to restrict parameter research to a *"reasonable"* domain.

__Note:__ this is work in progress which requires an in-depth analysis before
any strong claim can be made.

For the sake of record we display the results obtained with both algorithms,
but it goes without saying that little importance should be assigned to the
results obtained via the *Nelder-Mead* algorithm.
Furthermore, the preliminary results obtained with *Gradient descent*
algorithm converge, albeit slowly, to those obtained with *BFGS*.

$ \omega_i^\mathrm{NM} = \{ 0.218, \, 0.0192, \, 0.00201, \, 0.0139 \}
\, \mathrm{d}^{-1} \; ;\quad
t_i^\mathrm{NM} = \{ 39.2, \, 77.0, \, 169.0 \} \, \mathrm{d}$

$ \nu_a^\mathrm{NM} = 0.0379 \, \mathrm{d}^{-1} \; \Leftrightarrow \;
\tau_a^\mathrm{NM} = 26.4 \, \mathrm{d}$

$ \nu_d^\mathrm{NM} = -0.00217 \, \mathrm{d}^{-1} \; \Leftrightarrow \;
\tau_d^\mathrm{NM} = -461.0 \, \mathrm{d}$

$ \omega_i^\mathrm{BFGS} = \{ 0.229, \, 0.0146, \, 1.93\times 10^{-5}, \, 0.0231 \}
\, \mathrm{d}^{-1} \; ;\quad
t_i^\mathrm{BFGS} = \{ 37.7, \, 74.9, \, 105.0 \} \, \mathrm{d}$

$ \nu_a^\mathrm{BFGS} = 0.0326 \, \mathrm{d}^{-1} \; \Leftrightarrow \;
\tau_a^\mathrm{BFGS} = 30.6 \, \mathrm{d}$

$ \nu_d^\mathrm{NM} = 0.000568 \, \mathrm{d}^{-1} \; \Leftrightarrow \;
\tau_d^\mathrm{NM} = 1760.0 \, \mathrm{d}$

$L_2^\mathrm{NM}   = 9.03\times 10^{-8}$ and
$L_2^\mathrm{BFGS} = 5.11\times 10^{-8}$

One could, and probably should, consider if we are just splitting hairs or
making valid refinements of the model.
Given the results and scope of the refinements, it is my assessment that
this level of refinement is relevant, particularly as recent news indicate
that immunity might be short-lived.
This introduces a new degree of freedom into the propagation of this disease,
namely a leakage of recovered individuals back to the susceptible population.

#### Denmark

Direct inspection of the raw data for the number of infected cases in Denmark
indicates at least two evident transitions occurring circa days $\sim\{67,95\}$.
While it is possible to fit truncated linear regressions to any of the
intervals defined by these dates, we find that the transitions occuring circa
day $67$, _i.e._ 2020-03-07 is not compatible with with a transition governed
by the distribution of incubation times that we are considering.

Below, we compare two truncated logistic regressions and our a regression using
two-steady state solution with data for Denmark.

The two steady state model reproduces closely the number of cases for
$t>67\,\mathrm{d}$, providing an estimate for the growth rates in these two
regions $\omega_i \simeq \{ 0.0889, \; 0.0236 \} \, \mathrm{d}^{-1}$, while the
offset and the transition times read
$t_i \simeq \{ 15.8  , \;   93.1 \} \, \mathrm{d}$.

From the growth rates, we estimate the doubling times
$\tau_2^{(i)} = \ln(2) /\omega_i =\{ 7.8, \; 29.4 \} \, \mathrm{d}$
and verify that since the transition to the last steady state, 2020-04-02,
$\tau_2 < \tau_a$.
Holding the present conditions constant, we are lead to believe that the
number of new infections will drop, but significantly slower than in South
Korea.

The respective standard errors and error margins, up to 3 significant digits,
for the parameters read
* $ \sigma_\mathrm{error}^{\omega_i}
= \{ 0.00168, \; 0.000945 \} \, \mathrm{d}^{-1}$;
* $ \sigma_\mathrm{error}^{t_i} = \{ 0.155, \; 0.33 \} \, \mathrm{d}$;
* $ \delta_\mathrm{error}^{\omega_i}
= \{ 0.00332 , \; 0.00187 \} \, \mathrm{d}^{-1}$;
* $ \delta_\mathrm{error}^{t_i} = \{ 0.306, \; 0.653 \} \, \mathrm{d}$.

#### Portugal

#### Generalized logistic

As in the Danish case, analysis of the number of cases in Portugal indicates the
presence of two transitions between different steady states.
Unlike the Danish case, both transitions can be modeled by the distribution of
incubations times.

Below, we compare how different models characterize the number of infections
in Portugal.

From the three steady state model, the growth rate coefficients read
$\omega_i \simeq \{ 0.166, \; 0.037, \; 0.00867 \} \, \mathrm{d}^{-1}$, while the
offset and transition times read
$t_i \simeq \{ 12.0, \; 24.8, \; 45.1 \} \, \mathrm{d}$, thus indicating
that the transitions occurred circa 2020-03-23 and 2020-04-06.

The doubling time estimates for Portugal
$\tau_2^{(i)} = \ln(2) /\omega_i
=\{ 4.17, \; 18.7, \; 80.0 \} \, \mathrm{d}$
are in line with those for Denmark, but remain significantly higher than in
South Korea.
In all cases, if the present conditions hold, the number of new infections
will drop as the doubling time is larger than the mean active time $\tau_a$.

The respective standard errors and error margins, up to 3 significant digits,
for the parameters read
* $ \sigma_\mathrm{error}^{\omega_i}
= \{ 0.00764, \; 0.00112, \; 0.000141 \} \, \mathrm{d}^{-1}$;
* $ \sigma_\mathrm{error}^{t_i} = \{ 0.204, \; 0.382, \; 0.413 \} \, \mathrm{d}$;
* $ \delta_\mathrm{error}^{\omega_i}
= \{ 0.0152, \; 0.00223, \; 0.00028 \} \, \mathrm{d}^{-1}$;
* $ \delta_\mathrm{error}^{t_i} = \{ 0.406, \; 0.759, \; 0.82\} \, \mathrm{d}$.

#### Limitations

The limitations are the same as those discussed in the context of the Korean
dataset, and thus an analysis based on a more general model is required.

In addition, the Portuguese authorities changed the criteria that defined the
transition from infected to recovered status on 2020-05-24, leading to a large
conversion of infected to recovered cases.
This in criteria breaks the characterization of the infected and recovered
time series.
Therefore, from this date onwards, we shall split our analysis into two parts.
The former contains data up to 2020-05-23, while the latter contains data from 2020-05-24 onwards.

Consider the following reports in the Portuguese press
[Público](https://www.publico.pt/2020/05/24/sociedade/noticia/nova-contagem-casos-traz-recorde-recuperados-covid19-1917895)
and
[Sabado](https://www.sabado.pt/portugal/detalhe/covid-19-numero-de-recuperados-vai-disparar-avisa-ministra).

#### SIR(D) models (WIP)

## 2. Brief review of the relevant differential equations

The present notebook concerns only population dynamics where the rate of change
of the population is proportional to the product of some power of the
population with a given time dependent term, ie $dn/dt \propto Q(n) P(t) $,
where $Q(n)$ is a polynomial of degree one or higher in $n$ with constant
coefficients.
Without loss of generality the differential equation that governs such processes
reads $$ \frac{dn}{dt} = F(t,n) \, ,$$ where $F(t,n)$ encodes the dependence on
$t$ and  $n$. Since we are considering a polynomial dependence on $n$ with
constant coefficients, $F(t,n)$ can be expressed by two separable functions for
$t$ and $n$, namely $F(t,n) = P(t) Q(n)$.
First order nonlinear differential equations in the form $dn/dt = P(t)Q(n)$,
known as separable first order differential equations, have straightforward
solutions in the form
$$ \int \frac{dn}{Q(n)} = \int P(t) dt + C\, : C \in \mathbb{R} \, ,$$ where
$C$ is an integration constant.
To proceed, we must define $Q(n)$ and $P(t)$. For constant environment (steady
state) problems, the rate of change has to be independent of time, ie
$dP(t)/dt=0$.
In this scenario, we find the differential equation for classic undergraduate
problems, namely the exponential and logistic growths for
$Q(n) = n$ and $Q(n) = n(1-n)$, respectively.

### Constant environment assumption
In constant environments $dP(t)/dt=0$, the differential equation reduces to
$$\int \frac{dn}{Q(n)} = \omega_o (t -t_0) \, ,$$ where we set
$P(t) = \omega_0\,;\;C=\omega_0 t_0\, :\; \omega_0, t_0 \in \mathbb{R}$.
All that is left to be done is to evaluate the indefinite integral
$\int 1/Q(n) dn$.
An easy to follow list of integrals of rational functions can be found at
[List of integrals of rational function](https://en.wikipedia.org/wiki/List_of_integrals_of_rational_functions).

#### The exponential growth/decay
The exponential growths, or decays, arise when the rate of change is linearly
proportional to the population, ie $Q(n)=n$. In this case the indefinite
integral has a trivial solution $\int dn/n = \ln (n)$, hence the solution reads
$$ \ln(n) = \omega_o(t-t_0) \Leftrightarrow n_E(t)
= \exp[ \omega_0(t-t_0)] \, , $$ the so called exponential growth/rate for
positive/negative $\omega_0$ (we introduce the subscript $E$ to identify this
particular solution).

In a naïve interpretation of the evolution of the CoViD19 infections, one might
be tempted to assume (or worst, to model and fit) an exponential growth of the
infections.
By taking the limit $$\lim_{t\to \infty} \exp[ \omega_0(t-t_0) ]
= \infty \Leftarrow \omega_0>0$$ it should become obvious that the exponential
growth will inevitably lead solutions were the infected population is larger
than the total population!
Alternatively, we can show that this solution exhibits a constant 'p-multiple'
time-scale
$$ n( t+\tau_p) = p n( t ) \Leftrightarrow \exp( \omega_0 \tau_p )
= p \Leftrightarrow \tau_p = \frac{ \ln( p ) }{ \omega_0 } \, .$$
Considering growth rates
$\omega_0 = \{ 0.1, 0.15, 0.2, 0.25 \} \, \mathrm{d}^{-1} $, the doubling times
$(p=2)$ are $ \tau_2 \simeq \{ 6.9, 4.6, 3.5, 2.8 \} \,  \mathrm{d} $.

The following natural step is the introduction of a saturation mechanism.

#### The logistic equation
The logistic equation is a simple and beautiful solution to the above-mentioned
problem, where instead of considering a rate of change of population linear
with the population, it considers the product of the population $n$ with the
remaining population $1-n$, ie $Q(n) = n(1-n)$.
Immediately, we can verify that rate of change vanishes as $n$ approaches the
total population, thus ensuring that population has a upper limit $n(t) <= 1$.

To solve the differential equation we must solve the indefinite integral
$ \int 1/Q(n) dn $ that reads
$$\int \frac{dn}{n(1-n)} = -\ln \bigg|\frac{1-n}{n}\bigg|\,.$$
In turn, the solution to the differential equation reads
$$ \begin{align}
-\ln \bigg|\frac{1-n}{n}\bigg| &= \omega_0(t-t_0) \\
\Leftrightarrow \bigg|\frac{1-n}{n}\bigg| &= \exp[ -\omega_0(t-t_0) ] \\
\Leftrightarrow \frac{1-n}{n} &= \exp[ -\omega_0(t-t_0) ] \quad \forall
\quad \omega_0,t,t_0 \in \mathbb{R} \\
\Leftrightarrow n_L(t) &= \sigma(t)= \frac{1}{1+\exp[ -\omega_0(t-t_0) ]}
= \frac{1}{1+e^{-\Omega}} \; : \Omega = \omega_0(t-t_0) \, ,
\end{align} $$
the so-called logistic equation.
We introduce the subscript $L$ to identify this particular solution and also
highlight that this is sigmoid function $\sigma(t)$, see (and references
therein)
* https://mathworld.wolfram.com/LogisticEquation.html
* https://en.wikipedia.org/wiki/Logistic_function .

As in the previous case, the sign of $\omega_o$ positive/negative defines
growth/decay.

Since the logistic growth is bounded, $ n(t) = [0,1] $, the analysis based on
$p-$multiplying time scales $\tau_p$ is, in general, flawed.
Alternatively, we can invert the solution and compute the time necessary to
reach a given fraction of the total population $n(t) = a/b$, it reads
$ t = t_0 + \omega_0 \ln[ a/(b-a)] \; \forall \; b>a \, .$
Notably, the time to reach half total population is $t_0$ and the total
population is reached asymptotically at $t = \infty$.

Notwithstanding the above-mentioned, the initial phase of population growth
$ t \ll t_0 $, conversely $n(t) \ll 1/2$, the exponential term in the logistic
function dominates and the solution can be approximated by
$n_L(t) \sim \exp[ \omega_0(t-t_0) ] = n_E(t)$.
Therefore, in this regime a $p-$multiplying time scale can be defined
$\tau_p = \ln( p )/ \omega_0$.

This solution is known to accurately characterize the growth of populations,
when the environment (parametrized via $\omega_0$) is kept constant.
This constraint is crucial, as small changes in $\omega_0$ can lead to a
drastically change the time necessary to infect large portion of the
population.

#### Growth rate coefficient $\omega_0$ (a very simple model)

As discussed in the motivation, the constant environment assumption is broken
when public health measures impose new rules and procedures designed to
mitigate the propagation of the infection.
This is not novel, it is actually well documented and policy definitely changes
the propagation rates, please refer to WHO infection prevention website for
[general](https://www.who.int/infection-prevention/en/) and
[CoViD19 specific](https://www.who.int/emergencies/diseases/novel-coronavirus-2019/technical-guidance/infection-prevention-and-control)
information.

The role of $\omega$ in logistic differential equation $dn/dt = \omega n(1-n)$
can be understood a representation of the likelihood of an infected individual
passing the infection to non-infected person.
This can then be mapped to a (very) simple model such as $\omega \propto E p$,
where $E$ is an estimate for the number of interactions between individuals
per unit of time and $p$ the probability of transmitting the infection at each
interaction.

Considering such model, it becomes evident that the growth rate will change as
governments impose restrictions, _e.g._:
* forcing people to stay at home reduces the number of interactions
* washing hands and face more frequently will likely reduce the probability of
    transmitting the infection as the virus is literally washed away from skin
* increasing the distance at which people have interactions

Is there evidence for a change in the rate? Yes, consider any of the plots
presented for South Korea, Denmark or Portugal.

### Time dependent environment
As discussed previously, we expect $d P(t)/dt = 0$ to no longer hold.
Therefore, we must define $P(t)$ and evaluate the respective integral.
There are infinitely many options to introduce an explicit time dependence on
$P(t)$.
Below, we will try to consider generalizations that obey, as far possible, the
following:
* the contanst rate model should be a limit of P(t);
* contain as few parameters as possible;
* incorporate the merits of the constant rate model;
* be compatible with real data.

#### $m$ transitions between $m+1$ steady states

The success of the constant rate logistic model suggests that even models with
simple time dependence could have potential to accurately characterize the
population growth.
A fairly simple model could consist of a series of $m+1$ steady states, each
a specific constant rate, that change discontinuously at $m$ transition times.
This model can be represented by
$$ P(t) = \omega_0 +\sum_{i=1}^m \big( \omega_i
-\omega_{i-1} \big) \Theta( t -t_i ) \; , $$
where $\Theta(t)$ is the Heaviside theta function, _i.e._ the step function
(see:
1. https://en.wikipedia.org/wiki/Heaviside_step_function;
2. https://mathworld.wolfram.com/HeavisideStepFunction.html.

This model naturally incorporates the merits of the constant rate model and can
accommodate discrete changes in policy, at the cost of adding $2m$ new
parameters ($m$ rates and $m$ times).
Furthermore, it is trivial to show that this model reduces to the constant rate
model when:
* $\omega_i =\omega_0$;
* $t_i \to \infty \; \forall \,i\geq 1$.

Finally, this model has an additional merit, the discrete change of rates would
be clearly visible in log plots, as different rates generate straight lines with
different slopes at this plotting scale.

While this model appears to tick all boxes, it always forces the rate change to
take place instantly at times $t_i$, which is not consistent with presently
available data, where data is changing smoothly.
Data from Denmark and other countries appears to include some transitions that
occur in time scales significantly short than the mean incubation time.
Nonetheless, I would not consider this as a solid argument to use this model.

In the case of an infection there is a natural mechanism that introduces a
variable delay on the propagation of the disease, the incubation time!

#### Smooth transitions and incubation times

The manifestation of infection is not immediate due to finite (distribution of)
incubation time(s).
Consequently, any change to the environment, _i.e._ the growth rate, will
necessarily be _delayed_ and also _smoothen_ by the distribution of incubation
times.

In short, from the moment that some one get infected, it take a given time
interval, $\mu_{inc}$, for the infection to manifest.
Furthermore, this incubation time is not constant, varying from case to case.

The details pertaining to disease incubation and its characterization lay
outside the scope of this project.
Hence, rather than trying to explain something that also lies outside my area
of knowledge, we refer to the follwing journal article
[*Euro Surveill. 2020;25(5):pii=2000062*](https://doi.org/10.2807/1560-7917.ES.2020.25.5.2000062).
In this article, the authors characterize the distribution of incubation times
with several distribution.
For the purposes of this project, we will start by considering only the Gamma
distribution, but other distributions might be considered in the future.

Here, we propose using the distribution of incubation times, namely its
cumulative distribution function (CDF), __as a proxy__ for the probability of
getting infected, in this model

$$ P(t) = \omega_0 +\sum_{i=1}^m \big( \omega_i
-\omega_{i-1} \big) F_X( t-t_i; \alpha, \beta ) \, $$
where $F_X( t-t_i; \alpha, \beta )$ is the CDF for the incubation times, with $X$ labeling the distribution.

>__Important notes:__
> * this model is a conjecture, even if it can be used to accurately model real
    data, it still requires evidence for its validity;
> * correlation between $F_X( t-t_i; \alpha, \beta )$ and the probability of
    transmitting infection, $p$ at a given transition, is to the best of my
    knowledge, a correlation.
    A the present moment I have little robust evidence to substantiate a causal
    relation (see the discussion: *Growth rate coefficient $\omega_0$*).

The above-mentioned article characterizes the incubation time with three
distributions, namely Weibull, Gamma
and LogNormal. In the present notebook, we consider only the Gamma distribution,
but a similar analysis can be computed with the Weibull distribution.
This reference provides the mean incubation time $\mu_{inc} = 6.5\; \mathrm{d}$
and the standard deviation $\sigma_{inc} = 2.6 \; \mathrm{d}$, from which we
determine the parametrization of the Gamma distribution, namely
$\alpha = \mu_{inc}^2/\sigma_{inc}^2 \sim 6.25 $ and
$\beta = \mu_{inc}/\sigma_{inc}^2 \sim 0.96 \; \mathrm{d}^{-1}$.

##### The Gamma distribution

The CDF for a Gamma distribution reads
$$ F_\Gamma(t;\alpha,\beta) = \gamma(\alpha, \beta t) / \Gamma(\alpha)$$
where $ \gamma(\alpha, \beta t) $ and $\Gamma(\alpha)$ are the lower incomplete
and the complete gamma functions, see:
* [*Abramovitz & Stegun* 1970 26.1.32](http://www.math.ubc.ca/~cbm/aands/abramowitz_and_stegun.pdf)
* https://en.wikipedia.org/wiki/Gamma_distribution
* https://dlmf.nist.gov/8.2
* https://dlmf.nist.gov/5.2.

The Gamma distribution is formally defined for $t \geq 0$, $\alpha>0$ and
$\beta>0$, where $t=0$ represents the instant at which an individual is
infected.
It is natural to extend the domain to negative times, by setting
$F_\Gamma(t<0;\alpha,\beta) = 0$.
This extension can lead to a discontinuities in $F_\Gamma(t;\alpha\beta)$ and
in its derivative
$\partial F_\Gamma(t;\alpha\beta)/\partial t \doteq F_\Gamma '(t;\alpha\beta)$
at $t=0$, by evaluating the limits
$\lim_{t\to 0^-} F_\Gamma  (t;\alpha,\beta) = \lim_{t\to 0^+} F_\Gamma  (t;\alpha,\beta) $
and
$\lim_{t\to 0^-} F_\Gamma '(t;\alpha,\beta) = \lim_{t\to 0^+} F_\Gamma '(t;\alpha,\beta) $
we find the additional constraint $\alpha > 1$.
The respective probability density function (PDF) is computed by deriving the
CDF, it reads
$$ f_\Gamma(t;\alpha,\beta) = \frac{ \partial F_\Gamma(t;\alpha,\beta) }{ \partial t}
= \frac{ \beta^\alpha t^{\alpha-1} e^{-\beta t} }{ \Gamma(\alpha) } \, .$$

Having defined $P(t)$, we can proceed with our calculation.
To highlight the variable number of parameters in this model, we add the
subscript $m$ to our definition, hence the integral in the time domain of the
new growth rate function reads
$$ \Omega_m(t) = \int_{-\infty}^t P(t')dt' = \omega_0(t-t_0)
+\sum_{i=1}^m \big( \omega_i -\omega_{i-1} \big)
I_\Gamma(t -t_i,\alpha,\beta) \; , $$ and the integral of the CDF is
$$ I_\Gamma(t,\alpha,\beta) = \int_{-\infty} ^t F_\Gamma(t',\alpha,\beta) dt'
= \frac{1}{\Gamma(\alpha)}\bigg[ \bigg( t-\frac{\alpha}{\beta} \bigg)\gamma(\alpha,\beta t)
+\beta^{\alpha-1} t^\alpha e^{-\beta t} \bigg] \, . $$
Despite, its nonlinear nature in the limit of large $t$, the integral of the
CDF reduces to a shifted linear funcition, namely
$ F_\Gamma(t;\alpha,\beta) \simeq t -\alpha/\beta $ when $\beta t \gg 1$.
Furthermore, this model reduces to the simple constant rate, whenever
$\omega_i = \omega_0$ or $t_i\to \infty$.

Since this model only changes the time-dependent part of the logistic equation,
$\Omega(t)$, the solution to this modified logistic differential equation, with $m+1$
constant rates, reads
$$n_m(t) = 1\big/\Big[ 1 + e^{-\Omega_m(t)} \Big]
= 1 \big /\bigg( 1 + \exp\Big[ -\omega_0(t-t_0)
-\sum_{i=1}^m \big( \omega_i -\omega_{i-1} \big) I_\Gamma(t,\alpha,\beta) \Big] \bigg) \, . $$


```python
from IPython.display import display, Markdown, Latex
import linear_systems as ls
import numpy as np
```

Using KVL in 3 meshes we have the relations:-
$$
\begin{align}
-10+10I_1+20+10(I_1-I_2)-10I_1  = 0 \\
-20 +10(I_2-I_1) + 30I_2+10(I_2-I_3) = 0 \\
-10 -20I_3-10(I_3-I_2)= 0
\end{align}
$$
In matrix form we have the following relation
$$
\begin{pmatrix}
4 & -1 & 0 \\
-1 & 5 & -1 \\
0 & 1 & -3 \\
\end{pmatrix}
\begin{pmatrix}
I_1 \\
I_2\\
I_3 \\
\end{pmatrix}
=
\begin{pmatrix}
-1 \\
2\\
1 \\
\end{pmatrix}
$$
$$

\left[\begin{array}{ccc|c}
4 & -1 & 0 &-1 \\
-1 & 5 & -1 &2\\
0 & 1 & -3& 1 \\
\end{array}\right]
$$

Using Gaussian elimination we have,

```python
A = np.array(
    [[4,-1,0],
    [-1,5,-1],
    [0,1,-3]] , dtype=float
)
b = np.array([-1,2,1])
loc= "electric_circuit.md"
(aug,steps) = ls.gauss_elim(A,b,loc)
aug = ls.gauss_jordan(aug)
display(Latex(steps))
display(Latex(ls.matrixp(aug)))
print(np.linalg.solve(A,b))
```

    Consistent system, unique solution, 3 3 3

$$\left[\begin{array}{ccc|c}
4 & -1 & 0 & -1\\
-1 & 5 & -1 & 2\\
0 & 1 & -3 & 1\\
\end{array}\right]$$

$$\left[\begin{array}{ccc|c}
1 & -0.25 & 0 & -0.25\\
-1 & 5 & -1 & 2\\
0 & 1 & -3 & 1\\
\end{array}\right]$$

$$\left[\begin{array}{ccc|c}
1 & -0.25 & 0 & -0.25\\
0 & 4.75 & -1 & 1.75\\
0 & 1 & -3 & 1\\
\end{array}\right]$$

$$\left[\begin{array}{ccc|c}
1 & -0.25 & 0 & -0.25\\
0 & 4.75 & -1 & 1.75\\
0 & 1 & -3 & 1\\
\end{array}\right]$$

$$\left[\begin{array}{ccc|c}
1 & -0.25 & 0 & -0.25\\
0 & 1 & -0.211 & 0.368\\
0 & 1 & -3 & 1\\
\end{array}\right]$$

$$\left[\begin{array}{ccc|c}
1 & -0.25 & 0 & -0.25\\
0 & 1 & -0.211 & 0.368\\
0 & 0 & -2.79 & 0.632\\
\end{array}\right]$$

$$\left[\begin{array}{ccc|c}
1 & -0.25 & 0 & -0.25\\
0 & 1 & -0.211 & 0.368\\
0 & 0 & -2.79 & 0.632\\
\end{array}\right]$$

$$\left[\begin{array}{ccc|c}
1 & -0.25 & 0 & -0.25\\
0 & 1 & -0.211 & 0.368\\
-0 & -0 & 1 & -0.226\\
\end{array}\right]$$

$$\left[\begin{array}{ccc|c}
1 & 0 & 0 & -0.17\\
0 & 1 & 0 & 0.321\\
-0 & -0 & 1 & -0.226\\
\end{array}\right]$$

    [-0.16981132  0.32075472 -0.22641509]

```python
A = np.array(
    [[4,1,1],
    [-1,2,2],
    [0,3,3]] , dtype=float
)
b = np.array([-1,2,1])
loc= "electric_circuit.md"
(aug,steps) = ls.gauss_elim(A,b,loc)
aug = ls.gauss_jordan(aug)
display(Latex(steps))
display(Latex(ls.matrixp(aug)))
print(np.linalg.solve(A,b))
```

```python
A = np.array(
    [[1,2],
    [1,1],
    [2,2]] , dtype=float
)
b = np.array([7,2,4])
(aug,steps) = ls.gauss_elim(A,b,loc)
aug = ls.gauss_jordan(aug)
display(Latex(steps))
display(Latex(ls.matrixp(aug)))
print(np.linalg.solve(A,b))
```


# 代数計算: SymPy

SymPy は、Mathematica や Maple の代替を目指して開発が進められている Python の代数計算ライブラリです。初歩的な数学の問題を解くときに重宝します。

## 準備

必要なモジュールをインポートします。

```
from sympy import *
```

Google Colabratory では、数式をmathjax で表示できるように設定しておくとよいでしょう。

```
def custom_latex_printer(exp,**options):
    from google.colab.output._publish import javascript
    url = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js"
    javascript(url=url)
    return printing.latex(exp,**options)
init_printing(use_latex="mathjax",latex_printer=custom_latex_printer)

```

## 代数計算とは

代数操作とは、数式を数値として計算することなく、シンボル(代数)の操作として計算することです。
変数などのシンボルは数値として評価されず、シンボルのまま計算されます。
計算結果は、 代数式のまま得られます。

通常のPythonの変数と区別するため、
変数は数値ではなく、Symbolを与えて初期化します。

```
x = Symbol('x')
y = Symbol('y')

x + y + y + x + x
```

$$3 x + 2 y$$

注意：これ以降は、変数$x,y$はシンボルで初期化されているものとします。

## 代数式

Symbol が含まれた式を代数式と呼ぶことにします。

```
x = Symbol('x')
y = Symbol('y')

z = x + y + y + x + x
z
```

$$3 x + 2 y$$

xもyも代数式なので、代入されたzも代数式となります。

また、代数式に対するPythonの演算は、**代数操作**となります。

```
z ** 2
```

$$\left(3 x + 2 y\right)^{2}$$

```

```

三角関数、$\pi$ などは、SymPyからインポートされたものを用います。

例. $\sin{\frac{\pi}{3}}$

```
sin(pi/3)
```

$$\frac{\sqrt{3}}{2}$$

数値解を求めたいときは、float()を用います。

例. $\sin{\frac{\pi}{3}}$

```
float(sin(pi/3))
```

$$0.8660254037844386$$

## 練習

次の式を書いてみよう

1.   $\sin{x}+\cos{y}$
2.   $e^{x}$
3.   $\frac{x+xy}{x}$

# 代数式の基本操作

数学でおなじみの基本操作をマスターしましょう。

1.   展開 (expand)
2.   因数分解(factor)
3.   簡易化 (symplify)
4.   代入 (substitution)

## 式の展開

式を展開するときは、expand()を用います。

例. $(x+y)^3$の展開

```
expand((x+y)**3)
```

$$x^{3} + 3 x^{2} y + 3 x y^{2} + y^{3}$$

部分分数への展開はapart()を用います。

例. $\frac{1}{x(x+1)}$

```
apart(1/(x*(x+1)))
```

$$- \frac{1}{x + 1} + \frac{1}{x}$$

## 因数分解

式を因数分解するときは、factor()を用います。

例. $x^3+y^3+z^3 - 3xyz$の因数分解

```
z = Symbol('z')

factor(x**3+y**3+z**3-3*x*y*z)
```

$$\left(x + y + z\right) \left(x^{2} - x y - x z + y^{2} - y z + z^{2}\right)$$

## 式の簡略化

式を簡易化するときはsimplify() を用います。

例. $\frac{x+xy}{x}$ の簡略化

```
simplify((x + x*y) / x)
```

$$y + 1$$

三角関数を含むときは、trigsimp() を用います。

例. $\cos^2{x}-\sin^2{x}$

```
trigsimp(cos(x)**2 - sin(x)**2)
```

$$\cos{\left (2 x \right )}$$

## 式の代入

式への代入は、subs()メソッドを用います。

例. $x^3+y^3+z^3-3xyz$ の $z=1$のとき

```
(x**3+y**3+z**3-3*x*y*z).subs(z, 1)
```

$$x^{3} - 3 x y + y^{3} + 1$$

# 方程式の解法

solve()を用いると、方程式の解を求めることができる。

例. $x^4=1$のxの解

```
solve(x**4-1, x)
```

$$\left [ -1, \quad 1, \quad - i, \quad i\right ]$$

連立方程式は、リストで与えます。

例. 連立方程式 $x + 5y = 2, -3x + 6y = 15$の解を求める

```
solve([x+5*y-2, -3*x+6*y-15], [x,y])
```

$$\left \{ x : -3, \quad y : 1\right \}$$

# 微積分

SymPyを使えば、微積分もお手のものです。

## 極限

極限は、limit()を用いて求めます。

例. $\lim_{x \to 0} x^x$

```
limit(x**x, x, 0)
```

$$1$$

無限大は、ooです。

例. $\lim_{x \to \infty} x^x$

```
limit(1/x, x, oo)
```

$$0$$

## 微分

微分は、diff()を用います。

例. $\frac{x}{dx}\sin{x}$

```
diff(sin(x), x)
```

$$\cos{\left (x \right )}$$

## 積分

初等関数, 特殊関数の有限無限区間での積分ができます。

例. $\int \log{x} dx$

```
integrate(log(x), x)
```

$$x \log{\left (x \right )} - x$$

例. 例. $\int_{-\infty}^\infty  e^{-x^2} dx$

```
integrate(exp(-x**2), (x, -oo, oo))
```

$$\sqrt{\pi}$$

### 練習

1.   $\int_{-\infty}^\infty \sin{x^2} dx$ を求めよ
2.   $\int_{0}^\infty  (\sqrt{x^2+1}-1)^2 dx$ を求めよ

# 総合問題

1. $f(x)=\frac{4x^2+2x+1}{x^2+1}$の極値を求めよ


# Task One : Fundamentals of Quantum Computation

## Basic Arithmetic Operations & Complex Numbers

```python
#Adding Two Numbers
x=2+5
print(x)
```

    7

```python
#Subtracting Two Numbers
y=8-5
print(y)
```

    3

```python
#Multiplying Two Numbers
a=3*5
print(a)
```

    15

```python
#Dividing Two Numbers
b=8/4
print(b)
```

    2.0

```python
#Calculate the Remainder
r=4%3
print(r)
```

    1

```python
#Exponent of a Number
e=5**3
print(e)
```

    125

Complex numbers are always of the form

\begin{align}
\alpha = a + bi
\end{align}

```python
c1=1j*1j
print(c1)
```

    (-1+0j)

```python
#Initialize two complex numbers
z=4+8j
w=5-5j
```

```python
import numpy as np
```

```python
print("Real part of z is:", np.real(z))
```

    Real part of z is: 4.0

```python
print("Imaginary part of w is:", np.imag(w))
```

    Imaginary part of w is: -5.0

```python
#Add two complex numbers
add=z+w
print(add)
```

    (9+3j)

```python
#Subtract two complex numbers
sub=z-w
print(sub)
```

    (-1+13j)

## Complex conjugate

```python
#complex conjugate of w
print(w)
print("Complex conjugate of w is:", np.conj(w))
```

    (5-5j)
    Complex conjugate of w is: (5+5j)

## Norms/Absolute Values

\begin{align} ||z|| &= \sqrt{zz^*} = \sqrt{|z|^2},\\ ||w|| &= \sqrt{ww^*} = \sqrt{|w|^2}, \end{align}

```python
#Calculating absolute values for z and w
print(z)
print("Norm/Absolute value of z is:", np.abs(z))
print(w)
print("Norm/Absolute value of w is:", np.abs(w))
```

    (4+8j)
    Norm/Absolute value of z is: 8.94427190999916
    (5-5j)
    Norm/Absolute value of w is: 7.0710678118654755

## Row Vectors, Column Vectors, and Bra-Ket Notation

\begin{align} \text{Column Vector:} \ \begin{pmatrix}
a_1 \\ a_2 \\ \vdots \\ a_n
\end{pmatrix}
\quad \quad \text{Row Vector:} \ \begin{pmatrix}
a_1, & a_2, & \cdots, & a_n
\end{pmatrix} \end{align}

```python
#Initialize a row vector
row_vector=np.array([1, 2+2j, 3])
print(row_vector)
```

    [1.+0.j 2.+2.j 3.+0.j]

```python
#Initialize a column vector
column_vector=np.array([[1],[2+2j],[3j]])
print(column_vector)
```

    [[1.+0.j]
     [2.+2.j]
     [0.+3.j]]

Row vectors in quantum mechanics are also called **bra-vectors**, and are denoted as follows:

\begin{align} \langle A| = \begin{pmatrix}
a_1, & a_2, \cdots, & a_n
\end{pmatrix} \end{align}

Column vectors are also called **ket-vectors** in quantum mechanics denoted as follows:

\begin{align} |B\rangle = \begin{pmatrix}
b_1 \\ b_2 \\ \vdots \\ b_n
\end{pmatrix} \end{align}

In general, if we have a column vector, i.e. a ket-vector:

\begin{align} |A\rangle = \begin{pmatrix}
a_1 \\ a_2 \\ \vdots \\ a_n
\end{pmatrix} \end{align}

the corresponding bra-vector:

\begin{align} \langle A| = \begin{pmatrix}
a_1^*, & a_2^*, & \cdots, & a_n^*
\end{pmatrix} \end{align}

## Inner Product

\begin{align} \langle A| = \begin{pmatrix}
a_1, & a_2, & \cdots, & a_n
\end{pmatrix}, \quad \quad
|B\rangle = \begin{pmatrix}
b_1 \\ b_2 \\ \vdots \\ b_n
\end{pmatrix} \end{align}

Taking the inner product of $\langle A|$ and $|B\rangle$ gives the following:

\begin{align} \langle A| B \rangle &= \begin{pmatrix}
a_1, & a_2, & \cdots, & a_n
\end{pmatrix}
\begin{pmatrix}
b_1 \\ b_2 \\ \vdots \\ b_n
\end{pmatrix}\\
&=
a_1b_1 + a_2b_2 + \cdots + a_nb_n\\
&= \sum_{i=1}^n a_ib_i
\end{align}

```python
# Define the 4x1 matrix version of a column vector
A=np.array([[1],[4-5j],[5],[-3]])
```

```python
# Define B as a 1x4 matrix
B=np.array([1, 5, -4j, -1j])
```

```python
# Compute <B|A>
np.dot(B,A)
```

    array([21.-42.j])

## Matrices

\begin{align}
M = \begin{pmatrix}
2-i & -3 \\
-5i & 2
\end{pmatrix}
\end{align}

```python
M=np.array([[2-1j, -3], [-5j, 2]])
#Note how the brackets are placed!
print(M)
```

    [[ 2.-1.j -3.+0.j]
     [-0.-5.j  2.+0.j]]

```python
M=np.matrix([[2-1j, 3],[-5j, 2]])
print(M)
```

    [[ 2.-1.j  3.+0.j]
     [-0.-5.j  2.+0.j]]

Hermitian conjugates are given by taking the conjugate transpose of the matrix

```python
#To calculate hermitian matrix simple follow: <your matrix>.H
hermitian = M.H
print(hermitian)
```

    [[ 2.+1.j -0.+5.j]
     [ 3.-0.j  2.-0.j]]

## Tensor Products of Matrices

\begin{align}
\begin{pmatrix}
a & b \\
c & d
\end{pmatrix} \otimes
\begin{pmatrix}
x & y \\
z & w
\end{pmatrix} =
\begin{pmatrix}
a \begin{pmatrix}
x & y \\
z & w
\end{pmatrix} & b \begin{pmatrix}
x & y \\
z & w
\end{pmatrix} \\
c \begin{pmatrix}
x & y \\
z & w
\end{pmatrix} & d \begin{pmatrix}
x & y \\
z & w
\end{pmatrix}
\end{pmatrix} =
\begin{pmatrix}
ax & ay & bx & by \\
az & aw & bz & bw \\
cx & cy & dx & dy \\
cz & cw & dz & dw
\end{pmatrix}
\end{align}

```python
#Use the np.kron method
np.kron(M,M)
```

    matrix([[  3. -4.j,   6. -3.j,   6. -3.j,   9. +0.j],
            [ -5.-10.j,   4. -2.j,   0.-15.j,   6. +0.j],
            [ -5.-10.j,   0.-15.j,   4. -2.j,   6. +0.j],
            [-25. +0.j,   0.-10.j,   0.-10.j,   4. +0.j]])

# That's all for Task 1
## Thank You!

Where can you find me?

LinkedIn : https://www.linkedin.com/in/arya--shah/

Twitter : https://twitter.com/aryashah2k

Github : https://github.com/aryashah2k

If you Like My Work, Follow me/ Connect with me on these platforms.
Show some Love ❤ Sponsor me on Github!

```python

```

Guided Project - Programming a Quantum Computer with Qiskit - IBM SDK/Task 1/Task 1 - Fundamentals of Quantum Computation.ipynb