# PRAKTIKUM 4
__Solusi Sistem Persamaan Linear__

*Topik*
1. Metode Substitusi Mundur
2. Metode Eliminasi Gauss
3. Metode Faktorisasi LU

# 1 Metode Substitusi Mundur
Metode dasar yang digunakan untuk mencari solusi dari sistem persamaan linear adalah metode substitusi. Metode substitusi ini membutuhkan persyaratan yaitu matriks $ A $ pada sistem $ AX=B $ merupakan matriks segitiga. Jika $ A $ merupakan matriks segitiga atas, maka metode substitusi yang digunakan adalah substitusi mundur. Contoh berikut ini menunjukkan penyelesaian dari sistem segitiga atas dengan metode substitusi mundur. Adapun untuk substitusi maju, dapat dipelajari sendiri dengan analogi yang sama dengan substitusi mundur.

__Pseudocode__:

```julia
function backsub(A,B)
    # Hitung ukuran matriks dan inisiasi solusi X
    n = length(B);
    X = zeros(n,1);
    # Hitung nilai solusi X ke-n
    X[n] = B[n]/A[n,n];
    # Hitung nilai solusi X ke- n-1 sampai X ke-1
    for i = n-1:-1:1
        X[i] = (B[i] - A[i,i+1:n]'X[i+1:n])/A[i,i];
    end
    return X
end
```

### Contoh Soal 1
Diberikan sistem segitiga atas seperti berikut.

\begin{align}
4x_1 -  x_2 + 2x_3 + 3x_4 &= 20\\
	 - 2x_2 + 7x_3 - 4x_4 &= -7\\
	 	 	  6x_3 + 5x_4 &=  4\\
	 	 	  		 3x_4 &=  6
\end{align}

Selesaikan sistem persamaan linear tersebut menggunakan metode substitusi mundur.

**Solusi:**

```julia
A = [ 4 -1  2  3
      0 -2  7 -4
      0  0  6  5
      0  0  0  3];
B = [20; -7; 4; 6];

X = backsub(A,B)
```

### Substitusi Maju
Dengan mengamati pseudocode metode substitusi mundur, buatlah suatu fungsi yang mencari solusi SPL $AX=B$ dengan $A$ matriks segitiga bawah, yaitu dengan substitusi maju.

```julia
function forwardsub(A,B)
    # Hitung ukuran matriks dan inisiasi solusi X
    n = length(B);
    X = zeros(n,1);
    # Hitung nilai solusi X ke-1
    X[1] = B[1]/A[1,1];
    # Hitung nilai solusi X ke-1 sampai X ke-n
    for i = 2:n
        X[i] = (B[i] - A[i,1:i-1]'X[1:i-1])/A[i,i];
    end
    return X
end
```

```julia
A = [1 0 0 0
     1 2 0 0
    -1 0 1 0
    -2 1 1 2]
B = [1, 12, 8, 7]
X = forwardsub(A,B)
```

```julia
A\B
```

# 2 Metode Eliminasi Gauss
Metode eliminasi Gauss berangkat dari metode substitusi mundur. Maksudnya adalah sistem persamaan linear dengan matriks $ A $ bukan matriks segitiga atas diubah terlebih dahulu menjadi matriks segitiga atas dengan eliminasi. Selanjutnya, matriks hasil eliminasi tersebut diselesaikan menggunakan substitusi mundur.

__Pseudocode__:

```julia
# Metode Eliminasi Gauss Tanpa Pivoting
function elimGaussNonPivoting(A,b)
    # Hitung ukuran matriks dan inisiasi solusi X
    n = length(b);
    X = zeros(n,1);
    # Buatlah matriks Gandeng [A|b]
    Aug = [A b];
    # Lakukan operasi baris dasar terhadap matriks gandeng
    for p = 1:n-1 # p menunjukkan kolom
        # Jika pivot bernilai nol, maka gagal.
        if Aug[p,p]==0
            error("Harus Pakai Pivoting")
        end
        # Lakukan eliminasi menggunakan operasi Eij(k)
        for i = p+1:n # i menunjukkan baris
            k = Aug[i,p]/Aug[p,p];
            Aug[i,:] = Aug[i,:] - k*Aug[p,:];
        end
    end
    # Pisahkan matriks gandeng menjadi A dan b, kemudian selesaikan menggunakan substitusi mundur.
    A = Aug[:,1:n];
    b = Aug[:,1+n];
    X = backsub(A,b);
end
```

```julia
# Metode Eliminasi Gauss Dengan Pivoting
function elimGaussWithPivoting(A,b)
    # Hitung ukuran matriks dan inisiasi solusi X
    n = length(b);
    X = zeros(n,1);
    # Buatlah matriks Gandeng [A|b]
    Aug = [A b];
    # Lakukan operasi baris dasar terhadap matriks gandeng
    for p = 1:n-1
        # Lakukan operasi pindah baris untuk menentukan nilai pivot
        val,j = findmax(abs.(Aug[p:n,p]));
        C = Aug[p,:];
        Aug[p,:] = Aug[j+p-1,:];
        Aug[j+p-1,:] = C;
        # Jika pivot bernilai nol, maka gagal.
        if Aug[p,p] == 0;
            error("Matriks tidak punya solusi tunggal")
        end
        # Lakukan eliminasi menggunakan operasi Eij(k)
        for i = p+1:n
            k = Aug[i,p]/Aug[p,p];
            Aug[i,:] = Aug[i,:] - k*Aug[p,:];
        end
    end
    # Pisahkan matriks gandeng menjadi A dan b, kemudian selesaikan menggunakan substitusi mundur.
    A = Aug[:,1:n];
    b = Aug[:,1+n];
    X = backsub(A,b);
end
```

### Contoh Soal 2
Diberikan sistem persamaan linear dengan 5 persamaan seperti berikut.
\begin{align*}
\begin{pmatrix}
  2 &  5 &  4 &  2 &  3 \\
  4 &  1 &0.4 &  6 &  5 \\
0.9 &  4 &  8 &  7 & 10 \\
  5 &  7 &0.5 &  2 &  7 \\
  2 &  6 &  8 &  9 &  4 \\
\end{pmatrix}
\begin{pmatrix}
x_1\\x_2\\x_3\\x_4\\x_5\\
\end{pmatrix}=
\begin{pmatrix}
26\\24.4\\32.8\\30.5\\53\\
\end{pmatrix}
\end{align*}
1. Selesaikan sistem persamaan tersebut menggunakan metode eliminasi Gauss tanpa _pivoting_ dan eliminasi Gauss dengan _pivoting_
2. Tunjukkan bahwa eliminasi Gauss dengan _pivoting_ memiliki **galat yang lebih kecil** daripada eliminasi Gauss tanpa _pivoting_, jika diketahui solusi eksak persamaan, yaitu $ X^*=\begin{pmatrix}
2&3&1&3&-1
\end{pmatrix}^T $.

**Solusi:**

Hitung solusi SPL

```julia
A = [ 2    5    4    2    3
      4    1  0.4    6    5
    0.9    4    8    7   10
      5    7  0.5    2    7
      2    6    8    9    4];
B = [26; 24.4; 32.8; 30.5; 53];
```

```julia
Xn = elimGaussNonPivoting(A,B)
```

```julia
Xp = elimGaussWithPivoting(A,B)
```

Hitung panjang vektor galat

```julia
using LinearAlgebra
```

```julia
Xeksak = [2;3;1;3;-1];
En = norm(Xn-Xeksak)
```

```julia
Xeksak = [2;3;1;3;-1];
Ep = norm(Xp-Xeksak)
```

# 3 Metode Faktorisasi LU
Metode faktorisasi $ LU $ tanpa _ivoting_ mendefinisikan matriks $ A $ pada sistem persamaan linear sebagai $ A=LU $

Sementara itu, metode faktorisasi $ LU $ dengan _pivoting_ mendefinisikan matriks $ A $ pada sistem persamaan linear sebagai $ PA=LU $.

Solusi sistem persamaan linear dapat dicari dengan substitusi maju dan mundur dari matriks $ P $, $ L $, dan $ U $.

__Pseudocode__:

```julia
# Metode Faktorisasi LU Tanpa Pivoting
function LUtanpaP(A);
    # Hitung ukuran matriks dan matriks L sebagai pencatat pengali.
    n,n = size(A);
    L = zeros(n,n);
    # Lakukan operasi baris dasar terhadap matriks
    Aug = copy(A);
    for p = 1:n-1
        # Jika pivot bernilai nol, maka gagal.
        if Aug[p,p]==0
            error("Pivot bernilai nol");
        end
        # Lakukan eliminasi menggunakan operasi Eij(k) untuk matriks A lalu simpan pengali m = k pada matriks L.
        for i = p+1:n
            k = Aug[i,p]/Aug[p,p];
            Aug[i,1:n] = Aug[i,1:n] - k*Aug[p,1:n];
            L[i,p] = k;
        end
    end
    # Simpan hasil eliminasi Gauss sebagai matriks U.
    U = Aug;
    # Tambahkan matriks identitas pada catatan pengali L.
    L = L .+ I(n);
    return L, U
end
```

```julia
# Metode Faktorisasi LU Dengan Pivoting
function LUdenganP(A)
    # Hitung ukuran matriks dan matriks L sebagai pencatat pengali serta matriks permutasi P.
    n,n = size(A);
    L = zeros(n,n);
    P = Array(I(n));
    # Lakukan operasi baris dasar terhadap matriks gandeng
    Aug = copy(A)
    for p = 1:n-1
        # Lakukan operasi pindah baris untuk menentukan nilai pivot
        val,j = findmax(abs.(Aug[p:n,p]));
        # Pivoting U
        C = Aug[p,:];
        Aug[p,:] = Aug[j+p-1,:];
        Aug[j+p-1,:] = C;
        # Pivoting L
        C = L[p,:];
        L[p,:] = L[j+p-1,:];
        L[j+p-1,:] = C;
        # Pivoting P
        C = P[p,:];
        P[p,:] = P[j+p-1,:];
        P[j+p-1,:] = C;
        # Jika pivot bernilai nol, maka gagal.
        if Aug[p,p]==0
            error("Matriks Singular")
        end
        # Lakukan eliminasi menggunakan operasi Eij(k) untuk matriks A dan simpan pengali m = k di matriks nol yang telah disediakan.
        for i = p+1:n
            k = Aug[i,p]/Aug[p,p]
            Aug[i,1:n] = Aug[i,1:n] - k*Aug[p,1:n];
            L[i,p] = k;
        end
    end
    # Simpan hasil eliminasi Gauss sebagai matriks U.
    U = Aug;
    # Tambahkan matriks identitas pada catatan pengali L.
    L = L + I(n);
    return L,U,P
end
```

### Contoh Soal 3
Diberikan sistem persamaan linear dengan 5 persamaan seperti berikut.
\begin{align*}
\begin{pmatrix}
  2 &  5 &  4 &  2 &  3 \\
  4 &  1 &0.4 &  6 &  5 \\
0.9 &  4 &  8 &  7 & 10 \\
  5 &  7 &0.5 &  2 &  7 \\
  2 &  6 &  8 &  9 &  4 \\
\end{pmatrix}
\begin{pmatrix}
x_1\\x_2\\x_3\\x_4\\x_5\\
\end{pmatrix}=
\begin{pmatrix}
26\\24.4\\32.8\\30.5\\53\\
\end{pmatrix}
\end{align*}
1. Selesaikan sistem persamaan tersebut menggunakan metode faktorisasi LU tanpa _pivoting_ dan faktorisasi LU dengan _pivoting_
2. Tunjukkan bahwa faktorisasi LU dengan _pivoting_ memiliki **galat yang lebih kecil** daripada faktorisasi LU tanpa _pivoting_, jika diketahui solusi eksak persamaan, yaitu $ X^*=\begin{pmatrix}
2&3&1&3&-1
\end{pmatrix}^T $.

**Solusi:**

Hitung solusi SPL

```julia
A = [ 2    5    4    2    3
      4    1  0.4    6    5
    0.9    4    8    7   10
      5    7  0.5    2    7
      2    6    8    9    4];
B = [26; 24.4; 32.8; 30.5; 53];
```

```julia
# Cari matriks L, dan U menggunakan faktorisasi LU tanpa pivoting
L,U = LUtanpaP(A)
L
```

```julia
U
```

```julia
# A = LU ---> Ax = B ---> LUx = B --- misalkan Ux = Y ---> LY=B
# Hitung solusi Y kemudian hitung solusi X
Y = forwardsub(L,B)
Xn = backsub(U,Y)
```

```julia
Xeksak = [2;3;1;3;-1];
En = norm(Xn-Xeksak)
```

```julia
# Cari matriks L, U, dan P menggunakan faktorisasi LU dengan pivoting
L,U,P = LUdenganP(A)
L
```

```julia
U
```

```julia
P
```

```julia
# PA = LU ---> Ax = B ----> PAx = PB ---> LUx = PB -- misalkan Ux = Y ---> LY=PB
# Hitung solusi Y kemudian hitung solusi X
Y = forwardsub(L,P*B);
Xp = backsub(U,Y)
```

```julia
Xeksak = [2;3;1;3;-1]
Ep = norm(Xp-Xeksak)
```

# Soal Latihan
Kerjakan soal berikut pada saat kegiatan praktikum berlangsung.

`Nama: ________`

`NIM: ________`

### Soal 1
Diberikan SPL seperti berikut.

$$\begin{align*}
\begin{pmatrix}
  4.0 &  2.0 &  1.0 &  3.0 &  9.0 &  1.0 \\
  5.0 &  3.0 &  9.0 &  3.0 &  1.0 &  2.0 \\
  10.0&  7.0 &  2.0 &  1.0 &  8.0 &  3.0 \\
  9.0 &  0.3 &  8.0 &  4.0 &  8.0 &  5.0 \\
  8.0 &  0.7 &  0.7 &  2.0 &  7.0 &  1.0 \\
  2.0 &  6.0 &  10.0&  2.0 &  6.0 &  5.0 \\
\end{pmatrix}
\begin{pmatrix}
x_1\\x_2\\x_3\\x_4\\x_5\\x_6\\
\end{pmatrix}
=
\begin{pmatrix}
  17.0 \\
  42.0 \\
  30.0 \\
  58.4 \\
  33.7 \\
  23.0 \\
\end{pmatrix}
\end{align*}$$

Gunakan metode eliminasi Gauss tanpa _pivoting_ dan eliminasi Gauss dengan _pivoting_ untuk mencari penyelesaikan sistem persamaan tersebut, kemudian tunjukkan bahwa metode eliminasi Gauss dengan _pivoting_ memiliki galat lebih kecil dibandingkan metode eliminasi Gauss tanpa _pivoting_.

```julia

```

### Soal 2

Diberikan sistem persamaan linear seperti berikut.

$$\begin{align*}
\begin{pmatrix}
  4.0 &  2.0 &  1.0 &  3.0 &  9.0 &  1.0 \\
  5.0 &  3.0 &  9.0 &  3.0 &  1.0 &  2.0 \\
  10.0&  7.0 &  2.0 &  1.0 &  8.0 &  3.0 \\
  9.0 &  0.3 &  8.0 &  4.0 &  8.0 &  5.0 \\
  8.0 &  0.7 &  0.7 &  2.0 &  7.0 &  1.0 \\
  2.0 &  6.0 &  10.0&  2.0 &  6.0 &  5.0 \\
\end{pmatrix}
\begin{pmatrix}
x_1\\x_2\\x_3\\x_4\\x_5\\x_6\\
\end{pmatrix}
=
\begin{pmatrix}
  17.0 \\
  42.0 \\
  30.0 \\
  58.4 \\
  33.7 \\
  23.0 \\
\end{pmatrix}
\end{align*}$$

Gunakan metode faktorisasi $ LU $ tanpa _pivoting_ dan faktorisasi $ LU $ dengan _pivoting_ untuk mencari penyelesaikan sistem persamaan tersebut, kemudian tunjukkan bahwa metode faktorisasi $ LU $ dengan _pivoting_ memiliki galat lebih kecil dibandingkan metode faktorisasi $ LU $ tanpa _pivoting_.

```julia

```


```python
import numpy as np
import sympy as sp
```

First, let's derive the inverse X matrix that maps from fluxes to "a" coefficients:

```python
X = np.vander([-1, 0, 1], 3)
np.linalg.inv(X)
```

    array([[ 0.5, -1. ,  0.5],
           [-0.5,  0. ,  0.5],
           [ 0. ,  1. ,  0. ]])

Then, let's propagate the uncertainties:

```python
np.linalg.inv(np.dot(X.T, X))
```

    array([[ 1.5,  0. , -1. ],
           [ 0. ,  0.5,  0. ],
           [-1. ,  0. ,  1. ]])

This means that a_1 and a_2 are independent with variances 0.5 and 1.5 respectively.

```python
f_minus, f_max, f_plus = sp.symbols("f_minus f_max f_plus")
f = sp.Matrix([[f_minus], [f_max], [f_plus]])
X_s = sp.Matrix(X)
A = X_s**-1 * f
a2 = A[0, 0]
a1 = A[1, 0]
a0 = A[2, 0]
print("a0 =", a0)
print("a1 =", a1)
print("a2 =", a2)
```

    a0 = 1.0*f_max
    a1 = -0.5*f_minus + 0.5*f_plus
    a2 = -1.0*f_max + 0.5*f_minus + 0.5*f_plus

```python
x0 = sp.symbols("x0")
x_max = x0 - a1 / (2*a2)
print("x_max =", x_max)
```

    x_max = x0 - (-0.5*f_minus + 0.5*f_plus)/(-2.0*f_max + f_minus + f_plus)

```python
y_max = a0 + a1 * (x_max - x0) + a2 * (x_max - x0)**2
print("y_max =", sp.simplify(y_max))
```

    y_max = 1.0*(4.0*f_max**2 - 2.0*f_max*f_minus - 2.0*f_max*f_plus + 0.25*f_minus**2 - 0.5*f_minus*f_plus + 0.25*f_plus**2)/(4.0*f_max - 2.0*f_minus - 2.0*f_plus)

```python
df_minus, df_max, df_plus = sp.symbols("df_minus df_max df_plus")
inv_Sigma = sp.Matrix([[1/df_minus**2, 0, 0], [0, 1/df_max**2, 0], [0, 0, 1/df_plus**2]])
Sigma_a = sp.simplify((X_s.T * inv_Sigma * X_s)**-1)

sig2_a1 = Sigma_a[0, 0]
sig2_a2 = Sigma_a[1, 1]
sig2_a0 = Sigma_a[2, 2]

sig_a1a2 = Sigma_a[0, 1]
sig_a0a2 = Sigma_a[0, 2]
sig_a0a1 = Sigma_a[1, 2]

print("sig2_a0 =", sig2_a0)
print("sig2_a1 =", sig2_a1)
print("sig2_a2 =", sig2_a2)

print("sig_a1a2 =", sig_a1a2)
print("sig_a0a2 =", sig_a0a2)
print("sig_a0a1 =", sig_a0a1)
```

    sig2_a0 = df_max**2
    sig2_a1 = df_max**2 + df_minus**2/4 + df_plus**2/4
    sig2_a2 = df_minus**2/4 + df_plus**2/4
    sig_a1a2 = -df_minus**2/4 + df_plus**2/4
    sig_a0a2 = -df_max**2
    sig_a0a1 = 0

```python
a0_, a1_, a2_, df = sp.symbols("a0_ a1_ a2_ df")
f = a1_ / (2*a2_)

sig2_f = sp.diff(f, a1_)*sp.diff(f, a2_)*sig_a1a2
sig2_f += sp.diff(f, a1_)**2*sig2_a1
sig2_f += sp.diff(f, a2_)**2*sig2_a2
print("x_max_var =", sp.simplify(sig2_f).evalf())

subs = [(df_minus, df), (df_plus, df), (df_max, df)]
sp.simplify(sig2_f.subs(subs))
```

    x_max_var = 0.0625*(a1_**2*(df_minus**2 + df_plus**2) + a1_*a2_*(df_minus**2 - df_plus**2) + a2_**2*(4.0*df_max**2 + df_minus**2 + df_plus**2))/a2_**4

    df**2*(a1_**2 + 3*a2_**2)/(8*a2_**4)

```python
tmp = -a1_/(2*a2_)
y_max = a0_ + a1_ * tmp + a2_ * tmp**2
print("y_max =", y_max)

sig2_y = sp.diff(y_max, a0_)*sp.diff(y_max, a1_)*sig_a0a1
sig2_y += sp.diff(y_max, a0_)*sp.diff(y_max, a2_)*sig_a0a2
sig2_y += sp.diff(y_max, a1_)*sp.diff(y_max, a2_)*sig_a1a2
sig2_y += sp.diff(y_max, a0_)**2*sig2_a0
sig2_y += sp.diff(y_max, a1_)**2*sig2_a1
sig2_y += sp.diff(y_max, a2_)**2*sig2_a2

print("sig2_y =", sp.simplify(sig2_y).evalf())
```

    y_max = a0_ - a1_**2/(4*a2_)
    sig2_y = 0.015625*(a1_**4*(df_minus**2 + df_plus**2) + 2.0*a1_**3*a2_*(df_minus**2 - df_plus**2) + 4.0*a1_**2*a2_**2*(df_minus**2 + df_plus**2) + 64.0*a2_**4*df_max**2)/a2_**4

```python
print("sig2_y =", sp.simplify(sig2_y.subs(subs)).evalf())
```

    sig2_y = df**2*(0.03125*a1_**4 + 0.125*a1_**2*a2_**2 + a2_**4)/a2_**4

```python

```


# Variational Monte Carlo: Harmonic Oscillator

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

%config InlineBackend.figure_format = 'retina'
%matplotlib inline
try: plt.style.use('./notebook.mplstyle')
except: pass

red,blue,green = '#e85c47','#4173b2','#7dcca4'
```

## Hamiltonian

\begin{equation}
\hat{H} = -\frac{1}{2} \frac{d^2}{dx^2} + \frac{1}{2} x^2
\end{equation}

where $\hbar = \omega = m = 1$.

## Local Energy and Transition Probability

\begin{equation}
E_L^\alpha (x) = \alpha + x^2\left(\frac{1}{2} - 2\alpha^2\right)
\end{equation}

\begin{equation}
\frac{\pi(x^\prime)}{\pi(x)} = \mathrm{e}^{-2\alpha({x^\prime}^2 - x^2)}
\end{equation}

```python
def EL(x,α):
    return α + x**2*(0.5-2*α**2)

def transition_probability(x,x̄,α):
    return np.exp(-2*α*(x̄**2-x**2))
```

```python
def vmc(num_walkers,num_MC_steps,num_equil_steps,α,δ=1.0):

    # initilaize walkers
    walkers = -0.5 + np.random.rand(num_walkers)

    # initialize energy and number of accepted updates
    estimator = {'E':np.zeros(num_MC_steps-num_equil_steps)}
    num_accepted = 0

    for step in range(num_MC_steps):

        # generate new walker positions
        new_walkers = np.random.normal(loc=walkers, scale=δ, size=num_walkers)

        # test new walkers
        for i in range(num_walkers):
            if np.random.random() < transition_probability(walkers[i],new_walkers[i],α):
                num_accepted += 1
                walkers[i] = new_walkers[i]

            # measure energy
            if step >= num_equil_steps:
                measure = step-num_equil_steps
                estimator['E'][measure] = EL(walkers[i],α)

    # output the acceptance ratio
    print('accept: %4.2f' % (num_accepted/(num_MC_steps*num_walkers)))

    return estimator
```

## Perform the VMC Simulation

```python
α = 0.45
num_walkers = 100
num_MC_steps = 20000
num_equil_steps = 5000

np.random.seed(1173)

estimator = vmc(num_walkers,num_MC_steps,num_equil_steps,α)
```

    accept: 0.62

## Compute the average energy and standard error

```python
from scipy.stats import sem
Ē,ΔĒ = np.average(estimator['E']),sem(estimator['E'])

print('Ē = %f ± %f' % (Ē,ΔĒ))
```

    Ē = 0.501036 ± 0.000566

## Due a brute-force minimizaiton search over the variational parameter $\alpha$

```python
Ēmin = []
ΔĒmin = []
α = np.array([0.45, 0.475, 0.5, 0.525, 0.55])
for cα in α:
    estimator = vmc(num_walkers,num_MC_steps,num_equil_steps,cα)
    Ē,ΔĒ = np.average(estimator['E']),sem(estimator['E'])
    Ēmin.append(Ē)
    ΔĒmin.append(ΔĒ)
    print('%5.3f \t %7.5f ± %f' % (cα,Ē,ΔĒ))
```

    accept: 0.62
    0.450 	 0.50387 ± 0.000635
    accept: 0.62
    0.475 	 0.50012 ± 0.000291
    accept: 0.61
    0.500 	 0.50000 ± 0.000000
    accept: 0.60
    0.525 	 0.50151 ± 0.000280
    accept: 0.59
    0.550 	 0.50239 ± 0.000543

## Compare VMC with the exact variational energy

\begin{equation}
E_v = \frac{\alpha}{2} + \frac{1}{8\alpha}
\end{equation}

```python
cα = np.linspace(α[0],α[-1],1000)
plt.plot(cα,0.5*cα + 1/(8*cα), '-', linewidth=1, color=green, zorder=-10,
         label=r'$\frac{\alpha}{2} + \frac{1}{8\alpha}$')
plt.errorbar(α,Ēmin,yerr=ΔĒmin, linestyle='None', marker='o', elinewidth=1.0,
             markersize=6, markerfacecolor=blue, markeredgecolor=blue, ecolor=blue, label='VMC')
plt.xlabel(r'$\alpha$')
plt.ylabel('E');
plt.xlim(0.44,0.56)
plt.legend(loc='upper center')
```

```python

```


# 函数和库

## 函数的例子

#### 问题：求阶乘\begin{equation}n!\end{equation}

```python
def fac(n):                        # 求阶乘
    if n == 1:                     # 收敛条件
        return 1
    else:
        return n * fac(n - 1)      # 递归引用本函数的函数名
fac(5)
```

    120

```python
for i in range(9):                      # 需要解决fac函数的一个bug
    print(fac(i))
```

#### \begin{equation}0!=1\end{equation}

```python
fac(0)
```

#### 用循环来定义阶乘函数
```python
def fac_loop(n):
    ret = 1
    for i in range(n):
        ret = ret * (i + 1)
    return ret
```

```python
def fac_loop(n):
    ret = 1
    for i in range(n):
        ret = ret * (i + 1)
    return ret
print(fac_loop(5))
print(fac_loop(0))
print(fac_loop(1))
```

#### 问题：求组合数 \begin{equation}{n\choose k}=\frac{n!}{(n-k)!k!}\end{equation}

```python
def combination(n, k):             # 求组合数
    return fac(n) / (fac(n - k) * fac(k))
combination(52, 4)
```

    270725.0

## 函数的类型

##### 函数的类型？
```python
type(fac)
```

```python
type(fac) # 和type(True)不是一样？
```

    function

>函数的类型是参数类型到返回值类型两个类型集合的映射关系
#### \begin{equation} fac : int→int \end{equation}\begin{equation} combination: int, int → int \end{equation}

```python
max([23, 37, 13, 47]) # 输入类型是一个list，输出类型是一个数值
```

    47

## 方法
>第一个参数是固定函数

```python
pl = [23, 37, 13, 47]
pl.sort()              # 输入类型是一个list，输出类型是一个list，类型实例和函数名之间用一个.连接
pl
```

    [13, 23, 37, 47]

```python
help(list.sort)        # 获得在线帮助
```

    Help on method_descriptor:

    sort(...)
        L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*

```python
pl.sort(reverse=True) # 命名参数
pl
```

    [47, 37, 23, 13]

```python
help(list)             # 列出list的全部方法
```

    Help on class list in module builtins:

    class list(object)
     |  list() -> new empty list
     |  list(iterable) -> new list initialized from iterable's items
     |
     |  Methods defined here:
     |
     |  __add__(self, value, /)
     |      Return self+value.
     |
     |  __contains__(self, key, /)
     |      Return key in self.
     |
     |  __delitem__(self, key, /)
     |      Delete self[key].
     |
     |  __eq__(self, value, /)
     |      Return self==value.
     |
     |  __ge__(self, value, /)
     |      Return self>=value.
     |
     |  __getattribute__(self, name, /)
     |      Return getattr(self, name).
     |
     |  __getitem__(...)
     |      x.__getitem__(y) <==> x[y]
     |
     |  __gt__(self, value, /)
     |      Return self>value.
     |
     |  __iadd__(self, value, /)
     |      Implement self+=value.
     |
     |  __imul__(self, value, /)
     |      Implement self*=value.
     |
     |  __init__(self, /, *args, **kwargs)
     |      Initialize self.  See help(type(self)) for accurate signature.
     |
     |  __iter__(self, /)
     |      Implement iter(self).
     |
     |  __le__(self, value, /)
     |      Return self<=value.
     |
     |  __len__(self, /)
     |      Return len(self).
     |
     |  __lt__(self, value, /)
     |      Return self<value.
     |
     |  __mul__(self, value, /)
     |      Return self*value.n
     |
     |  __ne__(self, value, /)
     |      Return self!=value.
     |
     |  __new__(*args, **kwargs) from builtins.type
     |      Create and return a new object.  See help(type) for accurate signature.
     |
     |  __repr__(self, /)
     |      Return repr(self).
     |
     |  __reversed__(...)
     |      L.__reversed__() -- return a reverse iterator over the list
     |
     |  __rmul__(self, value, /)
     |      Return self*value.
     |
     |  __setitem__(self, key, value, /)
     |      Set self[key] to value.
     |
     |  __sizeof__(...)
     |      L.__sizeof__() -- size of L in memory, in bytes
     |
     |  append(...)
     |      L.append(object) -> None -- append object to end
     |
     |  clear(...)
     |      L.clear() -> None -- remove all items from L
     |
     |  copy(...)
     |      L.copy() -> list -- a shallow copy of L
     |
     |  count(...)
     |      L.count(value) -> integer -- return number of occurrences of value
     |
     |  extend(...)
     |      L.extend(iterable) -> None -- extend list by appending elements from the iterable
     |
     |  index(...)
     |      L.index(value, [start, [stop]]) -> integer -- return first index of value.
     |      Raises ValueError if the value is not present.
     |
     |  insert(...)
     |      L.insert(index, object) -- insert object before index
     |
     |  pop(...)
     |      L.pop([index]) -> item -- remove and return item at index (default last).
     |      Raises IndexError if list is empty or index is out of range.
     |
     |  remove(...)
     |      L.remove(value) -> None -- remove first occurrence of value.
     |      Raises ValueError if the value is not present.
     |
     |  reverse(...)
     |      L.reverse() -- reverse *IN PLACE*
     |
     |  sort(...)
     |      L.sort(key=None, reverse=False) -> None -- stable sort *IN PLACE*
     |
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |
     |  __hash__ = None

#### 问题：伟人排序
|像|姓名|出生年|寿命|
|-|-|-|-|
|        |  佛陀       | 480BC  |   80 |
|  |  孔子       | 551BC  |   73 |
|          |  柏拉图     | 428BC  |   80 |
|  | 琐罗亚斯德 | 500BC  |    ?  |

```python
# 新数据类型：dict
great = [{'name':'Buddha', 'birth':-480, 'age':80},
         {'name':'Confucius', 'birth':-551, 'age':73},
         {'name':'Plato', 'birth':-428, 'age':80},
         {'name':'Zoroaster', 'birth':-500, 'age':float('nan')}]
```

```python
great[0]['name'] # dict可以按照名字索引
```

    'Buddha'

```python
great[1]['age']  # dict本身可以是list的元素
```

    73

##### 无名函数
\begin{equation}\lambda\end{equation}
>先定义一个函数再使用它有时不如在用它的地方定义它

```python
great = [{'name':'Buddha', 'birth':-480, 'age':80},
         {'name':'Confucius', 'birth':-551, 'age':73},
         {'name':'Plato', 'birth':-428, 'age':80},
         {'name':'Zoroaster', 'birth':-500, 'age':float('nan')}]
great.sort(key=lambda person: person['age'], reverse=True) # key 参数要求一个函数类型，lambda在这里非常方便
great
```

    [{'name': 'Buddha', 'birth': -480, 'age': 80},
     {'name': 'Plato', 'birth': -428, 'age': 80},
     {'name': 'Confucius', 'birth': -551, 'age': 73},
     {'name': 'Zoroaster', 'birth': -500, 'age': nan}]

```python
great = [{'name':'Buddha', 'birth':-480, 'age':80},
         {'name':'Confucius', 'birth':-551, 'age':73},
         {'name':'Plato', 'birth':-428, 'age':80},
         {'name':'Zoroaster', 'birth':-500, 'age':float('nan')}]
great.sort(key=lambda o: o['birth'])
great
```

    [{'name': 'Confucius', 'birth': -551, 'age': 73},
     {'name': 'Zoroaster', 'birth': -500, 'age': nan},
     {'name': 'Buddha', 'birth': -480, 'age': 80},
     {'name': 'Plato', 'birth': -428, 'age': 80}]

## 库的安装和引用

```sh
pip3 install algorithms
```

```sh
python3 -c 'from algorithms.sort import merge_sort'
```

```python
from algorithms.sort import merge_sort
test_list = [1, 8, 3, 5, 6]
result_list = merge_sort(test_list)
result_list
```

##### 在哪里找到这些库？
[pypi](https://pypi.org/)


```python
import numpy as np
import matplotlib.pyplot as plt
import sympy as sy
import math
```

Finding $f(x)=e^{-x}$ using:

a. Compound Simpson's Rule

b. Compound Trapezoidal Rule

```python
#Defining f(x)
def integ(arr):
    integ=0
    analytical=[]
    for num in arr:
        integ+=-sy.exp(-num)
        analytical.append(integ)
    return analytical

def func(arr):
    return sy.exp(-arr)
```

```python
#Defining the array and other related variables
x=10.0
nx=21
xarr=np.linspace(0,x,nx)
print(xarr,dx)
```

    [ 0.   0.5  1.   1.5  2.   2.5  3.   3.5  4.   4.5  5.   5.5  6.   6.5
      7.   7.5  8.   8.5  9.   9.5 10. ] 0.05

```python
#Finding the analytical solution
ex=symbols('x')
print(integrate(func(ex),ex))
integrate(func(ex), (ex,0,10))
```

    -exp(-x)

$\displaystyle 1 - e^{-10}$

```python
#Compound Trapezoidal Rule
integral=0
trapint=[]
for i in range(nx-1):
    integral = 0.5*(xarr[i+1]-xarr[i])*(func(xarr[i]) + func(xarr[i+1]))
    trapint.append(integral)
print(integral)
print(trapint)
```

    3.00629399125464e-5
    [0.401632664928158, 0.243602525221019, 0.147752400329968, 0.0896163608462606, 0.0543550704651279, 0.0329680167479407, 0.0199961129475456, 0.0121282555777632, 0.00735615885674412, 0.00446173588433194, 0.00270617960938738, 0.00164138090378261, 0.000995547842410983, 0.000603830289633022, 0.000366241583925587, 0.000222136749512586, 0.000134732749228289, 8.17195432743309e-5, 4.95654084935950e-5, 3.00629399125464e-5]

```python
#Compound Simpson's Rule
integral=0
simpint=[]
for i in range(0,nx-1,2):
    integral = 1.0/3.0*(xarr[1]-xarr[0])*(func(xarr[i]) + 4*func(xarr[i+1]) + func(xarr[i+2]))
    print(integral)
    simpint.append(integral)
    #simpint.append(1.0/3.0*(xarr[1]-xarr[0])*(func(xarr[i]) + 4*func(xarr[i+1]) + func(xarr[i+2])))
print(integral)
print(simpint)
```

    0.632333680003663
    0.232622560833629
    0.0855770576833453
    0.0314820401576454
    0.0115815953401315
    0.00426063082160135
    0.00156739848568853
    0.000576613679008060
    0.000212124318005295
    7.80361755666611e-5
    7.80361755666611e-5
    [0.632333680003663, 0.232622560833629, 0.0855770576833453, 0.0314820401576454, 0.0115815953401315, 0.00426063082160135, 0.00156739848568853, 0.000576613679008060, 0.000212124318005295, 7.80361755666611e-5]

```python
#Plotting the values
print(integ(xarr))
plt.plot(xarr,-np.exp(-xarr))
plt.plot(xarr[1:],trapint)
plt.plot(xarr[:-1:2], simpint)
```

```python

```

Differentiation & Integration/Compound Integration.ipynb


# Benjamin-Bona-Mahony equation

```julia
include("setup.jl")
```

# Periodic boundary conditions

```julia
function relaxation_functional(γ, unew, uold, param)
    @unpack D, D2, tmp1, tmp2 = param

    @. tmp2 = (1-γ)*uold + γ*unew
    mul!(tmp1, D2, tmp2)
    @. tmp1 = tmp2^2 - tmp2 * tmp1
    energy = integrate(tmp1, D)
end
function relaxation_functional(u, param)
    @unpack D, D2, tmp1 = param

    mul!(tmp1, D2, u)
    @. tmp1 = u^2 - u * tmp1
    energy = integrate(tmp1, D)
end

function relaxation!(integrator)
    told = integrator.tprev
    uold = integrator.uprev
    tnew = integrator.t
    unew = integrator.u

    γ = one(tnew)
    terminate_integration = false
    γlo = one(γ)/2
    γhi = 3*one(γ)/2
    energy_old = relaxation_functional(uold, integrator.p)
    if (relaxation_functional(γlo, unew, uold, integrator.p)-energy_old) * (relaxation_functional(γhi, unew, uold, integrator.p)-energy_old) > 0
        terminate_integration = true
    else
        γ = find_zero(g -> relaxation_functional(g, unew, uold, integrator.p)-energy_old, (γlo, γhi), Roots.AlefeldPotraShi())
    end
    if γ < eps(typeof(γ))
        terminate_integration = true
    end

    @. unew = uold + γ * (unew - uold)
    DiffEqBase.set_u!(integrator, unew)
    if !(tnew ≈ top(integrator.opts.tstops))
        tγ = told + γ * (tnew - told)
        DiffEqBase.set_t!(integrator, tγ)
    end

    if terminate_integration
        terminate!(integrator)
    end

    nothing
end

function save_func_bbm_periodic(u, t, integrator)
    @unpack D, D2, tmp1, usol = integrator.p

    mass = integrate(u, D)

    mul!(tmp1, D2, u)
    @. tmp1 = u^2 - u * tmp1
    quadratic = integrate(tmp1, D)

    @. tmp1 = (u + 1)^3
    cubic = integrate(tmp1, D)

    SVector(mass, quadratic, cubic)
end

function bbm_periodic!(du, u, param, t)
    @unpack D, invImD2, tmp1, tmp2 = param
    one_third = one(t) / 3

    # conservative semidiscretization
    @. tmp1 = -one_third * u^2
    mul!(tmp2, D, tmp1)
    mul!(tmp1, D, u)
    @. tmp2 += -one_third * u * tmp1 - tmp1
    ldiv!(du, invImD2, tmp2)

    nothing
end

function solve_ode_bbm_periodic(usol, D, D2, tspan, alg, tol, dt, adaptive)
    invImD2 = isa(D2, AbstractMatrix) ? factorize(I - D2) : I - D2

    x = grid(D)
    u0 = usol.(tspan[1], x, x[1], -x[1])
    tmp1 = similar(u0); tmp2 = similar(tmp1)
    param = (D=D, D2=D2, invImD2=invImD2, tmp1=tmp1, tmp2=tmp2, usol=usol)

    ode = ODEProblem(bbm_periodic!, u0, tspan, param)

    saveat = range(tspan..., length=100)
    saved_values_baseline = SavedValues(eltype(D), SVector{3,eltype(D)})
    saving_baseline = SavingCallback(save_func_bbm_periodic, saved_values_baseline, saveat=saveat)
    saved_values_relaxation = SavedValues(eltype(D), SVector{3,eltype(D)})
    saving_relaxation = SavingCallback(save_func_bbm_periodic, saved_values_relaxation, saveat=saveat)
    relaxation = DiscreteCallback((u,t,integrator) -> true, relaxation!, save_positions=(false,true))
    cb_baseline = CallbackSet(saving_baseline)
    cb_relaxation = CallbackSet(relaxation, saving_relaxation)

    sol_relaxation = solve(ode, alg, abstol=tol, reltol=tol, dt=dt, adaptive=adaptive, save_everystep=false,
        callback=cb_relaxation, tstops=saveat)
    sol_baseline = solve(ode, alg, abstol=tol, reltol=tol, dt=dt, adaptive=adaptive, save_everystep=false,
        callback=cb_baseline, tstops=saveat)

    unum_baseline   = sol_baseline[end]
    unum_relaxation = sol_relaxation[end]
    uana = usol.(tspan[end], x, x[1], -x[1])
    @printf("Error in u (baseline):   %.3e\n", integrate(u->u^2, unum_baseline - uana, D) |> sqrt)
    @printf("Error in u (relaxation): %.3e\n", integrate(u->u^2, unum_relaxation - uana, D) |> sqrt)
    @printf("Difference of baseline and relaxation in u: %.3e\n",
        integrate(u->u^2, unum_baseline - unum_relaxation, D) |> sqrt)

    sleep(0.1)
    fig_u, ax = plt.subplots(1, 1)
    plt.plot(x, u0, label=L"u^0")
    plt.plot(x, uana, label=L"$u^\mathrm{ana}$")
    plt.plot(x, unum_baseline, label=L"$u^\mathrm{num}$ (baseline)")
    plt.plot(x, unum_relaxation, label=L"$u^\mathrm{num}$ (relaxation)")
    plt.xlabel(L"x"); plt.ylabel(L"u")
    plt.legend(loc="center left", bbox_to_anchor=(1.0, 0.5));

    t_baseline = saved_values_baseline.t
    t_relaxation = saved_values_relaxation.t
    mass_baseline      = map(x->x[1], saved_values_baseline.saveval)
    mass_relaxation    = map(x->x[1], saved_values_relaxation.saveval)
    quadratic_baseline   = map(x->x[2], saved_values_baseline.saveval)
    quadratic_relaxation = map(x->x[2], saved_values_relaxation.saveval)
    cubic_baseline       = map(x->x[3], saved_values_baseline.saveval)
    cubic_relaxation     = map(x->x[3], saved_values_relaxation.saveval)

    fig_invariants, ax = plt.subplots(1, 1)
    ax.set_yscale("symlog", linthreshy=1.0e-14)
    plt.plot(t_baseline,   mass_baseline   .- mass_baseline[1], label=L"$\int u$ (baseline)",
                color="#E69F00", linestyle="-")
    plt.plot(t_relaxation, mass_relaxation .- mass_relaxation[1], label=L"$\int u$ (relaxation)",
                color="#56B4E9", linestyle="-")
    plt.plot(t_baseline,   quadratic_baseline   .- quadratic_baseline[1], label=L"$\int (u^2 + (\partial_x u)^2)$ (baseline)",
                color="#E69F00", linestyle="--")
    plt.plot(t_relaxation, quadratic_relaxation .- quadratic_relaxation[1], label=L"$\int (u^2 + (\partial_x u)^2)$ (relaxation)",
                color="#56B4E9", linestyle="--")
    plt.plot(t_baseline,   cubic_baseline    .- cubic_baseline[1], label=L"$\int (u + 1)^3$ (baseline)",
                color="#E69F00", linestyle=":")
    plt.plot(t_relaxation, cubic_relaxation  .- cubic_relaxation[1], label=L"$\int (u + 1)^3$ (relaxation)",
                color="#56B4E9", linestyle=":")
    plt.xlabel(L"t"); plt.ylabel("Change of Invariants")
    plt.legend(loc="center left", bbox_to_anchor=(1.0, 0.5))

    fig_u, fig_invariants
end

```

```julia
# traveling wave solution
xmin = -90.
xmax = -xmin
get_c() = 1.2
function usol(t, x, xmin, xmax)
    c = get_c()
    A = 3 * (c-1)
    K = 0.5 * sqrt(1 - 1/c)
    x_t = mod(x - c*t - xmin, xmax - xmin) + xmin

    A / cosh(K * x_t)^2
end

println("c = ", get_c())
println("xmin = ", xmin)
println("xmax = ", xmax)
@show usol(0., xmin, xmin, xmax)
@show usol(0., xmax, xmin, xmax)
@show N = 2^8
@show dt = 1.0 * (xmax - xmin) / (N * get_c())
flush(stdout)

# tspan = (0., (xmax-xmin)/(3*get_c()))
# tspan = (0.0, (xmax-xmin)/(3*get_c()) + 1*(xmax-xmin)/get_c())
tspan = (0.0, (xmax-xmin)/(3*get_c()) + 10*(xmax-xmin)/get_c())
# tspan = (0.0, (xmax-xmin)/(3*get_c()) + 100*(xmax-xmin)/get_c())
# tspan = (0.0, (xmax-xmin)/(3*get_c()) + 1000*(xmax-xmin)/get_c())

tol = 1.0e-7
adaptive = false
D = fourier_derivative_operator(xmin, xmax, N)
# D = periodic_derivative_operator(1, 6, xmin, xmax, N+1)
# D = periodic_derivative_operator(Holoborodko2008(), 1, 4, xmin, xmax, N+1)
D2 = D^2
# D2 = periodic_derivative_operator(2, 6, xmin, xmax, N+1)
# D2 = periodic_derivative_operator(Holoborodko2008(), 1, 4, xmin, xmax, N+1)^2
# p = 7; mesh = UniformPeriodicMesh1D(xmin, xmax, N÷p)
# D = couple_continuosly(legendre_derivative_operator(-1., 1., p+1), mesh)
# D2 = sparse(couple_continuosly(legendre_second_derivative_operator(-1., 1., p+1), mesh))
# D2 = sparse(D)^2
# p = 7; Dop = legendre_derivative_operator(-1., 1., p+1); mesh = UniformPeriodicMesh1D(xmin, xmax, N÷(p+1))
# D = couple_discontinuosly(Dop, mesh); D₊ = couple_discontinuosly(Dop, mesh, Val(:plus)); D₋ = couple_discontinuosly(Dop, mesh, Val(:minus))
# D2 = sparse(D₊) * sparse(D₋)
# D2 = sparse(D₋) * sparse(D₊)
# D2 = sparse(D)^2

fig_u, fig_invariants = solve_ode_bbm_periodic(usol, D, D2, tspan, RK4(), tol, dt, adaptive)
```

# Convergence study with manufactured solutions

```julia
import SymPy; sp = SymPy

function math_replacements(s)
    s = replace(s, "cos(pi*" => "cospi(")
    s = replace(s, "sin(pi*" => "sinpi(")
end

function usol(t, x)
    exp(t/2) * sinpi(2*(x-t/2))
end

let (t, x) = sp.symbols("t, x", real=true)
    u = usol(t, x)

    println("u:")
    sp.diff(u, t) - sp.diff(u, x, 2, t, 1) + sp.diff(u, x) + sp.diff(u^2/2, x) |>
        sp.simplify |> sp.string |> math_replacements |> println
end
```

```julia
function save_func_bbm_manufactured(u, t, integrator)
    @unpack D, tmp1, usol = integrator.p

    x = grid(D)
    tmp1 .= ( usol.(t, x) .- u ).^2
    error_u = integrate(tmp1, D) |> sqrt

    error_u
end

function bbm_periodic_manufactured!(du, u, param, t)
    @unpack D, invImD2, tmp1, tmp2 = param
    x = grid(D)
    one_third = one(t) / 3

    # conservative semidiscretization
    @. tmp1 = -one_third * u^2
    mul!(tmp2, D, tmp1)
    mul!(tmp1, D, u)
    @. tmp2 += -one_third * u * tmp1 - tmp1
    @. tmp2 += -2*pi^2*(sinpi((t - 2*x)) + 2*pi*cospi((t - 2*x)))*exp(t/2) - exp(t/2)*sinpi((t - 2*x))/2 + pi*exp(t/2)*cospi((t - 2*x)) - pi*exp(t)*sinpi((2*t - 4*x))
    ldiv!(du, invImD2, tmp2)

    nothing
end

function errors_bbm_periodic_manufactured(usol, D, D2, tspan, alg, tol, dt, adaptive)
    invImD2 = isa(D2, AbstractMatrix) ? factorize(I - D2) : I - D2

    x = grid(D)
    u0 = usol.(tspan[1], x)
    tmp1 = similar(u0); tmp2 = similar(tmp1)
    param = (D=D, D2=D2, invImD2=invImD2, tmp1=tmp1, tmp2=tmp2, usol=usol)

    ode = ODEProblem(bbm_periodic_manufactured!, u0, tspan, param)

    saveat = range(tspan..., length=2)
    saved_values_baseline = SavedValues(eltype(D), eltype(D))
    saving_baseline = SavingCallback(save_func_bbm_manufactured, saved_values_baseline, saveat=saveat)
    cb_baseline = CallbackSet(saving_baseline)

    sol_baseline = solve(ode, alg, abstol=tol, reltol=tol, dt=dt, adaptive=adaptive, save_everystep=false,
        callback=cb_baseline, tstops=saveat)

    error_u = saved_values_baseline.saveval[end]
end

xmin = 0.0
xmax = 1.0
tspan = (0.0, 1.0)

tol = 1.0e-12

# val_N = round.(Int, 2 .^ range(4, 5, length=2))
# val_N = round.(Int, 2 .^ range(4, 8, length=7)) |> evenodd_values
val_N = round.(Int, 2 .^ range(3, 4.5, length=7)) |> evenodd_values
# val_N = 2 .^ (2:7) .+ 1
val_error_u = Float64[]
for N in val_N
#     D = fourier_derivative_operator(xmin, xmax, N)
#     p = 6; D = periodic_derivative_operator(1, p, xmin, xmax, N+1)
#     p = 4; D = periodic_derivative_operator(Holoborodko2008(), 1, p, xmin, xmax, N+1)
#     D2 = D^2
#     D2 = periodic_derivative_operator(2, p, xmin, xmax, N+1)
#     D2 = periodic_derivative_operator(Holoborodko2008(), 1, p, xmin, xmax, N+1)^2
    p = 3; mesh = UniformPeriodicMesh1D(xmin, xmax, N)
    D = couple_continuosly(legendre_derivative_operator(-1., 1., p+1), mesh)
    D2 = sparse(couple_continuosly(legendre_second_derivative_operator(-1., 1., p+1), mesh))
#     D2 = sparse(D)^2
    p = 6; Dop = legendre_derivative_operator(-1., 1., p+1); mesh = UniformPeriodicMesh1D(xmin, xmax, N)
    D = couple_discontinuosly(Dop, mesh); D₊ = couple_discontinuosly(Dop, mesh, Val(:plus)); D₋ = couple_discontinuosly(Dop, mesh, Val(:minus))
    D2 = sparse(D₊) * sparse(D₋)
    D2 = sparse(D₋) * sparse(D₊)
    D2 = sparse(D)^2

    error_u = errors_bbm_periodic_manufactured(usol, D, D2, tspan, Tsit5(), tol, 1/N, true)
    push!(val_error_u, error_u)
end

fig, ax = plt.subplots(1, 1)
ax.set_prop_cycle(marker_cycler)
plt.plot(val_N, val_error_u, label=L"$\| u - u_{\mathrm{ana}} \|$")
c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
plt.plot(val_N, exp(c0) .* val_N.^c1, marker="", linestyle=":", color="gray", label=@sprintf("Order %.2f", -c1))
plt.xscale("log", basex=2)
plt.yscale("symlog", linthreshy=1.0e-13)
plt.legend(loc="center left", bbox_to_anchor=(1.0, 0.5))
ax.locator_params(axis="y", numticks=9)
```

## Convergence study: Plots FD

```julia
function do_stuff(p, val_N)
    val_error = Float64[]
    for N in val_N
        D = periodic_derivative_operator(1, p, xmin, xmax, N+1)
#         D2 = D^2
        D2 = periodic_derivative_operator(2, p, xmin, xmax, N+1)
        error_u = errors_bbm_periodic_manufactured(usol, D, D2, tspan, Tsit5(), tol, 1/N, true)
        push!(val_error, error_u)
    end
    Dict("val_p$(p)_N" => val_N, "val_p$(p)_error" => val_error)
end

filename = "bbm_periodic_manufactured_convergence_FD_narrow"
even_odd_values = evenodd_values
res = Dict{String,Any}()

p = 2
val_N = round.(Int, 2 .^ range(4, 10, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 4
val_N = round.(Int, 2 .^ range(4, 9, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 6
val_N = round.(Int, 2 .^ range(4, 8, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 8
val_N = round.(Int, 2 .^ range(4, 6.5, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

open("../data/" * filename * ".json", "w") do io
    JSON.print(io, res, 2)
end
```

```julia
fig, ax = plt.subplots(1, 1)
ax.set_prop_cycle(marker_cycler)

filename = "bbm_periodic_manufactured_convergence_FD_narrow"
res = JSON.parsefile("../data/" * filename * ".json")

let p = 2
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("\$ p = %d \$, EOC %.2f", p, -c1), (val_N[4], 2.0*val_error_u[4]), color="gray")
end

let p = 4
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("\$ p = %d \$, EOC %.2f", p, -c1), (val_N[6], 2.0*val_error_u[6]), color="gray")
end

let p = 6
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("\$ p = %d \$, EOC %.2f", p, -c1), (val_N[12], 2.0*val_error_u[12]), color="gray")
end

let p = 8
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("\$ p = %d \$, EOC %.2f", p, -c1), (0.15*val_N[end], val_error_u[end]), color="gray")
end

ax.set_ylim(1.0e-13, 1.5e0)
ax.set_xscale("log", basex=2)
ax.set_yscale("symlog", linthreshy=1.0e-13)
ax.set_xlabel(L"N")
ax.set_ylabel(L"\| u - u_{\mathrm{ana}} \|")
ax.locator_params(axis="y", numticks=5)
fig.savefig("../figures/" * filename * ".pdf", bbox_inches="tight")
```

```julia
function do_stuff(p, val_N)
    val_error = Float64[]
    for N in val_N
        D = periodic_derivative_operator(1, p, xmin, xmax, N+1)
        D2 = D^2
#         D2 = periodic_derivative_operator(2, p, xmin, xmax, N+1)
        error_u = errors_bbm_periodic_manufactured(usol, D, D2, tspan, Tsit5(), tol, 1/N, true)
        push!(val_error, error_u)
    end
    Dict("val_p$(p)_N" => val_N, "val_p$(p)_error" => val_error)
end

filename = "bbm_periodic_manufactured_convergence_FD_wide"
even_odd_values = evenodd_values
res = Dict{String,Any}()

p = 2
val_N = round.(Int, 2 .^ range(4, 10, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 4
val_N = round.(Int, 2 .^ range(4, 9, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 6
val_N = round.(Int, 2 .^ range(4, 8, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 8
val_N = round.(Int, 2 .^ range(4, 6.5, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

open("../data/" * filename * ".json", "w") do io
    JSON.print(io, res, 2)
end
```

```julia
fig, ax = plt.subplots(1, 1)
ax.set_prop_cycle(marker_cycler)

filename = "bbm_periodic_manufactured_convergence_FD_wide"
res = JSON.parsefile("../data/" * filename * ".json")

let p = 2
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("\$ p = %d \$, EOC %.2f", p, -c1), (val_N[4], 2.0*val_error_u[4]), color="gray")
end

let p = 4
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("\$ p = %d \$, EOC %.2f", p, -c1), (val_N[6], 2.0*val_error_u[6]), color="gray")
end

let p = 6
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("\$ p = %d \$, EOC %.2f", p, -c1), (val_N[12], 2.0*val_error_u[12]), color="gray")
end

let p = 8
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("\$ p = %d \$, EOC %.2f", p, -c1), (0.15*val_N[end], val_error_u[end]), color="gray")
end

ax.set_ylim(1.0e-13, 1.5e0)
ax.set_xscale("log", basex=2)
ax.set_yscale("symlog", linthreshy=1.0e-13)
ax.set_xlabel(L"N")
ax.set_ylabel(L"\| u - u_{\mathrm{ana}} \|")
ax.locator_params(axis="y", numticks=5)
fig.savefig("../figures/" * filename * ".pdf", bbox_inches="tight")
```

## Convergence study: Plots CG

```julia
function do_stuff(p, val_N)
    val_error = Float64[]
    for N in val_N
        mesh = UniformPeriodicMesh1D(xmin, xmax, N)
        D = couple_continuosly(legendre_derivative_operator(-1., 1., p+1), mesh)
        D2 = sparse(D)^2
#         D2 = sparse(couple_continuosly(legendre_second_derivative_operator(-1., 1., p+1), mesh))
        error_u = errors_bbm_periodic_manufactured(usol, D, D2, tspan, Tsit5(), tol, 1/N, true)
        push!(val_error, error_u)
    end
    Dict("val_p$(p)_N" => val_N, "val_p$(p)_error" => val_error)
end

filename = "bbm_periodic_manufactured_convergence_CG_D1c2"
even_odd_values = evenodd_values
res = Dict{String,Any}()

p = 1
val_N = round.(Int, 2 .^ range(6, 12, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 2
val_N = round.(Int, 2 .^ range(5, 10, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 3
val_N = round.(Int, 2 .^ range(4, 8, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 4
val_N = round.(Int, 2 .^ range(4, 7, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 5
val_N = round.(Int, 2 .^ range(2.5, 5.5, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 6
val_N = round.(Int, 2 .^ range(2, 4.75, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

open("../data/" * filename * ".json", "w") do io
    JSON.print(io, res, 2)
end
```

```julia
fig, ax = plt.subplots(1, 1)
ax.set_prop_cycle(marker_cycler)

filename = "bbm_periodic_manufactured_convergence_CG_D1c2"
res = JSON.parsefile("../data/" * filename * ".json")

let p = 1
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (val_N[2], 2.0*val_error_u[2]), color="gray")
end

let p = 2
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (val_N[end], 0.1*val_error_u[end]), color="gray")
end

let p = 3
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (val_N[end], 1.5*val_error_u[end]), color="gray")
end

let p = 4
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (0.4*val_N[end], 0.1*val_error_u[end]), color="gray")
end

let p = 5
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (1.2*val_N[end], 0.2*val_error_u[end]), color="gray")
end

let p = 6
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (0.2*val_N[end], 0.1*val_error_u[end]), color="gray")
end

plt.figure()
handles, labels = ax.get_legend_handles_labels()
plt.figlegend(handles, labels, loc="center", ncol=8)
plt.savefig("../figures/Galerkin_legend_p1_p6.pdf", bbox_inches="tight")

ax.set_xlim(2^1-0.5, 2^13-1)
ax.set_ylim(1.0e-13, 1.5e0)
ax.set_xscale("log", basex=2)
ax.set_yscale("symlog", linthreshy=1.0e-13)
ax.set_xlabel(L"N")
ax.set_ylabel(L"\| u - u_{\mathrm{ana}} \|")
ax.locator_params(axis="y", numticks=5)
fig.savefig("../figures/" * filename * ".pdf", bbox_inches="tight")
```

```julia
function do_stuff(p, val_N)
    val_error = Float64[]
    for N in val_N
        mesh = UniformPeriodicMesh1D(xmin, xmax, N)
        D = couple_continuosly(legendre_derivative_operator(-1., 1., p+1), mesh)
#         D2 = sparse(D)^2
        D2 = sparse(couple_continuosly(legendre_second_derivative_operator(-1., 1., p+1), mesh))
        error_u = errors_bbm_periodic_manufactured(usol, D, D2, tspan, Tsit5(), tol, 1/N, true)
        push!(val_error, error_u)
    end
    Dict("val_p$(p)_N" => val_N, "val_p$(p)_error" => val_error)
end

filename = "bbm_periodic_manufactured_convergence_CG_narrow"
even_odd_values = evenodd_values
res = Dict{String,Any}()

p = 1
val_N = round.(Int, 2 .^ range(6, 12, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 2
val_N = round.(Int, 2 .^ range(2, 8.5, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 3
val_N = round.(Int, 2 .^ range(2, 7, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 4
val_N = round.(Int, 2 .^ range(2, 5.5, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 5
val_N = round.(Int, 2 .^ range(2, 4.5, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 6
val_N = round.(Int, 2 .^ range(1, 4.25, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

open("../data/" * filename * ".json", "w") do io
    JSON.print(io, res, 2)
end
```

```julia
fig, ax = plt.subplots(1, 1)
ax.set_prop_cycle(marker_cycler)

filename = "bbm_periodic_manufactured_convergence_CG_narrow"
res = JSON.parsefile("../data/" * filename * ".json")

let p = 1
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (val_N[2], 2.0*val_error_u[2]), color="gray")
end

let p = 2
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (val_N[end], 2.0*val_error_u[end]), color="gray")
end

let p = 3
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (1.2*val_N[end], 0.8*val_error_u[end]), color="gray")
end

let p = 4
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (1.2*val_N[end], 0.2*val_error_u[end]), color="gray")
end

let p = 5
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (1.1*val_N[end], 0.1*val_error_u[end]), color="gray")
end

let p = 6
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (0.15*val_N[end], 0.5*val_error_u[end]), color="gray")
end

ax.set_xlim(2^1-0.5, 2^13-1)
ax.set_ylim(1.0e-13, 1.5e0)
ax.set_xscale("log", basex=2)
ax.set_yscale("symlog", linthreshy=1.0e-13)
ax.set_xlabel(L"N")
ax.set_ylabel(L"\| u - u_{\mathrm{ana}} \|")
ax.locator_params(axis="y", numticks=5)
fig.savefig("../figures/" * filename * ".pdf", bbox_inches="tight")
```

## Convergence study: Plots DG

```julia
function do_stuff(p, val_N)
    val_error = Float64[]
    for N in val_N
        Dop = legendre_derivative_operator(-1., 1., p+1); mesh = UniformPeriodicMesh1D(xmin, xmax, N)
        D = couple_discontinuosly(Dop, mesh); D₊ = couple_discontinuosly(Dop, mesh, Val(:plus)); D₋ = couple_discontinuosly(Dop, mesh, Val(:minus))
        D2 = sparse(D)^2
#         D2 = sparse(D₊) * sparse(D₋)
#         D2 = sparse(D₋) * sparse(D₊)
        error_u = errors_bbm_periodic_manufactured(usol, D, D2, tspan, Tsit5(), tol, 1/N, true)
        push!(val_error, error_u)
    end
    Dict("val_p$(p)_N" => val_N, "val_p$(p)_error" => val_error)
end

filename = "bbm_periodic_manufactured_convergence_DG_D1c2"
even_odd_values = evenodd_values
res = Dict{String,Any}()

p = 1
val_N = round.(Int, 2 .^ range(6, 12, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 2
val_N = round.(Int, 2 .^ range(5, 10, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 3
val_N = round.(Int, 2 .^ range(4, 8, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 4
val_N = round.(Int, 2 .^ range(3, 7, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 5
val_N = round.(Int, 2 .^ range(3, 5.75, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 6
val_N = round.(Int, 2 .^ range(2.5, 4.75, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

open("../data/" * filename * ".json", "w") do io
    JSON.print(io, res, 2)
end
```

```julia
fig, ax = plt.subplots(1, 1)
ax.set_prop_cycle(marker_cycler)

filename = "bbm_periodic_manufactured_convergence_DG_D1c2"
res = JSON.parsefile("../data/" * filename * ".json")

let p = 1
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (val_N[2], 2.0*val_error_u[2]), color="gray")
end

let p = 2
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (val_N[end], 2.0*val_error_u[end]), color="gray")
end

let p = 3
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (0.5*val_N[end], 0.2*val_error_u[end]), color="gray")
end

let p = 4
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (1.2*val_N[end], 0.2*val_error_u[end]), color="gray")
end

let p = 5
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (0.45*val_N[end], 0.1*val_error_u[end]), color="gray")
end

let p = 6
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (0.5*val_N[end], 0.1*val_error_u[end]), color="gray")
end

ax.set_xlim(2^2, 2^13-1)
ax.set_ylim(1.0e-13, 1.5e0)
ax.set_xscale("log", basex=2)
ax.set_yscale("symlog", linthreshy=1.0e-13)
ax.set_xlabel(L"N")
ax.set_ylabel(L"\| u - u_{\mathrm{ana}} \|")
ax.locator_params(axis="y", numticks=5)
fig.savefig("../figures/" * filename * ".pdf", bbox_inches="tight")
```

```julia
function do_stuff(p, val_N)
    val_error = Float64[]
    for N in val_N
        Dop = legendre_derivative_operator(-1., 1., p+1); mesh = UniformPeriodicMesh1D(xmin, xmax, N)
        D = couple_discontinuosly(Dop, mesh); D₊ = couple_discontinuosly(Dop, mesh, Val(:plus)); D₋ = couple_discontinuosly(Dop, mesh, Val(:minus))
#         D2 = sparse(D)^2
        D2 = sparse(D₊) * sparse(D₋)
#         D2 = sparse(D₋) * sparse(D₊)
        error_u = errors_bbm_periodic_manufactured(usol, D, D2, tspan, Tsit5(), tol, 1/N, true)
        push!(val_error, error_u)
    end
    Dict("val_p$(p)_N" => val_N, "val_p$(p)_error" => val_error)
end

filename = "bbm_periodic_manufactured_convergence_DG_D1pD1m"
even_odd_values = evenodd_values
res = Dict{String,Any}()

p = 1
val_N = round.(Int, 2 .^ range(6, 12, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 2
val_N = round.(Int, 2 .^ range(5, 10, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 3
val_N = round.(Int, 2 .^ range(4, 8, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 4
val_N = round.(Int, 2 .^ range(3, 7, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 5
val_N = round.(Int, 2 .^ range(3, 5.5, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

p = 6
val_N = round.(Int, 2 .^ range(2.5, 4.5, length=7)) |> even_odd_values
res = merge(res, do_stuff(p, val_N))

open("../data/" * filename * ".json", "w") do io
    JSON.print(io, res, 2)
end
```

```julia
fig, ax = plt.subplots(1, 1)
ax.set_prop_cycle(marker_cycler)

filename = "bbm_periodic_manufactured_convergence_DG_D1pD1m"
res = JSON.parsefile("../data/" * filename * ".json")

let p = 1
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (val_N[2], 2.0*val_error_u[2]), color="gray")
end

let p = 2
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (val_N[end], 2.0*val_error_u[end]), color="gray")
end

let p = 3
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (0.5*val_N[end], 0.2*val_error_u[end]), color="gray")
end

let p = 4
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (1.2*val_N[end], 0.2*val_error_u[end]), color="gray")
end

let p = 5
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (0.7*val_N[end], 0.1*val_error_u[end]), color="gray")
end

let p = 6
    val_N, val_error_u = res["val_p$(p)_N"], res["val_p$(p)_error"]
    ax.plot(val_N, val_error_u, label="\$ p = $p \$")
    c0, c1 = linear_regression(log.(val_N), log.(val_error_u))
    plt.plot(val_N, exp(c0).*val_N.^c1, marker="", linestyle=":", color="gray")
    plt.annotate(@sprintf("EOC %.2f", -c1), (0.25*val_N[end], 0.1*val_error_u[end]), color="gray")
end

ax.set_xlim(2^2, 2^13-1)
ax.set_ylim(1.0e-13, 1.5e0)
ax.set_xscale("log", basex=2)
ax.set_yscale("symlog", linthreshy=1.0e-13)
ax.set_xlabel(L"N")
ax.set_ylabel(L"\| u - u_{\mathrm{ana}} \|")
ax.locator_params(axis="y", numticks=5)
fig.savefig("../figures/" * filename * ".pdf", bbox_inches="tight")
```

```julia

```

```julia

```