###### Content under Creative Commons Attribution license CC-BY 4.0, code under MIT license (c)2014 L.A. Barba, G.F. Forsyth, I. Hawke. Partly based on [HyperPython](http://nbviewer.ipython.org/github/ketch/HyperPython/tree/master/) by D.I. Ketcheson, also under CC-BY.

# Riding the wave

This is the fourth and final lesson of Module 3, _Riding the wave: convection problems_, of the course **"Practical Numerical Methods with Python"** (a.k.a., [#numericalmooc](https://twitter.com/hashtag/numericalmooc)). We learned about conservation laws and the traffic-flow model in the [first lesson](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_01_conservationLaw.ipynb), and then about better numerical schemes for convection in [lesson 2](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_02_convectionSchemes.ipynb).

By then, you should have started to recognize that both mathematical models and numerical schemes work together to give us a good solution to a problem. To drive the point home, [lesson 3](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_03_aBetterModel.ipynb) deals only with an improved model—and showed you some impressive SymPy tricks!

In this lesson, we'll learn about a new class of discretization schemes, known as finite-volume methods. They are the _most widely used_ methods in computational fluid dynamics, and for good reasons! Let's get started ...

## Finite-volume method

Are you curious to find out why the finite-volume method (FVM) is the _most popular method_ in computational fluid dynamics? In fact, almost all of the commercial CFD software packages are based on the finite-volume discretization. Here are some reasons:

* FVM discretizations are very general and have no requirement that the grid be structured, like in the finite-difference method. This makes FVM very _flexible_.

* FVM gives a _conservative discretization_ automatically by using directly the conservation laws in integral form.

### Conservative discretization

Let's go right back to the start of this module, where we explained conservation laws looking at a tiny control volume. To simplify the discussion, we just looked at flow in one dimension, with velocity $u$. Imagining a tiny cylindrical volume, like the one shown in Figure 1, there is flux on the left face and right face and we easily explained conservation of mass in that case.

#### Figure 1. Tiny control volume in the shape of a cylinder.

The law of conservation of mass says that the rate of change of mass in the control volume, plus the net rate of flow of mass across the control surfaces must be zero. The same idea works for other conserved quantities.

Conservation means that any change in the quantity within a volume is due to the amount of that quantity that crosses the boundary. Sounds simple enough. (Remember that we are ignoring possible internal sources of the quantity.) The amount crossing the boundary is the flux. A general conservation law for a quantity $e$ is thus:

\begin{equation}
\frac{\partial}{\partial t}\int_{\text{cv}}e \, dV + \oint_{\text{cs}}\vec{F}\cdot d\vec{A} =0
\end{equation}

where $\vec{F}$ is the flux, and $\text{cv}$ denotes the control volume with control surface $\text{cs}$.

**Why not make the control volume itself our computational cell?**

Imagine that the one-dimensional domain of interest is divided using grid points $x_i$. But instead of trying to compute local values at the grid points, like we did before, we now want to follow the time evolution of _average_ values within each one-dimensional cell of width $\Delta x$ with center at $x_i$ (the idea is that as long as the cells are small enough, the average values will be a good representation of the quantities we are interested in).

Define $e_i$ as the integral average across the little control volume on the cell with center at $x_i$ (see Figure 2).

$$
\begin{equation}
  e_i = \frac{1}{\Delta x} \int_{x_i - \Delta x / 2}^{x_i + \Delta x / 2} e(x, t) \, dx.
\end{equation}
$$

If we know the flux terms at the boundaries of the control volume, which are at $x_{i-1/2}$ and $x_{i+1/2}$, the general conservation law for this small control volume gives:

$$
\begin{equation}
  \frac{\partial}{\partial t} e_i + \frac{1}{\Delta x} \left[ F \left( x_{i+1/2}, t \right) - F \left( x_{i - 1 / 2}, t \right) \right] = 0.
\end{equation}
$$

This now just requires a time-stepping scheme, and is easy to solve *if* we can find $F$ on the control surfaces.

#### Figure 2. Discretizing a 1D domain into finite volumes.

We've seen with the traffic model that the flux can depend on the conserved quantity (in that case, the traffic density). That is generally the case, so we write that $F = F(e)$. We will need to compute, or approximate, the flux terms at the cell edges (control surfaces) from the integral averages, $e_i$.

If we had a simple convection equation with $c>0$, then the flux going into the cell centered at $x_i$ from the left would be $F(e_{i-1})$ and the flux going out the cell on the right side would be $F(e_{i})$ (see Figure 2). Applying these fluxes in Equation (3) results in a scheme that is equivalent to our tried-and-tested backward-space (upwind) scheme!

We know from previous lessons that the backward-space scheme is first order and the error introduces numerical diffusion. Also, remember what happened when we tried to use it with the non-linear [traffic model](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_01_conservationLaw.ipynb) in the green-light problem? *It blew up!* That was because the problem contains both right-moving and left-moving waves (if you don't remember that discussion, go back and review it; it's important!).

To skirt this difficulty in the green-light problem, we chose initial conditions that don't produce negative wave speeds. But that's cheating! A genuine solution would be to have a scheme that can deal with both positive and negative wave speeds. Here is where Godunov comes in.

### Godunov's method

Godunov proposed a first-order method in 1959 that uses the integral form of the conservation laws, Equation (1), and a piecewise constant representation of the solution, as shown in Figure (2). Notice that representing the solution in this way is like having a myriad of little shocks at the cell boundaries (control surfaces).

For each control surface, we have *two values* for the solution $e$ at a given time: the constant value to the left, $e_L$, and the constant value to the right, $e_R$. A situation where you have a conservation law with a constant initial condition, except for a single jump discontinuity is called a **Riemann problem**.

The Riemann problem has an exact solution for the Euler equations (as well as for any scalar conservation law). The [shock-tube problem](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_05_Sods_Shock_Tube.ipynb), subject of your assignment for this course module, is a Riemann problem! And because it has an analytical solution, we can use it for testing the accuracy of numerical methods.

But Godunov had a better idea. With the solution represented as piecewise constant (Figure 2), why not use the analytical solution of the Riemann problem at each cell boundary? Solving a Riemann problem gives all the information about the characteristic structure of the solution, including the sign of the wave speed. The full solution can then be reconstructed from the union of all the Riemann solutions at cell boundaries. *Neat idea, Godunov!*

Figure 3 illustrates a Riemann problem for the Euler equations, associated to the shock tube. The space-time plot shows the characteristic lines for the left-traveling expansion wave, and the right-traveling contact discontinuity and shock.

#### Figure 3. The shock tube: a Riemann problem for Euler's equations. Physical space (top) and $x, t$ space (bottom).

We need to solve many Riemann problems from $t$ to $\Delta t$, one on each cell boundary (illustrated in Figure 4). The numerical flux on $x_{i+1/2}$ is

\begin{equation}
  F_{i+1/2}= \frac{1}{\Delta t} \int_{t^n}^{t^{n+1}} F\left(e(x_{i+1/2},t) \right)\,dt
\end{equation}

To be able to solve each Riemann problem independently, they should not interact, which imposes a limit on $\Delta t$. Looking at Figure 4, you might conclude that we must require a CFL number of 1/2 to avoid interactions between the Riemann solutions, but the numerical flux above only depends on the state at $x_{i+1/2}$, so we're fine as long as the solution there is not affected by that at $x_{i-1/2}$—i.e., the CFL limit is really 1.

#### Figure 4. Riemann problems on each cell boundary.

The Riemann solution, even though known analytically, can get quite hairy for non-linear systems of conservation laws (like the Euler equations). And we need as many Riemann solutions as there are finite-volume cell boundaries, and again at each time step! This gets really cumbersome.

Godunov solved the Riemann problems exactly, but many after him proposed *approximate* Riemann solutions instead. We'll be calculating the full solution numerically, after all, so some controlled approximations can be made. You might imagine a simple approximation for the flux at a cell boundary that is just the average between the left and right values, for example: $\frac{1}{2}\left[F(e_L)+F(e_R)\right]$. But that leads to a central scheme and, on its own, is unstable. Adding a term proportional to the difference between left and right states, $e_R-e_L$, supplies artificial dissipation and gives stability (see van Leer et al., 1987).

One formula for the numerical flux at $x_{i+1/2}$ called the Rusanov flux, a.k.a. Lax-Friedrichs flux, is given by

\begin{equation}
  F_{i+1/2}= \frac{1}{2} \left[ F \left( e_L \right) + F \left( e_R \right)  \right] -  \frac{1}{2}  \max \left|F'(e)\right| \left( e_R - e_L \right)
\end{equation}

where $F'(e)$ is the Jacobian of the flux function and $\max\left|F'(e)\right|$ is the local propagation speed of the fastest traveling wave. The Riemann solutions at each cell boundary do not interact if $\max|F'(e)|\leq\frac{\Delta x}{\Delta t}$, which leads to a flux formula we can now use:

\begin{equation}
  F_{i+1/2}= \frac{1}{2} \left( F \left( e_{i} \right) + F \left( e_{i+1} \right) -  \frac{\Delta x}{\Delta t} \left( e_{i+1} - e_{i} \right) \right)
\end{equation}

### Let's try it!

Let's apply Godunov's method to the [LWR traffic model](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_01_conservationLaw.ipynb). In [lesson 2](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_02_convectionSchemes.ipynb) we already wrote functions to set the initial conditions for a red-light problem and to compute the fluxes.  To save us from  writing this out again, we saved those functions into a Python file named `traffic.py` (found in the same directory of the course repository).  Now, we can use those functions by importing them in the same way that we import NumPy or any other library. Like this:

```python
from traffic import rho_red_light, computeF
```

You've probably noticed that we have the habit of writing detailed explanations of what a function does after defining it, in comments.  These comments are called *docstrings* and it is good practice to include them in all your functions.  It can be very useful when loading a function that you aren't familiar with (or don't remember!), because the `help` command will print them out for you.  Check it out:

```python
help(rho_red_light)
```

    Help on function rho_red_light in module traffic:

    rho_red_light(nx, rho_max, rho_in)
        Computes "red light" initial condition with shock

        Parameters
        ----------
        nx        : int
            Number of grid points in x
        rho_max   : float
            Maximum allowed car density
        rho_in    : float
            Density of incoming cars

        Returns
        -------
        rho: array of floats
            Array with initial values of density

Now, we can write some code to set up our notebook environment, and set the calculation parameters, with the functions imported above readily available.

```python
%matplotlib inline
import numpy
from matplotlib import pyplot
from matplotlib import rcParams
rcParams['font.family'] = 'serif'
rcParams['font.size'] = 16
from matplotlib import animation
from IPython.display import HTML
```

```python
#Basic initial condition parameters
#defining grid size, time steps, CFL condition, etc...
nx = 100
nt = 30
dx = 4.0/nx

rho_in = 5.
rho_max = 10.

V_max = 1.

# x_i now is the center of the i-th cell
x = numpy.linspace(0+0.5*dx, 4-0.5*dx, nx)

rho = rho_red_light(nx, rho_max, rho_in)
```

```python
def animate(data):
    x = numpy.linspace(0+0.5*dx, 4-0.5*dx, nx)
    y = data
    line.set_data(x,y)
    return line,
```

The cells above are code that you are already familiar with from lesson 2.  Below is a new function for applying Godunov's method with Lax-Friedrichs fluxes. Study it carefully.

```python
def godunov(rho, nt, dt, dx, rho_max, V_max):
    """ Computes the solution with the Godunov scheme using the Lax-Friedrichs flux.

    Parameters
    ----------
    rho    : array of floats
            Density at current time-step
    nt     : int
            Number of time steps
    dt     : float
            Time-step size
    dx     : float
            Mesh spacing
    rho_max: float
            Maximum allowed car density
    V_max  : float
            Speed limit

    Returns
    -------
    rho_n : array of floats
            Density after nt time steps at every point x
    """

    #initialize our results array with dimensions nt by nx
    rho_n = numpy.zeros((nt,len(rho)))
    #copy the initial u array into each row of our new array
    rho_n[:,:] = rho.copy()

    #setup some temporary arrays
    rho_plus = numpy.zeros_like(rho)
    rho_minus = numpy.zeros_like(rho)
    flux = numpy.zeros_like(rho)

    for t in range(1,nt):

        rho_plus[:-1] = rho[1:] # Can't do i+1/2 indices, so cell boundary
        rho_minus = rho.copy() # arrays at index i are at location i+1/2
        flux = 0.5 * (computeF(V_max, rho_max, rho_minus) +
                      computeF(V_max, rho_max, rho_plus) +
                      dx / dt * (rho_minus - rho_plus))
        rho_n[t,1:-1] = rho[1:-1] + dt/dx*(flux[:-2]-flux[1:-1])
        rho_n[t,0] = rho[0]
        rho_n[t,-1] = rho[-1]
        rho = rho_n[t].copy()

    return rho_n
```

We can run using a CFL of $1$, to start with, but you should experiment with different values.

```python
sigma = 1.0
dt = sigma*dx/V_max

rho = rho_red_light(nx, rho_max, rho_in)      #make sure that u is set to our expected initial conditions
rho_n = godunov(rho, nt, dt, dx, rho_max, V_max)
```

```python
fig = pyplot.figure();
ax = pyplot.axes(xlim=(0,4),ylim=(4.5,11),xlabel=('Distance'),ylabel=('Traffic density'));
line, = ax.plot([],[],color='#003366', lw=2);
```

```python
anim = animation.FuncAnimation(fig, animate, frames=rho_n, interval=50)
HTML(anim.to_html5_video())
```

You'll see that the result is very similar to the original Lax-Friedrichs method, and with good reason: they're essentially the same! But this is only because we are using a uniform grid. In the finite-volume approach, using the integral form of the equations, we were free to use a spatially varying grid spacing, if we wanted to.

The original Godunov method is first-order accurate, due to representing the conserved quantity by a piecewise-constant approximation. That is why you see considerable numerical diffusion in the solution. But Godunov's method laid the foundation for all finite-volume methods to follow and it was a milestone in numerical solutions of hyperbolic conservation laws. A whole industry developed inventing "high-resolution" methods that offer second-order accuracy and higher.

##### Dig deeper

* Godunov's method works in problems having waves moving with positive or negative wave speeds. Try it on the green-light problem introduced in [lesson 1](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_01_conservationLaw.ipynb) using the initial condition containing waves traveling in both directions.

* Investigate two or three different numerical flux schemes (you can start with van Leer et al., 1987, or Google for other references. Implement the different flux schemes and compare!

## MUSCL schemes

Godunov's method is first-order accurate, which we already know is not appropriate for hyperbolic conservation laws, due to the high numerical diffusion. This poses particular difficulty near sharp gradients in the solution.

To do better, we can replace the piecewise constant representation of the solution with a piecewise linear version (still discontinuous at the edges). This leads to the MUSCL scheme (for Monotonic Upstream-Centered Scheme for Conservation Laws), invented by van Leer (1979).

### Reconstruction in space

The piecewise linear reconstruction consists of representing the solution inside each cell with a *straight line* (see Figure 5). Define the cell representation as follows:

\begin{equation}
  e(x) = e_i + \sigma_i (x - x_i).
\end{equation}

where $\sigma_i$ is the *slope* of the approximation within the cell (to be defined), and $e_i$ is the Godunov cell average. The choice $\sigma_i=0$ gives Godunov's method.

Standard central differencing would give

\begin{equation}
  \sigma_i = \frac{e_{i+1} - e_{i-1}}{2 \Delta x}.
\end{equation}

#### Figure 5. Piecewise linear approximation of the solution.

But we saw with the results [in the second lesson](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_02_convectionSchemes.ipynb) that this can lead to oscillations near shocks. These [Gibbs oscillations](http://en.wikipedia.org/wiki/Gibbs_phenomenon) will always appear (according to [Godunov's theorem](http://en.wikipedia.org/wiki/Godunov's_theorem)) unless we use constant reconstruction. So we have to modify, or *limit* the slope, near shocks.

The easiest way to limit is to compute one-sided slopes

\begin{equation}
  \Delta e^- = \frac{e_i - e_{i-1}}{\Delta x}, \quad \Delta e^+ = \frac{e_{i+1} - e_{i}}{\Delta x},
\end{equation}

#### Figure 6. One-sided slopes

Now build the *minmod* slope

\begin{align}
  \sigma_i & = \text{minmod}(\Delta e^-, \Delta e^+) \\
  & = \begin{cases} \min(\Delta e^-, \Delta e^+) & \text{ if } \Delta e^-, \Delta e^+ > 0 \\
  \max(\Delta e^-, \Delta e^+) & \text{ if } \Delta e^-, \Delta e^+ < 0 \\
0 & \text{ if } \Delta e^- \cdot \Delta e^+ \leq 0
  \end{cases}
\end{align}

That is, use the *smallest* one-sided slope in magnitude, unless the slopes have different sign, in which cases it uses the constant reconstruction (i.e., Godunov's method).

Once the *minmod* slope is calculated, we can use it to obtain the values at the interfaces between cells.

\begin{align}
e^{R}_{i-1/2} &= e_i - \sigma_i \frac{\Delta x}{2}\\
e^{L}_{i+1/2} &= e_i + \sigma_i \frac{\Delta x}{2}
\end{align}

where $e^R$ and $e^L$ are the local interpolated values of the conserved quantity immediately to the right and left of the cell boundary, respectively.

##### Index headache

Notice that for the cell with index $i$, we calculate $e^R_{i-1/2}$ and $e^L_{i+1/2}$.  Look at Figure 5: those are the two local values of the solution are at opposite cell boundaries.

However, when we calculate the local flux at the cell boundaries, we use the local solution values on either side of that cell boundary. That is:

\begin{equation}
F_{i+1/2} = f(e^L_{i+1/2}, e^R_{i+1/2})
\end{equation}

You can calculate two flux vectors; one for the right-boundary values and one for the left-boundary values. Be careful that you know which boundary value a given index in these two vectors might refer to!

_____

Here is a Python function implementing minmod.

```python
def minmod(e, dx):
    """
    Compute the minmod approximation to the slope

    Parameters
    ----------
    e : array of float
        input data
    dx : float
        spacestep

    Returns
    -------
    sigma : array of float
            minmod slope
    """

    sigma = numpy.zeros_like(e)
    de_minus = numpy.ones_like(e)
    de_plus = numpy.ones_like(e)

    de_minus[1:] = (e[1:] - e[:-1])/dx
    de_plus[:-1] = (e[1:] - e[:-1])/dx

    # The following is inefficient but easy to read
    for i in range(1, len(e)-1):
        if (de_minus[i] * de_plus[i] < 0.0):
            sigma[i] = 0.0
        elif (numpy.abs(de_minus[i]) < numpy.abs(de_plus[i])):
            sigma[i] = de_minus[i]
        else:
            sigma[i] = de_plus[i]

    return sigma
```

### Evolution in time

Since we are aiming for second-order accuracy in space, we might as well try for second-order in time, as well. We need a method to evolve the *ordinary* differential equation forwards in time:

\begin{equation}
  \frac{\partial}{\partial t} e_i + \frac{1}{\Delta x} \left[ F \left( x_{i+1/2}, t \right) - F \left( x_{i - 1 / 2}, t \right) \right] = 0
\end{equation}

A second-order Runge-Kutta method with special characteristics (due to Shu & Osher, 1988) gives the following scheme:

\begin{align}
e^*_i & = e^n_i + \frac{\Delta t}{\Delta x}\left( F^n_{i-1/2} - F^n_{i+1/2} \right) \\
e^{n+1}_i & = \frac{1}{2} e^n_i + \frac{1}{2}\left( e^*_i + \frac{\Delta t}{\Delta x}\left( F^*_{i-1/2} - F^*_{i+1/2} \right) \right)
\end{align}

Recall that the Rusanov flux is defined as

$$
F_{i+1/2}= \frac{1}{2} \left[ F \left( e_L \right) + F \left( e_R \right)  \right] -  \frac{1}{2}  \max \left|F'(e)\right| \left( e_R - e_L \right)
$$

Armed with the interpolated values of $e$ at the cell boundaries we can generate a more accurate Rusanov flux. At cell boundary $i+1/2$, for example, this is:

\begin{equation}
F_{i+1/2} = \frac{1}{2} \left( F \left( e^L_{i+1/2} \right) + F \left( e^R_{i+1/2} \right)   -  \frac{\Delta x}{\Delta t} \left( e^R_{i+1/2} - e^L_{i+1/2} \right) \right)
\end{equation}

Now we are ready to try some MUSCL!

```python
def muscl(rho, nt, dt, dx, rho_max, V_max):
    """ Computes the solution with the MUSCL scheme using the Lax-Friedrichs flux,
    RK2 in time and minmod slope limiting.

    Parameters
    ----------
    rho    : array of floats
            Density at current time-step
    nt     : int
            Number of time steps
    dt     : float
            Time-step size
    dx     : float
            Mesh spacing
    rho_max: float
            Maximum allowed car density
    V_max  : float
            Speed limit

    Returns
    -------
    rho_n : array of floats
            Density after nt time steps at every point x
    """

    #initialize our results array with dimensions nt by nx
    rho_n = numpy.zeros((nt,len(rho)))
    #copy the initial u array into each row of our new array
    rho_n[:,:] = rho.copy()

    #setup some temporary arrays
    rho_plus = numpy.zeros_like(rho)
    rho_minus = numpy.zeros_like(rho)
    flux = numpy.zeros_like(rho)
    rho_star = numpy.zeros_like(rho)

    for t in range(1,nt):

        sigma = minmod(rho,dx) #calculate minmod slope

        #reconstruct values at cell boundaries
        rho_left = rho + sigma*dx/2.
        rho_right = rho - sigma*dx/2.

        flux_left = computeF(V_max, rho_max, rho_left)
        flux_right = computeF(V_max, rho_max, rho_right)

        #flux i = i + 1/2
        flux[:-1] = 0.5 * (flux_right[1:] + flux_left[:-1] - dx/dt *\
                          (rho_right[1:] - rho_left[:-1] ))

        #rk2 step 1
        rho_star[1:-1] = rho[1:-1] + dt/dx * (flux[:-2] - flux[1:-1])

        rho_star[0] = rho[0]
        rho_star[-1] = rho[-1]

        sigma = minmod(rho_star,dx) #calculate minmod slope

        #reconstruct values at cell boundaries
        rho_left = rho_star + sigma*dx/2.
        rho_right = rho_star - sigma*dx/2.

        flux_left = computeF(V_max, rho_max, rho_left)
        flux_right = computeF(V_max, rho_max, rho_right)

        flux[:-1] = 0.5 * (flux_right[1:] + flux_left[:-1] - dx/dt *\
                          (rho_right[1:] - rho_left[:-1] ))

        rho_n[t,1:-1] = .5 * (rho[1:-1] + rho_star[1:-1] + dt/dx * (flux[:-2] - flux[1:-1]))

        rho_n[t,0] = rho[0]
        rho_n[t,-1] = rho[-1]
        rho = rho_n[t].copy()

    return rho_n
```

```python
sigma = 1.
dt = sigma*dx/V_max
rho = rho_red_light(nx, rho_max, rho_in)      #make sure that u is set to our expected initial conditions
rho_n = muscl(rho, nt, dt, dx, rho_max, V_max)
```

```python
anim = animation.FuncAnimation(fig, animate, frames=rho_n, interval=50)
HTML(anim.to_html5_video())
```

This MUSCL scheme does not show any of the oscillations you might see with MacCormack or Lax-Wendroff, but the features are not as sharp. Using the _minmod_ slopes led to some smearing of the shock, which motivated many researchers to investigate other options. Bucketloads of so-called _shock-capturing_ schemes exist and whole books are written on this topic. Some people dedicate their lives to developing numerical methods for hyperbolic equations!

##### Challenge task

* Go back to Sod! Calculate the shock-tube problem using the MUSCL scheme and compare with your previous results. What do think?

## References

* Godunov, S.K. (1959), "A difference scheme for numerical computation of discontinuous solutions of equations of fluid dynamics," _Math. Sbornik_, Vol. 47, pp. 271–306.

* van Leer, Bram (1979), "Towards the ultimate conservative difference scheme, V. A second-order sequel to Godunov's method," _J. Comput. Phys._, Vol. 32, pp. 101–136

* van Leer, B., J.L. Thomas, P.L. Roe, R.W. Newsome (1987). "A comparison of numerical flux formulas for the Euler and Navier-Stokes equations," AIAA paper 87-1104 // [PDF from umich.edu](http://deepblue.lib.umich.edu/bitstream/handle/2027.42/76365/AIAA-1987-1104-891.pdf), checked 11/01/14.

* Shu, Chi-Wang and Osher, Stanley (1988). "Efficient implementation of essentially non-oscillatory shock-capturing schemes," _J. Comput. Phys._, Vol. 77, pp. 439–471 // [PDF from NASA Tech. Report server](http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19870013797.pdf)

---

###### The cell below loads the style of the notebook.

```python
from IPython.core.display import HTML
css_file = '../../styles/numericalmoocstyle.css'
HTML(open(css_file, "r").read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nixie+One' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 750px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1000px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
    margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Alegreya Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:600px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Nixie One', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}

.text_cell_render h2 {
    font-family: 'Nixie One', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Nixie One', serif;
    margin-top:16px;
    font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Nixie One', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Nixie One', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
    font-family: "Source Code Pro";
    font-size: 90%;
}

.alert-box {
    padding:10px 10px 10px 36px;
    margin:5px;
}

.success {
    color:#666600;
    background:rgb(240,242,229);
}
</style>


# Tarea 1

_Tarea 1_ de _Benjamín Rivera_ para el curso de __Métodos Numéricos__ impartido por _Joaquín Peña Acevedo_. Fecha limite de entrega __6 de Septiembre de 2020__.

## Como ejecutar

#### Requerimientos

Este programa se ejecuto en mi computadora con la version de __Python 3.8.2__ y con estos
[requerimientos](https://github.com/BenchHPZ/UG-Compu/blob/master/MN/requerimientos.txt)

### Jupyter

En caso de tener acceso a un _servidor jupyter_ ,con los requerimientos antes mencionados, unicamente basta con ejecutar todas las celdas de este _notebook_. Probablemente no todas las celdas de _markdown_ produzcan el mismo resultado por las
[_Nbextensions_](jupyter-contrib-nbextensions.readthedocs.io).

### Consola

Habrá archivos e instrucciones para poder ejecutar cada uno de los ejercicios desde la consola.

### Si todo sale mal

En caso de que todo salga mal, tratare de dejar una copia disponible en __GoogleColab__ que se pueda ejecutar con la versión de __Python__ de _GoogleColab_

## Ejercicio 1

Supongamos que una computadora tiene 8 dígitos para representar la parte fraccionaria
de un número de punto flotante.

### Calcular el valor del épsilon de la máquina

En clase vimos que si conocemos la cantidad de bits $p$ para representar la parte fraccionaria de la mantisa, entonces

\begin{equation} \epsilon_m = (1.0)_2 \times 2^{-p} \label{eq:epsilon}\end{equation}

Dado que sabemos que la máquina de este ejercicio usara $8bits$ para representar la parte fraccionaria entonces, por \ref{eq:epsilon}, el $\epsilon_m$ para este ejercicio es

\begin{equation*} \epsilon_m = (1.0)_2 \times 2^{-8} = 3.90625 \times 10^{-3}
\label{eq:epsilon maquina} \end{equation*}

```python
print(2**(-8))
```

    0.00390625

### Dar la representación en notación cientifica (mantisa base 2, multiplicada por 2 elevda al exponente correspondiente) del número 5.

Sabemos que el número $5$ se representa en binario como $(101)_2$. Como los números se prefieren normalizados entonces debemos representar $(1.01)_2$ en la notación solicitada. Por lo que

\begin{equation*} 5 = (101)_2 \times 2^0 = (1.01)_2 \times 2^2 = 1.25 \times 2^2
\end{equation*}

### Dar la representación científica del número consecutico a 5 en la computadora. Escribir la distancia $d_c$ entre 5 y su consecutivo. Expresar $d_c$ en términos del épslon de la máquina.

Sabemos que el número consecutivo $(5_c)$ a $5$ en esta computadora es $5+\epsilon_m$. Si extendemos toda la mantisa de $5$ este se ve $(1.01000000)$. Y sumar $\epsilon_m$ implica sumar $1$ a la mantisa, enspecificamente en esta computadora, al $bit 8$ de la mantisa. De manera que

\begin{eqnarray*}
    5_c &=& 5 + \epsilon_m
    = (1.01)_2 \times 2^2 + (1.0)_2 \times 2^{2-8} \\
    &=& ((1.01000000)_2 + (0.00000001)_2) \times 2^2 \\
    &=& (1.01000001)_2 \times 2^2 \\
    &=& 5.015625
\end{eqnarray*}

por lo que la distancia entre $5$ y su consecutivo $5+\epsilon_m$ es

\begin{equation*} d_c = (5+\epsilon_m)-5 = \epsilon_m \times 2^2 = (0.00000001)_2 \times 2^2 = 0.015625
\end{equation*}

### Tenemos que el consecutivo de $5$ es expresable como $5 + d_c$. Si tenemos un $x$ real tal que $x \in (5, 5+d_c)$ entonces la computadora representara a $x$ como $fl(x)=5$ o $fl(x)=5+d_c$. Escribir una cota para el error relativo para las dos posibles represetnaciones de x

> Error relativo
\begin{equation*}
    \left| \frac{fl(x) - x}{x} \right|
\end{equation*}

Sabemos que las dos posibles representaciones son el _truncamiento_ y el _redondeo_ ,para dar la cota se deben calcular los valores minimos y maximos del error relativo. En general trabajaremos con

\begin{equation} \label{eq:error relativo}
    \left| \frac{fl(x) - x}{x} \right| = \left| \frac{fl(x)}{x} - 1 \right|
\end{equation}

Antes de continuar es importante notar lo siguiente. Sabemos que, para ambos casos, el dominio de la función sera $[5, 5+d_c]$. Además, notemos que el rango de $\frac{fl(x)}{x}$, para nuestro dominio y los dos valores que puede tomar $fl(x)$, es $\left[ \frac{5}{5+d_c}, \frac{5+d_c}{5} \right]$. Por ultimo, sabemos que $\frac{fl(x)}{x}$ es descendente para $x>0$.

__Truncamiento__ Para el truncamiento $fl(x) = 5$ por lo que debemos calcular la cota para

\begin{equation*} Er_\_ = \left| \frac{5}{x} - 1 \right|
\end{equation*}

En este caso, el rango de $\frac{fl(x)}{x} = \frac{5}{x}$ es $\left[ \frac{5}{5+d_c}, \frac{5}{5}=1 \right]$ por lo que el rango de $Er_\_$ es $\left[ 0, |\frac{5}{5+d_c}-1| \right]$. Por iluminación divina, suponemos que esta función es ascendente, y por lo tanto $Er_\_(5) < Er_\_(5+dc)$. Primero evaluamos la función en estos puntos

\begin{equation*} Er_\_(5) = \left|\frac{5}{5} -1\right| = 0 \quad\quad Er_\_(5+d_c) = \left|\frac{5}{5+d_c} -1 \right| \sim 0.00311
\end{equation*}

Por lo que, usando el truncamiento, esta __función esta acotada__ por $\left[ Er_\_(5), Er_\_(5+d_c) \right]$

__Redondeo__ Procedemos de manera similar al anterior. Para el redondeo tenemos que $fl(x) = 5+d_c$, esto nos da la expresion

\begin{equation*} Er_+ = \left| \frac{5+d_c}{x} - 1 \right|
\end{equation*}

Y de manera similar al anterior podemos ver que esta función es descendente, por lo que $Er_+(5+d_c) < Er_+(d_c)$

\begin{equation*} Er_+(5+d_c) = \left| \frac{5+d_c}{5+d_c} -1 \right| = 0 \quad\quad Er_+(5) = \left| \frac{5+d_c}{5} -1 \right| \sim 0.00312
\end{equation*}

De manera que esta función queda acotada por $\left[ Er_+(5+d_c),Er_+(5) \right]$

### Explique si los siguientes números tienen respresentación exacta en la computadora, es decir, si $fl(a_i)=a_i$

 - $a_1 = \epsilon/2$
 \par Sabemos que en el sistema de este ejercicio ($8bits$ parte flotante) $\epsilon_m = (1.0)_2 \times 2^{-8}$, de manera que,

 \begin{equation*} \epsilon_m/2 = \frac{(1.0)_2 \times 2^{-8}}{2} = (1.0)_2 \times 2^{-9}
 \end{equation*}

 por lo que, mientras que el rango del exponente sea suficiente (dado que el ejercicio solo da información del tamaño de la \textit{matisa}), este número __es representable__ en el sistema.

 - $a_2 = 1+ \epsilon/2$
 \par De manera que $a_2$ se expresa como

 \begin{equation*} a_2 = (1.0)_2 + \epsilon/2 = (1.0)_2 + (1.0)_2 \times 2^{-9} = (1.000000001)_2
 \end{equation*}

 pero como este sistema solo usa $8bits$ para la mantisa entonces, para este sistema

 \begin{equation*} a_2 = (1.0)_2 + \epsilon/2 = (1.000000001)_2 = (1.0)_2
 \end{equation*}

 por lo que este número __no tiene representación__ en este sistema.

 - $a_3 = 1- \epsilon$
 \par Este número se escribe como

 \begin{eqnarray*}
     a_3 = 1- \epsilon &=& (1.0)_2 - (1.0)_2 \times 2^{-8} \\
         &=& (1.00000000)_2 - (0.00000001)_2 \\
         &=& (0.11111111)_2 \\
         &=& (1.1111111)_2 \times 2^{-1}
 \end{eqnarray*}

 por lo que este número __si tiene una representación__ en este sistema.

 - $a_4 = 1- \epsilon/2$
 \par Como con los anteriores, esta operación se expresa como

 \begin{eqnarray*}
     a_4 = 1- \epsilon/2 &=& (1.0)_2 - (1.0)_2 \times 2^{-9} \\
         &=& (1.00000000)_2 - (0.000000001)_2 \\
         &=& (0.111111111)_2 \\
         &=& (1.11111111)_2 \times 2^{-1}
 \end{eqnarray*}

 el cual __si tiene respresentación__ en el sistema de este ejercicio.

 - $a_5 = 1- \epsilon/4$
 \par En este inciso primero calcularemos $\epsilon/4$, para el cual expandimos lo siguiente

 \begin{equation*} \epsilon/4 = \frac{(1.0)_2 \times 2^{-8}}{2^2} = (1.0)_2 \times 2^{-10}
 \end{equation*}

 el cual, mientras el exponente alcance, _si es representable_ en el sistema. Por otro lado, el numero de este inciso nos da

 \begin{eqnarray*}
     a_5 = 1 - \epsilon/4 &=& (1.0)_2 - (1.0)_2 \times 2^{-10} \\
         &=& (1.00000000)_2 - (0.0000000001)_2 \\
         &=& (0.1111111111)_2 \\
         &=& (1.111111111)_2 \times 2^{-1}
 \end{eqnarray*}

 pero este número tiene una mantisa de $9bits$, por lo cual, __no tiene representación__ en el sistema.

 - $a_6 = \epsilon^2$
 \par Para este inciso

 \begin{equation} a_6 = \epsilon^2 = ((1.0)_2 \times 2^{-8})_2^2 = (1.0)_2 \times 2^{-16}
 \end{equation}

 el cual __si tiene represetación__ en el sistema.

 - $a_7 = 0.125$
 \par Primero pasamos de el número de decimal a binario, de manera que $(0.125)_{10} = (0.001)_{2}$. Luego hay que normalizarlo, por lo que $(0.001)_{2} = (1.0)_{2} \times 2^{-3}$. Por lo que este numero __si tiene respresentacion__ en el sistema.

 - $a_8 = 2^{-10}$
 \par Y por \'ultimo, tenemos que

 \begin{equation*} a_8 = 2^{-10} = ((10.0)_2)^{-10} = ((1.0)_2 \times 2^{1})^{-10} = (1.0)_2 \times 2^{-10}
 \end{equation*}

### De una cota para el error relativo de las restas \ref{eq:resta 1} y \ref{eq:resta 2} respecto al verdader valor. Suponga que $fl(x)$ se obtiene por redondeo hacia abajo _(truncamiento)_

\begin{equation}
    fl(0.9) - fl(0.5)   \label{eq:resta 1}
\end{equation} \begin{equation}
    fl(0.9) - fl(0.895) \label{eq:resta 2}
\end{equation}

Para este ejercicio, dado que nos pide usar el \textit{truncamiento}, definimos a la \textbf{unidad de redondeo} como $u = \epsilon_m/2$. Esto tambien nos define a $fl(x)=x(1+\delta)$ y $|\delta| \leq u$. Adem\'as, siguiendo las notas, podemos usar la cota que ah\'i se proporciona y sustituir $u$.

\begin{equation} |\delta_{x-y}| \leq \frac{\epsilon}{2}\frac{|x|+|y|}{|x-y|} \label{eq:cota}
\end{equation}

Y antes de continuar calculamos $\epsilon_m/2$ para poder usarlo mas adelante. De manera que

\begin{equation*} \epsilon_m/2 = \frac{(1.0)_2 \times 2^{-8}}{2} = (1.0)_2 \times 2^{-9}
\end{equation*}

Y, como tambien lo usaremos, es bueno tener en cuenta.

\begin{equation*} \delta_{x-y} = \frac{fl(x)-fl(y) - (x-y)}{x-y}
\end{equation*}

 Empezamos por la operaci\'on \ref{eq:resta 1}, de donde obtenemos $x=0.9$ y $y=0.5$. Primero calculamos los valores de maquina de estos.
 \begin{eqnarray*}
     fl(x) = fl(0.9) &=& (1.1100(1100))_2\times 2^{-1} \\
             &=& (1.11001100)_2\times 2^{-1}  \quad\quad\quad \text{(Truncamiento (no cabe))} \\
             &\sim& 0.8984375
            &&\\&&\\
     fl(y) = fl(0.5) &=& (1.0)_2\times 2^{-1} \\
             &=& (1.00000000)_2\times 2^{-1}  \quad\quad\quad \text{(Truncamiento (si cabe))} \\
             &=& 0.5
 \end{eqnarray*}

 De manera que, como ya conocemos $\epsilon_m$, los valores de la operaci\'on y sus representaciones en el sistema, procedemos a encontrar la cota. Dado que usamos la cota de las notas, unicamente queda sustituir, de esto obtenemos que:

\begin{eqnarray*}
    \delta_{x-y} \sim -0.00390625 \\
    \frac{\epsilon}{2}\frac{|x|+|y|}{|x-y|} = 0.00683593
\end{eqnarray*}

Por lo que, siguiendo la cota de la ecuaci\'on \ref{eq:cota}, podemos decir que esta operaci\'on esta acotada por

\begin{equation*}
    |\delta_{x-y}| \sim 0.00390625 \leq 0.00683593 \sim \frac{\epsilon}{2}\frac{|x|+|y|}{|x-y|}
\end{equation*}

De manera similar, para la operaci\'on \ref{eq:resta 2}, tenemos que $x=0.9$ y $y=0.895$. Calculamos los redondondeos de la computadora.

 \begin{eqnarray*}
     fl(x) = fl(0.9) &=& (1.1100(1100))_2\times 2^{-1} \\
             &=& (1.11001100)_2\times 2^{-1}  \quad\quad\quad \text{(Truncamiento (no cabe))} \\
             &\sim& 0.8984375
            &&\\&&\\
     fl(y) = fl(0.895) &=& (1.1100101000111101)_2\times 2^{-1} \\
             &=& (1.11001010)_2\times 2^{-1}  \quad\quad\quad \text{(Truncamiento (no cabe))} \\
             &=& 0.89453125
 \end{eqnarray*}

Ahora procedemos a calcular los limites de la cota, por lo que

\begin{eqnarray*}
    \delta_{x-y} \sim  -0.21875\\
    \frac{\epsilon}{2}\frac{|x|+|y|}{|x-y|} = 0.70117187
\end{eqnarray*}

Y por \'ultimo, seg\'un  la cota \ref{eq:cota}, acotamos esta operaci\'on por

\begin{equation*}
    |\delta_{x-y}| \sim -0.21875 \leq 0.70117187 \sim \frac{\epsilon}{2}\frac{|x|+|y|}{|x-y|}
\end{equation*}

```python
# Funciones auxiliares para el Ejercicio 2.3
def deltaXY(fx, fy, x, y):
    return ((fx-fy)-(x-y))/(x-y)

def cotaSup(x, y):
    ep2 = 0.001953125
    return ep2*(abs(x) + abs(y))/abs(x - y)

#print(deltaXY(0.8984375, 0.89453125, 0.9, 0.895))
#print(cotaSup(0.9, 0.895))
```

## Ejercicio 2

Programe la función `epsilonFloat` que devuelve el épsilon de la máquina $\epsilon_m$ para números de simple precisión y la función `epsilonDouble` para números de doble precisión.
```
epsilon = 1.0
unidad = 1.0
valor = unidad + epsilon
while valor > unidad
    epsilon = epsilon/2
    valor = end + epsilon
end while
epsilon = epsilon*2
```
Usar el algoritmo visto en clase.

```python
# Primera parte del ejercicio 1

import numpy as np

def epsilonMaquina(tipoDato):
    """ Funcion que trata de calcular el epsilon
    de la maquina mediante el algoritmo antes pre_
    sentado.
    Input:
        tipoDato := esta pensado para ser uno de
            los tipos proporcionados por la li_
            breria numpy.
    Output:
        Regresa el epsilon de la maquina calcula_
            do con el tipo de dato especificado.
    """
    epsilon = tipoDato(1.0)
    unidad = tipoDato(1.0)
    valor = unidad + epsilon

    while valor > unidad:
        epsilon = epsilon/tipoDato(2.0)
        valor = unidad + epsilon

    return epsilon*2

def epsilonFloat():
    """ Calculamos el epsilon de la maquina con
    precision de 32bits
    """
    return epsilonMaquina(np.float32)

def epsilonDouble():
    """ Calculamos el epsilon de la maquina con
    precision de 64 bits
        A pesar de que el flotante de python ya
    tiene esta precision, creo que es convenien_
    te especificarlo.
    """
    return epsilonMaquina(np.float64)

# Epsilons calculados
eF = np.float32(epsilonFloat())
eD = np.float64(epsilonDouble())

# Imprimir en pantalla
print(f'Se calculalron los epsilons \n\t{eF=} y \n\t{eD=} \n'
      'para 32 y 64 bits correspondientemente.\n')
```

    Se calculalron los epsilons
    	eF=1.1920929e-07 y
    	eD=2.220446049250313e-16
    para 32 y 64 bits correspondientemente.

```python
# Segunda parte del ejercicio 1

def respuesta(res):
    """ Funcion para formatear la respuesta. """
    return "iguales" if res else "diferentes"

def comparacion(epsilon):
    """ Esta funcion resivira el epsilon a eva_
    luar y el tipo de dato al que este correspon_
    de para hacer las comparaciones solicitadas
    en el ejercicio.
    Input:
        En caso de que
    Output:
        Las respuestas son procesadas por la
        funcion respuesta para obtener el for_
        mato solicitado
            True implica que son iguales
            False implica que son diferentes
    """
    tD = type(epsilon) # Para escribir menos
    print(f'Con {epsilon=} y tipo de dato = {str(tD)} se da que')

    # Comprobaciones
    print(f'{respuesta( tD(1 + epsilon )   == 1 )  =}')
    print(f'{respuesta( tD( epsilon/2 )    == 0 )  =}')
    print(f'{respuesta( tD(1 + epsilon/2 ) == 1 )  =}')
    print(f'{respuesta( tD(1 - epsilon/2 ) == 1 )  =}')
    print(f'{respuesta( tD(1 - epsilon/4 ) == 1 )  =}')
    print(f'{respuesta( tD( epsilon**2 )   == 0 )  =}')
    print(f'{respuesta(epsilon + tD(epsilon**2) == epsilon) =}')
    print(f'{respuesta(epsilon - tD(epsilon**2) == epsilon) =}')

    print('...\n')

# Hacemos la comparacion para 32bits
comparacion(eF)
# y para 64
comparacion(eD)
```

    Con epsilon=1.1920929e-07 y tipo de dato = <class 'numpy.float32'> se da que
    respuesta( tD(1 + epsilon )   == 1 )  ='diferentes'
    respuesta( tD( epsilon/2 )    == 0 )  ='diferentes'
    respuesta( tD(1 + epsilon/2 ) == 1 )  ='iguales'
    respuesta( tD(1 - epsilon/2 ) == 1 )  ='diferentes'
    respuesta( tD(1 - epsilon/4 ) == 1 )  ='iguales'
    respuesta( tD( epsilon**2 )   == 0 )  ='diferentes'
    respuesta(epsilon + tD(epsilon**2) == epsilon) ='diferentes'
    respuesta(epsilon - tD(epsilon**2) == epsilon) ='diferentes'
    ...

    Con epsilon=2.220446049250313e-16 y tipo de dato = <class 'numpy.float64'> se da que
    respuesta( tD(1 + epsilon )   == 1 )  ='diferentes'
    respuesta( tD( epsilon/2 )    == 0 )  ='diferentes'
    respuesta( tD(1 + epsilon/2 ) == 1 )  ='iguales'
    respuesta( tD(1 - epsilon/2 ) == 1 )  ='diferentes'
    respuesta( tD(1 - epsilon/4 ) == 1 )  ='iguales'
    respuesta( tD( epsilon**2 )   == 0 )  ='diferentes'
    respuesta(epsilon + tD(epsilon**2) == epsilon) ='diferentes'
    respuesta(epsilon - tD(epsilon**2) == epsilon) ='diferentes'
    ...

De manera que el programa calculo que el _epsilon de la maquina_ es {{eF}} y {{eD}} para las precisiones de _32_ y _64 bits_ correspondientemente, y repecto a las comparaciones unicamente se encontraron __dos igualdades__ cuando se uso preciosion de _64bits_.

#### Como ejecutar

[GoogleColab](https://colab.research.google.com/gist/BenchHPZ/cd5bc176f3e3841fa1e84924feca9ec2/tarea1-benjamin_rivera-metodos_numericos.ipynb)

Para ejecutar este ejercicio en __consola__ es importante ubicarse en la misma carpeta del
[archivo `T1.py`](https://github.com/BenchHPZ/UG-Compu/blob/master/MN/Tareas/T1/T1.py)
y ejecutar el siguiente comando en consola
```console
python3 T1.py
```
Este programa no espera recibir argumento alguno. La salida debe ser similar a la siguiente imagen

```python

```


#Work on getting $E$ vs $\theta$ plot parameters w/ 3 extrema

```python
%matplotlib inline
from sympy import *
import matplotlib.pyplot as plt
import numpy as np
init_printing(use_unicode=True)
```

```python
r, u, v, c, r_c, u_c, v_c, E, p, r_p, u_p, v_p, e, a, b, q, b_0, b_1, b_2, b_3, q_0, q_1, q_2, q_3, q_4, q_5, beta, rho, epsilon, delta, d, K_3, Omega, Lambda, lamda, C, mu, Gamma, tau, nu, xi, P_x, eta, varep, gamma, P_0, theta_0, z, a_0, alpha_0, alpha, T_p, T_c, T = symbols('r u v c r_c u_c v_c E p r_p u_p v_p e a b q b_0 b_1 b_2 b_3 q_0 q_1 q_2 q_3 q_4 q_5 beta rho epsilon delta d K_3 Omega Lambda lamda C mu Gamma tau nu xi_2 P_x eta varepsilon gamma P_0 theta_0 z a_0 alpha_0 alpha T_p T_c T')
```

```python
eptil, atil, btil, ctil, Ctil, gtil, thetatil, Ptil, gprm, thetaprm, Pprm, Tprm, chiprm = symbols('epsilontilde atilde btilde ctilde Ctilde gtilde_{0} thetatilde_{0} Ptilde_{0} gprm thetaprm Pprm Tprm chiprm')
```

#Generalized Landau Model of Ferroelectric Liquid Crystals

```python
def g1(a,b,q,xi,P_x,varep,eta,C,Omega):
    return (a*xi**2)/2+(b*xi**4)/4+(q*xi**6)/6+P_x**2/(2*varep)+(eta*P_x**4)/4+C*P_x*xi-(Omega*(P_x*xi)**2)/2
```

```python
g1(a,b,q,xi,P_x,varep,eta,C,Omega)
```

```python
g = (a*xi**2)/2+(b*xi**4)/4+(q*xi**6)/6+P_x**2/(2*epsilon)+(eta*P_x**4)/4+C*P_x*xi-(Omega*(P_x*xi)**2)/2
```

```python
g
```

####$f(c,p) = \dfrac{1}{2}r_{c}c^{2}+\dfrac{1}{4}u_{c}c^{4}+\dfrac{1}{6}v_{c}c^{6}+\dfrac{1}{2}r_{p}p^{2}+\dfrac{1}{4}u_{p}p^{4}+\dfrac{1}{6}v_{p}p^{6}-\gamma cp-\dfrac{1}{2}ec^{2}p^{2}-Ep$

###'Our' version of the equation

$P_x \neq 0 = p$

$P_y = 0$

$\xi_1 = 0$

$\xi_2 \neq 0 = c$

$r_p = \dfrac{1}{\epsilon}$

$u_p = \eta$

$\gamma = -C$

$e = \dfrac{1}{2}\Omega$

###Missing: $p^6$ and $E$ terms

```python
g.subs([(epsilon,1/r_p),(eta,u_p),(C,-gamma),(Omega,2*e),(xi,c),(P_x,p)])
```

#Electrically Induced Tilt in Achiral Bent-Core Liquid Crystals

```python
fc = a*c**2+b*c**4+q*c**6
fp = alpha*p**2+beta*p**4+gamma*p**6-E*p
fcp = -Omega*(p*c)**2
```

```python
fc
```

```python
fp
```

```python
fcp
```

###Mapping:

$\dfrac{1}{2}r_c = a = a_{0}(T-T_c),\ a_{0} = 6\times10^4$ $\text{ N/K m}^2$

$\dfrac{1}{4}u_c = b = 10^4 \text{ N/m}^2$

$\dfrac{1}{6}v_c = q = 9\times10^7 \text{ N/m}^2$

$\dfrac{1}{2}r_p = \alpha = \alpha_{0}(T-T_p) = 0.6\times10^9 \text{ J m/C}^2$

$\dfrac{1}{4}u_p = \beta \approx 10^4 \dfrac{ J m^5}{C^4}$

$\dfrac{1}{6}v_p = \gamma = 0$

$e = \Omega \approx 10^{10}-10^{11} \text{ N/C}$

```python
# fc = fc.subs([(a_0*(T-T_c),a)])#,6e4),(b,10**4),(q,9e7)])
# fc
```

```python
fp = fp.subs([(gamma,0)])#,(alpha_0*(T-T_p),alpha),(beta,10**4)])
fp
```

###$\dfrac{\partial f}{\partial c} = 0$

```python
collect((fc+fp+fcp).diff(c),c)
```

###Solve for $p$

```python
pc = solve((fc+fp+fcp).diff(c),p)[1]
pc
```

###Solve $\dfrac{\partial f}{\partial p} = 0$ for $E$

```python
solve((fc+fp+fcp).diff(p),E)[0]
```

###Sub $p(c)$ into $E(c,p)$

```python
(solve((fc+fp+fcp).diff(p),E)[0]).subs(p,pc)
```

```python
simplify(series((solve((fc+fp+fcp).diff(p),E)[0]).subs(p,pc),c,n=7))
```

###Define above equation for plotting

```python
def Ecc(a,b,q,c,Omega,alpha,beta):
    return [2*np.sqrt((a+2*b*c**2+3*q*c**4)/Omega)*(alpha-Omega*c**2+(2*beta/Omega)*(a+2*b*c**2+3*q*c**4)),
            (Omega*a**3*(4*beta*(a/Omega)**(3/2)+2*alpha*np.sqrt(a/Omega))
            +2*(a*c)**2*np.sqrt(a/Omega)*(-Omega**2*a+Omega*alpha*b+6*a*b*beta)
            +a*c**4*np.sqrt(a/Omega)*(Omega*a*(-2*Omega*b+3*alpha*q)-Omega*alpha*b**2+18*a**2*beta*q+6*a*b**2*beta)
            +c**60*np.sqrt(a/Omega)*(-3*q*(Omega*a)**2+Omega*a*b*(Omega*b-3*alpha*q)+Omega*alpha*b**3+18*a**2*b*beta*q-2*a*b**3*beta))/(Omega*a**3)]
```

```python
def coeff0(a,alpha,beta,Omega):
    return 4*beta*(a/Omega)**(3/2)+2*alpha*np.sqrt(a/Omega)

def coeff2(a,Omega,alpha,b,beta):
    return 2*a**2*np.sqrt(a/Omega)*(-Omega**2*a+Omega*alpha*b+6*a*b*beta)/(Omega*a**3)

def coeff4(a, Omega, b, alpha, q, beta):
    return a*np.sqrt(a/Omega)*(Omega*a*(-2*Omega*b+3*alpha*q)-Omega*alpha*b**2+18*a**2*beta*q+6*a*b**2*beta)/(Omega*a**3)

def coeff6(a, Omega, b, alpha, q, beta):
    return np.sqrt(a/Omega)*(-3*q*(Omega*a)**2+Omega*a*b*(Omega*b-3*alpha*q)+Omega*alpha*b**3+18*a**2*b*beta*q-2*a*b**3*beta)/(Omega*a**3)
```

```python
a = 6e5*(116-114.1)
Omega = 8e4
q = 1
b = 1.01e3
alpha = 0.5e3
beta = 1.05e6
```

```python
coeff0(a,alpha,beta,Omega)
```

```python
coeff2(a,Omega,alpha,b,beta)
```

```python
coeff4(a, Omega, b, alpha, q, beta)
```

```python
coeff6(a, Omega, b, alpha, q, beta)
```

```python
plt.figure(figsize=(11,8))
plt.plot(np.linspace(-17.5,17.5,201),Ecc(a,b,q,np.linspace(-17.5,17.5,201),Omega,alpha,beta)[0],label='$\mathregular{E}$')
# plt.plot(np.linspace(-17.5,17.5,201),Ecc(a,b,q,np.linspace(-17.5,17.5,201),Omega,alpha,beta)[1])
plt.scatter(0,Ecc(a,b,q,0,Omega,alpha,beta)[0],label='$\mathregular{E_{th}}$')
plt.ylim(2.259e8,2.2595e8),plt.xlim(-4,4)
plt.xlabel(c,fontsize=18)
plt.ylabel(E,fontsize=18,rotation='horizontal',labelpad=25)
plt.legend(loc='lower right',fontsize=18);
```

```python
150000+2.258e8
```

```python
plt.figure(figsize=(11,8))
plt.scatter(np.linspace(-17.5,17.5,201),Ecc(6e4*(116-114.1),10**4,9e7,np.linspace(-17.5,17.5,201),10**10,0.6e9,10**4)[1],color='r',label='expand')
plt.ylim(-0.5e14,0.25e14),plt.xlim(-5,5)
plt.xlabel('c',fontsize=18)
plt.ylabel('E',rotation='horizontal',labelpad=25,fontsize=18);
```

```python
def quadterm(alpha,Omega,Eth,E,a):
    return [np.sqrt((Eth-E)/np.sqrt(4*Omega*a)),-np.sqrt((Eth-E)/np.sqrt(4*Omega*a))]
```

```python
# np.linspace(0,60e6,1000)
```

```python
plt.plot(np.linspace(0,60e6,1000),quadterm(0.6e9,10**10,23663663.66366366,np.linspace(0,60e6,1000),6e4*(116-114.1))[0],'b')
plt.plot(np.linspace(0,60e6,1000),quadterm(0.6e9,10**10,23663663.66366366,np.linspace(0,60e6,1000),6e4*(116-114.1))[1],'r');
```

```python
plt.figure(figsize=(11,8))
plt.scatter(Ecc(6e4*(116-114.1),10**4,9e7,np.linspace(7.5,17.5),10**10,0.6e9,10**4)[0],np.linspace(7.5,17.5),color='r',label='no expand')
plt.ylim(0,35)
plt.xlabel('E',fontsize=18)
plt.ylabel('c',rotation='horizontal',labelpad=25,fontsize=18);
```

###$\dfrac{\partial f}{\partial p} = 0$

```python
(fc+fp+fcp).diff(p)
```

###Solve for $c$

```python
solve((fc+fp+fcp).diff(p),c)[1]
```

###Sub $c(p)$ into $p(c)$

```python
expand(simplify(pc.subs(c,solve((fc+fp+fcp).diff(p),c)[1])),p)
```

###Solve for $E(p)$

```python
simplify(solve(expand(simplify(pc.subs(c,solve((fc+fp+fcp).diff(p),c)[1])),p)-p,E)[1])
```

###Define above equation for plotting

```python
def Epp(a,b,q,alpha,beta,Omega,p):
    return (2*p*(Omega*b+3*alpha*q+6*beta*q*p**2)+2*Omega*p*np.sqrt(3*Omega*q*p**2-3*a*q+b**2))/(3*q)
```

```python
plt.figure(figsize=(11,8))
plt.scatter(Epp(6e4*(116-114.1),10**4,9e7,0.6e9,10**4,10**11,np.linspace(0,0.005))/10**7,np.linspace(0,0.005),color='r',label='T = 116')
plt.scatter(Epp(6e4*(110-114.1),10**4,9e7,0.6e9,10**4,10**11,np.linspace(0,0.005))/10**7,np.linspace(0,0.005),color='g',label='T = 110')
plt.scatter(Epp(6e4*(105-114.1),10**4,9e7,0.6e9,10**4,10**11,np.linspace(0,0.005))/10**7,np.linspace(0,0.005),color='b',label='T = 105')
plt.ylim(0,0.006),plt.xlim(0,20)
plt.xlabel('E',fontsize=18)
plt.ylabel('p',rotation='horizontal',labelpad=25,fontsize=18)
plt.legend(loc='upper right',fontsize=16);
```

```python
def ppp(a,b,q,Omega,c):
    return np.sqrt((a+2*b*c**2+3*q*c**4)/Omega)
```

```python
plt.figure(figsize=(11,8))
plt.scatter(np.linspace(0,35),ppp(6e4*(116-114.1),10**4,9e7,10**10,np.linspace(0,35)))
plt.ylim(-2,220),plt.xlim(-1,36)
plt.xlabel('c',fontsize=18)
plt.ylabel('p',rotation='horizontal',labelpad=25,fontsize=18);
```

```python
simplify((solve((fp+fcp).diff(p),E)[0]).subs(p,solve((fc+fp+fcp).diff(c),p)[1]))
```

```python
def Ec(om, a, b, c, q, alp, beta):
    return (2/om)*np.sqrt((a+2*b*c**2+3*q*c**4)/om)*(alp*om-(c*om)**2+2*beta*a+4*b*beta*c**2+6*beta*q*c**4)
```

```python
expand((alpha-E/(2*p)+2*beta*p**2)**2)
```

```python
plt.scatter(Ec(10**11,6e4,10**4,np.linspace(0,15,200),9e7,0.6e9,10**4),np.linspace(0,15,200),marker='s');
```

```python
def Eth(T):
    E = []
    for i in T:
        if i > 114.1:
            E.append(0.64e6*(i-100)*np.sqrt(i-114.1))
        else:
            E.append(0)
    return E
```

```python
plt.plot(np.linspace(113,119,10),Eth(np.linspace(113,119,10)))
plt.xlim(113,122),plt.ylim(0,3e7);
```

```python

```


```
"""Demonstration of quantum gate identity search."""

from sympy.physics.quantum.gate import (X, Y, Z, H, S, T, CNOT,
        IdentityGate, CGate, gate_simp)
from sympy.physics.quantum.identitysearch import *
from sympy.physics.quantum.dagger import Dagger
```

```
# Declare a few quantum gates
x = X(0)
y = Y(0)
z = Z(0)
h = H(0)
cnot = CNOT(1,0)
cgate_z = CGate((0,), Z(1))
```

```
# Start with the trivial cases
gate_list = [x]

bfs_identity_search(gate_list, 1, max_depth=2)
```

    set([GateIdentity(X(0), X(0))])

```
gate_list = [y]

bfs_identity_search(gate_list, 1, max_depth=2)
```

    set([GateIdentity(Y(0), Y(0))])

```
# bfs_identity_search looks for circuits that reduce to a
# scalar value unless told otherwise.
# The following list should produce 4 identities as a result.
gate_list = [x, y, z]

bfs_identity_search(gate_list, 2)
```

    set([GateIdentity(X(0), X(0)),
         GateIdentity(Z(0), Z(0)),
         GateIdentity(X(0), Y(0), Z(0)),
         GateIdentity(Y(0), Y(0))])

```
gate_list = [x, y, z, h]

bfs_identity_search(gate_list, 2)
```

    set([GateIdentity(Y(0), H(0), Y(0), H(0)),
         GateIdentity(X(0), Y(0), X(0), Y(0)),
         GateIdentity(X(0), Y(0), Z(0)),
         GateIdentity(X(0), H(0), Z(0), H(0)),
         GateIdentity(Z(0), Z(0)),
         GateIdentity(X(0), X(0)),
         GateIdentity(Y(0), Y(0)),
         GateIdentity(X(0), Z(0), X(0), Z(0)),
         GateIdentity(Y(0), Z(0), Y(0), Z(0)),
         GateIdentity(H(0), H(0))])

```
# One has the option to limit the max size of the circuit.
# The default size is the size of the gate list.
bfs_identity_search(gate_list, 2, max_depth=3)
```

    set([GateIdentity(X(0), X(0)),
         GateIdentity(X(0), Y(0), Z(0)),
         GateIdentity(Z(0), Z(0)),
         GateIdentity(H(0), H(0)),
         GateIdentity(Y(0), Y(0))])

```
# One also has the option to find circuits that only reduce
# to the Identity matrix rather than only scalar matrices.
bfs_identity_search(gate_list, 2, identity_only=True)
```

    set([GateIdentity(X(0), X(0)),
         GateIdentity(Z(0), Z(0)),
         GateIdentity(H(0), H(0)),
         GateIdentity(Y(0), Y(0))])

```
gate_list = [cnot, cgate_z, h]

bfs_identity_search(gate_list, 2, max_depth=4)
```

    set([GateIdentity(CNOT(1,0), H(0), C((0),Z(1)), H(0)),
         GateIdentity(H(0), H(0)),
         GateIdentity(C((0),Z(1)), C((0),Z(1))),
         GateIdentity(CNOT(1,0), CNOT(1,0))])


# Dispersion relations for cold fluid/hybrid electrons

## 1. Cold fluid electrons

Only frequencies much larger than the ion plasma frequency $\Omega_\text{pi}$ are considered why ions are treated as a neutralizing stationary background. The remaining linear model for the perturbed electric field $\textbf{E}_1$, magnetic field $\textbf{B}_1$ and electron current density $\textbf{j}_1=qn_0\textbf{u}_1$ then reads (omit subscripts):

\begin{align}
\frac{\partial \textbf{j}}{\partial t} - \epsilon_0\Omega_\text{pe}^2\textbf{E} + \Omega_0\times\textbf{j} = 0, \\
\frac{1}{c^2}\frac{\partial \textbf{E}}{\partial t} - \nabla\times\textbf{B} + \mu_0\textbf{j} = 0, \\
\frac{\partial \textbf{B}}{\partial t} + \nabla\times\textbf{E} = 0,
\end{align}

with $\Omega_0=\frac{q\textbf{B}_0}{m}$ and $\Omega_\text{pe}^2=\frac{q^2 n_0}{\epsilon_0 m}$.

Assuming that all quantites ~$e^{i(kz-\omega t)}$ and $\textbf{B}_0=B_0\textbf{e}_z$ the following dispersion relations can be obtained for R- and L-waves, respectivley:

\begin{align}
D_\text{cold,R/L}(k,\omega)=1-\frac{c^2k^2}{\omega^2}-\frac{\Omega_\text{pe}^2}{\omega(\omega\pm\Omega_\text{ce})}=0 \Leftrightarrow k_\text{R/L} = \frac{\omega}{c}\sqrt{1-\frac{\Omega_\text{pe}^2}{\omega(\omega\pm\Omega_\text{ce})}},
\end{align}

where $\Omega_\text{ce}=\frac{qB_0}{m}=-\frac{eB_0}{m}<0$ for electrons.

## 2. Cold fluid electrons with artificial energetic particles

In order to get a damping/growth of the wave field, it is assumed that there is current density $\textbf{j}_\text{h}$ coming from energetic particles. For simplicity it is assumed that the there is linear response to the electric field of the from

\begin{align}
\textbf{j}_\text{h} = \sigma_\text{h}\textbf{E},
\end{align}

with some constant conductivity tensor

\begin{align}
\sigma_\text{h}=
\begin{pmatrix}
c_1 & c_2 &0 \\
-c_2 &c_1 &0 \\
0 &0 &c_3 \\
\end{pmatrix}.
\end{align}

The model then reads

\begin{align}
\frac{\partial \textbf{j}}{\partial t} - \epsilon_0\Omega_\text{pe}^2\textbf{E} + \Omega_0\times\textbf{j} = 0, \\
\frac{1}{c^2}\frac{\partial \textbf{E}}{\partial t} - \nabla\times\textbf{B} + \mu_0(\textbf{j}+\sigma_\text{h}\textbf{E}) = 0, \\
\frac{\partial \textbf{B}}{\partial t} + \nabla\times\textbf{E} = 0,
\end{align}

with the dispersion relation

\begin{align}
D_\text{art,R/L}(k,\omega)=D_\text{cold,R/L}(k,\omega)+\frac{c^2\mu_0}{\omega}(ic_1\mp c_2)
\end{align}

for R/L-waves ($c_3=0$).

## 3. Hybrid model
The self-consistend electron hybrid model with energetic electrons which are treated kinetically reads

\begin{align}
\frac{\partial \textbf{j}_\text{c}}{\partial t} = \epsilon_0\Omega_{\text{pe}}^2\textbf{E} + \Omega_\text{ce}\textbf{j}_\text{c}\times\textbf{e}_z,  \\
\frac{1}{c^2}\frac{\partial \textbf{E}}{\partial t} = \nabla\times\textbf{B} - \mu_0(\textbf{j}_\text{c} + \textbf{j}_\text{h}), \\
\frac{\partial \textbf{B}}{\partial t}=-\nabla\times\textbf{E}, \\
\frac{\partial f_\text{h}}{\partial t} + \textbf{v}\cdot\nabla f_\text{h} + \frac{q}{m}(\textbf{E}+\textbf{v}\times\textbf{B})\cdot\nabla_v f_\text{h}=0, \\
\textbf{j}_\text{h} = q\int\text{d}^3\textbf{v}\,\textbf{v}f_\text{h}.
\end{align}

Linear theory of the above model leads to the following general dispersion relation for an arbitrary equilibrium distribution function $f^0=f^0(v_\parallel,v_\bot)$:

\begin{align}
D_{\text{R/L}}(k,\omega)=1-\frac{c^2k^2}{\omega^2}-\frac{\Omega_{\text{pe}}^2}{\omega(\omega\pm\Omega_{\text{ce}})}+\nu_\text{h}\frac{\Omega_{\text{pe}}^2}{\omega}\int\text{d}^3\textbf{v}\frac{v_\bot}{2}\frac{\hat{G}f_\text{h}^0}{\omega\pm\Omega_{\text{ce}}-kv_\parallel}=0.
\end{align}

Here $\nu_\text{h}=n_\text{h}/n_\text{c}\ll1$ is the ratio between the hot and cold electron number densities, respectively, $\text{d}^3\textbf{v}=\text{d}v_\parallel\text{d}v_\bot v_\bot 2\pi$ and the differential operator

\begin{align}
\hat{G}=\frac{\partial}{\partial v_\bot}+\frac{k}{\omega}\left(v_\bot\frac{\partial}{\partial v_\parallel}-v_\parallel\frac{\partial}{\partial v_\bot}\right).
\end{align}

For an anisotropic Maxwellian

\begin{align}
f^0(v_\parallel,v_\bot) = \frac{1}{(2\pi)^{3/2}w_\parallel w_\bot^2}\exp\left(-\frac{v_\parallel^2}{2w_\parallel^2}-\frac{v_\bot^2}{2w_\bot^2}\right)
\end{align}

the dispersion relation is given by

\begin{align}
D_{\text{R/L}}(k,\omega)=D_{\text{cold,R/L}}(k,\omega)+\nu_\text{h}\frac{\Omega_{\text{pe}}^2}{\omega^2}\left[\frac{\omega}{k\sqrt{2}w_\parallel}Z(\xi^{\pm})-\left(1-\frac{w_\bot^2}{w_\parallel^2}\right)(1+\xi^{\pm} Z(\xi^{\pm}))\right]=0,
\end{align}

where $Z$ is the plasma dispersion function and

\begin{align}
\xi^{\pm} = \frac{\omega\pm\Omega_\text{ce}}{k\sqrt{2}w_\parallel}.
\end{align}

# 1. Cold plasma

```python
import numpy as np
import matplotlib.pyplot as plt
import scipy.special as sp
import utilitis_opt as utils

# ... parameters
wce = -1                           # ... electron cyclotron frequency
wpe = 5*np.abs(wce)                # ... cold plasma frequency (normalized to electron cyclotron frequency)
wpar = 0.2                         # ... parallel thermal velocity of hot particles (normalized to speed of light)
wperp = 0.55                       # ... perpendiculer thermal velocity of hot particles (normalized to speed of light)
nuh = 0.006                        # ... density ratio of cold/hot particles
c1 = -0.5                          # ... artificial energetic current
c2 = -0.1                          # ... artificial energetic current
mu0 = 1                            # ... vacuum permeability
c = 1                              # ... speed of light
# ...

# ... plasma dispersion function and its derivative
def Z(xi):
    return np.sqrt(np.pi)*np.exp(-xi**2)*(1j - sp.erfi(xi))

def Zprime(xi):
    return -2*(1 + xi*Z(xi))
# ...

# ... Newton method for cold plasma dispersion relation for fixed k
k1 = np.linspace(0.1,10.0,40)

w1_1 = np.zeros(40)
w1_2 = np.zeros(40)
w1_3 = np.zeros(40)

w1_1[0] = 0.0001
w1_2[0] = 2.0001
w1_3[0] = 2.5001

for i in range(0,40):

    if i == 0:
        w1_1[i] = utils.solveDispersionCold(k1[i],+1,c,wce,wpe,w1_1[i],1e-6,100)[0]
        w1_2[i] = utils.solveDispersionCold(k1[i],-1,c,wce,wpe,w1_2[i],1e-6,100)[0]
        w1_3[i] = utils.solveDispersionCold(k1[i],+1,c,wce,wpe,w1_3[i],1e-6,100)[0]

    else:
        w1_1[i] = utils.solveDispersionCold(k1[i],+1,c,wce,wpe,w1_1[i-1],1e-6,100)[0]
        w1_2[i] = utils.solveDispersionCold(k1[i],-1,c,wce,wpe,w1_2[i-1],1e-6,100)[0]
        w1_3[i] = utils.solveDispersionCold(k1[i],+1,c,wce,wpe,w1_3[i-1],1e-6,100)[0]

# ...

# ... plot results
f1 = plt.figure()
f1.set_figheight(8)
f1.set_figwidth(12)
plt.subplots_adjust(wspace = 0.3, hspace = 0.4)

plt.rc('text',usetex = True)
plt.rc('font',family = 'serif')
plt.rcParams.update({'font.size': 16})
plt.rc('xtick',labelsize = 16)
plt.rc('ytick',labelsize = 16)

plt.subplot(221)
plt.plot(k1,np.real(w1_1),label = 'R-wave')
plt.plot(k1,np.real(w1_2),label = 'L-wave')
plt.plot(k1,np.real(w1_3),label = 'R-wave')
plt.ylim((0,4))
plt.plot(k1,np.ones(len(k1))*(-wce),'k--',label = r'$\Omega_\mathrm{ce}$')
plt.title('Cold plasma dispersion relation',fontsize = 16)
plt.xlabel(r'$kc / \Omega_\mathrm{ce}$')
plt.ylabel(r'$\mathrm{Re}(\omega) / \Omega_\mathrm{ce}$')
plt.legend()
# ...
```

# 2. Artificial energetic electrons

```python
# ... Newton method for artificial plasma dispersion relation for fixed k
k2 = np.linspace(0.1,8.0,40)

w2_1 = np.zeros(40,dtype = complex)
w2_2 = np.zeros(40,dtype = complex)
w2_3 = np.zeros(40,dtype = complex)

w2_1[0] = 0.0001 + 0j
w2_2[0] = 2.0001 + 0j
w2_3[0] = 2.5001 + 0j

for i in range(0,40):

    if i == 0:
        w2_1[i] = utils.solveDispersionArtificial(k2[i],+1,c,wce,wpe,c1,c2,mu0,w2_1[i],1e-6,100)[0]
        w2_2[i] = utils.solveDispersionArtificial(k2[i],-1,c,wce,wpe,c1,c2,mu0,w2_2[i],1e-6,100)[0]
        w2_3[i] = utils.solveDispersionArtificial(k2[i],+1,c,wce,wpe,c1,c2,mu0,w2_3[i],1e-6,100)[0]

    else:
        w2_1[i] = utils.solveDispersionArtificial(k2[i],+1,c,wce,wpe,c1,c2,mu0,w2_1[i-1],1e-6,100)[0]
        w2_2[i] = utils.solveDispersionArtificial(k2[i],-1,c,wce,wpe,c1,c2,mu0,w2_2[i-1],1e-6,100)[0]
        w2_3[i] = utils.solveDispersionArtificial(k2[i],+1,c,wce,wpe,c1,c2,mu0,w2_3[i-1],1e-6,100)[0]

# ...

# ... plot results
f2 = plt.figure()
f2.set_figheight(8)
f2.set_figwidth(12)
plt.subplots_adjust(wspace = 0.3, hspace = 0.4)

plt.subplot(221)
plt.plot(k2,np.real(w2_1),label = 'R-wave')
plt.plot(k2,np.real(w2_2),label = 'L-wave')
plt.plot(k2,np.real(w2_3),label = 'R-wave')
plt.plot(k1,np.real(w1_1),'--',label = 'cold plasma')
plt.ylim((0,4))
plt.plot(k2,np.ones(len(k2))*(-wce),'k--')
plt.xlabel(r'$kc / \Omega_\mathrm{ce}$')
plt.ylabel(r'$\mathrm{Re}(\omega) / \Omega_\mathrm{ce}$')
plt.title('Real oscillation frequency',fontsize = 16)
plt.legend()

plt.subplot(222)
plt.plot(k2,np.imag(w2_1),label = 'R-wave: artificial')
plt.plot(k2,np.imag(w2_2),label = 'L-wave: artificial')
plt.plot(k2,np.imag(w2_3),label = 'R-wave: artificial')
plt.xlabel(r'$kc / \Omega_\mathrm{ce}$')
plt.ylabel(r'$\mathrm{Im}(\omega) / \Omega_\mathrm{ce}$')
plt.title('Growth rate',fontsize = 16)
plt.legend()

plt.subplot(223)
plt.plot(np.real(w2_1),np.imag(w2_1))
plt.plot(np.real(w2_2),np.imag(w2_2))
plt.plot(np.real(w2_3),np.imag(w2_3))
plt.title('Growth rate',fontsize = 16)
plt.xlabel(r'$\mathrm{Re}(\omega) / \Omega_\mathrm{ce}$')
plt.ylabel(r'$\mathrm{Im}(\omega) / \Omega_\mathrm{ce}$')
# ...
```

# 3. Hybrid model (nonrelativistic)

```python
# ... Newton method for hybrid plasma dispersion relation for fixed k
k3 = np.linspace(0.4,8.0,100)

w3_1 = np.zeros(len(k3),dtype = complex)
w3_2 = np.zeros(len(k3),dtype = complex)
w3_3 = np.zeros(len(k3),dtype = complex)

w3_1[0] = 0.0001 + 0j
w3_2[0] = 2.0001 + 0j
w3_3[0] = 2.5001 + 0j

gamma_explicit = np.zeros(len(k3))

for i in range(0,len(k3)):

    if i == 0:
        w3_1[i] = utils.solveDispersionHybrid(k3[i],+1,c,wce,wpe,wpar,wperp,nuh,w3_1[i],1e-6,100)[0]
        w3_2[i] = utils.solveDispersionHybrid(k3[i],-1,c,wce,wpe,wpar,wperp,nuh,w3_2[i],1e-6,100)[0]
        w3_3[i] = utils.solveDispersionHybrid(k3[i],+1,c,wce,wpe,wpar,wperp,nuh,w3_3[i],1e-6,100)[0]

        gamma_explicit[i] = utils.solveDispersionHybridExplicit(k3[i],+1,c,wce,wpe,wpar,wperp,nuh,np.real(w3_1[i]),1e-6,1000)[1]

    else:
        w3_1[i] = utils.solveDispersionHybrid(k3[i],+1,c,wce,wpe,wpar,wperp,nuh,w3_1[i-1],1e-6,100)[0]
        w3_2[i] = utils.solveDispersionHybrid(k3[i],-1,c,wce,wpe,wpar,wperp,nuh,w3_2[i-1],1e-6,100)[0]
        w3_3[i] = utils.solveDispersionHybrid(k3[i],+1,c,wce,wpe,wpar,wperp,nuh,w3_3[i-1],1e-6,100)[0]

        gamma_explicit[i] = utils.solveDispersionHybridExplicit(k3[i],+1,c,wce,wpe,wpar,wperp,nuh,np.real(w3_1[i]),1e-6,1000)[1]
# ...

# ... plot results
f2 = plt.figure()
f2.set_figheight(8)
f2.set_figwidth(12)
plt.subplots_adjust(wspace = 0.3, hspace = 0.4)

def plot_DispersionFunction():
    plt.subplot(221)
    xi = np.linspace(-4,4,100)
    plt.plot(xi,np.real(Z(xi)),label = r'Re($Z(\xi)$)')
    plt.plot(xi,np.imag(Z(xi)),label = r'Im($Z(\xi)$)')
    plt.title(r'Plasma dispersion function $Z$',fontsize = 16)
    plt.xlabel(r'$\xi$')
    plt.ylim((-2,2))
    plt.legend()

    plt.subplot(222)
    plt.plot(xi,np.real(Zprime(xi)),label = r'Re($Z^\prime(\xi)$)')
    plt.plot(xi,np.imag(Zprime(xi)),label = r'Im($Z^\prime(\xi)$)')
    plt.title(r'Derivative of Plasma dispersion function $\frac{\mathrm{d}Z}{\mathrm{d}\xi}$',fontsize = 16)
    plt.xlabel(r'$\xi$')
    plt.ylim((-2.5,2.5))
    plt.legend()

plot_DispersionFunction()

plt.subplot(223)
plt.plot(k3,np.real(w3_1),label = 'R-wave',linewidth = 2)
plt.plot(k3,np.real(w3_2),label = 'L-wave',linewidth = 2)
plt.plot(k3,np.real(w3_3),label = 'R-wave',linewidth = 2)
plt.plot(k1,np.real(w1_1),'r--',label = 'cold plasma')
plt.ylim((0,4))
plt.plot(k1,np.ones(len(k1))*(-wce),'k--')
plt.xlabel(r'$kc / \Omega_\mathrm{ce}$')
plt.ylabel(r'$\mathrm{Re}(\omega) / \Omega_\mathrm{ce}$')
plt.title('Real oscillation frequency',fontsize = 16)
plt.legend()

plt.subplot(224)
plt.plot(k3,np.imag(w3_1),label = 'R-wave',linewidth = 2)
plt.plot(k3,np.imag(w3_2),label = 'L-wave',linewidth = 2)
plt.plot(k3,np.imag(w3_3),label = 'R-wave',linewidth = 2)
plt.plot(k3,gamma_explicit,'k--',label = 'R-wave - explicit',linewidth = 2)
plt.xlabel(r'$kc / \Omega_\mathrm{ce}$')
plt.ylabel(r'$\mathrm{Im}(\omega) / \Omega_\mathrm{ce}$')
plt.title('Growth rate',fontsize = 16)
plt.legend()
# ...

# ... save figure as .pdf
#plt.savefig('/home/florian/Schreibtisch/Masterarbeit/05_Präsentationen/02_Summary_HalfTime/01_Figures/HybridDispersion.pdf',bbox_inches = 'tight')
# ...
```

# 3.1 Maximum growth rate for different anisotropies

```python
wperp = np.linspace(0.3, 0.7, 10)

k4 = np.linspace(0.4, 8.0, 800)
w4 = np.zeros((len(wperp), len(k4)), dtype = complex)
w4[:, 0] = 0.0001 + 0j
max_growth = np.zeros(len(wperp))
max_freq = np.zeros(len(wperp))
ind_max = np.zeros(len(wperp), dtype = int)

for j in range(len(wperp)):

    for i in range(0, len(k4)):

        if i == 0:
            w4[j, i] = utils.solveDispersionHybrid(k4[i], +1, c, wce, wpe, wpar, wperp[j], nuh, w4[j, i], 1e-6, 100)[0]

        else:
            w4[j, i] = utils.solveDispersionHybrid(k4[i], +1, c, wce, wpe, wpar, wperp[j], nuh, w4[j, i-1], 1e-6, 100)[0]

    plt.plot(np.real(w4[j]), np.imag(w4[j]))
    ind_max[j] = np.where(np.imag(w4[j]) == np.max(np.imag(w4[j])))[0]
    max_growth[j] = np.imag(w4[j, ind_max[j]])
    max_freq[j] = np.real(w4[j, ind_max[j]])

plt.xlabel(r'$\mathrm{Re}(\omega) / \Omega_\mathrm{ce}$')
plt.ylabel(r'$\mathrm{Im}(\omega) / \Omega_\mathrm{ce}$')
```

```python
plt.plot(wperp, max_freq)
plt.xlabel(r'$w_\bot$')
plt.ylabel(r'$\mathrm{Re}(\omega)_\mathrm{max} / \Omega_\mathrm{ce}$')
```

# 3. Hybrid model (relativistic)

```python
# ... Newton method for hybrid plasma dispersion relation for fixed k
gammal = np.sqrt(1 + wpar**2 + wperp**2)

k4 = np.linspace(0.4, 8.0, 100)

wr4 = np.zeros(len(k4))

wr4[0] = 0.0001

gamma4 = np.zeros(len(k4))

for i in range(len(k4)):

    if i == 0:
        wr4[i], gamma4[i] = utils.solveDispersionHybridRelativistic(k4[i], +1, c, wce, wpe, wpar, wperp, nuh, wr4[i], 1e-6, 1000)[0:2]

    else:
        wr4[i], gamma4[i] = utils.solveDispersionHybridRelativistic(k4[i], +1, c, wce, wpe, wpar, wperp, nuh, wr4[i - 1], 1e-6, 1000)[0:2]
# ...
```

```python
plt.plot(k4, gamma4)
plt.plot(k3, np.imag(w3_1), 'k--')
```

```python
plt.plot(wr4, 100*gamma4)
```

```python
gamma4.max()
```

    0.010517837152266217

```python

```


```python
import matplotlib.pyplot as plt
import numpy as np
from ipywidgets import interact
from sympy.solvers import solve
from sympy import *
```

```python

a1, c1 = 0.005, 0.005
r1,b11 = -0.1, 0.01

x = Symbol('x')
```

```python

defxis = x*(r1 + (b11-a1)*x - c1*b11*x**2)
exis = solve(defxis,x)

print(exis, defxis)

```

    [0.0, 27.6393202250021, 72.3606797749979] x*(-5.0e-5*x**2 + 0.005*x - 0.1)

```python
lista1 = np.zeros(len(exis))

plt.plot(exis,lista1,'ro')
plt.axhline(c='k')

x1 =np.linspace(float(min(exis)),float(max(exis)),100)
y1 = [defxis.subs(x,uno) for uno in x1]
plt.plot(x1,y1)

plt.show()
```

```python
lista1
```

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

```python
xxlist=[]
yylist=[]

xZ = [complex(k) for k in xlist]
yZ = [complex(k) for k in ylist]

for k in range(len(xlist)):
    if (xZ[k].real>=0) and (yZ[k].real>=0):
        xxlist.append(xZ[k].real)
        yylist.append(yZ[k].real)

#for k in range(len(xlist)):
#    if (complex(xlist[k]))
#    if not (np.iscomplex(complex(xlist[k])) or np.iscomplex(complex(ylist[k]))):

#            xxlist.append(xlist[k])
#            yylist.append(ylist[k])

print(xxlist)
print(yylist)
```

    [0.0, 0.0, 0.0, 200.0, 27.46948379799888, 257.2156000536751]
    [0.0, 70.90403614008034, 132.22096385991966, 0.0, 20.190409250127118, 206.49558922992583]

```python
l = Symbol('l')
lambs=[]
for j in range(len(xlist)):
    xx = xlist[j]
    yy = ylist[j]

    J11 = r1 + b12*yy + 2*(b11-a1)*xx - 2*c1*b12*xx*yy - 3*c1*b11*xx*xx
    J12 = b12*xx*(1. - c1*xx)
    J21 = b21*yy*(1. - c2*yy)
    J22 = r2 + b21*xx + 2*(b22-a2)*yy - 2*c2*b21*xx*yy - 3*c2*b22*yy*yy
    lamb = solve((J11-l)*(J22-l)-J12*J21,l)
    lambs.append(lamb)

print(lambs)
```

    [[-0.150000000000000, 0.150000000000000], [-0.360509060208578, 0.0695618825447389], [-0.801990939791422, -0.129718132544739], [-0.150000000000000, 0.570000000000000], [0.0199791814585587 - 0.105627506349973*I, 0.0199791814585587 + 0.105627506349973*I], [-1.90042436607258 - 4.31279455571633e-20*I, -1.37509392154655 + 5.81100713615625e-20*I], [-1.46069476364267 + 4.67380114958431e-21*I, 1.33923999196228 - 1.85972934060127e-20*I]]

```python
plt.axvline(0)
plt.axhline(0)
plt.plot(xxlist,yylist,'ro')
y, x = np.mgrid[min(yylist)-1:max(yylist)+10:100j, min(xxlist)-1:max(xxlist)+10:100j]
eq1 = x*(r1 + (b11-a1)*x + b12*y - c1*x*(b11*x + b12*y))
eq2 = y*(r2 + (b22-a2)*y + b21*x - c2*y*(b21*x + b22*y))
plt.streamplot(x, y, eq1, eq2, color=eq1, linewidth=2)
plt.savefig('predator-prey+intraspecifi.jpg')
plt.show()
```

```python
def catalogo(r1,r2,b11,b12,b21,b22):
        a1 = 0.00075
        a2 = 0.00075
        c1 = 0.004
        c2 = 0.004
        x = Symbol('x')
        y = Symbol('y')
        lista = solve([x*(r1 + (b11-a1)*x + b12*y - c1*x*(b11*x + b12*y)),y*(r2 + (b22-a2)*y + b21*x - c2*y*(b21*x + b22*y))],[x,y], check=False)
        xlist = [i[0] for i in lista]
        ylist = [i[1] for i in lista]

        print(xlist)
        print(ylist)
        xxlist=[]
        yylist=[]
        for k in range(len(xlist)):
            if not (np.iscomplex(complex(xlist[k])) or np.iscomplex(complex(ylist[k]))):
                xxlist.append(xlist[k])
                yylist.append(ylist[k])
        plt.axvline(0)
        plt.axhline(0)
        plt.plot(xxlist,yylist,'ro')
        y, x = np.mgrid[min(yylist)-1:max(yylist)+10:100j, min(xxlist)-1:max(xxlist)+10:100j]
        eq1 = x*(r1 + (b11-a1)*x + b12*y - c1*x*(b11*x + b12*y))
        eq2 = y*(r2 + (b22-a2)*y + b21*x - c2*y*(b21*x + b22*y))
        plt.streamplot(x, y, eq1, eq2, color=eq1, linewidth=2)
        plt.savefig('predator-prey+intraspecifi.jpg')
        plt.show()

        l = Symbol('l')
        lambs=[]
        for j in range(len(xlist)):
            xx = xlist[j]
            yy = ylist[j]

            J11 = r1 + b12*yy + 2*(b11-a1)*xx - 2*c1*b12*xx*yy - 3*c1*b11*xx*xx
            J12 = b12*xx*(1. - c1*xx)
            J21 = b21*yy*(1. - c2*yy)
            J22 = r2 + b21*xx + 2*(b22-a2)*yy - 2*c2*b21*xx*yy - 3*c2*b22*yy*yy
            lamb = solve((J11-l)*(J22-l)-J12*J21,l)
            lambs.append(lamb)
        return (lista,lambs)
catalogo(0.15, -0.15, 0.0000, -0.0072, 0.0036, 0.004)
```

```python
solve?

```

```python

```


# Ch01_XOR을 위한 신경망

* 사용할 패키지 불러오기

```python
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
import tensorflow as tf

print(tf.__version__)
```

    2.0.0-alpha0

* 시드값 고정

```python
tf.random.set_seed(777)
```

* 사용할 데이터 가져오기

```python
x_data = [[0, 0],
          [0, 1],
          [1, 0],
          [1, 1]]
y_data = [[0],
          [1],
          [1],
          [0]]
```

* 데이터 배열

```python
plt.scatter(x_data[0][0],x_data[0][1], c='red' , marker='^')
plt.scatter(x_data[3][0],x_data[3][1], c='red' , marker='^')
plt.scatter(x_data[1][0],x_data[1][1], c='blue' , marker='^')
plt.scatter(x_data[2][0],x_data[2][1], c='blue' , marker='^')
```

* 각 축에 이름 새기기

```python
plt.scatter(x_data[0][0],x_data[0][1], c='red' , marker='^')
plt.scatter(x_data[3][0],x_data[3][1], c='red' , marker='^')
plt.scatter(x_data[1][0],x_data[1][1], c='blue' , marker='^')
plt.scatter(x_data[2][0],x_data[2][1], c='blue' , marker='^')

plt.xlabel("x1")
plt.ylabel("x2")
plt.show()
```

## 모델링 시작

* 데이터셋 설정

```python
dataset = tf.data.Dataset.from_tensor_slices((x_data, y_data)).batch(len(x_data))
```

* 데이터 값을 실수로 전환

```python
def preprocess_data(features, labels):
    features = tf.cast(features, tf.float32)
    labels = tf.cast(labels, tf.float32)
    return features, labels
```

### 1) Logistic Regression 활용해 XOR 해결

[](https://postimg.cc/gXvTWmpX)

* Weight, bias 초기값 세팅

```python
W = tf.Variable(tf.zeros((2, 1)), name = 'weight')
b = tf.Variable(tf.zeros((1, )), name = 'bias')
```

* Sigmoid 함수 설정

```python
def logistic_regression(features):
    hypothesis = tf.divide(1., 1. + tf.exp(tf.matmul(features, W) + b))
    return hypothesis
```

* Cost 함수 설정
$$
\begin{align}
cost(h(x),y) & = −y log(h(x))−(1−y)log(1−h(x))
\end{align}
$$

```python
def loss_fn(hypothesis, features, labels):
    cost = tf.reduce_mean(-labels *tf.math.log(hypothesis) - (1- labels) * tf.math.log(1 - hypothesis))
    return cost
```

* Optimizer 설정

```python
optimizer = tf.keras.optimizers.SGD(learning_rate = 0.01)
```

* Sigmoid를 통해 0.5를 기준으로 y값을 0, 1 중 선택

```python
def accuracy_fn(hypothesis, labels):
    predicted = tf.cast(hypothesis > 0.5, dtype = tf.float32)
    accuracy = tf.reduce_mean(tf.cast(tf.equal(predicted, labels), dtype = tf.float32)) # predicted와 labels이 동일하면 1, 아니면 0
    return accuracy
```

* GradientTape으로 경사값 구하기

```python
def grad(hypothesis, features, labels):
    with tf.GradientTape() as tape:
        loss_value = loss_fn(logistic_regression(features), features, labels)
    return tape.gradient(loss_value, [W, b])
```

* Session 실행

```python
EPOCHS = 1001

for step in range(EPOCHS):
    for features, labels in dataset:
        features, labels = preprocess_data(features, labels)
        grads = grad(logistic_regression(features), features, labels)
        optimizer.apply_gradients(grads_and_vars = zip(grads, [W, b]))

        if step % 100 ==0:
            print("Iter: {}, Loss: {:.4f}".format(step, loss_fn(logistic_regression(features),features,labels)))
print("W = {}, B = {}".format(W.numpy(), b.numpy()))
```

    Iter: 0, Loss: 0.6931
    Iter: 100, Loss: 0.6931
    Iter: 200, Loss: 0.6931
    Iter: 300, Loss: 0.6931
    Iter: 400, Loss: 0.6931
    Iter: 500, Loss: 0.6931
    Iter: 600, Loss: 0.6931
    Iter: 700, Loss: 0.6931
    Iter: 800, Loss: 0.6931
    Iter: 900, Loss: 0.6931
    Iter: 1000, Loss: 0.6931
    W = [[0.]
     [0.]], B = [0.]

```python
x_data, y_data = preprocess_data(x_data, y_data)
test_acc = accuracy_fn(logistic_regression(x_data),y_data)
print("Testset Accuracy: {:.4f}".format(test_acc))
```

    Testset Accuracy: 0.5000

## 2) Neural Net 활용해 XOR 해결

[](https://postimg.cc/K45m7yT8)

```python
dataset = tf.data.Dataset.from_tensor_slices((x_data, y_data)).batch(len(x_data))

def preprocess_data(features, labels):
    features = tf.cast(features, tf.float32)
    labels = tf.cast(labels, tf.float32)
    return features, labels
```

```python
W1 = tf.Variable(tf.random.normal((2, 1)), name='weight1')
b1 = tf.Variable(tf.random.normal((1,)), name='bias1')

W2 = tf.Variable(tf.random.normal((2, 1)), name='weight2')
b2 = tf.Variable(tf.random.normal((1,)), name='bias2')

W3 = tf.Variable(tf.random.normal((2, 1)), name='weight3')
b3 = tf.Variable(tf.random.normal((1,)), name='bias3')
```

```python
def neural_net(features):
    layer1 = tf.sigmoid(tf.matmul(features, W1) + b1)
    layer2 = tf.sigmoid(tf.matmul(features, W2) + b2)
    layer3 = tf.concat([layer1, layer2],-1)
    layer3 = tf.reshape(layer3, shape = [-1,2])
    hypothesis = tf.sigmoid(tf.matmul(layer3, W3) + b3)
    return hypothesis

def loss_fn(hypothesis, labels):
    cost = -tf.reduce_mean(labels * tf.math.log(hypothesis) + (1 - labels) * tf.math.log(1 - hypothesis))
    return cost

optimizer = tf.keras.optimizers.SGD(learning_rate=0.01)

def accuracy_fn(hypothesis, labels):
    predicted = tf.cast(hypothesis > 0.5, dtype=tf.float32)
    accuracy = tf.reduce_mean(tf.cast(tf.equal(predicted, labels), dtype=tf.float32))
    return accuracy

def grad(hypothesis, features, labels):
    with tf.GradientTape() as tape:
        loss_value = loss_fn(neural_net(features),labels)
    return tape.gradient(loss_value, [W1, W2, W3, b1, b2, b3])
```

```python
EPOCHS = 50000

for step in range(EPOCHS):
    for features, labels  in dataset:
        features, labels = preprocess_data(features, labels)
        grads = grad(neural_net(features), features, labels)
        optimizer.apply_gradients(grads_and_vars=zip(grads,[W1, W2, W3, b1, b2, b3]))
        if step % 5000 == 0:
            print("Iter: {}, Loss: {:.4f}".format(step, loss_fn(neural_net(features),labels)))
```

    Iter: 0, Loss: 0.8487
    Iter: 5000, Loss: 0.6847
    Iter: 10000, Loss: 0.6610
    Iter: 15000, Loss: 0.6154
    Iter: 20000, Loss: 0.5722
    Iter: 25000, Loss: 0.5433
    Iter: 30000, Loss: 0.5211
    Iter: 35000, Loss: 0.4911
    Iter: 40000, Loss: 0.4416
    Iter: 45000, Loss: 0.3313

```python
x_data, y_data = preprocess_data(x_data, y_data)
test_acc = accuracy_fn(neural_net(x_data),y_data)
print("Testset Accuracy: {:.4f}".format(test_acc))
```

    Testset Accuracy: 1.0000

## 3) 모델링(Class 생성)

```python
nb_classes = 10
```

```python
class wide_deep_nn():
    def __init__(self, nb_classes):
        super(wide_deep_nn, self).__init__()

        self.W1 = tf.Variable(tf.random.normal((2, nb_classes)), name='weight1')
        self.b1 = tf.Variable(tf.random.normal((nb_classes,)), name='bias1')

        self.W2 = tf.Variable(tf.random.normal((nb_classes, nb_classes)), name='weight2')
        self.b2 = tf.Variable(tf.random.normal((nb_classes,)), name='bias2')

        self.W3 = tf.Variable(tf.random.normal((nb_classes, nb_classes)), name='weight3')
        self.b3 = tf.Variable(tf.random.normal((nb_classes,)), name='bias3')

        self.W4 = tf.Variable(tf.random.normal((nb_classes, 1)), name='weight4')
        self.b4 = tf.Variable(tf.random.normal((1,)), name='bias4')

        self.variables = [self.W1,self.b1,self.W2,self.b2,self.W3,self.b3,self.W4,self.b4]

    def preprocess_data(self, features, labels):
        features = tf.cast(features, tf.float32)
        labels = tf.cast(labels, tf.float32)
        return features, labels

    def deep_nn(self, features):
        layer1 = tf.sigmoid(tf.matmul(features, self.W1) + self.b1)
        layer2 = tf.sigmoid(tf.matmul(layer1, self.W2) + self.b2)
        layer3 = tf.sigmoid(tf.matmul(layer2, self.W3) + self.b3)
        hypothesis = tf.sigmoid(tf.matmul(layer3, self.W4) + self.b4)
        return hypothesis

    def loss_fn(self, hypothesis, features, labels):
        cost = -tf.reduce_mean(labels * tf.math.log(hypothesis) + (1 - labels) * tf.math.log(1 - hypothesis))
        return cost

    def accuracy_fn(self, hypothesis, labels):
        predicted = tf.cast(hypothesis > 0.5, dtype=tf.float32)
        accuracy = tf.reduce_mean(tf.cast(tf.equal(predicted, labels), dtype=tf.float32))
        return accuracy

    def grad(self, hypothesis, features, labels):
        with tf.GradientTape() as tape:
            loss_value = self.loss_fn(self.deep_nn(features),features,labels)
        return tape.gradient(loss_value,self.variables)

    def fit(self, dataset, EPOCHS=20000, verbose=500):
        optimizer =  tf.keras.optimizers.SGD(learning_rate=0.01)
        for step in range(EPOCHS):
            for features, labels  in dataset:
                features, labels = self.preprocess_data(features, labels)
                grads = self.grad(self.deep_nn(features), features, labels)
                optimizer.apply_gradients(grads_and_vars=zip(grads, self.variables))
                if step % verbose == 0:
                    print("Iter: {}, Loss: {:.4f}".format(step, self.loss_fn(self.deep_nn(features),features,labels)))

    def test_model(self,x_data, y_data):
        x_data, y_data = self.preprocess_data(x_data, y_data)
        test_acc = self.accuracy_fn(self.deep_nn(x_data),y_data)
        print("Testset Accuracy: {:.4f}".format(test_acc))
```

* DNN을 통한 XOR 문제 해결

```python
model = wide_deep_nn(nb_classes)
```

```python
model.fit(dataset)
```

    Iter: 0, Loss: 1.0398
    Iter: 500, Loss: 0.6936
    Iter: 1000, Loss: 0.6923
    Iter: 1500, Loss: 0.6912
    Iter: 2000, Loss: 0.6901
    Iter: 2500, Loss: 0.6890
    Iter: 3000, Loss: 0.6879
    Iter: 3500, Loss: 0.6867
    Iter: 4000, Loss: 0.6855
    Iter: 4500, Loss: 0.6842
    Iter: 5000, Loss: 0.6827
    Iter: 5500, Loss: 0.6811
    Iter: 6000, Loss: 0.6793
    Iter: 6500, Loss: 0.6772
    Iter: 7000, Loss: 0.6749
    Iter: 7500, Loss: 0.6721
    Iter: 8000, Loss: 0.6690
    Iter: 8500, Loss: 0.6653
    Iter: 9000, Loss: 0.6610
    Iter: 9500, Loss: 0.6560
    Iter: 10000, Loss: 0.6501
    Iter: 10500, Loss: 0.6431
    Iter: 11000, Loss: 0.6348
    Iter: 11500, Loss: 0.6249
    Iter: 12000, Loss: 0.6131
    Iter: 12500, Loss: 0.5989
    Iter: 13000, Loss: 0.5817
    Iter: 13500, Loss: 0.5607
    Iter: 14000, Loss: 0.5351
    Iter: 14500, Loss: 0.5039
    Iter: 15000, Loss: 0.4667
    Iter: 15500, Loss: 0.4237
    Iter: 16000, Loss: 0.3762
    Iter: 16500, Loss: 0.3268
    Iter: 17000, Loss: 0.2787
    Iter: 17500, Loss: 0.2345
    Iter: 18000, Loss: 0.1962
    Iter: 18500, Loss: 0.1642
    Iter: 19000, Loss: 0.1380
    Iter: 19500, Loss: 0.1169

* Test 데이터의 값은

```python
model.test_model(x_data, y_data)
```

    Testset Accuracy: 1.0000

Part-2 DNN/Ch01_Neural Net for XOR.ipynb


```python
import numpy as np
from pydgrid.plot_bokeh import plot_results
import sympy as sym
import pydae.build as db
from pydae.grid_urisi import unb_ri_si
import json
```

```python
data = {
        "buses":[
                 {"bus": "B1",  "pos_x":   0, "pos_y":  0, "units": "m", "U_kV":0.4},
                 {"bus": "B2",  "pos_x":  20, "pos_y":  0, "units": "m", "U_kV":0.4},
                 {"bus": "B3",  "pos_x": 120, "pos_y":  0, "units": "m", "U_kV":0.4},
                 {"bus": "B4",  "pos_x": 140, "pos_y":  0, "units": "m", "U_kV":0.4}
                ],
        "grid_formers":[
                        {"bus": "B1",
                        "bus_nodes": [1, 2, 3], "deg": [0, -120, -240],
                        "kV": [0.231, 0.231, 0.231]},
                        {"bus": "B4",
                        "bus_nodes": [1, 2, 3], "deg": [0, -120, -240],
                        "kV": [0.231, 0.231, 0.231]}
                       ],
        "lines":[
                 {"bus_j": "B1",  "bus_k": "B2",  "code": "lv_cu_150", "m":  20.0},
                 {"bus_j": "B2",  "bus_k": "B3",  "code": "lv_cu_150", "m": 100.0},
                 {"bus_j": "B3",  "bus_k": "B4",  "code": "lv_cu_150", "m":  20.0},
                ],
        "loads":[
                 {"bus": "B2" , "kVA": [30.0,30.0,30.0], "pf":[ 1]*3,"type":"3P+N"},
                 {"bus": "B3" , "kVA": [10.0,10.0,70.0], "pf":[ 1]*3,"type":"3P+N"}
                ],
        "shunts":[
                 {"bus": "B1" , "R": 0.001, "X": 0.0, "bus_nodes": [4,0]},
                 {"bus": "B4" , "R": 0.001, "X": 0.0, "bus_nodes": [4,0]}
                 ],
        "line_codes":
            {"lv_cu_150":  {"Rph":0.167,"Xph":0.08, "Rn":0.167, "Xn": 0.08}
            }
       }
```

```python
grid_dae = unb_ri_si(data)
```

```python
params_dict  = grid_dae['params']
f_list = grid_dae['f']
x_list = grid_dae['x']
g_list = grid_dae['g']
y_list = grid_dae['y']
u_dict = grid_dae['u']
a = sym.Symbol('a')
h_dict = grid_dae['h_v_m_dict']

sys_dict = {'name':'grid_4bus4wire',
           'params_dict':params_dict,
           'f_list':f_list,
           'g_list':g_list,
           'x_list':x_list,
           'y_ini_list':y_list,
           'y_run_list':y_list,
           'u_run_dict':u_dict,
           'u_ini_dict':u_dict,
           'h_dict':h_dict
           }

db.system(sys_dict)
db.sys2num(sys_dict)

data = json.dumps(grid_dict['xy_0_dict'], indent=4)
fobj = open("xy_0_dict.json","w")
fobj.write(data)
fobj.close()
```

```python
Y_ii = grid_1.Y_ii.toarray()
Y_vv = grid_1.Y_vv
Y_vi = grid_1.Y_vi
inv_Y_ii = np.linalg.inv(Y_ii)
N_nz_nodes = grid_1.params_pf[0].N_nz_nodes
N_v = grid_1.params_pf[0].N_nodes_v
nodes_list = grid_1.nodes
Y_primitive = grid_1.Y_primitive_sp.toarray()
A_conect = grid_1.A_sp.toarray()
node_sorter  = grid_1.node_sorter
N_v = grid_1.N_nodes_v

np.savez('matrices',Y_primitive=Y_primitive,A_conect=A_conect,nodes_list=nodes_list,
         node_sorter=node_sorter,N_v=N_v, Y_vv=Y_vv, Y_vi=Y_vi)

with open("grid_data.json", "w") as fobj:
    json.dump(grid_1.data, fobj, indent=4, sort_keys=True)

```

```python

```

```python

```


```python
import numpy as np
import queue
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pyplot import figure
import re
from sympy.ntheory.factor_ import factorint
```

```python
def is_Ag(n:int) -> bool:
    return n % 24 == 17

def operation_v(n:int) -> int:
    return 4*n+1

def operation_v_n_times(start:int, n:int) -> int:
    res = start
    for i in range(1, n):
        res = operation_v(res)
    return res

def generate_v_series(start:int, n:int) -> list[int]:
    res = [0]*n
    for i in range(0, n):
        res[i] = operation_v_n_times(start, i+1)
    return res

def remove_0mod3(v_series:list[int]) -> list[int]:
    res = []
    for i in v_series:
        if i%3 > 0:
            res.append(i)
    return res

def syr_min_1(v_series:list[int]) -> list[int]:
    res = []
    for i in v_series:
        if i> 1:
            if i%3 == 1:
                #Type C case
                res.append((4*i-1)//3)
            elif i%3 == 2:
                #Type A case
                res.append((2*i-1)//3)
    return res

array_growth = []
def func2(s:int, max_bin_len:int)->int:
    global array_growth
    array_growth = []

    x=0
    arr=[]
    arr.append(s)
    q = queue.Queue()
    q.put(s)
    n = s
    while len(arr) > 0:
        n = arr[0]
        del arr[0]
        array_growth.append(len(arr))

        if n%3 == 2:
            #Type A case
            temp1 = (2*n-1)//3
            arr.append(temp1)
            if is_Ag(n):
                x += 1
            if len(np.binary_repr(n)) < len(np.binary_repr(s))+max_bin_len-1:
                temp2 = operation_v(n)
                arr.append(temp2)
        elif n%3 == 0:
            #Type B case
            if len(np.binary_repr(n)) < len(np.binary_repr(s))+max_bin_len-1:
                temp1 = operation_v(n)
                arr.append(temp1)
        elif n%3 == 1:
            #Type C case
            if len(np.binary_repr(n)) < len(np.binary_repr(s))+max_bin_len and n>1:
                temp1 = (4*n-1)//3
                arr.append(temp1)
            if len(np.binary_repr(n)) < len(np.binary_repr(s))+max_bin_len-1:
                temp2 = operation_v(n)
                arr.append(temp2)

    return x

start = 3
max_bin_len = 10
print(func2(start, max_bin_len))

l = len(array_growth)
y_val = array_growth
x_val = range(0, l)

d = {"x":x_val, "array_growth":y_val}
df = pd.DataFrame(d)
df.to_csv('array_growth.csv', index=False)

plt.plot(x_val, y_val)
plt.show()
```

```python
start = 3
max_bin_len = 20
x_val=[]
y_val=[]
y_val2=[]
for mbl in range(5, max_bin_len+1):
    x_val.append(mbl)
    y_val.append(func2(start, mbl))
    y_val2.append(2**(mbl-1)/24)

d = {"x":x_val, "max_bin_length":y_val}
df = pd.DataFrame(d)
df.to_csv('max_bin_length.csv', index=False)

plt.plot(x_val, y_val)
plt.plot(x_val, y_val2)
plt.show()
```

```python
def func3(s:int, max_bin_len:int)->int:
    blen_s = s.bit_length()
    x = 0
    q = queue.Queue()
    q.put(s)
    while not q.empty():
        n = q.get()
        blen_n = n.bit_length()
        if n%3 == 2:
            #Type A case
            q.put((2*n-1)//3)
            if is_Ag(n):
                x += 1
            if blen_n < blen_s + max_bin_len-1:
                q.put(operation_v(n))
        elif n%3 == 0:
            #Type B case
            if blen_n < blen_s + max_bin_len-1:
                q.put(operation_v(n))
        elif n%3 == 1:
            #Type C case
            if blen_n < blen_s + max_bin_len and n>1:
                q.put((4*n-1)//3)
            if blen_n < blen_s + max_bin_len-1:
                q.put(operation_v(n))
    return x

start = 3
max_bin_len = 10
print(func3(start, max_bin_len))
```

    85

```python
start = 3
max_bin_len = 10
x_val=[]
y_val=[]
for mbl in range(5, max_bin_len+1):
    x_val.append(mbl)
    y_val.append(func3(start, mbl))

d = {"x":x_val, "max_bin_length":y_val}
df = pd.DataFrame(d)
df.to_csv('s_{0}_max_bin_length_{1}.csv'.format(start, max_bin_len), index=False)
```

```python
start = 3
df = pd.read_csv('s_3_max_bin_length.csv')
#rowc_ount = len(df.index)
#x_start = df.at[0, 'x']
#x_stop = df.at[rowc_ount-1, 'x']

def prod_sum(x:int,n:int)->int:
    sum=0
    for l in range(1,(n-2)//x+1):
        prod=1
        for k in range(1,l+1):
            prod*=(n-x*k)//12
        sum+=prod
    return sum

x_val=df['x'].tolist()
y_val=df['max_bin_length'].tolist()
y_val2=[2**(x)/24 for x in x_val]

text_file = open("../prod_sum.txt", "r")
content = text_file.read()[1:-1]
text_file.close()
content_list = content.split(",")
y_val3 = [0]*len(x_val)
for idx, str in enumerate(content_list):
    m = re.search('mpz\(([0-9]+)\)', str)
    y_val3[idx]=int(m.group(1))

#y_val3=[prod_sum(start,n) for n in y_val]

#prime_factors=[factorint(x) for x in y_val]
#df = df.join(pd.DataFrame({'prime_factors': prime_factors}))
#print(df)

plt.plot(x_val, y_val, color='red')
plt.plot(x_val, y_val2, color='orange')
#plt.plot(x_val, y_val3, color='blue')
plt.show()

```

```python
recursion_jumps = []
def func(start:int, max_bin_len:int, x:int)->int:
    global recursion_jumps
    recursion_jumps.append(1)

    b=len(np.binary_repr(start))
    if is_Ag(start):
        x += 1
    v_series = generate_v_series(start, (max_bin_len-b)//2)
    thinned_out_series = remove_0mod3(v_series)
    syr_min1_series = syr_min_1(thinned_out_series)
    for i in syr_min1_series:
        if is_Ag(i):
            x += 1
        x = func(i, max_bin_len, x)
    recursion_jumps.append(-1)
    return x

start = 3
max_bin_len = 10
print(func(start, max_bin_len, 0))

l = len(recursion_jumps)
x_val = range(0, l)
y_val = [1]*l
for i in range(1, l):
    y_val[i] = y_val[i-1]+recursion_jumps[i]

#print(y_val)
plt.plot(x_val, y_val)
plt.show()
```

```python
start = 1
max_bin_len = 20
x_val=[]
y_val=[]
for mbl in range(5, max_bin_len+1):
    x_val.append(mbl)
    y = func(start, mbl, 0)
    y = y/(2**(mbl-1)/24)
    y_val.append(y)

print(x_val)
print(y_val)

plt.plot(x_val, y_val)
plt.show()
```

```python
start = 1
max_bin_len = 200
b=len(np.binary_repr(start))
v_series = generate_v_series(start, (max_bin_len-b)//2)
thinned_out_series = remove_0mod3(v_series)
syr_min1_series = syr_min_1(thinned_out_series)

res = syr_min1_series
l = len(res)
x = range(1, l+1)
y = [1]*l
colors = ["black"]*l
for idx, i in enumerate(res):
    if i%3 == 2:
        colors[idx]="orange"
    if is_Ag(i):
        colors[idx]="red"

#print("v_series: {0}", v_series)
#print("thinned_out_series: {0}", thinned_out_series)
print("syr_min1_series: {0}", syr_min1_series)

figure(figsize=(16, 1))
plt.scatter(x, y, s=16, c=colors, alpha=1)
plt.axis('off')
plt.show()
```

```python
def numberToBase(n, b):
    if n == 0:
        return [0]
    digits = []
    while n:
        digits.append(int(n % b))
        n //= b
    return digits[::-1]

def count_17mod24(series:list[int]) -> int:
    res = 0
    for i in series:
        if i%24 == 17:
            res+=1
    return res

```

```python

```