```python
import distmesh
from IPython.display import HTML
import ipywidgets
import itertools
import math
from matplotlib import pyplot, animation
from mpl_toolkits.mplot3d import Axes3D
import numpy
import sympy
%matplotlib inline
```

```python
pyplot.rc('font', family='serif', size=16)
```

```python
def rolling(t, A, f):
    """Return the instantaneous rolling angle.

    Parameters
    ----------
    t : float
        Time.
    A : float
        Rolling amplitude.
    f : float
        Frequency.

    Returns
    -------
    phi : float
        Rolling angle (in radians).

    """
    phi = -A * numpy.cos(2 * numpy.pi * f * t)
    return phi

def rolling_angular_velocity(t, A, f):
    """Return the instantaneous rolling angular velocity.

    Parameters
    ----------
    t : float
        Time.
    A : float
        Rolling amplitude.
    f : float
        Frequency.

    Returns
    -------
    phi_dot : float
        Angular velocity.

    """
    w = 2 * numpy.pi * f
    phi_dot = w * A * numpy.sin(w * t)
    return phi_dot

def pitching(t, A, f, psi, bias=0.0):
    """Return the instantaneous pitching angle.

    Parameters
    ----------
    t : float
        Time.
    A : float
        Pitching amplitude.
    f : float
        Frequency.
    psi : float
        Phase difference (in radians) between pitching and rolling.
    bias : float (optional)
        Static pitching bias angle (in radians); default: 0.0.

    Returns
    -------
    theta : float
        Pitching angle (in radians).

    """
    theta = -A * numpy.cos(2 * numpy.pi * f * t + psi) + bias
    return theta

def pitching_angular_velocity(t, A, f, psi):
    """Return the instantaneous pitching angular velocity.

    Parameters
    ----------
    t : float
        Time.
    A : float
        Pitching amplitude.
    f : float
        Frequency.
    psi : float
        Phase difference (in radians) between pitching and rolling.

    Returns
    -------
    theta_dot : float
        Angular velocity.

    """
    w = 2 * numpy.pi * f
    theta_dot = w * A * numpy.sin(w * t + psi)
    return theta_dot
```

```python
# Parameters.
c = 1.0  # chord length
AR = 1.27  # aspect ratio (AR = 1.27 --> S / c = 1)
S = math.pi * c * AR / 4  # span
A_plan = math.pi * c * S / 4  # planform area of the plate

A_phi = numpy.radians(45.0)  # rolling amplitude
print('Rolling amplitude: A_phi = {}'.format(A_phi))

A_theta = numpy.radians(45.0)  # pitching amplitude
# A_theta = 0.0
print('Pitching amplitude: A_theta = {}'.format(A_theta))

psi = numpy.radians(90.0)  # phase difference
print('Phase difference: psi = {}'.format(psi))

theta_bias = numpy.radians(0.0)  # static pitching bias
print('Pitching bias: theta_bias = {}'.format(theta_bias))

U_inf = 1.0  # freestream velocity
R_avg = S / 2  # average rotational radius
St = 0.6  # Strouhal number
f = St * U_inf / (2 * A_phi * R_avg)  # flapping frequency
print('Flapping frequency: f = {}'.format(f))
T = 1 / f  # time period
print('Time period: T = {}'.format(T))

Re = 200.0  # Reynolds number
nu = U_inf * c / Re  # kinematic viscosity
print('Kinematic viscosity: nu = {}'.format(nu))
```

```python
t = numpy.linspace(3.75 * T, 4.75 * T, num=50)

phi = rolling(t, A_phi, f)
theta = pitching(t, A_theta, f, psi, bias=theta_bias)

w_phi = rolling_angular_velocity(t, A_phi, f)
w_theta = pitching_angular_velocity(t, A_theta, f, psi)

fig, ax = pyplot.subplots(figsize=(6.0, 6.0))
ax.grid()
ax.set_xlabel('t / T')
ax.set_ylabel('Plate Rotation ($^o$)')
ax.plot(t / T, numpy.degrees(phi), label='Rolling')
ax.plot(t / T, numpy.degrees(theta), label='Pitching')
ax.legend()
ax.set_xticks([3.75, 4.0, 4.25, 4.5, 4.75])
ax.set_ylim(-60.0, 60.0)
ax2 = ax.twinx()
ax2.set_ylabel('Angular velocity (---)')
ax2.plot(t / T, w_phi, linestyle='--')
ax2.plot(t / T, w_theta, linestyle='--');
```

```python
hook = [0.0, 0.0, 0.0]  # fixed center of rotation

# Create distance function.
a, b = c / 2, S / 2  # semi axes of ellipse
xc, yc, zc = hook[0], hook[1], hook[2] + b
fd = lambda p: ((p[:, 0] - xc)**2 / a**2 +
                (p[:, 1] - zc)**2 / b** 2 - 1)

# Discretize the ellipse.
ds = 0.05  # mesh resolution
bbox = (xc - a, zc - b, xc + a, zc + b)  # bounding box
p, t = distmesh.distmesh2d(fd, distmesh.huniform, ds, bbox, fig=None)

x0, z0 = p[:, 0], p[:, 1]
y0 = numpy.zeros_like(x0)

# Plot the discrete points.
fig, ax = pyplot.subplots(figsize=(6.0, 6.0))
ax.grid()
ax.set_xlabel('x')
ax.set_ylabel('z')
ax.scatter(x0, z0, s=10)
ax.axis('scaled', adjustable='box');
```

```python
def rotation(x, y, z,
             roll=0.0, yaw=0.0, pitch=0.0, center=[0.0, 0.0, 0.0]):
    """Rotate point.

    Parameters
    ----------
    x : float
        x-coordinate of point.
    y : float
        y-coordinate of point.
    z : float
        z-coordinate of point.
    roll : float (optional)
        Roll angle (in radians); default: 0.0.
    yaw : float (optional)
        Yaw angle (in radians); default: 0.0.
    pitch : float (optional)
        Pitch angle (in radians); default: 0.0.
    center : list of floats
        Coordinates of the center of rotation;
        default: [0.0, 0.0, 0.0].

    Returns
    -------
    xr : float
        x-coordinate of rotated point.
    yr : float
        y-coordinate of rotated point.
    zr : float
        z-coordinate of rotated point.

    """
    center = numpy.array(center)
    Rx = numpy.array([[1.0, 0.0, 0.0],
                      [0.0, math.cos(roll), math.sin(roll)],
                      [0.0, -math.sin(roll), math.cos(roll)]])
    Ry = numpy.array([[math.cos(yaw), 0.0, math.sin(yaw)],
                      [0.0, 1.0, 0.0],
                      [-math.sin(yaw), 0.0, math.cos(yaw)]])
    Rz = numpy.array([[math.cos(pitch), math.sin(pitch), 0.0],
                      [-math.sin(pitch), math.cos(pitch), 0.0],
                      [0.0, 0.0, 1.0]])
    point = numpy.array([x, y, z])
    new = Rx.dot(Ry.dot(Rz.dot(point - center))) + center
    xr, yr, zr = new
    return xr, yr, zr

vrotation = numpy.vectorize(rotation,
                            excluded=['roll', 'yaw', 'pitch',
                                      'center'])
```

```python
sympy.init_printing(use_unicode=True)

c_a, s_a = sympy.symbols('c_theta s_theta')  # pitch
c_c, s_c = sympy.symbols('c_phi s_phi')  # roll
c_b, s_b = 1.0, 0.0

Rx = sympy.Matrix([[1, 0, 0], [0, c_c, s_c], [0, -s_c, c_c]])
Ry = sympy.Matrix([[1, 0, 0], [0, 1, 0], [0, 0, 1]])
Rz = sympy.Matrix([[c_a, s_a, 0], [-s_a, c_a, 0], [0, 0, 1]])
```

```python
Rx * Ry * Rz
```

```python
def velocity(x, y, z, phi_dot, theta_dot, phi,
             center=[0.0, 0.0, 0.0]):
    """Compute the velocity on the body.

    The center of rotation is assumed to be fixed in time.

    Parameters
    ----------
    x : numpy.ndarray
        x-coordinate of the body.
    y : numpy.ndarray
        y-coordinate of the body.
    z : numpy.ndarray
        z-coordinate of the body.
    phi_dot : float
        Angular velocity of the rolling motion.
    theta_dot : float
        Angular velocity of the pitching motion.
    center : list of floats (optional)
        Center of rotation; default: [0.0, 0.0, 0.0].

    Returns
    -------
    ux : numpy.ndarray
        The x-component of the velocity.
    uy : numpy.ndarray
        The y-component of the velocity.
    uz : numpy.ndarray
        The z-component of the velocity.

    """
    xc, yc, zc = center
    ux = -theta_dot * (numpy.sin(phi) * (z - zc) -
                      numpy.cos(phi) * (y - yc))
    uy = -theta_dot * numpy.cos(phi) * (x - xc) + phi_dot * (z - zc)
    uz = +theta_dot * numpy.sin(phi) * (x - xc) - phi_dot * (y - yc)
    return ux, uy, uz
```

```python
pyplot.rc('font', family='serif', size=12)
fig = pyplot.figure(figsize=(6.0, 6.0))
ax = fig.gca(projection='3d')
ax.set_xlabel('x')
ax.set_ylabel('z')
ax.set_zlabel('y')

t = 0.0
roll = rolling(t, A_phi, f)
pitch = pitching(t, A_theta, f, psi)
x, y, z = vrotation(x0, y0, z0,
                    roll=roll, yaw=0.0, pitch=pitch,
                    center=hook)
title = ax.set_title('t / T = {}'.format(t / T))
ax.scatter(*hook, depthshade=False, c='C3', marker='o', s=80)
sc = ax.scatter(x, z, y, depthshade=False, c='C1', marker='.', s=10)

phi_dot = rolling_angular_velocity(t, A_phi, f)
theta_dot = pitching_angular_velocity(t, A_theta, f, psi)
ux, uy, uz = velocity(x, y, z, phi_dot, theta_dot, roll, center=hook)
start, end, s = 0, -1, 4
q = ax.quiver(x[start:end:s], z[start:end:s], y[start:end:s],
              ux[start:end:s], uz[start:end:s], uy[start:end:s],
              normalize=False, length=0.2)

ax.set_xlim(-c, c)
ax.set_ylim(2 * c, 0.0)
ax.set_zlim(-c, c)
ax.view_init(elev=0.0, azim=-90.0)
# Draw cube.
d1, d2, d3 = c, S, 2 * S * math.cos(A_phi)
r1 = [-d1 / 2, d1 / 2]
r2 = [0.0, d2]
r3 = [-d3 / 2, d3 / 2]
points = numpy.array(list(itertools.product(r1, r2, r3)))
for s, e in itertools.combinations(points, 2):
    v = numpy.sum(numpy.abs(s - e))
    if v == d1 or v == d2 or v == d3:
        ax.plot3D(*zip(s, e), color='black', linestyle=':')

fig.tight_layout();
```

```python
def update_figure(n, title, sc, q, display_fig=False):
    """Update Matplotlib Figure at given time-step index.

    Parameters
    ----------
    n : integer
        Time-step index.
    title : matplotlib.text.Text
       Title of the figure.
    sc : mpl_toolkits.mplot3d.art3d.Path3DCollection
        Scatter object to display the body markers.
    q : mpl_toolkits.mplot3d.art3d.Line3DCollection
        Quiver to display the velocity vectors.
    display_fig : boolean (optional)
        Set to True to display the figure in the Notebook;
        default: False.

    """
    dt = T / (nt_per_period - 1)  # time-step size
    t = n * dt  # time values
    # Rotate the geometry.
    roll = rolling(t, A_phi, f)
    pitch = pitching(t, A_theta, f, psi)
    x, y, z = vrotation(x0, y0, z0,
                        roll=roll, yaw=0.0, pitch=pitch,
                        center=hook)
    phi_dot = rolling_angular_velocity(t, A_phi, f)
    theta_dot = pitching_angular_velocity(t, A_theta, f, psi)
    ux, uy, uz = velocity(x, y, z, phi_dot, theta_dot, roll, center=hook)

    # Update the figure.
    sc._offsets3d = (x, z, y)
    del(ax.collections[-1])
    start, end, s = 0, -1, 4
    q = ax.quiver(x[start:end:s], z[start:end:s], y[start:end:s],
                  ux[start:end:s], uz[start:end:s], uy[start:end:s],
                  normalize=False, length=0.2)
    title.set_text('t / T = {}'.format(t / T))

    if display_fig:
        display(fig)
```

```python
# Set temporal parameters.
nt_per_period = 17  # number of time steps per period
n_periods = 5  # number of periods
nt = (nt_per_period - 1) * n_periods + 1  # total number of time steps

interval_per_period = 1000.0  # animation interval per period (ms)
interval = interval_per_period / (nt_per_period - 1)
```

```python
# Create and display the interactive widgets.
n_slider = ipywidgets.IntSlider(value=0, min=0, max=nt - 1, step=1,
                                description='Step')
w = ipywidgets.interactive(update_figure, n=n_slider,
                           display_fig=ipywidgets.fixed(True),
                           title=ipywidgets.fixed(title),
                           sc=ipywidgets.fixed(sc),
                           q=ipywidgets.fixed(q))
display(w)
```

```python
# Create and display Matplotlib animation.
anim = animation.FuncAnimation(fig, update_figure,
                               frames=nt, interval=interval,
                               fargs=(title, sc, q))
HTML(anim.to_html5_video())
```

---


# The Discrete Fourier Transform

*This Jupyter notebook is part of a [collection of notebooks](../index.ipynb) in the bachelors module Signals and Systems, Comunications Engineering, Universität Rostock. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Definition

The [discrete Fourier transform](https://en.wikipedia.org/wiki/Discrete_Fourier_transform) (DFT) is defined as

\begin{equation}
X[\mu] = \sum_{k=0}^{N-1} x[k] \; e^{-j \mu \frac{2 \pi}{N} k}
\end{equation}

where $N \in \mathbb{N}$ denotes the length of the DFT and $\mu \in \mathbb{Z}$ the discrete frequency. The DFT maps a discrete signal $x[k]$ to its discrete spectrum $X[\mu] = \text{DFT}_N \{ x[k] \}$. Since its kernel $e^{-j \mu \frac{2 \pi}{N} k}$ is periodic with period $N$ with respect to the discrete frequency $\mu$, the spectrum $X[\mu]$ is periodic with period $N$

\begin{equation}
X[\mu] = X[\mu + \nu N]
\end{equation}

for $\nu \in \mathbb{Z}$. It follows from the periodicity that the unique range for $\mu = 0,1, \dots, N-1$. For $k = 0,1, \dots, N-1$ the inverse discrete Fourier transform (IDFT) is consequently defined as

\begin{equation}
x[k] = \frac{1}{N} \sum_{\mu=0}^{N-1} X[\mu] \; e^{j \mu \frac{2 \pi}{N} k}
\end{equation}

The kernel $e^{j \mu \frac{2 \pi}{N} k}$ of the IDFT is periodic with respect to the sample index $k$. It follows that the IDFT $x[k] = \text{IDFT}_M \{ X[\mu] \}$ is periodic

\begin{equation}
x[k] = x[k + \nu N]
\end{equation}

Although $k$ and $\mu$ can take any integer value it can be concluded from the periodicity, that the DFT is only unique for $k, \mu = 0, 1, \dots N-1$. As shown later, the periodicity of the DFT and IDFT has wide-ranging consequences for the properties and theorems of the DFT.

The kernel of the DFT is often represented in terms of the primitive $N$-th [root of unity](https://en.wikipedia.org/wiki/Root_of_unity) abbreviated by $w_N = e^{-j \frac{2 \pi}{N}}$

\begin{equation}
X[\mu] = \sum_{k=0}^{N-1} x[k] \; w_N^{\mu k}
\end{equation}

The kernel of the IDFT is given by reversing the sign of the exponent of $w_N$. Other common definitions of the DFT and IDFT may be found in the literature. They differ with respect to the sign of the exponent and the normalization factor $\frac{1}{N}$. As a consequence, the properties, theorems and transforms may differ from the ones given here.

### Link to the Discrete-Time Fourier Transform

The [discrete-time Fourier transform](../discrete_time_fourier_transform/definition.ipynb) (DTFT) $X(e^{j \Omega}) = \mathcal{F}_* \{ x[k] \}$ of a causal signal of finite length $x[k] = 0$ for $k \in \mathbb{Z} \setminus \{0,1,\dots, N-1 \}$ is continuous with respect to the normalized angular frequency $\Omega$. The DFT $X[\mu]$ of the same signal is discrete. In order to derive the relation between both transforms, the definition of the DTFT is compared with the definition of the DFT. This reveals that both are linked by

\begin{equation}
X[\mu] = X(e^{j \Omega}) \big\rvert_{\Omega = \mu \frac{2 \pi}{N}}
\end{equation}

The DFT $X[\mu]$ of a finite-length discrete signal $x[k]$ can be derived by sampling its DTFT $X(e^{j \Omega})$ equidistantly at $\Omega = \mu \frac{2 \pi}{N}$.

The questions arises if the DFT $X[\mu]$ fully represents a discrete signal $x[k]$ of finite length? In order to elaborate on this, the DTFT of a discrete signal is investigated. The [DTFT of a discrete signal is periodic](../discrete_time_fourier_transform/definition.ipynb#Definition) with period $2 \pi$. This holds also for a discrete signal $x[k]$ of finite length. This is depicted below

The [spectrum of a periodic signal](../periodic_signals/spectrum.ipynb) consists of a series of weighted Dirac impulses. It can be described fully by the weights of the Dirac impulses. The same holds also for the spectrum $X_\text{p} (e^{j \Omega})$ of the periodic continuation $x_\text{p}[k] = \sum_{\nu = - \infty}^{\infty} x[k - \nu N]$ of the finite length signal $x[k]$. The weights of the Dirac pulses are given by equidistant sampling of the spectrum of one period $X(e^{j \Omega})$ at $\Omega = \mu \frac{2 \pi}{N}$. This is depicted below

It can be concluded from above reasoning that the DFT of a causal finite-length discrete signal contains all information on the signal.

### Interpretation of the Spectrum

Inspection of the definition of the inverse DFT reveals, that a discrete signal $x[k]$ of finite length can be represented as a superposition of weighted harmonic exponential signals $z^k$ with the complex frequencies $z = e^{j \mu \frac{2 \pi}{N}}$ for $\mu = 0, 1, \dots, N-1$. The corresponding normalized angular frequencies of the exponential signal are given as $\Omega = \mu \frac{2 \pi}{N}$. In the following, the complex frequencies $z$ are illustrated in the $z$-plane for the case of an even ($N=10$) and odd length ($N=11$) of the DFT

The distribution of the complex frequencies in the $z$-plane is different for an even or odd length of the DFT. Only in the case of an even length, the normalized frequency $\Omega = \pi$ is included. For a further interpretetation of the discrete frequency $\mu$, the link between the DFT and the DTFT is used. The DFT $X[\mu]$ can be derived from the DTFT by sampling $X(e^{j \Omega})$ at $\Omega = \mu \frac{2 \pi}{N}$. The DTFT $X(e^{j \Omega})$ is sampled in the range of $0 \leq \Omega < 2 \pi$. Since the DTFT is periodic with a period of $2 \pi$, it can be conluded that the DTFT $X(e^{j \Omega})$ for $\pi < \Omega < 2 \pi$ is equal to the DTFT for $-\pi < \Omega < 0$. It follows, that the DFT $X[\mu]$ for $\frac{N}{2} < \mu \leq (N-1)$ is equal to the sampled DTFT for [negative normalized angular frequencies](https://en.wikipedia.org/wiki/Negative_frequency) $-\pi < \Omega < 0$. The resulting normalized frequency axis of the DFT is illustrated for the case of an even length ($N=10$)

For a discrete signal which has been derived by sampling from a continuous signal $x[k] = x(k T)$, the normalized angular frequency $\Omega$ can be related to the frequency $f$ by $\Omega = 2 \pi \frac{f}{f_\text{s}}$, where $T$ and $f_\text{s} = \frac{1}{T}$ denote the sampling interval and frequency, respectively. The lowest axis illustrates the resulting frequency axis. It can be concluded that the first half of the spectrum represents the complex values of $X[\mu]$ for increasing positive frequencies, while the second half represents the complex values for decreasing negative frequencies. This structure has to be kept in mind when interpreting a DFT.

The DFT of a discrete signal $x[k] \in \mathbb{C}$ is in general complex valued, $X[\mu] \in \mathbb{C}$. It is commonly illustrated by its real and imaginary part

\begin{equation}
X[\mu] = \Re \{ X[\mu] \} + j \cdot \Im \{ X[\mu] \}
\end{equation}

or by its magnitude and phase

\begin{equation}
X[\mu] = |X[\mu]| \cdot e^{j \varphi[\mu]}
\end{equation}

The magnitude spectrum $|X[\mu]|$ provides insights into the superposition of a discrete signal in terms of the level of its harmonic contributions. It is therefore a valuable tool for the analysis of signals and systems.

### Matrix/Vector Representation

Above definition of the DFT is given in terms of a summation over the samples $x[k]$ for $k=0, 1, \dots, N-1$. The summation has to be evaluated for $\mu = 0, 1, \dots, N-1$ in order to yield the DFT $X[\mu] = \text{DFT}_N \{ x[k] \}$. The calculation can be represented in terms of a matrix/vector multiplication. First the $N$ samples of the signal $x[k]$ are combined into the $N \times 1$ vector

\begin{equation}
\mathbf{x} = \left[ x[0], x[1], \dots, x[N-1] \right]^T
\end{equation}

In the same manner, the $N$ samples of the spectrum $X[\mu]$ are combined  into the $N \times 1$ vector

\begin{equation}
\mathbf{X} = \left[ X[0], X[1], \dots, X[N-1] \right]^T
\end{equation}

The computation of all $N$ values of $X[\mu]$ from all $N$ values of $x[k]$ can now be expressed as matrix/vector product

\begin{equation}
\mathbf{X} = \mathbf{F} \mathbf{x}
\end{equation}

where the $N \times N$ [DFT matrix](https://en.wikipedia.org/wiki/DFT_matrix) is defined as

\begin{equation}
\mathbf{F} = \left[ \begin{array}{ccccc}
1 & 1 & 1 & \dots & 1 \\
1 & w_N & w_N^2 & \dots & w_N^{N-1} \\
1 & w_N^2 & w_N^6 & \dots & w_N^{2 (N-1)} \\
\vdots & \vdots & \vdots & \ddots & \vdots \\
1 & w^{N-1} & w^{2(N-1)} & \dots & w^{(N-1)(N-1)}
\end{array} \right]
\end{equation}

Above DFT matrix is, besides the scaling factor $N$, [unitary](https://en.wikipedia.org/wiki/Unitary_matrix)

\begin{equation}
\mathbf{F} \, \mathbf{F}^H = N \cdot \mathbf{I}
\end{equation}

where $(\cdot)^H$ denotes the [Hermitian](https://en.wikipedia.org/wiki/Hermitian_adjoint) (conjugate transpose) of a matrix and $\mathbf{I}$ the $N \times N$ [identity matrix](https://en.wikipedia.org/wiki/Identity_matrix). Multiplying the left- and right-hand side of above matrix/vector representation of the DFT from the left by $\frac{1}{N} \mathbf{F}^H$ yields the representation of the inverse DFT as

\begin{equation}
\mathbf{x} = \frac{1}{N} \cdot \mathbf{F}^H \mathbf{X}
\end{equation}

The matrix/vector representation of the DFT is useful for the discussion of its properties and its implementation. Functions for the computation of the DFT matrix are provided in many toolkits for numerical mathematics. In the following the Python function [`scipy.linalg.dft`](http://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.dft.html) is used. The real part of $w_N^\mu$ for $\mu=0, 1, \dots, N-1$ is plotted for illustration of the kernel of the DFT. The application of the matrix/vector representation is shown in the following section.

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import dft

F = dft(8)

plt.figure(figsize=(10, 10))
for n in range(8):
    ax = plt.subplot(4, 2, n+1)
    ax.stem(np.real(F[:, n]))
    plt.title('$\mu = %d$'%n)
    plt.ylabel(r'$\Re \{ e^{-j \mu \frac{2 \pi}{N} k} \}$')
    plt.xlabel('$k$')
plt.tight_layout()
```

### Transformation of the Dirac Impulse

The $\text{DFT}_N \{ \delta[k] \}$ of the [Dirac impulse](../discrete_signals/standard_signals.ipynb#Dirac-Impulse) is derived by introducing $\delta[k]$ into the definition of the DFT and exploiting the sifting property of the Dirac impulse

\begin{equation}
\text{DFT}_N \{ \delta[k] \} = \sum_{k = 0}^{N-1} \delta[k] \, e^{-j \mu \frac{2 \pi}{N} k} = 1
\end{equation}

The transform of the Dirac impulse is equal to one. Hence, all discrete frequencies $\mu$ are present with equal weight. This is an important property in the theory of discrete signals and systems, since the Dirac impulse is used to characterize linear time-invariant (LTI) systems by their [impulse response](../discrete_systems/impulse_response.ipynb) $h[k] = \mathcal{H} \{ \delta[k] \}$.

**Exercise**

* Derive the $\text{DFT}_N \{ \delta[k - \kappa] \}$ of the shifted Dirac impulse for $0 \leq \kappa < N$.

### Transformation of the Exponential Signal

The DFT $X[\mu] = \text{DFT}_N \{ e^{j \Omega_0 k} \}$ of the [harmonic exponential signal](../discrete_signals/standard_signals.ipynb#Complex-Exponential-Signal) with normalized frequency $\Omega_0$ is derived by introducing it into the definition of the DFT. This results in

\begin{equation}
X[\mu] = \sum_{k=0}^{N-1} e^{j \Omega_0 k} \; e^{-j \mu \frac{2 \pi}{N} k}
= \sum_{k=0}^{N-1} e^{j k (\Omega_0 - \mu \frac{2 \pi}{N})}
= \frac{1 - e^{j (\Omega_0 - \mu \frac{2 \pi}{N}) N}}{1 - e^{j (\Omega_0 - \mu \frac{2 \pi}{N})}}
= e^{-j (\Omega_0 - \mu \frac{2 \pi}{N} ) \frac{N-1}{2} } \cdot \frac{\sin \left( \frac{N (\Omega_0 - \mu \frac{2\pi}{N})}{2} \right)}{\sin \left( \frac{ \Omega_0 - \mu \frac{2\pi}{N}}{2} \right)}
\end{equation}

The second last equality has been derived by noting that the sum constitutes a [finite geometrical series](https://en.wikipedia.org/wiki/Geometric_series) with the common ratio $e^{-j (\Omega - \mu \frac{2 \pi}{N})}$.

Unlike the [DTFT of the exponential signal](../discrete_time_fourier_transform/definition.ipynb#Transformation-of-the-Exponential-Signal), its DFT is in general not given by a series of shifted Dirac impulses. In order to gain further insight, let's consider an harmonic exponential signal which is periodic with period $N$. The lowest normalized frequency such a periodic signal can have is $\Omega_\text{min} =\frac{2 \pi}{N}$. Note that an exponential signal whose frequency is a multiple of $\Omega_\text{min}$ is also periodic with period $N$. It can be concluded that the normalized frequency of an harmonic exponential signal with period $N$ is given as $\Omega_0 = M \cdot \frac{2 \pi}{N}$ for $M = 0,1,\dots, N-1$. Introducing this specific choice for $\Omega_0$ into the DFT above yields

\begin{equation}
\text{DFT}_N \{ e^{j M \cdot \frac{2 \pi}{N} k} \} = N \cdot \delta[\mu - M]
\end{equation}

since

\begin{equation}
\frac{\sin \left(\pi (M-\mu) \right)}{\sin \left(\frac{\pi}{N} (M - \mu) \right)} = \begin{cases}
N & \text{for } M=\mu \\
0 & \text{otherwise}
\end{cases}
\end{equation}

Note that [L'Hôpital's rule](https://en.wikipedia.org/wiki/L'H%C3%B4pital's_rule) was applied for $M=\mu$. The DFT of an harmonic exponential signal which is periodic with period $N$ is given as a Dirac impulse. The case of an aperiodic exponential signal is discussed in the following example.

**Example**

In the following example the DFT of an harmonic exponential signal $e^{j \Omega_0 k}$ with $\Omega_0 = M \cdot \frac{2 \pi}{N}$ is computed by the matrix/vector representation of the DFT. The signal is periodic with period $N$ for $M \in \mathbb{Z}$ and aperiodic for $M \notin \mathbb{Z}$. The latter case is considered first, the former in the exercises below.

```python
N = 16
M = 3.33
W0 = M * 2*np.pi/N

F = dft(N)
k = np.arange(N)
x = np.exp(1j*W0*k)
X = np.matmul(F, x)
X
```

    array([ 1.31266387 +5.28685729e-01j,  1.63083183 +1.06667837e+00j,
            2.38037882 +2.33409311e+00j,  7.49352177 +1.09799457e+01j,
           -2.57047932 -6.03735081e+00j, -0.54711115 -2.61602209e+00j,
           -0.01688123 -1.71945225e+00j,  0.24191786 -1.28184690e+00j,
            0.40512534 -1.00587811e+00j,  0.52515992 -8.02910730e-01j,
            0.62378131 -6.36151064e-01j,  0.71244674 -4.86226008e-01j,
            0.79885258 -3.40121704e-01j,  0.88992974 -1.86118644e-01j,
            0.99422729 -9.76111721e-03j,  1.12563462 +2.12436536e-01j])

The real and imaginary part of the signal $x[k]$ as well as its DFT $X[\mu]$ are plotted for illustration

```python
plt.figure(figsize=(10, 8))

plt.subplot(2, 2, 1)
plt.stem(x.real)
plt.xlabel('$k$')
plt.ylabel('$\Re \{ x[k] \}$')
plt.ylim([-1.1, 1.1])

plt.subplot(2, 2, 2)
plt.stem(x.imag)
plt.xlabel('$k$')
plt.ylabel('$\Im \{ x[k] \}$')
plt.ylim([-1.1, 1.1])

plt.subplot(2, 2, 3)
plt.stem(X.real)
plt.xlabel('$\mu$')
plt.ylabel('$\Re \{ X[\mu] \}$')
plt.ylim([-N, N])

plt.subplot(2, 2, 4)
plt.stem(X.imag)
plt.xlabel('$\mu$')
plt.ylabel('$\Im \{ X[\mu] \}$')
plt.ylim([-N, N]);

plt.tight_layout()
```

**Exercise**

* Rerun the example with different normalized frequencies $\Omega_0 = M \frac{2 \pi}{N}$ resulting in periodic or aperiodic harmonic exponential signals.
* Where is the maximum of the real and imaginary part, and the absolute value $|X[\mu]|$ located in case of an aperiodic signal?

It should become obvious that depending on the chosen frequency a more or less 'smeared' Dirac impulse can be observed. This effect is known as [leakage effect](https://en.wikipedia.org/wiki/Spectral_leakage) of the DFT, since the Dirac impulse leaks into the neighboring bins.

**Example**

In order to facilitate the interpretation of a spectrum, many libraries for signal processing provide functionality to compute the values of the frequency axis for a sampled signal. This is illustrated for the spectrum $X[\mu]$ of the exponential signal from the previous example. The magnitude spectrum $|X[\mu]|$ is plotted for a sampling frequency of $f_\text{s} = 48000$ Hz.

```python
fs = 48000
f = np.fft.fftfreq(N, 1/fs)

plt.stem(f, abs(X))
plt.xlabel('$f[\mu]$ in Hz')
plt.ylabel(r'$|X[\mu]|$')
plt.xlim([-fs/2, fs/2]);
```

**Copyright**

The notebooks are provided as [Open Educational Resource](https://de.wikipedia.org/wiki/Open_Educational_Resources). Feel free to use the notebooks for your own educational purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Lecture Notes on Signals and Systems* by Sascha Spors.


Plot a parabola

Example from: https://stackoverflow.com/questions/30553585/graphing-a-parabola-using-matplotlib-in-python

More examples: https://github.com/nishadhka/Learning-Calculus-with-Python/blob/master/p1_sympy_intro/basic_calculus_sympy.ipynb

```python
!pip install matplotlib
!pip install numpy
!pip install sympy

```

    WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.
    Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.
    To avoid this problem you can invoke Python with '-m pip' instead of running pip directly.
    Collecting matplotlib
      Downloading matplotlib-3.1.3-cp37-cp37m-manylinux1_x86_64.whl (13.1 MB)
         |████████████████████████████████| 13.1 MB 4.2 MB/s eta 0:00:01    |█████▊                          | 2.4 MB 3.2 MB/s eta 0:00:04     |████████▊                       | 3.6 MB 3.2 MB/s eta 0:00:03     |█████████████████████▌          | 8.8 MB 4.6 MB/s eta 0:00:01     |█████████████████████████████   | 11.9 MB 4.2 MB/s eta 0:00:01
    Collecting cycler>=0.10
      Downloading cycler-0.10.0-py2.py3-none-any.whl (6.5 kB)
    Collecting kiwisolver>=1.0.1
      Downloading kiwisolver-1.1.0-cp37-cp37m-manylinux1_x86_64.whl (90 kB)
         |████████████████████████████████| 90 kB 6.0 MB/s eta 0:00:011
    Requirement already satisfied: python-dateutil>=2.1 in /srv/conda/envs/notebook/lib/python3.7/site-packages (from matplotlib) (2.8.1)
    Collecting pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1
      Downloading pyparsing-2.4.6-py2.py3-none-any.whl (67 kB)
         |████████████████████████████████| 67 kB 6.9 MB/s  eta 0:00:01
    Collecting numpy>=1.11
      Downloading numpy-1.18.1-cp37-cp37m-manylinux1_x86_64.whl (20.1 MB)
         |████████████████████████████████| 20.1 MB 4.0 MB/s eta 0:00:01     |██████                          | 3.7 MB 14.1 MB/s eta 0:00:02     |███████▎                        | 4.5 MB 3.4 MB/s eta 0:00:05     |███████████▎                    | 7.1 MB 3.4 MB/s eta 0:00:04
    Requirement already satisfied: six in /srv/conda/envs/notebook/lib/python3.7/site-packages (from cycler>=0.10->matplotlib) (1.14.0)
    Requirement already satisfied: setuptools in /srv/conda/envs/notebook/lib/python3.7/site-packages (from kiwisolver>=1.0.1->matplotlib) (45.1.0.post20200119)
    Installing collected packages: cycler, kiwisolver, pyparsing, numpy, matplotlib
    Successfully installed cycler-0.10.0 kiwisolver-1.1.0 matplotlib-3.1.3 numpy-1.18.1 pyparsing-2.4.6
    WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.
    Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.
    To avoid this problem you can invoke Python with '-m pip' instead of running pip directly.
    Requirement already satisfied: numpy in /srv/conda/envs/notebook/lib/python3.7/site-packages (1.18.1)
    WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip.
    Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue.
    To avoid this problem you can invoke Python with '-m pip' instead of running pip directly.
    Collecting sympy
      Downloading sympy-1.5.1-py2.py3-none-any.whl (5.6 MB)
         |████████████████████████████████| 5.6 MB 3.5 MB/s eta 0:00:01     |██████████▌                     | 1.8 MB 3.5 MB/s eta 0:00:02
    Collecting mpmath>=0.19
      Downloading mpmath-1.1.0.tar.gz (512 kB)
         |████████████████████████████████| 512 kB 40.4 MB/s eta 0:00:01
    Building wheels for collected packages: mpmath
      Building wheel for mpmath (setup.py) ... done
      Created wheel for mpmath: filename=mpmath-1.1.0-py3-none-any.whl size=532238 sha256=ca029f4987204b2da4e00e4e5272410660a8319bde9c9dd77930cffb6f831f62
      Stored in directory: /home/jovyan/.cache/pip/wheels/e2/46/78/e78f76c356bca9277368f1f97a31b37a8cb937176d9511af31
    Successfully built mpmath
    Installing collected packages: mpmath, sympy
    Successfully installed mpmath-1.1.0 sympy-1.5.1

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

```

```python
from sympy import *
init_printing(use_latex='mathjax')
from IPython.display import HTML, Math
display(HTML(""))
Math(r"e^\alpha")

```

$\displaystyle e^\alpha$

```python
a=[]
b=[]

for x in range(-50, 50,1):
  y=x**2
  a.append(x)
  b.append(y)
```

```python
fig = plt.figure()
axes= fig.add_subplot(111)
axes.plot(a,b)
plt.show()

```

Plot derivative of x**2

f(x) = 2x

```python
a=[]
b=[]

for x in range(-50, 50,1):
  y=2*x
  a.append(x)
  b.append(y)
```

```python
fig = plt.figure()
axes= fig.add_subplot(111)
axes.plot(a,b)
plt.show()
```

Plot tangent lines for x**2

We'll need the slope 2*x

Then we need a point on the parabola
x = 10

```python
a=[]
b=[]

def func(x):
  return x**2

def slope(x):
  return 2*x

#define the tangent line
def line(x, x1, y1):
  return slope(x1)*(x-x1)+y1

x1 = -30
y1 =  func(x1)

```

```python
# https://stackoverflow.com/questions/54961306/how-to-plot-the-slope-tangent-line-of-parabola-at-any-point
```

```python
# Define x data range for parabola
xa = np.linspace(-50,50,101)
#xa = np.linspace(-5,5,11)

yb = slope(xa)

#print(xa)
#print(yb)

print("x={},y={}".format(x1,y1))

```

    x=-30,y=900

```python

# Plot the figure
plt.figure()
plt.plot(xa, func(xa))
plt.scatter(x1, y1, color='C1', s=50)
plt.plot(xa, line(xa, x1, y1), 'C1--', linewidth = 2)

```

Test the sympy options and graph with matplotlib

```python
# start testing sympy
x, y, z = symbols('x y z')

def f(x):
  return x**3

figure = diff(x**3,x)
print(figure)
display(figure)

```

    3*x**2

$\displaystyle 3 x^{2}$

```python

```


```python
import numpy as np
import matplotlib.pyplot as plt
import plotutils as pu
import sympy
%matplotlib inline
```

# noise

Our goal is to produce noise in $n$ dimensions where $1 \le n$. In practical terms we'll not be able to produce noise in infinite dimensions but at least we will consider our algorithms as such we might someday. For this to work the input to our noise function will have to be a vector that describes a point in abstract vector space. We will call this point $x$ from now on. Formally, it is a vector of $n$ components $(A_1, A_2, \ldots, A_n)$ where $A_n \in \mathbb{R}$ and $-1 \le A_n \le 1$.

So we could write $f(\vec{x}) = y$. Note that $x$ here is not some point along the x-axis. It's a vector of $n$ components that specifies some point in abstract n-dimensional vector space. However, when we are dealing with one-dimensional noise this *will* be a point along the x-axis.

Also, we will add that we really would like $y$ to be in the range of $-1 \le y \le 1$. It's not a *strict* requirement for the noise function $f$ but it would be perfect. Another thing that's important is that our noise function *cannot* be random. If we feed it some point $p$ we should expect the outcome $y$ to be the same *every* time.

What we are looking for is sometimes known as *value noise* and it's basically a seemlingly random value $y$ mapped to some point $p$ in n-dimensional space.

# value noise in 1-d
We'll start with a function that operates on *integer* $\mathbb{Z}$ coordinates first but we'll quickly look at expanding our range to *real* $\mathbb{R}$ coordinates as well. For now, we'll start with a function $f_0(\vec{p}) = n$ where $\vec{p}$ is a scalar (a vector with one component, so basically just a number) and we'll shoot for $-1 \le n \le 1$.

```python
# Our `wave' function just an array of numbers. The length is
# important though - it will determine the period or `wavelength` of
# our noise function.
R = np.random.ranf(8) * 2 - 1

# We'll use a cheap modulo to place `p' in our vector space.
f_0 = lambda p: R[p % len(R)]

# With n steps we'll have n/len(R) periods of noise.
x = np.arange(0, 32, 1)

with plt.xkcd():
    plt.xlim(0, 32)
    plt.plot(x, f_0(x), 'c^')
    plt.plot([8, 8], [-1, 1], 'r--')
    plt.plot([16, 16], [-1, 1], 'r--')
    plt.plot([24, 24], [-1, 1], 'r--')
    arrowprops = {
        'head_length': 1.4,
        'head_width': 0.12,
        'length_includes_head': True,
        'fc': 'b',
        'ec': 'b'
    }
    plt.arrow(0, -0.6, 8, 0, **arrowprops)
    plt.annotate('(len(R), 0)', (1.15, -0.5))
```

A few things to notice here. First we setup this pretty important table `R` which contains a random selection of numbers. We use `numpy.random.ranf` which gives us a random number between $0$ and $1$. In order to get it into our desired range of $-1 \le y \le 1$ we multiply it by two and substract one. Multiplying (or scaling it) by two will give us a number in the range of $0 \le y \le 2$ so substracting one from that will get us right in the desired range $0 -1 \le y \le 2 - 1$ which ends up being $-1 \le y \le 1$.

> #### scaling output
When using `numpy.random.ranf` we get values of $y$ where $0 \le y \le 1$ but we want values where $-1 \le y \le 1$. In order to scale it up we need something that looks like $y = 2x - 1$. We van verify it by checking it. If $x = 0$ we get $y = -1$ and if $x = 2$ we get $y = 1$. Which is exactly what we want.

> Another way to look at this is as just a *scale* and *translate* operation where we scale something by two and then translate it by one in the oposite direction. In order to get it aligned with the origin again. This also explains why we have `R = np.random.ranf(8) * 2 - 1` in the code above.

We'll need to formalize this a bit more. For one thing, we know that `R` is just some values. In the example above we used eight values so we got a *period* (or *wavelength*) of 8. This is `len(R)`. If we increase the size of `R` we get a bigger period of noise. Of course we're just using integers as input so in our plot we'll have to do with points and not lines.

Let's summarize and consider that $R_n$ is a vector with $n$ components $(A_1, A_2, \ldots, A_n)$ where:
* $0 \lt n$ and $n \in \mathbb{N}$
* $A_1, A_2, \ldots, A_n \in \mathbb{R}$
* $-1 \le A_n \le 1$.

Now we have: $f_0(p) = R_{p \pmod q} = y$ where `q = len(R)`.

And this (or is intended to be) equivalent to `f_0 = lambda p: R[p % len(R)]` in the code above. Basically what we have is a table $n$ of $q$ values and when given some $x$ it'll give you some value $n_{x \pmod q}$. We use the $mod$ operation to make sure we are always in a valid range of $q$ with respect to the amount of components in $R$.

# finalizing noise
One thing we've struggled a bit is with the amount of compoents in $R$. Remember, $R$ is nothing more than just a bag of random numbers. Let's now try to formalize all the definitions we have so far. We'll start with $R$. Which is actually pretty hard.

```python

```


# Introduction to Machine Learning in Python, May 2020
By Dr. Anders Christensen `anders.christensen @ unibas.ch`

---

## Part 1: Exploratory Data Analysis and Classificaton: Fisher's *Iris* Data Set

Our data is in the file `iris.csv`: https://www.dropbox.com/s/g3njhhml16kvaci/iris.csv

The file contains 151 lines, the first 9 are here:

```
Petal length,Petal Width,Sepal Length,Sepal Width,Species
5.1,3.5,1.4,0.2,setosa
4.9,3.0,1.4,0.2,setosa
4.7,3.2,1.3,0.2,setosa
4.6,3.1,1.5,0.2,setosa
5.0,3.6,1.4,0.2,setosa
5.4,3.9,1.7,0.4,setosa
4.6,3.4,1.4,0.3,setosa
5.0,3.4,1.5,0.2,setosa
```

Download the file using Linux's `wget` command:

```
!wget -O iris.csv https://www.dropbox.com/s/g3njhhml16kvaci/iris.csv
```

```
#Pandas DataFrames
import pandas as pd

# Read in the CSV as a Pandas DataFrame
data = pd.read_csv("iris.csv")

type(data)
```

Can also read Excel and a bunch of other formats.

```
data.head(n=8)

```

Let's get an overview of the data?

```
data.describe()

```

#Part 1: Plotting Pandas DataFrames with Seaborn

```
import seaborn as sns
import matplotlib.pyplot as plt

grid = sns.pairplot(data)
plt.show()
```

Much room for customization: https://seaborn.pydata.org/generated/seaborn.pairplot.html

```
import seaborn as sns
import matplotlib.pyplot as plt

grid = sns.pairplot(data, kind="reg", diag_kind="kde",
                    hue="Species", corner=True)
grid.fig.suptitle("Fisher's Iris Data Set", size=32)
plt.show()
```

```
plt.figure(figsize=(10,11))
sns.heatmap(data.corr(),annot=True)
plt.plot()
```

## Code demonstration 1: K-NearestNeighbor (KNN) Classifier for the Iris Dataset.

Look at the label of the $K$ nearest neighbors. A given point is probably the same as its closest neighbors!

Training and test data?

Splitting of dataset?

Often a dataset is split into two sets:

*   **Training set:** Data to train your model
*   **Test set:** Data to evaluate/benchmark your model

Important that the two do ***not*** overlap -- this is overfitting!

```
# "Cheating" using function from Scikit-learn
from sklearn.model_selection import train_test_split

# Make test set 70%/30% split
train, test = train_test_split(data, test_size=0.30)
```

Let's see what is in the training and test:

```
train.head()
```

```
test.head()
```

```
train_features = train[["Sepal Length","Sepal Width","Petal length","Petal Width"]]
train_labels = train["Species"]

test_features = test[["Sepal Length","Sepal Width","Petal length","Petal Width"]]
test_labels = test["Species"]
```

First step to a simple classifier:

```
import numpy as np

# L2 distance
def distance(A, B):

  difference = A - B
  d = np.linalg.norm(difference)

  return d

query = np.asarray(test_features)[0]
train_features = np.asarray(train_features)

for i in range(len(train_features)):

  dist = distance(query, train_features[i])

  print(i, dist)
```

## Demonstration of Scikit-Learn:

Fortunately we don't always have to implement a classifier, etc from scratch. Sklearn already has a KNN classifier we can use:

https://scikit-learn.org/stable/index.html

```
# Import the classifier
from sklearn.neighbors import KNeighborsClassifier

# Initialize the object
classifier = KNeighborsClassifier(n_neighbors=15)

# Fit the model
classifier.fit(train_features, train_labels)
```

KNN documentation: https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html#sklearn.neighbors.KNeighborsClassifier

```
# Make a prediction
predicted_labels = classifier.predict(test_features)
print(predicted_labels)
```

```

from sklearn import metrics

accuracy = metrics.accuracy_score(predicted_labels, test_labels)
print("KNN Classifier Accuracy is {:4.1f}%".format(accuracy * 100))
```

How about another classifier?

```
from sklearn.neighbors import NearestCentroid
classifier = NearestCentroid()
```

## Part 2: Linear Least Squares Regression

In the exercieses today, you will implment a linear least squares regression model (often just called linear regression). Here is an introduction to linear least squares regression.

First, lets start with some data for a linear function:
\begin{equation}
y = 1.2x + \mathrm{noise}
\end{equation}

```
np.random.seed(666)

# X-values
x = np.arange(0,20.0, 0.2)

# Y-values: Y = 1.2*X + random noise
y = 1.2 * x + np.random.normal(scale=2.0, size=len(x))

print(x.shape)
print(y.shape)
```

Our little "Exploratory Data Analysis":

```
import matplotlib.pyplot as plt

plt.scatter(x,y, color="C0", label="Training")
plt.plot(x, x*1.2, color="C2", label="Truth")
plt.grid(True)
plt.legend()
plt.show()
```

Linear Regression approximates a linear function, e.g.:

\begin{equation}
y(\mathbf{x}) = x_1 \alpha_1 + x_2 \alpha_2 + \dots + x_n \alpha_n
\end{equation}
or in vector notation:
\begin{equation}
y(\mathbf{x}) = \mathbf{x} \cdot \mathbf{\alpha}
\end{equation}

Where $\mathbf{x}$ is our feature vector/descriptor/representation for a given datapoint. $\mathbf{\alpha}$ is the vector of regression coefficients.

"Fitting" is what you do to find the best set of $\alpha$-values. This is done by finding the solution with the "least squares":

\begin{equation}
\mathbf{y} = \mathbf{X}\mathbf{\alpha}
\end{equation}

Minimze the error:
\begin{equation}
\mathbf{\hat{\alpha}} = \text{arg min} || \mathbf{y}^\text{ref} - \mathbf{X}\mathbf{\alpha}||^2
\end{equation}

```
#Small hack for 1-d representations
X_matrix = x.reshape((100,1))

# Run the solver
alpha, residual, rank, singular_values = np.linalg.lstsq(X_matrix, y)

print(alpha)
```

How can we interpret $\alpha$ for our 1-D function?
\begin{equation}
y(\mathbf{x}) = x_1 \alpha_1
\end{equation}

Let's try some predictions:

```
# Random x-values:
x_test = np.random.random(size=(15,1)) * 20.0

print(x_test)
```

```
print(alpha.shape)
print(x_test.shape)

y_test = np.matmul(x_test, alpha)

plt.plot(x, x*1.2, color="C2", label="Truth", zorder=-1)
plt.scatter(x,y, color="C0", label="Training")
plt.scatter(x_test,y_test, color="C3", label="Test")

plt.legend()
plt.show()
```