**Exercise set 9**
==============

>The goal of this exercise is to use PLSR regression for predicting concentrations from
>from near-infrared spectra unsing partial least
>squares regression, and we will also do a PCA analysis.

**Exercise 9.1**

[Windig and Stephenson](https://doi.org/10.1021/ac00046a015) have measured near-infrared spectra
for 140 mixtures of the solvents methylene chloride, 2-butanol, methanol,
dichloropropane, and acetone. We will in this exercise see if we can
predict the compositions of the mixtures from the spectra.
Each of the $140$ spectra have been sampled at $700$ wavelengths
between $1100$ and $2500$ nm. The raw data containing the spectra
and the corresponding concentrations can be found in the file
[`Data/windig.csv`](Data/windig.csv).

**(a)**  Create a partial least squares regression (PLSR) model for predicting
the concentrations. Use $1$ PLS component for your first model and
assess it using $R^2$, RMSEC, RMSECV and RMSEP.

These values (RMSEC, RMSECV, and RMSEP) are all based on calculating the
root mean squared error (RMSE) given by,

\begin{equation}
\text{RMSE} = \sqrt{\frac{1}{n} \sum_{i=1}^N (y_i - \hat{y}_i)^2},
\tag{1}\end{equation}

where $y_i$ are our measured $y$-values and $\hat{y}_i$ are the
values predicted by our model. The difference between RMSEC,
RMSEP, and RMSECV lies in the part of the data we use to
calculate them. This is based on first splitting the data into
a *training* and *test* set, and then
performing what we call
*cross-validation* using
the training set:

*  When we use the training set to create our model, we are doing
   a *calibration*. If we calculate RMSE based on using
   the training set, we refer to this as the RMSEC (root mean squared
   error of calibration).
   This number
   quantifies the error we get in connection with making (calibrating)
   the model

*  When we use the test set to test our model, we are
   checking how well our model *predicts* "new" samples
   (that is, samples that were not used when making the model). If
   we calculate RMSE based on the training set, we refer to
   this as the RMSEP (room mean squared error of prediction). This
   number quantifies the error we can expect to make when using
   our model for predicting samples that were not used when making
   the model.

*  Cross-validation is based on doing a further split
   of the training set. Typically, we split the training set into
   $k$ smaller subsamples and we repeat the fitting of the
   model $k$ times.
   For each time we repeat the fitting, we retain a single
   subsample for validation, and we fit the model using the
   $k-1$ other subsamples. For the subsample we retained
   for validation, we can calculate the RMSE value of how
   well this is predicted by our model. Since we repeat this $k$ times,
   we can make it so that each of the $k$ subsamples is
   used exactly once for validation. Finally, we can
   obtain the average RMSE of the $k$ fittings and we
   refer to this value as the RMSECV (root mean squared error
   of cross-validation). This number indicates how well
   our model predict samples that were obtained among
   the calibration cases.

Luckily, methods for splitting our data into training and test
sets, calculating RMSE and doing cross-validation are already
available in sklearn. There is also
a nice illustration of cross validation [here](https://scikit-learn.org/stable/modules/cross_validation.html). An example of using these methods are given below:

```python
"""Example for RMSEC, RMSEP and RMSECV"""
import numpy as np
from sklearn.model_selection import (
    train_test_split,
    cross_val_score,
)
from sklearn.metrics import r2_score, mean_squared_error
from sklearn.linear_model import LinearRegression
from sklearn.datasets import load_diabetes

X, y = load_diabetes(return_X_y=True)  # Load example data

X_train, X_test, y_train, y_test = train_test_split(
    X, y,
    test_size=0.2,  # Use 20 % of the data for the test set.
    shuffle=True,   # Randomly shuffle the data
)

# Make a model using the training set:
model = LinearRegression()
model.fit(X_train, y_train)
# Predict using the training set:
y_hat_train = model.predict(X_train)
# Predict using the test set:
y_hat_test = model.predict(X_test)

# For training set: calculate R^2 and RMSEC:
r2_train = r2_score(y_train, y_hat_train)
rmsec = np.sqrt(mean_squared_error(y_train, y_hat_train))
print(f'R² (training): {r2_train}')
print(f'RMSEC: {rmsec}')

# For test set: calculate R^2 and RMSEP:
r2_test = r2_score(y_test, y_hat_test)
rmsep = np.sqrt(mean_squared_error(y_test, y_hat_test))
print(f'\nR² (test): {r2_test}')
print(f'RMSEP: {rmsep}')

# Run cross-validation:
cvscore = cross_val_score(
    model,  # Select the model we are going to score
    X_train,  # Give the X-training set
    y_train,  # Give the y-training set
    scoring='neg_mean_squared_error',  # select scoring method
    cv=5  # Number of splits to make
)

# Note: the scoring is here "neg_mean_squared_error".
# This is the negative of the MSE!
# The cross_val_score method is often used in
# connection with optimization where we would like to
# maximize something, and the score can be used to pick
# the best value. Since we usually do not want to
# maximize the error, this method is made so that it
# calculates the negative of the error.

cvscore = np.sqrt(-cvscore)  # Account for the negative sign.
rmsecv = cvscore.mean()
print(f'\nRMSECV: {rmsecv}')
```

The raw data for this exercise can
be loaded as shown below:

```python
"""Load the Windig data set."""
import pandas as pd
data = pd.read_csv('Data/windig.csv')
X = data.filter(like='data', axis=1).values  # NIR spectra
Y = data.filter(like='concentrations', axis=1).values  # Concentrations
```

```python
# Your code here
```

**Your answer to question 9.1(a):** *Double click here*

**(b)** Improve your PLSR model by including more
PLS components. Try components in the
range from $2$ up to $15$ and compare the different models. How many
PLS components are you satisfied with? In the following, we will refer
to the model you are most satisfied with as "model A".

```python
# Your code here
```

**Your answer to question 9.1(b):** *Double click here*

**(c)**  Plot the regression coefficients for model A (see point **(b)**).

```python
# Your code here
```

**Your answer to question 9.1(c):** *Double click here*

**(d)**  If you are given a new spectrum of a mixture of methylene chloride,
2-butanol, methanol, dichloropropane, and acetone, how well would
your model A predict the concentrations of the different solvents
in the mixture?

```python
# Your code here
```

**Your answer to question 9.1(d):** *Double click here*

**(e)**  Create a least squares model for predicting the concentrations.
Assess it using $R^2$, RMSEC, RMSECV and RMSEP. Does this model
perform as you expect?

```python
# Your code here
```

**Your answer to question 9.1(e):** *Double click here*

**Exercise 9.2**

[Schummer *et al.*](https://doi.org/10.1016/S0378-1119(99)00342-X) studied ovarian cancer by measuring gene expression
values for $1536$ genes in both normal and tumor tissues. One of their goals was
to find genes that were overexpressed in tumor samples compared with normal samples.
This knowledge may be used for tumor diagnosis. The raw data can be
found in the file [`Data/ovo.csv`](Data/ovo.csv).

**(a)**  Perform a principal component analysis (PCA) on the gene expression data,
and obtain the explained variance when using $1$, $2$, $5$ and $10$
components.

Center the data before performing the PCA. This can be
done as follows with the `scale` method
from `sklearn.preprocessing`: `X = scale(X, with_std=False)`.

The raw data can
be loaded as shown below.

```python
"""Load the data set."""
import pandas as pd
data = pd.read_csv('Data/ovo.csv')
classes = data['objlabels']  # Classification of data points.
X = data.filter(like='X.', axis=1)  # Gene expressions.
```

```python
# Your code here
```

**Your answer to question 9.2(a):** *Double click here*

**(b)**  Inspect the data by plotting the scores and loadings for principal component
number $1$ and principal component number $2$:

* (i)  Can you observe any clustering
of the samples?

* (ii)  Are there any outliers among the samples?

* (iii)  Can you identify some
genes which are overexpressed in tumors?

* (iv)  Can you identify some
genes which are underexpressed in tumors?

```python
# Your code here
```

**Your answer to question 9.2(b):** *Double click here*

**(c)**  Based on your answer in **(b)**, can
you identify some pairs of genes that seem to distinguish between
normal and tumor tissues? Support your findings by plotting the raw data.

```python
# Your code here
```

**Your answer to question 9.2(c):** *Double click here*


### Prime Calculator

Going to work ok, see how many I can get in 1 second

```python
print("It's been a while since I opened an ipynb")

my_list = [ 1,2,3]
print(type(my_list ))
my_list.append(4)
print(my_list)
```

    It's been a while since I opened an ipynb
    <class 'list'>
    [1, 2, 3, 4]

```python
import time
import sympy # validator

primes = [2]

start_time = time.time()
end_time = time.time() + 1

potential_prime = 3

while time.time() < end_time:
    divider = 3
    #print(potential_prime)
    is_prime = True
    while divider <= potential_prime / 3:
        if potential_prime % divider == 0:
            #print("Not Prime: %d divisibly by %d" % (potential_prime,divider))
            is_prime = False
            break;
        divider +=2
    if is_prime:
        #print("Prime: %d" % potential_prime)
        primes.append(potential_prime)

    #if potential_prime > 100:
    #    break
    potential_prime += 2

end_time = time.time()

#print(primes)
print("Found %d primes in %f sec" % (len(primes),(end_time-start_time)))
print("The last 5 are:")
for i in range(5):
    print(primes[-i-1])

# Validator:
for prime in primes:
    if not sympy.isprime(prime):
        print("%d is not prime" % prime)
        raise Exception("NOT PRIME FOUND")

# Validated count with https://www.dcode.fr/prime-number-pi-count

```

    Found 3899 primes in 1.000001 sec
    The last 5 are:
    36779
    36767
    36761
    36749
    36739

```python
# ignore below, optimization calc
potential_prime = 13

for i in range(10):
    print("Testing: %d" % potential_prime)
    divider = potential_prime // 2 + 1
    while divider > 1:
        print(divider)
        divider-=2
    potential_prime +=2
```

    Testing: 13
    7
    5
    3
    Testing: 15
    8
    6
    4
    2
    Testing: 17
    9
    7
    5
    3
    Testing: 19
    10
    8
    6
    4
    2
    Testing: 21
    11
    9
    7
    5
    3
    Testing: 23
    12
    10
    8
    6
    4
    2
    Testing: 25
    13
    11
    9
    7
    5
    3
    Testing: 27
    14
    12
    10
    8
    6
    4
    2
    Testing: 29
    15
    13
    11
    9
    7
    5
    3
    Testing: 31
    16
    14
    12
    10
    8
    6
    4
    2


# Approximate solvers for the Euler equations of gas dynamics

In this chapter we discuss approximate solvers for the one-dimensional Euler equations:

\begin{align}
    \rho_t + (\rho u)_x & = 0 \\
    (\rho u)_t + (\rho u^2 + p)_x & = 0 \\
    E_t + ((E+p)u)_x & = 0.
\end{align}

As in [Euler](Euler.ipynb), we focus on the case of an ideal gas, for which the total energy is given by

\begin{align} \label{EA:EOS}
    E = \frac{p}{\gamma-1} + \frac{1}{2}\rho u^2.
\end{align}

To examine the Python code for this chapter, and for the exact Riemann solution, see:

 - [exact_solvers/euler.py](exact_solvers/euler.py) ...
   [on github.](https://github.com/clawpack/riemann_book/blob/FA16/exact_solvers/euler.py)

## Roe solver

We first derive a Roe solver for the Euler equations, following the same approach as in [Shallow_water_approximate](Shallow_water_approximate.ipynb).  Namely, we assume that $\hat{A} = f'(\hat{q})$ for some average state $\hat{q}$, and impose the condition of conservation:

\begin{align} \label{EA:cons}
    f'(\hat{q}) (q_r - q_\ell) & = f(q_r) - f(q_\ell).
\end{align}

We will need the following quantities:

\begin{align}
q & = \begin{pmatrix} \rho \\ \rho u \\ E \end{pmatrix}, \ \ \ \ \ \  f(q) = \begin{pmatrix} \rho u \\ \rho u^2 + p \\ H u \rho \end{pmatrix}, \\
f'(\hat{q}) & = \begin{pmatrix}
                0 & 1 & 0 \\
                \frac{\gamma-3}{2}\hat{u}^2 & (3-\gamma)\hat{u} & \gamma-1 \\
                \frac{\gamma-1}{2}\hat{u}^3 - \hat{u}\hat{H} & \hat{H} - (\gamma-1)\hat{u}^2 & \gamma \hat{u} \end{pmatrix}.
\end{align}

Here $H = \frac{E+p}{\rho}$ is the enthalpy.  We have rewritten most expressions involving $E$ in terms of $H$ because it simplifies the derivation that follows.  We now solve (\ref{EA:cons}) to find $\hat{u}$ and $\hat{H}$.  It turns out that, for the case of a polytropic ideal gas, the average density $\hat{\rho}$ plays no role in the Roe solver.

The first equation of (\ref{EA:cons}) is an identity, satisfied independently of our choice of $\hat{q}$.  The second equation is (using (\ref{EA:EOS}))

\begin{align}
    \frac{\gamma-3}{2}\hat{u}^2 (\rho_r - \rho_\ell) + (3-\gamma)\hat{u}(\rho_r u_r - \rho_\ell u_\ell) \\ + (\gamma-1)\left( \frac{p_r-p_\ell}{\gamma-1} + \frac{1}{2}(\rho_r u_r^2 - \rho_\ell u_\ell^2) \right) & = \rho_r u_r^2 - \rho_\ell u_\ell^2 + p_r - p_\ell,
\end{align}

which simplifies to a quadratic equation for $\hat{u}$:

\begin{align} \label{EA:u_quadratic}
    (\rho_r - \rho_\ell)\hat{u}^2 - 2(\rho_r u_r - \rho_\ell u_\ell) \hat{u} + (\rho_r u_r^2 - \rho_\ell u_\ell^2) & = 0,
\end{align}

with roots

\begin{align}
    \hat{u}_\pm & = \frac{\rho_r u_r - \rho_\ell u_\ell \mp \sqrt{\rho_r \rho_\ell} (u_\ell - u_r)}{\rho_r - \rho_\ell} = \frac{\sqrt{\rho_r} u_r \pm \sqrt{\rho_\ell} u_\ell}{\sqrt{\rho_r}\pm\sqrt{\rho_\ell}}
\end{align}

Notice that this is identical to the Roe average of the velocity for the shallow water equations, if we replace the density $\rho$ with depth $h$.  As before, we choose the root $u_+$ since it is well defined for all values of $\rho_r, \rho_\ell$.

Next we find $\hat{H}$ by solving the last equation of (\ref{EA:cons}), which reads
\begin{align}
    \left( \frac{\gamma-1}{2}\hat{u}^3 - \hat{u}\hat{H} \right)(\rho_r - \rho_\ell) \\ + \left( \hat{H} - (\gamma-1)\hat{u}^2 \right)(\rho_r u_r - \rho_\ell u_\ell) + \gamma \hat{u}(E_r - E_\ell) & = H_r u_r \rho_r - H_\ell u_\ell \rho_\ell.
\end{align}
We can simplify this using the equality $\gamma E = \rho H + \frac{\gamma-1}{2}\rho u^2$ and solve for $\hat{H}$ to find
\begin{align}
    \hat{H}_{\pm} & = \frac{\rho_r H_r (u_r - \hat{u}_+) - \rho_\ell H_\ell (u_\ell - \hat{u}_+)}{\rho_r u_r - \rho_\ell u_\ell - \hat{u}_\pm(\rho_r -\rho_\ell)} \\
    & = \frac{\rho_r H_r (u_r - \hat{u}_+) - \rho_\ell H_\ell (u_\ell - \hat{u}_+)}{\pm\sqrt{\rho_r \rho_\ell}(u_r-u_\ell)} \\
    & = \frac{\rho_r H_r - \rho_\ell H_\ell \mp\sqrt{\rho_r \rho_\ell}(H_r - H_\ell)}{\rho_r - \rho_\ell} \\
    & = \frac{\sqrt{\rho_r}H_r \pm \sqrt{\rho_\ell} H_\ell}{\sqrt{\rho_r}\pm\sqrt{\rho_\ell}}.
\end{align}
Once more, we take the plus sign in the final expression for $\hat{H}$, giving the Roe averages
$$
\hat{u} = \frac{\sqrt{\rho_r} u_r + \sqrt{\rho_\ell} u_\ell}{\sqrt{\rho_r} + \sqrt{\rho_\ell}},
\qquad \hat{H} = \frac{\sqrt{\rho_r}H_r + \sqrt{\rho_\ell} H_\ell}{\sqrt{\rho_r} + \sqrt{\rho_\ell}}.
$$

To implement the Roe solver, we also need the eigenvalues and eigenvectors of the averaged flux Jacobian $f'(\hat{q})$.  These are just the eigenvalues of the true Jacobian, evaluated at the averaged state:
\begin{align}
    \lambda_1 & = \hat{u} - \hat{c}, & \lambda_2 & = \hat{u} & \lambda_3 & = \hat{u} + \hat{c},
\end{align}
\begin{align}
r_1 & = \begin{bmatrix} 1 \\ \hat{u}-\hat{c} \\ \hat{H}-\hat{u}\hat{c}\end{bmatrix} &
r_2 & = \begin{bmatrix} 1 \\ \hat{u} \\ \frac{1}{2}\hat{u}^2 \end{bmatrix} &
r_3 & = \begin{bmatrix} 1 \\ \hat{u}+\hat{c} \\ \hat{H}+\hat{u}\hat{c}\end{bmatrix}.
\end{align}
Here $\hat{c} = \sqrt{(\gamma-1)(\hat{H}-\hat{u}^2/2)}$.

Solving the system of equations
\begin{align}
q_r - q_\ell & = \sum_{p=1}^3 {\mathcal W}_p = \sum_{p=1}^3 \alpha_p r_p
\end{align}
for the wave strengths gives
\begin{align}
    \alpha_2 & = \delta_1 + (\gamma-1)\frac{\hat{u}\delta_2 - \delta_3}{\hat{c}^2} \\
    \alpha_3 & = \frac{\delta_2 + (\hat{c}-\hat{u})\delta_1 - \hat{c}\alpha_2}{2\hat{c}} \\
    \alpha_1 & = \delta_1 - \alpha_2 - \alpha_3,
\end{align}
where $\delta = q_r - q_\ell$.  We now have everything we need to implement the Roe solver.

```python
%matplotlib inline
```

```python
%config InlineBackend.figure_format = 'svg'
import numpy as np
from exact_solvers import euler
from utils import riemann_tools as rt
from ipywidgets import interact
from ipywidgets import widgets
State = euler.Primitive_State
```

```python
def roe_averages(q_l, q_r, gamma=1.4):
    rho_sqrt_l = np.sqrt(q_l[0])
    rho_sqrt_r = np.sqrt(q_r[0])
    p_l = (gamma-1.)*(q_l[2]-0.5*(q_l[1]**2)/q_l[0])
    p_r = (gamma-1.)*(q_r[2]-0.5*(q_r[1]**2)/q_r[0])
    denom = rho_sqrt_l + rho_sqrt_r
    u_hat = (q_l[1]/rho_sqrt_l + q_r[1]/rho_sqrt_r)/denom
    H_hat = ((q_l[2]+p_l)/rho_sqrt_l + (q_r[2]+p_r)/rho_sqrt_r)/denom
    c_hat = np.sqrt((gamma-1)*(H_hat-0.5*u_hat**2))

    return u_hat, c_hat, H_hat

def Euler_roe(q_l, q_r, gamma=1.4):
    """
    Approximate Roe solver for the Euler equations.
    """

    rho_l = q_l[0]
    rhou_l = q_l[1]
    u_l = rhou_l/rho_l
    rho_r = q_r[0]
    rhou_r = q_r[1]
    u_r = rhou_r/rho_r

    u_hat, c_hat, H_hat = roe_averages(q_l, q_r, gamma)

    dq = q_r - q_l

    s1 = u_hat - c_hat
    s2 = u_hat
    s3 = u_hat + c_hat

    alpha2 = (gamma-1.)/c_hat**2 *((H_hat-u_hat**2)*dq[0]+u_hat*dq[1]-dq[2])
    alpha3 = (dq[1] + (c_hat - u_hat)*dq[0] - c_hat*alpha2) / (2.*c_hat)
    alpha1 = dq[0] - alpha2 - alpha3

    r1 = np.array([1., u_hat-c_hat, H_hat - u_hat*c_hat])
    r2 = np.array([1., u_hat, 0.5*u_hat**2])
    q_l_star = q_l + alpha1*r1
    q_r_star = q_l_star + alpha2*r2

    states = np.column_stack([q_l,q_l_star,q_r_star,q_r])
    speeds = [s1, s2, s3]
    wave_types = ['contact','contact', 'contact']

    def reval(xi):
        rho = (xi<s1)*states[0,0] + (s1<=xi)*(xi<s2)*states[0,1] + \
              (s2<=xi)*(xi<s3)*states[0,2] + (s3<=xi)*states[0,3]
        mom = (xi<s1)*states[1,0] + (s1<=xi)*(xi<s2)*states[1,1] + \
              (s2<=xi)*(xi<s3)*states[1,2] + (s3<=xi)*states[1,3]
        E = (xi<s1)*states[2,0] + (s1<=xi)*(xi<s2)*states[2,1] + \
              (s2<=xi)*(xi<s3)*states[2,2] + (s3<=xi)*states[2,3]
        return rho, mom, E

    return states, speeds, reval, wave_types
```

An implementation of this solver for use in Clawpack can be found [here](https://github.com/clawpack/riemann/blob/FA16/src/rp1_euler_with_efix.f90).  Recall that an exact Riemann solver for the Euler equations appears in [exact_solvers/euler.py](exact_solvers/euler.py).

### Examples

Let's compare the Roe approximation to the exact solution.  As a first example, we use the Sod shock tube.

```python
def compare_solutions(left_state, right_state, solvers=['Exact','HLLE']):
    q_l = np.array(euler.primitive_to_conservative(*left_state))
    q_r = np.array(euler.primitive_to_conservative(*right))

    outputs = []
    states = {}

    for solver in solvers:
        if solver.lower() == 'exact':
            outputs.append(euler.exact_riemann_solution(q_l,q_r))
        if solver.lower() == 'hlle':
            outputs.append(Euler_hlle(q_l, q_r))
            states['hlle'] = outputs[-1][0]
        if solver.lower() == 'roe':
            outputs.append(Euler_roe(q_l, q_r))
            states['roe'] = outputs[-1][0]

    plot_function = \
        rt.make_plot_function([val[0] for val in outputs],
                              [val[1] for val in outputs],
                              [val[2] for val in outputs],
                              [val[3] for val in outputs],
                              solvers, layout='vertical',
                              variable_names=euler.primitive_variables,
                              derived_variables=euler.cons_to_prim,
                              vertical_spacing=0.15,
                              show_time_legend=True)

    interact(plot_function,
             t=widgets.FloatSlider(min=0,max=0.9,step=0.1,value=0.4));

    return states
```

```python
left  = State(Density = 3.,
              Velocity = 0.,
              Pressure = 3.)
right = State(Density = 1.,
              Velocity = 0.,
              Pressure = 1.)

states = compare_solutions(left, right, solvers=['Exact','Roe'])
```

    interactive(children=(FloatSlider(value=0.4, description='t', max=0.9), Output()), _dom_classes=('widget-inter…

```python
euler.phase_plane_plot(left, right, approx_states=states['roe'])
```

Recall that in the true solution the middle wave is a contact discontinuity and carries only a jump in the density. For that reason the three-dimensional phase space plot is generally shown projected onto the pressure-velocity plane as shown above: The two intermediate states in the true solution have the same pressure and velocity, and so are denoted by a single Middle state in the phase plane plot.

The Roe solver, on the other hand, generates a middle wave that carries a jump in all 3 variables  and there are two green dots appearing in the plot above for the two middle states (though the pressure jump is quite small in this example).  For a Riemann problem like this one with zero initial velocity on both sides, the Roe average velocity must also be zero, so the middle wave is stationary; this is of course not typically true in the exact solution, even when $u_\ell=u_r=0$.

Here is a second example.  Experiment with the initial states to explore how the Roe solution compares to the exact solution.

```python
left  = State(Density = 0.1,
              Velocity = 0.,
              Pressure = 0.1)
right = State(Density = 1.,
              Velocity = 1.,
              Pressure = 1.)

states = compare_solutions(left, right, solvers=['Exact','Roe'])
```

    interactive(children=(FloatSlider(value=0.4, description='t', max=0.9), Output()), _dom_classes=('widget-inter…

```python
euler.phase_plane_plot(left, right, approx_states=states['roe'])
```

### Single-shock solution
Next we demonstrate the exactness property of the Roe solver by applying it to a case where the left and right states are connected by a single shock wave.

```python
M = 2.  # Mach number of the shock wave
gamma = 1.4
mu = 2*(M**2-1)/(M*(gamma+1.))
right = State(Density = 1.,
              Velocity = 0.,
              Pressure = 1.)
c_r = np.sqrt(gamma*right.Pressure/right.Density)

rho_l = right.Density * M/(M-mu)
p_l = right.Pressure * ((2*M**2-1)*gamma+1)/(gamma+1)
u_l = mu*c_r

left = State(Density = rho_l,
             Velocity = u_l,
            Pressure = p_l)

states = compare_solutions(left, right, solvers=['Exact','Roe'])
```

    interactive(children=(FloatSlider(value=0.4, description='t', max=0.9), Output()), _dom_classes=('widget-inter…

```python
euler.phase_plane_plot(left, right, approx_states=states['roe'])
```

It is evident that the solution consists of a single right-going shock.  The exact solution cannot be seen because it coincides exactly with the Roe solution.  The path of the shock in the first plot also cannot be seen since it is plotted under the path of the rightmost Roe solution wave.  The two solutions differ only in the wave speeds predicted for the other two waves, but since these waves have zero strength this makes no difference.

### Transonic rarefactions and an entropy fix
Here is an example of a Riemann problem whose solution includes a transonic 2-rarefaction:

```python
left  = State(Density = 0.1,
              Velocity = -2.,
              Pressure = 0.1)
right = State(Density = 1.,
              Velocity = -1.,
              Pressure = 1.)

states = compare_solutions(left, right, solvers=['Exact','Roe'])
```

    interactive(children=(FloatSlider(value=0.4, description='t', max=0.9), Output()), _dom_classes=('widget-inter…

Notice that in the exact solution, the right edge of the rarefaction travels to the right.  In the Roe solution, all waves travel to the left.  As in the case of the shallow water equations, here too this behavior can lead to unphysical solutions when this approximate solver is used in a numerical discretization.  In order to correct this, we can split the single wave into two when a transonic rarefaction is present, in a way similar to what is done in the shallow water equations.  We do not go into details here.

## HLLE Solver

Recall that an HLL solver uses only two waves with a constant state between them.  The Euler equations are our first example for which the number of waves in the true solution is larger than the number of waves in the approximate solution.  As one might expect, this leads to noticeable inaccuracy in solutions produced by the solver.

Again following Einfeldt, the left-going wave speed is chosen to be the minimum of the Roe speed for the 1-wave and the characterstic speed $\lambda^1$ in the left state $q_\ell$.  The right-going wave speed is chosen to be the maximum of the Roe speed for the 3-wave and the characteristic speed $\lambda^3$ in the right state $q_r$.  Effectively, this means that
\begin{align}
    s_1 & = \min(u_\ell - c_\ell, \hat{u}-\hat{c}) \\
    s_2 & = \max(u_r + c_r, \hat{u}+\hat{c})
\end{align}

Recall that once we have chosen these two wave speeds, conservation dictates the value of the intermediate state:
\begin{align}  \label{SWA:hll_middle_state}
q_m = \frac{f(q_r) - f(q_\ell) - s_2 q_r + s_1 q_\ell}{s_1 - s_2}.
\end{align}

```python
def Euler_hlle(q_l, q_r, gamma=1.4):
    """HLLE approximate solver for the Euler equations."""

    rho_l = q_l[0]
    rhou_l = q_l[1]
    u_l = rhou_l/rho_l
    rho_r = q_r[0]
    rhou_r = q_r[1]
    u_r = rhou_r/rho_r
    E_r = q_r[2]
    E_l = q_l[2]

    u_hat, c_hat, H_hat = roe_averages(q_l, q_r, gamma)
    p_r = (gamma-1.) * (E_r - rho_r*u_r**2/2.)
    p_l = (gamma-1.) * (E_l - rho_l*u_l**2/2.)
    H_r = (E_r+p_r) / rho_r
    H_l = (E_l+p_l) / rho_l
    c_r = np.sqrt((gamma-1.)*(H_r-u_r**2/2.))
    c_l = np.sqrt((gamma-1.)*(H_l-u_l**2/2.))

    s1 = min(u_l-c_l,u_hat-c_hat)
    s2 = max(u_r+c_r,u_hat+c_hat)

    rho_m = (rhou_r - rhou_l - s2*rho_r + s1*rho_l)/(s1-s2)
    rhou_m = (rho_r*u_r**2 - rho_l*u_l**2 \
              + p_r - p_l - s2*rhou_r + s1*rhou_l)/(s1-s2)
    E_m = ( u_r*(E_r+p_r) - u_l*(E_l+p_l) - s2*E_r + s1*E_l)/(s1-s2)
    q_m = np.array([rho_m, rhou_m, E_m])

    states = np.column_stack([q_l,q_m,q_r])
    speeds = [s1, s2]
    wave_types = ['contact','contact']

    def reval(xi):
        rho  = (xi<s1)*rho_l + (s1<=xi)*(xi<=s2)*rho_m + (s2<xi)*rho_r
        mom  = (xi<s1)*rhou_l + (s1<=xi)*(xi<=s2)*rhou_m + (s2<xi)*rhou_r
        E  = (xi<s1)*E_l + (s1<=xi)*(xi<=s2)*E_m + (s2<xi)*E_r
        return rho, mom, E

    return states, speeds, reval, wave_types
```

### Examples

```python
left  = State(Density = 3.,
              Velocity = 0.,
              Pressure = 3.)
right = State(Density = 1.,
              Velocity = 0.,
              Pressure = 1.)

states = compare_solutions(left, right, solvers=['Exact','HLLE'])
```

    interactive(children=(FloatSlider(value=0.4, description='t', max=0.9), Output()), _dom_classes=('widget-inter…

```python
euler.phase_plane_plot(left, right, approx_states=states['hlle'])
```

### Preservation of positivity
Just as we saw in the case of the shallow water equations, the Roe solver (or any linearized solver) for the Euler equations fails to preserve positivity of the pressure and/or density in some situations.  Here is one example.

```python
left  = State(Density = 1.,
              Velocity = -5.,
              Pressure = 1.)
right = State(Density = 1.,
              Velocity = 1.,
              Pressure = 1.)

states = compare_solutions(left, right, solvers=['Exact', 'Roe'])
```

    interactive(children=(FloatSlider(value=0.4, description='t', max=0.9), Output()), _dom_classes=('widget-inter…

As we can see, in this example each Roe solver wave moves much more slowly than the leading edge of the corresponding true rarefaction.  In order to maintain conservation, this implies that the middle Roe state must have lower density than the true middle state.  This leads to a negative density.  Note that the velocity and pressure take huge values in the intermediate state.

The HLLE solver, on the other hand, guarantees positivity of the density and pressure.  Since the HLLE wave speed in the case of a rarefaction is always the speed of the leading edge of the true rarefaction, and since the HLLE solution is conservative, the density in a rarefaction will always be at least as great as that of the true solution.  This can be seen clearly in the example below.

```python
left  = State(Density = 1.,
                    Velocity = -10.,
                    Pressure = 1.)
right = State(Density = 1.,
                    Velocity = 1.,
                    Pressure = 1.)

states = compare_solutions(left, right, solvers=['Exact', 'HLLE']);
```

    interactive(children=(FloatSlider(value=0.4, description='t', max=0.9), Output()), _dom_classes=('widget-inter…

```python
euler.phase_plane_plot(left,right,approx_states=states['hlle'])
```

Again recall that we are only considering a single Riemann solution in this chapter.  In [FV_compare](FV_compare.ipynb) we observe the effect of using these approximate solvers in a full discretization.

```python

```


# Primer Exámen Métodos Numéricos
# 03 Mayo, 2021
### Alumno: Mario Rosas Otero
### Licenciatura en Tecnología
### Profesor: José Manuel Torres Valencia

```python
# Librerías
from sympy import *
import math as m
import mpmath
import pandas as pd
import numpy as np
from MetNum import *
```

#### Ejercicio 1: Se implementa el método de Newton-Raphson para la ecuación de Redlich-Kwong
 $ab = PT^{0.5}V^3-RT^{1.5}V^2-(Pb^2T^{0.5}-a+RT^{1.5}b)V$

```python
# Creamos una función que realice el Método de Newton - Raphson para
# la función de Redlich-Kwong ab = PT^(0.5)V^(3)-RT^(1.5)V^(2)-(Pb^(2)T^(0.5)-a+RT^(1.5)b)V

def Newton_Raphson(x_0,P,R,T,a,b): # Metemos las magnitudes que intervienen en la ecuación
    gxs = []
    results = []
    fin = 1
    while fin == 1:
        # Configuramos la operación iterativa que representada como x_(i+1) = f(x_i)/f'(x_i)
        # Función -> f(x) PT^(0.5)V^(3)-RT^(1.5)V^(2)-(Pb^(2)T^(0.5)-a+RT^(1.5)b)V-ab = 0
        # Derivada dF/dV -> f'(x) = 3PT^(0.5)V^(2)-2RT^(1.5)V-(Pb^(2)T^(0.5)-a+RT^(1.5)b)
        x_0 = x_0 - ((P*(T**0.5)*x_0**3)-(R*(T**1.5)*x_0**2)-(((P*b**2*(T**0.5))-(a)+(R*(T**1.5)*b))*x_0)-a*b) / ((3*P*(T**0.5)*x_0**2)-(2*R*(T**1.5)*x_0)-((P*b**2*(T**0.5))-(a)+(R*(T**1.5)*b)))
        # Se evalúa el valor recien calculado en la función original, para verificar la aproximación
        resu =  ((P*(T**0.5)*x_0**3)-(R*(T**1.5)*x_0**2)-(((P*b**2*(T**0.5))-(a)+(R*(T**1.5)*b))*x_0)-a*b)
        print(x_0)
        gxs.append(x_0)
        results.append(resu)
        # Se evalúa la precisión de la aproximación para detener la ejecución.
        if -0.000000001 < resu < 0.000000001:
            fin = 0

    allres = { 'x_i': gxs,
                'f(x)': results}

    allresdf = pd.DataFrame(allres)
    return allresdf
```

```python
# Estabecemos las magnitudes generales dadas, y las específicas para el Helio

#Presión en atm
P = 50
# Temperatura en Kelvins
T = 373.15
# Temperatura crítica del Helio en Kelvins
Tc = 5.26
#Presión crítica en atms
Pc = 2.26
# Constante ideal de los gases en (atm L)/(gmol K)
R = 0.08205
#Volumen molar inicial en L/gmol
V = (R*T)/P
x_0 = V
# Calculamos a
a = 0.4278*((R**2*(Tc**2.5))/(Pc))
#Calculamos b
b = 0.0867*((R*Tc)/(Pc))

# Llamamos a la función para correr el métos Newton-Raphson sobre la función de Redlich-Kwong
Newton_Raphson(x_0,P,R,T,a,b)
```

    0.629688446466771
    0.6287722156097847
    0.6287695756313703
    0.628769575609484

      x_i
      f(x)

      0
      0.629688
      3.610827e-01

      1
      0.628772
      1.034445e-03

      2
      0.628770
      8.575740e-09

      3
      0.628770
      3.235606e-14

#### Ejercicio 2

Para la función

 $x^3-6x+2=0$

Corremos tres depejes distintos y vemos con cual se encuentra una raíz entre [2,3].

######  Con el despeje a) $x = \sqrt[3]{6x-2} $

```python
# Elegimos un valor intermedio como nuestro valor inicial dados los parametros [2,3]
res = 2.5

fin = 1
gxs = []
results = []

while fin == 1:
   # Evaluamos el valor inicial en el depeje a)
    res = np.cbrt(6*res-2)
    # Evaluamos el valor recien calculado en la función original
    resu = res**3-6*res+2
    gxs.append(res)
    results.append(resu)
    # Establecemos un criterio de paro del calculo, con el resultado de la evaluación
    # del valor en la función original
    if -0.00001 < resu < 0.00001:
        fin =0

allres = { 'g(x)': gxs,
           'f(x)': results}

allresdf = pd.DataFrame(allres)
allresdf
```

      g(x)
      f(x)

      0
      2.351335
      0.891992

      1
      2.296277
      0.330347

      2
      2.275201
      0.126458

      3
      2.267028
      0.049034

      4
      2.263843
      0.019108

      5
      2.262600
      0.007461

      6
      2.262114
      0.002915

      7
      2.261924
      0.001140

      8
      2.261850
      0.000445

      9
      2.261821
      0.000174

      10
      2.261810
      0.000068

      11
      2.261805
      0.000027

      12
      2.261803
      0.000010

      13
      2.261803
      0.000004

######  Con el despeje b) $\sqrt{6-\frac{2}{x}}$

```python
# Elegimos un valor intermedio como nuestro valor inicial dados los parametros [2,3]
res = 2.5

fin = 1
gxs = []
results = []

while fin == 1:
    # Evaluamos el valor inicial en el depeje b)
    res = np.sqrt(6-(2/res))
    # Evaluamos el valor recien calculado en la función original
    resu = res**3-6*res+2
    gxs.append(res)
    results.append(resu)
    # Establecemos un criterio de paro del calculo, con el resultado de la evaluación
    # del valor en la función original
    if -0.00001 < resu < 0.00001:
        fin =0

allres = { 'g(x)': gxs,
           'f(x)': results}

allresdf = pd.DataFrame(allres)
allresdf
```

      g(x)
      f(x)

      0
      2.280351
      0.175719

      1
      2.263392
      0.014874

      2
      2.261940
      0.001283

      3
      2.261814
      0.000111

      4
      2.261803
      0.000010

##### Con el despeje c) $x = \frac{-2}{x^2-6}$

```python
# Elegimos un valor intermedio como nuestro valor inicial dados los parametros [2,3]
res = 2.5
fin = 1

gxs = []
results = []
while fin == 1:
     # Evaluamos el valor inicial en el depeje b)
    res = (-2)/(res**2-6)
    # Evaluamos el valor recien calculado en la función original
    resu = res**3-6*res+2
    gxs.append(res)
    results.append(resu)
    # Establecemos un criterio de paro del calculo, con el resultado de la evaluación
    # del valor en la función original
    if -0.00001 < resu < 0.00001:
        fin = 0

allres = { 'g(x)': gxs,
           'f(x)': results}

allresdf = pd.DataFrame(allres)
allresdf
```

      g(x)
      f(x)

      0
      -8.000000
      -462.000000

      1
      -0.034483
      2.206856

      2
      0.333399
      0.036663

      3
      0.339625
      0.001423

      4
      0.339867
      0.000056

      5
      0.339876
      0.000002

##### Ejercicio 3: Implementamos el método de Newton-Raphson Mutivariable para el sistemad de ecuaciones:

 $ln(xy)+x^2y^2=8$
 $sen(x)+y\; e^x=2$

```python
#Proponiendo valores iniciales
x = 100
y = 100

fin = 1
xs = []
ys = []
cambiosx = []
cambiosy = []
cont = 0

while fin == 1:
    cont += 1

    x_anterior = x
    y_anterior = y

    # Función 1  ln(xy) + x^2 y^2 - 8 = 0
    f1 = m.log(x*y)+(x**2)*(y**2)-8
    #Función 2 sen(x) + y e^x - 2 = 0
    f2 = m.sin(mpmath.radians(x)) +y*m.exp(x) -2

    # Obtenemos la derivada df1/dx = 1/x + 2x y^2
    df1dx = (1/x)+2*x*(y**2)
    # Obtenemos la derivada df1/dy = 1/y +2y x^2
    df1dy = (1/y)+2*y*(x**2)
    # Obtenemos la derivada df2/dx = cos(x)+y e^x7
    df2dx = m.cos(mpmath.radians(x))+y*m.exp(x)
    # Obtenemos la derivada df2/dy
    df2dy = m.exp(x)
    # Creamos la Matriz Jacobiana y sacamos su inversa
    jac_inv = np.linalg.inv([[df1dx,df1dy], [df2dx,df2dy]])
    primat = Matrix(jac_inv)
    # Se multiplica (J(x,y)^-1)([[-f1],[-f2]])
    h = jac_inv.dot([-f1,-f2])
#     h = np.array([(-f1)*m_i[0,0]+(-f1)*m_i[1,0],(-f2)*m_i[0,1]+(-f2)*m_i[1,1]])

    # Recalculamos los valores de 'x' y 'y'
    x += h[0]
    y += h[1]

    xs.append(x)
    ys.append(x)

    # Medimos el cambio respecto a la iteración anterior
    cambiox = x_anterior - x
    cambioy = y_anterior - y

    cambiosx.append(cambiox)
    cambiosy.append(cambioy)

    print(cont,'\t x: {0:.6f}'.format(x), ' y: {0:.6f}'.format(y) ,' cambio en x: {0:.6f}'.format(cambiox), ' cambio en y: {0:.6f}'.format(cambioy))

    # Si el cambio es lo suficientemente pequeño se detiene el proceso.
    if 0.0000001 < cambiox < 0.0000001 and 0.0000001 < cambioy < 0.0000001:
        fin = 0

# primat
```

#### Ejercicio 4:
Utilizando los valores iniciales x = 1.2, y = 1.2 se usa el método de punto fijo multivariable

 $y=-x^2+x+0.75$
 $y+5xy=x^2$ </center

```python
#Valores iniciales
X0 = 1.2
Y0 = 1.2

fin = 1
gxs = []
gys = []
print('X0: ', X0, 'Y0: ', Y0)
cont = 0

while fin == 1:
    cont += 1

    #Se guardan los valores de la iteración anterior
    x_anterior = X0
    y_anterior = Y0
#     X0 = X0**2+Y0-0.75
#     Y0 = X0**2-(5*X0*Y0)
#     X0 = np.sqrt(Y0+5*X0*Y0)
#     Y0 = -X0**2+X0+0.75
#     Y0 = X0**2-5*X0*Y0
#     X0 = np.sqrt(X0+0.75-Y0)
#     Y0 = (X0**2-Y0)/(5*X0)
#     X0 = np.sqrt(X0+0.75-Y0)
    # Se evaluan los valores en los despejes realizados
    # x = (x^2 - y)/5y
    X0 = (X0**2-Y0)/(5*Y0)
    # y = -x^2 + x + 0.75
    Y0 = -X0**2+X0+0.75

    print('It: {}'.format(cont),' X0: {0:.6f}'.format(X0), ' Y0: {0:.6f}'.format(Y0))

    # Comparamos la iteración anterior con la actual para medir el cambio
    resx = x_anterior - X0
    resy = y_anterior - Y0
    gxs.append(X0)
    gys.append(Y0)

    # Si el cambio es lo suficientemente bajo se detiene el proceso
    if -0.00000001 < resx < 0.00000001 and -0.00000001 < resy < 0.00000001:
        fin =0

allres = { 'X': gxs,
           'Y': gys}

allresdf = pd.DataFrame(allres)
allresdf
```

    X0:  1.2 Y0:  1.2
    It: 1  X0: 0.040000  Y0: 0.788400
    It: 2  X0: -0.199594  Y0: 0.510568
    It: 3  X0: -0.184395  Y0: 0.531604
    It: 4  X0: -0.187208  Y0: 0.527745
    It: 5  X0: -0.186718  Y0: 0.528418
    It: 6  X0: -0.186804  Y0: 0.528300
    It: 7  X0: -0.186789  Y0: 0.528320
    It: 8  X0: -0.186792  Y0: 0.528317
    It: 9  X0: -0.186792  Y0: 0.528317
    It: 10  X0: -0.186792  Y0: 0.528317
    It: 11  X0: -0.186792  Y0: 0.528317
    It: 12  X0: -0.186792  Y0: 0.528317

      X
      Y

      0
      0.040000
      0.788400

      1
      -0.199594
      0.510568

      2
      -0.184395
      0.531604

      3
      -0.187208
      0.527745

      4
      -0.186718
      0.528418

      5
      -0.186804
      0.528300

      6
      -0.186789
      0.528320

      7
      -0.186792
      0.528317

      8
      -0.186792
      0.528317

      9
      -0.186792
      0.528317

      10
      -0.186792
      0.528317

      11
      -0.186792
      0.528317


# Relativistic Boris method

\begin{align}
\textbf{u}=\gamma\textbf{v} \\
q^\prime=\Delta t\frac{q}{2m} \\
\textbf{u}^-=\textbf{u}^{n-1/2}+q^\prime\textbf{E}(\textbf{x}^n) \\
(\gamma^n)^2=1+(u^-/c)^2 \\
\textbf{h} = \frac{q^\prime}{\gamma^n}\textbf{B}(\textbf{x}^n) \\
\textbf{s}=2\textbf{h}/(1+h^2) \\
\textbf{u}^+=\textbf{u}^-+(\textbf{u}^-+(\textbf{u}^-\times\textbf{h}))\times\textbf{s} \\
\textbf{u}^{n+1/2}=\textbf{u}^++q^\prime\textbf{E}(\textbf{x}^n) \\
\textbf{x}^{n+1}=\textbf{x}^n+\textbf{v}^{n+1/2}\Delta t=\textbf{x}^n+\frac{\textbf{u}^{n+1/2}\Delta t}{\gamma^{n+1/2}}
\end{align}

```python
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from copy import deepcopy

# ... parameters
q = -1.0
m = 1.0
c = 1
B0 = 1
wce = q*B0/m
xi = 0.862
dt = 0.02
Nt = 7500
T = Nt*dt
E0 = np.zeros((2,3))
# ...

def gamma(v):
    return 1/np.sqrt(1 - np.linalg.norm(v)**2/c**2)

def gammau(u):
    return np.sqrt(1 + np.linalg.norm(u,axis = 1)**2/c**2)

# ... create two test particles
tn = np.linspace(0,T,Nt+1)

particles3d3v = np.zeros((Nt+1,2,6))
particles1d3v = np.zeros((Nt+1,2,4))
# ...

# ... set initial values
vtest1 = np.array([0.117,0,0.0795])
vtest2 = np.array([0.167,0,0.0595])

utest1 = gamma(vtest1)*vtest1
utest2 = gamma(vtest2)*vtest2

xtest1 = np.array([0,np.abs(utest1[0]*m/(q*B0)),0])
xtest2 = np.array([0,np.abs(utest2[0]*m/(q*B0)),0])

particles3d3v[0,0,3:6] = utest1
particles3d3v[0,1,3:6] = utest2

particles3d3v[0,0,0:3] = xtest1
particles3d3v[0,1,0:3] = xtest2

particles1d3v[0,0,1:4] = utest1
particles1d3v[0,1,1:4] = utest2

particles1d3v[0,0,0] = -0.00
particles1d3v[0,1,0] = -1.62
# ...

def pushBorisRelativistic3d3v(particles,q,m,dt,B,E):

    qprime = dt*q/(2*m)
    uminus = particles[:,3:6] + qprime*E

    h = qprime*B/np.sqrt(1 + np.linalg.norm(uminus,axis = 1)**2/c**2)[:,None]
    s = 2*h/(1 + np.linalg.norm(h,axis = 1)**2)[:,None]

    uplus = uminus + np.cross(uminus + np.cross(uminus,h),s)
    unew = uplus + qprime*E
    xnew = particles[:,0:3] + dt*unew/np.sqrt(1 + np.linalg.norm(unew,axis = 1)**2/c**2)[:,None]

    return xnew,unew

def pushBorisRelativistic1d3v(particles,q,m,dt,B,E):

    qprime = dt*q/(2*m)
    uminus = particles[:,1:4] + qprime*E

    h = qprime*B/np.sqrt(1 + np.linalg.norm(uminus,axis = 1)**2/c**2)[:,None]
    s = 2*h/(1 + np.linalg.norm(h,axis = 1)**2)[:,None]

    uplus = uminus + np.cross(uminus + np.cross(uminus,h),s)
    unew = uplus + qprime*E
    znew = particles[:,0] + dt*unew[:,2]/np.sqrt(1 + np.linalg.norm(unew,axis = 1)**2/c**2)

    return znew,unew

def Bfield3d3v(x,B0,xi):

    Bx = -x[:,0]*B0*xi*x[:,2]
    By = -x[:,1]*B0*xi*x[:,2]
    Bz = B0*(1 + xi*x[:,2]**2)

    return np.transpose(np.array([Bx,By,Bz]))

def Bfield1d3v(u,z,B0,xi):

    Bz = B0*(1 + xi*z**2)
    Omega = q*Bz/m
    rho = -np.cross(u,np.array([0,0,1]))/Omega[:,None]

    Bx = -rho[:,0]*B0*xi*z
    By = -rho[:,1]*B0*xi*z

    return np.transpose(np.array([Bx,By,Bz]))

particles3d3v[0,:,3:6] = pushBorisRelativistic3d3v(particles3d3v[0],q,m,-dt/2,Bfield3d3v(particles3d3v[0,:,0:3],B0,xi),E0)[1]
particles1d3v[0,:,1:4] = pushBorisRelativistic1d3v(particles1d3v[0],q,m,-dt/2,Bfield1d3v(particles1d3v[0,:,1:4],particles1d3v[0,:,0],B0,xi),E0)[1]

# ... time integration
for n in range(0,Nt):
    #particles3d3v[n+1,:,0:3],particles3d3v[n+1,:,3:6] = pushBorisRelativistic3d3v(particles3d3v[n],q,m,dt,Bfield3d3v(particles3d3v[n,:,0:3],B0,xi),E0)
    particles1d3v[n+1,:,0],particles1d3v[n+1,:,1:4] = pushBorisRelativistic1d3v(particles1d3v[n],q,m,dt,Bfield1d3v(particles1d3v[n,:,1:4],particles1d3v[n,:,0],B0,xi),E0)
# ...

# ... analytical solution
def zana(t):

    z0 = particles1d3v[0,0,0]
    Bz0 = B0*(1 + xi*z0**2)
    Omega = q*Bz0/m
    rho = -np.cross(utest1,np.array([0,0,1]))/Omega

    omegab = vtest1[0]*np.sqrt(xi/np.sqrt((1 + xi*z0**2)**2 + (rho[0]*B0*xi*z0)**2 + (rho[1]*B0*xi*z0)**2))

    return particles1d3v[0,0,0]*np.cos(omegab*t) + vtest1[2]/omegab*np.sin(omegab*t)
# ...

plt.plot(tn[::200],particles1d3v[::200,0,0],'k+',label = 'Simulation')
plt.plot(tn,zana(tn),label = 'Theory')
plt.xlabel('t')
plt.ylabel('z')
plt.ylim((-4,6))

# ... check energy conservation
gammas = gammau(particles1d3v[:,0,1:4])
gammamax = np.max(gammas)
gammamin = np.min(gammas)

diff = (gammamax - gammamin)/gammamin
plt.plot(tn,gammas/gammas[0],label = 'gamma')
# ...

plt.legend()
```

```python

```


# The Fourier Transform

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

## Theorems

The theorems of the Fourier transform relate basic time-domain operations to their equivalents in the Fourier domain. They are of use for the computation of Fourier transforms of signals composed from modified [standard signals](../continuous_signals/standard_signals.ipynb) and for the computation of the response of systems to an input signal. The theorems allow further to predict the consequences of modifying a signal or system by certain operations.

### Temporal Scaling Theorem

A signal $x(t)$ for which the Fourier transform $X(j \omega) = \mathcal{F} \{ x(t) \}$ is existing and known is given. The Fourier transform of the [temporally scaled signal](../continuous_signals/operations.ipynb#Temporal-Scaling) $x(a t)$ with $a \in \mathbb{R} \setminus \{0\}$ reads

\begin{equation}
\mathcal{F} \{ x(a t) \} = \frac{1}{|a|} \cdot X \left( \frac{j \omega}{a} \right)
\end{equation}

The Fourier transformation of a temporally scaled signal is given by inverse scaling of the Fourier transform of the unscaled signal and weighting with $\frac{1}{|a|}$. This relation is known as scaling theorem of the Fourier transform. It can be proven by introducing the scaled signal $x(a t)$ into the definition of the Fourier transformation together with the substitution $t' = a t$. For $a>0$ this reads

\begin{equation}
\begin{split}
\mathcal{F} \{ x(a t) \} &= \int_{-\infty}^{\infty} x(a t) e^{-j \omega t} dt \\
&= \frac{1}{a} \int_{-\infty}^{\infty} x(t') e^{-j \frac{\omega}{a} t'} dt' \\
&= \frac{1}{a} X \left( \frac{\omega}{a} \right)
\end{split}
\end{equation}

Note that the cases $a<0$ and $a>0$ have to be regarded separately.

**Example**

According to the scaling theorem and the [Fourier transform of the rectangular signal](definition.ipynb#Transformation-of-the-Rectangular-Signal) $\mathcal{F} \{ \text{rect}(t) \} = \text{sinc} \left( \frac{\omega}{2} \right)$, the transform of the scaled rectangular signal $\text{rect}(a t)$ is given as

\begin{equation}
\mathcal{F} \{ \text{rect}(a t) \} = \frac{1}{|a|} \cdot \text{sinc} \left( \frac{\omega}{2 a} \right)
\end{equation}

This can be confirmed by explicitly calculating the Fourier transformation of $\text{rect}(a t)$

```python
import sympy as sym
%matplotlib inline
sym.init_printing()

def fourier_transform(x):
    return sym.transforms._fourier_transform(x, t, w, 1, -1, 'Fourier')

t, w = sym.symbols('t omega')
a = sym.symbols('a', positive=True)

x = sym.Heaviside(t + sym.S.Half/a) - sym.Heaviside(t - sym.S.Half/a)
X = fourier_transform(x)
X
```

For illustration both the signal $x(t)$ and its Fourier transform $X(j \omega)$ are plotted for a specific value of $a$

```python
values = {a: 1/2}

sym.plot(x.subs(values), (t, -5, 5), xlabel=r'$t$', ylabel=r'$\mathrm{rect}(a t)$');
sym.plot(X.subs(values), (w, -20, 20), xlabel=r'$\omega$', ylabel=r'$\mathcal{F} \{ \mathrm{rect}(a t) \}$');
```

**Exercise**

* Change the width $1/a$ of the rectangular signal. How does its spectrum change?
* On a qualitative level, what happens when $1/a$ increases towards infinity?

### Convolution Theorem

The [convolution theorem](https://en.wikipedia.org/wiki/Convolution_theorem) states that the Fourier transform of the convolution of two signals $x(t)$ and $y(t)$ is equal to the scalar multiplication of their Fourier transforms $X(j \omega) = \mathcal{F} \{ x(t) \}$ and $Y(j \omega) = \mathcal{F} \{ y(t) \}$

\begin{equation}
\mathcal{F} \{ x(t) * y(t) \} = X(j \omega) \cdot Y(j \omega)
\end{equation}

The theorem can be proven by introducing the [definition of the convolution](../systems_time_domain/convolution.ipynb) into the [definition of the Fourier transform](definition.ipynb) and changing the order of integration

\begin{align}
\mathcal{F} \{ x(t) * y(t) \} &= \int_{-\infty}^{\infty} \left( \int_{-\infty}^{\infty} x(\tau) \cdot y(t-\tau) \; d \tau \right) e^{-j \omega t} \; dt \\
&= \int_{-\infty}^{\infty} \left( \int_{-\infty}^{\infty} y(t-\tau) \, e^{-j \omega t} \; dt \right) x(\tau) \; d\tau \\
&= Y(j \omega) \cdot \int_{-\infty}^{\infty} x(\tau) \, e^{-j \omega \tau} \; d \tau \\
&= Y(j \omega) \cdot X(j \omega)
\end{align}

under the assumption that both Fourier transforms $X(j \omega)$ and $Y(j \omega)$ exist.

The convolution theorem is very useful in the context of linear time-invariant (LTI) systems. The output signal $y(t)$ of an LTI system is given as the convolution of the input signal $x(t)$ with the impulse response $h(t)$. Hence, the signals and the system can be represented equivalently in the time and frequency domain

where $H(j \omega) = \mathcal{F} \{ h(t) \}$.
Calculation of the system response by transforming the problem into the Fourier domain can be beneficial since this replaces the evaluation of the convolution integral by a scalar multiplication. The (inverse) Fourier transforms are known for many signals or can be derived by applying the properties and theorems to standard signals and their transforms. This procedure simplifies the calculation of the system response significantly in many cases.

The convolution theorem can also be useful to derive an unknown Fourier transform. The key is here to express the signal as convolution of two other signals for which the Fourier transforms are known. This is illustrated by the following example.

#### Transformation of the triangular signal

A prominent example for the derivation of a Fourier transform by the convolution theorem is the [trianguar signal](https://en.wikipedia.org/wiki/Triangular_function)

\begin{equation}
\Lambda(t) = \begin{cases} 1- |t| & \text{for } |t| < 1 \\ 0 & \text{otherwise} \end{cases}
\end{equation}

which can be expressed as the convolution of two rectangular signals

\begin{equation}
\Lambda(t) = \text{rect}(t) * \text{rect}(t)
\end{equation}

Applying the convolution theorem yields

\begin{equation}
\mathcal{F} \{ \Lambda(t) \} = \text{sinc}\left( \frac{\omega}{2} \right) \cdot \text{sinc}\left( \frac{\omega}{2} \right) = \text{sinc}^2 \left( \frac{\omega}{2} \right)
\end{equation}

Both the signal and its transform are plotted for illustration

```python
x = sym.Piecewise((1 - abs(t), abs(t) < 1), (0, True))
X = sym.sinc(w/2)**2

sym.plot(x, (t, -2, 2), xlabel=r'$t$', ylabel=r'$\Lambda(t)$')
sym.plot(X, (w, -20, 20), xlabel=r'$\omega$', ylabel=r'$\mathcal{F} \{ \Lambda(t) \}$');
```

**Exercise**

* Extend above example by temporal scaling of the triangular signal.
* Derive the Fourier transform of a trapezoidal shaped signal by convolving two rectangular signals of different width.

### Temporal Shift Theorem

The [temporal shift](../continuous_signals/operations.ipynb#Temporal-Shift) $x(t-\tau)$ of a signal $x(t)$ can be expressed by a convolution with a shifted Dirac impulse

\begin{equation}
x(t - \tau) = x(t) * \delta(t - \tau)
\end{equation}

for $\tau \in \mathbb{R}$. This follows from the sifting property of the Dirac impulse. Applying a Fourier transform to the left- and right-hand side and exploiting the convolution theorem yields

\begin{equation}
\mathcal{F} \{ x(t - \tau) \} = X(j \omega) \cdot e^{- j \omega \tau}
\end{equation}

where $X(j \omega) = \mathcal{F} \{ x(t) \}$. Note that $\mathcal{F} \{ \delta(t - \tau) \} = e^{- j \omega \tau}$ can be derived from the definition of the Fourier transform together with the sifting property of the Dirac impulse. Above relation is known as shift theorem of the Fourier transform.

Expressing $X(j \omega)$ by its absolute value $|X(j \omega)|$ and phase $\varphi(j \omega)$ results in

\begin{equation}
\mathcal{F} \{ x(t - \tau) \} = | X(j \omega) | \cdot e^{j (\varphi(j \omega) - \omega \tau)}
\end{equation}

Temporal shifting of a signal does not change the absolute value of its spectrum but it subtracts the linear contribution $\omega \tau$ from the phase of its spectrum.

### Differentiation Theorem

Let's assume a signal $x(t)$ whose temporal derivative $\frac{d x(t)}{dt}$ exists. Using the [derivation property of the Dirac impulse](../continuous_signals/standard_signals.ipynb#Dirac-Impulse), the derivative of the signal can be expressed by the convolution

\begin{equation}
\frac{d x(t)}{dt} = \frac{d \delta(t)}{dt} * x(t)
\end{equation}

Fourier transformation of the left- and right-hand side together with the [convolution theorem](#Convolution-Theorem) yields the Fourier transform of the derivative of $x(t)$

\begin{equation}
\mathcal{F} \left\{ \frac{d x(t)}{dt} \right\} = j \omega \cdot X(j \omega)
\end{equation}

where $X(j \omega) = \mathcal{F} \{ x(t) \}$. The Fourier transform $\mathcal{F} \{ \frac{d \delta(t)}{dt} \} = j \omega$ can be derived by applying the definition of the Fourier transform together with the derivation property of the Dirac impulse. Above result is known as differentiation theorem of the Fourier transform. It states that the differentiation of a signal in the time domain is equivalent to a multiplication of its spectrum by $j \omega$. Splitting the spectrum of the differentiated signal into its magnitude and phase

\begin{equation}
j \omega \cdot X(j \omega) = |\omega \, X(j \omega)| \; e^{j (\varphi(j \omega) + \frac{\pi}{2} )}
\end{equation}

reveals that differentiation of a signal results in an attenuation of the higher frequencies of its magnitude spectrum and adds a phase shift of $\frac{\pi}{2}$.

The main applications of the differentiation theorem are the transformation of differential equations or the derivation of transforms of signals which can be expressed as derivatives of other signals. This latter is illustrated by the following example.

#### Transformation of the sign signal

The derivative of the [sign signal](../continuous_signals/standard_signals.ipynb#Sign-Signal) $\text{sgn}(t)$ [is given by the Dirac impulse](https://en.wikipedia.org/wiki/Sign_function#Properties)

\begin{equation}
\frac{d}{dt} \text{sgn}(t) = 2 \cdot \delta(t)
\end{equation}

Fourier transformation of the left- and right-hand side and application of the differentiation theorem yields

\begin{equation}
\mathcal{F} \{ \text{sgn}(t) \} = \frac{2}{j \omega}
\end{equation}

#### Transformation of the Heaviside signal

The transformation of the [Heaviside signal](../continuous_signals/standard_signals.ipynb#Heaviside-Signal) $\epsilon(t)$ is derived by expressing it in terms of the sign signal

\begin{equation}
\epsilon(t) = \frac{1}{2} ( \text{sgn}(t) + 1 )
\end{equation}

Using the linearity of the Fourier transform yields

\begin{equation}
\mathcal{F} \{ \epsilon(t) \} = \frac{1}{j \omega} + \pi \cdot \delta(\omega)
\end{equation}

The Fourier transformation of the Heaviside signal plays an important role in the context of [analytic signals](https://en.wikipedia.org/wiki/Analytic_signal) and their generation by the [Hilbert transform](https://en.wikipedia.org/wiki/Hilbert_transform).

### Integration Theorem

Let's assume a signal $x(t)$ for which the integral $\int_{-\infty}^{t} x(\tau) \; d\tau$ exists. The integration can be regarded as inverse operation to the temporal derivative. The integration can be represented as convolution with the rectangular signal $\epsilon(t)$ ([see integration theorem of the Laplace transform](../laplace_transform/theorems.ipynb#Integration-Theorem))

\begin{equation}
\int_{-\infty}^{t} x(\tau) \; d\tau = \int_{-\infty}^{\infty} x(\tau) \cdot \epsilon(t - \tau) \; d\tau = x(t) * \epsilon(t)
\end{equation}

Fourier transformation of the left- and right-hand side, application of the convolution theorem and using the Fourier transform of the Heaviside signal $\epsilon(t)$ yields the integration theorem of the Fourier transform

\begin{equation}
\mathcal{F} \left\{ \int_{-\infty}^{t} x(\tau) \; d\tau \right\}
= X(j \omega) * \left( \frac{1}{j \omega} + \pi \delta(\omega) \right)
= \frac{1}{j \omega} X(j \omega) + \pi X(0) \delta(\omega)
\end{equation}

**Example**

A signal whose amplitude increases linear over a finite temporal interval to a finite value can be defined as

\begin{equation}
x(t) = \begin{cases}
0 & \text{for } t < - \frac{1}{2} \\
t + \frac{1}{2} & \text{for } - \frac{1}{2} < t < \frac{1}{2} \\
1 & \text{for } t > \frac{1}{2}
\end{cases}
\end{equation}

Such functions are known as [ramp functions](https://en.wikipedia.org/wiki/Ramp_function). In order to derive the Fourier transform of the ramp signal $x(t)$ it is important to note that it can be expressed as integration over the rectangular signal $\text{rect}(t)$

\begin{equation}
x(t) = \int_{-\infty}^{t} \text{rect}(\tau) \; d\tau
\end{equation}

Now its straightforward to apply the integration theorem to yield the desired Fourier transform

\begin{equation}
X(j \omega) = \frac{1}{j \omega} \text{sinc}\left( \frac{\omega}{2} \right) + \pi \cdot \text{sinc}(0) \cdot \delta(\omega)
\end{equation}

### Multiplication Theorem

The Fourier transform of a multiplication of two signals $x(t)$ and $y(t)$ is given as

\begin{equation}
\mathcal{F} \{ x(t) \cdot y(t) \} = \frac{1}{2 \pi} X(j \omega) * Y(j \omega)
\end{equation}

The Fourier transform of a multiplication of two signals $x(t) \cdot y(t)$ is given by the convolution of their Fourier transforms $X(j \omega) = \mathcal{F} \{ x(t) \}$ and $Y(j \omega) = \mathcal{F} \{ y(t) \}$ weighted by $\frac{1}{2 \pi}$. Note, the convolution is performed with respect to the angular frequency $\omega$.

The multiplication theorem can be proven in a similar way as the convolution theorem by computing the inverse Fourier transform of the convolved spectra

\begin{equation}
\begin{split}
\mathcal{F}^{-1} \left\{ \frac{1}{2 \pi} X(j \omega) * Y(j \omega) \right\} &= \frac{1}{2 \pi} \int_{-\infty}^{\infty} \left( \frac{1}{2 \pi} \int_{-\infty}^{\infty} X(j \nu) \cdot Y(j (\omega-\nu)) \; d \nu \right) e^{j \omega t} \; d\omega \\
&= \frac{1}{2 \pi} \int_{-\infty}^{\infty} \left( \frac{1}{2 \pi} \int_{-\infty}^{\infty} Y(j (\omega-\nu)) \, e^{j \omega t} \; d\omega \right) X(j \nu) \; d\nu \\
&= y(t) \cdot \frac{1}{2 \pi} \int_{-\infty}^{\infty} X(j \nu) \, e^{j \nu t} \; d \nu \\
&= y(t) \cdot x(t)
\end{split}
\end{equation}

Alternatively the duality of the Fourier transform can be applied to the [convolution theorem](#Convolution-Theorem). Applications of the multiplication theorem include the modulation and windowing of signals. The former leads to the modulation theorem introduced later, the latter is illustrated by the following example.

**Example**

Windowing of signals is often applied to derive signals of finite duration from signals of infinite duration. Here the signal $x(t)$ is multiplied by a weighting function $w(t)$ in order to derive the finite length signal

\begin{equation}
y(t) = w(t) \cdot x(t)
\end{equation}

The following example considers the Fourier transform of a finite-length cosine signal. Following the concept of windowing, the signal can be defined as

\begin{equation}
y(t) = \text{rect}(a t) \cdot \cos(\omega_0 t)
\end{equation}

where $\text{rect}(a t)$ denotes the scaled [rectangular signal](../continuous_signals/standard_signals.ipynb#Rectangular-Signal) which is zero for $|t| > \frac{1}{2 a}$. The Fourier transform $Y(j \omega) = \mathcal{F} \{ y(t) \}$ is derived by the multiplication theorem, the [Fourier transform of the rectangular signal](definition.ipynb#Transformation-of-the-Rectangular-Signal), and the [Fourier transform of the cosine signal](properties.ipynb#Transformation-of-the-cosine-and-sine-signal) as

\begin{align}
Y(j \omega) &= \frac{1}{2 \pi} \cdot \frac{1}{|a|} \text{sinc}\left( \frac{\omega}{2 a} \right) * \pi \left( \delta(\omega + \omega_0) + \delta(\omega - \omega_0) \right) \\
&= \frac{1}{2 |a|} \left( \text{sinc}\left( \frac{\omega + \omega_0}{2 a} \right) + \text{sinc}\left( \frac{\omega - \omega_0}{2 a} \right) \right)
\end{align}

The latter identity results from the sifting property of the Dirac impulse. The convolution is computed in the following in `SymPy` by symbolic evaluation of the convolution integral.

```python
def convolve(x, y, var):
    tau = sym.symbols('tau')
    return sym.integrate(x.subs(var, tau) * y.subs(var, var - tau), (tau, -sym.oo, sym.oo))
```

Using above function, both the Fourier transform of the cosine signal and the rectangular window are convolved which each other

```python
w0, a = sym.symbols('omega0 a')

class rect(sym.Function):

    @classmethod
    def eval(cls, arg):
        return sym.Heaviside(arg + sym.S.Half) - sym.Heaviside(arg - sym.S.Half)

y = rect(a*t) * sym.cos(w0*t)
W = 1/abs(a) * sym.sinc(w/(2*a))
X = sym.pi*(sym.DiracDelta(w + w0) + sym.DiracDelta(w - w0))

Y = 1/(2*sym.pi) * convolve(W, X, w)
Y
```

Both the signal and its Fourier transform are plotted for specific values $\omega_0$, $a$

```python
values = {w0: 10, a: 1/2}

sym.plot(y.subs(values), (t, -5, 5), xlabel=r'$t$', ylabel=r'$y(t)$')
sym.plot(Y.subs(values), (w, -20, 20), xlabel=r'$\omega$', ylabel=r'$Y(j \omega)$');
```

**Exercise**

* Change the length $1/a$ of the signal by modifying the substitution in the example. How does the Fourier transform change if you decrease or increase the length $1/a$?

* What happens if you change the angular frequency $\omega_0$ of the signal?

* Assume a signal that is composed from a superposition of two finite length cosine signals with different frequencies. What qualitative condition has to hold that you can derive these frequencies from the spectrum of the superposition?

### Modulation Theorem

The complex modulation of a signal $x(t)$ is defined as $e^{j \omega_0 t} \cdot x(t)$ with $\omega_0 \in \mathbb{R}$. Its Fourier transform can be derived by applying the multiplication theorem

\begin{equation}
\mathcal{F} \left\{ e^{j \omega_0 t} \cdot x(t) \right\} = \frac{1}{2 \pi} \cdot 2 \pi \cdot \delta(\omega - \omega_0) * X(j \omega)
= X \big( j \, (\omega - \omega_0) \big)
\end{equation}

where $X(j \omega) = \mathcal{F} \{ x(t) \}$.  Above result states that the complex modulation of a signal leads to a shift of its spectrum. For $\omega_0 > 0$ the spectrum is shifted to higher frequencies. This lays the ground for wireless transmission of signals. Above result is known as modulation theorem.

**Example**

Lets assume a signal $x(t)$ which has a triangular spectrum $X(j \omega) = 2 \pi \cdot \Lambda(\omega)$. Due to the definition of the triangular function, the spectrum has only non-zero contributions for $|\omega| < 1$. Applying the duality principle to the [Fourier transform of $\Lambda(t)$ derived above](#Transformation-of-the-triangular-signal) yields the corresponding signal $x(t) = \mathcal{F}^{-1} \{ X(j \omega) \}$

\begin{equation}
x(t) = \text{sinc}^2 \left( \frac{t}{2} \right)
\end{equation}

Now lets consider the complex modulation $y(t) = e^{-j \omega_0 t} \cdot x(t)$ of the signal $x(t)$. The Fourier transform of the modulated signal is given as

\begin{equation}
\mathcal{F} \{ e^{j \omega_0 t} \cdot x(t) \} = 2 \pi \cdot \Lambda(\omega - \omega_0)
\end{equation}

The spectrum of the modulated signal has non-zero contributions for $\omega_0 -1 < \omega < \omega_0 + 1$. For large $\omega_0$ it only occupies a small part of the spectrum. This is an important property of signals used for wireless transmission. The signal $y(t)$ is defined

```python
y = sym.exp(sym.I*w0*t) * sym.sinc(t/2)**2
y
```

and the real part of the complex modulated signal is plotted for illustration

```python
values = {w0: 5}
sym.plot(sym.re(y.subs(values)), (t, -20, 20), xlabel=r'$t$', ylabel=r'$\Re \{ y(t) \}$');
```

**Exercise**

* Derive the spectrum of the signal $x(t)$ modulated by $\cos(\omega_0 t)$. Hint: Express $\cos(\omega_0 t)$ by Euler's formula and exploit the linearity of the Fourier transform. What is different in the resulting spectrum when comparing to above result?

### Parseval's Theorem

[Parseval's theorem](https://en.wikipedia.org/wiki/Parseval's_theorem) relates the energy of a signal in the time domain to its spectrum. The squared absolute value of a signal $x(t)$ represents its instantaneous power. It can be expressed as

\begin{equation}
| x(t) |^2 = x(t) \cdot x^*(t)
\end{equation}

where $x^*(t)$ denotes the complex conjugate of $x(t)$. Fourier transformation of the right-hand side and application of the multiplication theorem results in

\begin{equation}
\mathcal{F} \{ x(t) \cdot x^*(t) \} = \frac{1}{2 \pi} \cdot X(j \omega) * X^*(-j \omega)
\end{equation}

Introducing the definition of the Fourier transform and the convolution

\begin{equation}
\int_{-\infty}^{\infty} x(t) \cdot x^*(t) \, e^{-j \omega t} \; dt =
\frac{1}{2 \pi} \int_{-\infty}^{\infty} X(j \eta) \cdot X^*(-j (\omega - \eta)) \, d\eta
\end{equation}

and setting $\omega = 0$ yields the desired relation

\begin{equation}
\int_{-\infty}^{\infty} | x(t) |^2 \; dt = \frac{1}{2 \pi} \int_{-\infty}^{\infty} | X(j \omega) |^2 \; d\omega
\end{equation}

The integral over the squared absolute signal in the time-domain is equal to the integral over its squared absolute spectrum divided by $2 \pi$. Since the left-hand side represents the energy $E$ of the signal $x(t)$, Parseval's theorem states that the energy can be computed either in the time or spectral domain by integrating over the squared absolute value of the signal/spectrum. Parseval's theorem can be generalized to the [Plancherel theorem](https://en.wikipedia.org/wiki/Plancherel_theorem) which considers the product of two signals $x(t) \cdot y^*(t)$.

**Example**

The energy of the rectangular signal $\text{rect}(t)$ is computed in the time and spectral domain by evaluation of the respective integrals. First the time domain is evaluated

\begin{equation}
E_t = \int_{-\infty}^{\infty} | \text{rect}(t) |^2 \; dt = \int_{- \frac{1}{2}}^{\frac{1}{2}} 1 \; dt
\end{equation}

```python
E_t = sym.integrate(1, (t, -sym.S.Half, sym.S.Half))
E_t
```

The energy in the spectral domain is computed by evaluating

\begin{equation}
E_f = \frac{1}{2 \pi} \int_{-\infty}^{\infty} \left| \text{sinc}\left(\frac{\omega}{2}\right) \right|^2 \; d\omega
\end{equation}

```python
E_f = 1/(2*sym.pi) * sym.integrate(sym.sinc(w/2)**2, (w, -sym.oo, sym.oo))
E_f
```

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own 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: *Sascha Spors, Continuous- and Discrete-Time Signals and Systems - Theory and Computational Examples*.