```python
%matplotlib inline
from qcodes.dataset.experiment_container import new_experiment
from qcodes.dataset.experiment_container import load_by_id
from qcodes.dataset.plotting import plot_by_id

import nanotune as nt
from nanotune.model.capacitancemodel import CapacitanceModel
```

## Capacitance model for quantum dots

The system of electrostatic gates, dots and reservoirs is represented by a system of conductors connected via resistors and capacitors. Albeit classical and simple, the capacitance model explains and qualitatively reproduces relevant transport features of gate-defined quantum dots.

The capacitance model is defined by a capacitance matrix $\mathbf{C}$, whose elements $C_{ij}$ are the capacitances between individual elements $i$ and $j$. We distinguish between two different types of elements, the charge and the voltage nodes, representing quantum dots and electrostatic gates respectively. Each node $i$ is defined by its charge $Q_{i}$ and electrical potential $V_{i}$. For simplicity, we write charges and potentials on all nodes of the system in vector notation. We denote charges on charge and voltages nodes by $\vec{Q_{c}}$ and $\vec{Q_{v}}$ respectively, and electrical potentials by $\vec{V}_{c}$ and $\vec{V}_{v}$. The capacitance model allows to calculate potentials on voltage nodes resulting in the desired number of charges on charge nodes. We consider a system of $N_{c}$ charge nodes and $N_{v}$ voltage nodes.

The capacitor connecting node $j$ and node $k$ has a capacitance $C_{jk}$ and stores a charge $q_{jk}$. The total charge on node $j$ is the sum of the charges of  all capacitors connected to it,
\begin{equation}
Q_{j} = \sum_{k} q_{jk} = \sum_{k} C_{jk} (V_{j} - V_{k}).
\end{equation}
Using the vector notation for charges and electrical potentials introduced above, this relation can be expressed using the capacitance matrix, $\vec{Q} = \mathbf{C} \vec{V}$. Distinguishing between charge and voltage node sub-systems, this relation becomes
\begin{equation}
\begin{pmatrix} \vec{Q_{c}} \\ \vec{Q_{v}} \end{pmatrix} =
\begin{pmatrix}
\mathbf{C_{cc}} & \mathbf{C_{cv}} \\
\mathbf{C_{vc}} & \mathbf{C_{vv}}
\end{pmatrix}
\begin{pmatrix}
\vec{V_{c}} \\
\vec{V}_{v}
\end{pmatrix}.
\end{equation}
Diagonal elements of the capacitance matrix, $C_{jj}$, are total capacitances of each node and carry the opposite sign of the matrix's off-diagonal elements.
The off-diagonal elements of $\mathbf{\mathbf{C_{cc}}}$ are capacitances between charge nodes, while the off-diagonal elements of $\mathbf{\mathbf{C_{vv}}}$ are capacitances between voltage nodes. The elements of $\mathbf{\mathbf{C_{cv}}}$ are capacitances between voltage and charge nodes, and allow to calculate so-called virtual gate coefficients - useful knobs in semiconductor qubit experiments.

Illustration of capacitances between gates and gates and dots:
Each gate voltage $V_{i}$ will tune the number of charges on each dot.  The capacitance of the dots, $C_{A}$ and $C_{B}$, are sums of all capacitances connects to $A$ and $B$ respectively.  Gates located further away will have a smaller capacitive coupling. Most labels of capacitance between gates are omitted for readability.

Names and layout indices of gates:
Six electrostatic gates, three barriers and two plungers, are used to define two, possibly coupled, quantum dots. Barrier gates are primarily used to create potential barriers, while plungers are used to tune electron density and thus the dot's electrochemical potentials.

Initiate or set database where data should be saved. When initializing a database a new qcodes experiment needs to be created as well.

```python
exp_name = 'capacitance_extraction'
sample_name = 'capa_model'
db_name = 'capa_model_test.db'
nt.new_database(db_name, '.')
new_experiment(exp_name, sample_name)
```

    Upgrading database; v0 -> v1: : 0it [00:00, ?it/s]
    Upgrading database; v1 -> v2: 100%|█████████████████████████████████████████████████████| 1/1 [00:00<00:00, 504.18it/s]
    Upgrading database; v2 -> v3: : 0it [00:00, ?it/s]
    Upgrading database; v3 -> v4: : 0it [00:00, ?it/s]
    Upgrading database; v4 -> v5: 100%|█████████████████████████████████████████████████████| 1/1 [00:00<00:00, 333.62it/s]
    Upgrading database; v5 -> v6: : 0it [00:00, ?it/s]
    Upgrading database; v6 -> v7: 100%|█████████████████████████████████████████████████████| 1/1 [00:00<00:00, 142.93it/s]
    Upgrading database; v7 -> v8: 100%|█████████████████████████████████████████████████████| 1/1 [00:00<00:00, 333.23it/s]
    Upgrading database; v8 -> v9: 100%|█████████████████████████████████████████████████████| 1/1 [00:00<00:00, 250.06it/s]

    capacitance_extraction#capa_model#1@C:\Workshop\nanotune\docs\examples\capa_model_test.db
    -----------------------------------------------------------------------------------------

Initialize an instance of CapacitanceModel with six gates and two dots

```python
voltage_nodes = {
    0: 'top_barrier',
    1: 'left_barrier',
    2: 'left_plunger',
    3: 'central_barrier',
    4: 'right_plunger',
    5: 'right_barrier',
}
charge_nodes = {
    0: 'A',
    1: 'B'
}

qdot = CapacitanceModel(
    'qdot',
    charge_nodes=charge_nodes,
    voltage_nodes=voltage_nodes,
    db_name=db_name,
    db_folder='.'
)

```

Set voltages, define capacitance matrix and calculate ground state dot occupation

```python
qdot.V_v([
    -1,  # top_barrier
    -1,  # left_barrier
    -0.5,  # left_plunger
    -1,  # central_barrier
    -0.1,  # right_plunger
    -1,  # right_barrier
])

# off-diagonal entries of C_cc, ie.e inter-dot capacitance
qdot.C_cc([[-2]])
# # capacitances between gates and dots
#           top_b    left_b left_p center right_p  right_b
qdot.C_cv([[-0.6, -0.6, -0.5, -0.5, -0.1, -0.1],   #  A
           [-0.5, -0.3, -0.2, -0.8, -0.5, -0.9]])    #  B

print(qdot.determine_N())
```

    [0.0, 0.0]

Define which dot occupancies to probe and calculate respective voltage ranges of two gates to sweep.

```python
N_limits = [(0, 2), (0, 2)]
sweep_ranges = qdot.determine_sweep_voltages([2, 4], N_limits=N_limits)
print(sweep_ranges)
```

    [(2.8260797502049875, 6.304308844864283), (3.8695883841630536, 6.478321143336265)]

```python
%%time
dataid = qdot.sweep_voltages(
    [2, 4],
    sweep_ranges,
    n_steps=[40,40],
    target_snr_db=100,
    add_noise=False,
    add_charge_jumps=False,
    normalize=False,
)
plot_by_id(dataid)
```

Sweep one voltage to show Coulomb oscillations

```python
qdot.V_v([
    -1,  # top_barrier
    -1,  # left_barrier
    3.2,  # left_plunger
    -1,  # central_barrier
    -0.1,  # right_plunger
    -1,  # right_barrier
])

dataid = qdot.sweep_voltage(
    4,
    sweep_ranges[1],
    n_steps=200,
    target_snr_db=100,
    normalize=False,
   )
plot_by_id(dataid)
```


# PyFolding

Let's test out our **PyFolding** installation using an IPython notebook. This is a nice way to store data, processing and results all in one place!

---

### Testing *PyFolding*

PyFolding contains a standard test function to make sure that the installation is functioning correctly. This standard test function automatically generates data for a two-state folding protein: $D \leftrightharpoons N$

This includes a two-state equilibrium denaturation curve:
\begin{equation}
F = \frac{\exp( m(x-d_{50})) / RT} { 1+\exp(m(x-d_{50}))/RT}
\end{equation}

and a two-state chevron plot:
\begin{equation}
k_{obs} = k_u^{H_2O}\exp(-m_{ku}x) + k_f^{H_2O}\exp(m_{kf}x)
\end{equation}

Here is an example script:

```python
%matplotlib inline

# import pyfolding
import pyfolding

# now test it
pyfolding.test()
```

---

### Displaying details of the fitting equations
 We can list all of the models available by calling the ```list_models``` function:

```python
from pyfolding.models import *

list_models()
```

    [('ChevronPolynomialFit', 'Verified: True'),
     ('HeteropolymerIsingEquilibrium', 'Verified: False'),
     ('HomozipperIsingEquilibrium', 'Verified: True'),
     ('ParallelTwoStateChevron', 'Verified: False'),
     ('ParallelTwoStateUnfoldingChevron', 'Verified: False'),
     ('TemplateModel', 'Verified: False'),
     ('ThreeStateChevron', 'Verified: True'),
     ('ThreeStateDimericIEquilibrium', 'Verified: True'),
     ('ThreeStateEquilibrium', 'Verified: True'),
     ('ThreeStateFastPhaseChevron', 'Verified: True'),
     ('ThreeStateMonoIEquilibrium', 'Verified: True'),
     ('ThreeStateSequentialChevron', 'Verified: True'),
     ('TwoStateChevron', 'Verified: True'),
     ('TwoStateChevronMovingTransition', 'Verified: True'),
     ('TwoStateDimerEquilibrium', 'Verified: True'),
     ('TwoStateEquilibrium', 'Verified: True'),
     ('TwoStateEquilibriumSloping', 'Verified: True')]

If we want to know any further information, we can print the equation. Each function has a pre-formatted $\LaTeX$ equation, details of the parameters and the primary reference. These can be displayed by using the ```info()``` function bound to each FitModel:

```python
TwoStateEquilibrium().info()
```

$$F = \frac{\exp( m(x-d_{50})) / RT} { 1+\exp(m(x-d_{50}))/RT}$$

     Two state equilibrium denaturation curve - No sloping baseline.

    	Folding Scheme:
    		N <-> D

    	Params:
    		F = Fraction unfolded
    		m = m-value
    		x = denaturant concentration (M)
    		d50 = denaturant midpoint (M)
    		R = Universal Gas Constant (kcal.mol-1.K-1)
    		T = Temperature (Kelvin)

    	Reference:
    		Clarke and Fersht. Engineered disulfide bonds as probes of
    		the folding pathway of barnase: Increasing the stability
    		of proteins against the rate of denaturation.
    		Biochemistry (1993) vol. 32 (16) pp. 4322-4329

```python
TwoStateChevron().info()
```

$$\begin{equation} \
				\begin{aligned} \
				& k_{obs} = k_f + k_u \\ \
				\\ \
				\text{where:} \\ \
				& k_f = k_f^{H_2O}\exp(-m_{kf}x)\\ \
				& k_u = k_u^{H_2O}\exp(m_{ku}x) \\ \
				\text{thus:} \\ \
				& k_{obs} = k_f^{H_2O}\exp(-m_{kf}x) + k_u^{H_2O}\exp(m_{ku}x)\\ \
				\end{aligned} \
				\end{equation}$$

     Two state chevron plot.

    	Folding Scheme:
    		N <-> D

    	Params:
    		k obs = rate constant of unfolding or refolding at a particular denaturant concentration
    		kf = rate constant of refolding at a particular denaturant concentration
    		mf = the gradient of refolding arm of the chevron
    		ku = rate constant of unfolding at a a particular denaturant concentration
    		mu = the gradient of unfolding arm of the chevron
    		x = denaturant concentration (M)

    	Reference:
    		Jackson SE and Fersht AR.  Folding of chymotrypsin inhibitor 2.
    		1. Evidence for a two-state transition.
    		Biochemistry (1991) 30(43):10428-10435.

```python
ThreeStateChevron().info()
```

$$\begin{equation} \
				\begin{aligned} \
				& k_{obs} = \frac{k_{fi} + k_{if}} {(1+1/K_{iu})} \\ \
				\\ \
				\text{where:} \\ \
				& k_{fi} = k_{fi}^{H_2O}\exp(-m_{fi}x)\\ \
				& k_{if} = k_{if}^{H_2O}\exp((m_i - m_{if})x)\\ \
				& K_{iu} = K_{iu}^{H_2O}\exp((m_u - m_i)x)\\ \
				\text{thus:} \\ \
				& k_{obs} = k_{fi}^{H_2O}\exp(-m_{if}x) + k_{if}^{H_2O}\exp((m_i - m_{if})x) /(1 + 1 / (K_{iu}^{H_2O}\exp((m_u-m_i)x)))\\ \
				\end{aligned} \
				\end{equation}$$

     Three state chevron with single intermediate.

    	Folding Scheme:
    		N <-> I <-> D

    	Params:
    		k obs = rate constant of unfolding or refolding at a particular denaturant concentration
    		kfi = microscopic rate constant for the conversion of folded to intermediate
    		kif = microscopic rate constant for the conversion of intermediate to folded
    		i.e. k_if = kif(H20) * exp((mi - mif)*x)
    		Kiu = equilibrium constant for the rapid equilibration between intermediate & unfolded
    		i.e. Kiu = Kiu(H2O) * exp((mu-mi)*x)
    		mif = m-value associated with the kinetic transition between intermediate & folded
    		mi = m-value associated with the equilibrium transition between intermediate & folded
    		mu = m-value associated with the equilibrium transition between unfolded & folded
    		x = denaturant concentration (M)

    	Reference:
    		Parker et al. An integrated kinetic analysis of
    		intermediates and transition states in protein folding reactions.
    		Journal of molecular biology (1995) vol. 253 (5) pp. 771-86

If we would like to get further details, for example the arguments of the equation, we can get those easily by request the fit function arguments:

```python
ThreeStateChevron().fit_func_args
```

    ['kfi', 'mif', 'kif', 'mi', 'Kiu', 'mu']

```python

```


###### Content provided under a Creative Commons Attribution license, CC-BY 4.0; code under BSD 3-Clause license (c)2015 L.A. Barba, Pi-Yueh Chuang.

# Joukowski Transformation

This is your [_AeroPython_](https://github.com/barbagroup/AeroPython) assignment for the second course module, titled **"Potential vortices and lift."** The first course module, "Building blocks of potential flow" (first three IPython Notebooks of the series) ended with the representation of potential flow around a 2D cylinder by means of superposing a doublet singularity and a free stream. And in the second module, you learned that adding a vortex singularity, you can get lift around the cylinder. You may ask: *is all this useful?*

Here's how it all starts getting useful. Using some simple techniques from complex-variable calculus, we can generate the flow around some airfoils starting with the flow around 2D cylinders. The trick is to use a [*conformal map*](http://en.wikipedia.org/wiki/Conformal_map) (a complex function that preserves angles) to move from the cylinder plane to the airfoil plane.

Let's explore this classic method!

## Introduction

You learned how to construct potential flow over a cylinder via superposition of a free stream and a [doublet](03_Lesson03_doublet.ipynb) singularity. You also learned that adding a vortex can generate [lift on the cylinder](06_Lesson06_vortexLift.ipynb) But what's the big deal? Why is this important to us? What can we do with this potential cylinder flow?

Back in the years when computers were not available, fluid dynamicists and mathematicians used a powerful toolâ€”complex analysisâ€”to study potential flows without directly solving partial-differential governing equations. With the magic of complex analysis and the known solution of potential flow over a cylinder, they could easily obtain many kinds of external potential flows, including flows over several types of airfoils.

Nowadays, though, we no longer use these magical tools. But, not just out of nostalgia, it's still interesting to know the basic concept behind the magic: conformal mappings. In this assignment, we will guide you step by step to obtain potential flow over an airfoil starting with the flow over a cylinder and the famous conformal mapping called the **Joukowski transformation**. You will realize how important potential cylinder flow is in the history of aerodynamics!

Don't worry. We won't talk too much about the mathematics. And you don't have to calculate by hand as the pioneers of aerodynamics did. Just follow the steps and let Python do the heavy lifting.

---

## 1. Complex numbers in Python

Let's start with two complex planes, one defined by points $z = x + iy$, and the other defined by points $\xi = \xi_x+i\xi_y$. The Joukowski transformation takes a point in the $z$-plane and "maps" it to the $\xi$-plane by:

$$
\begin{equation}
\xi = z + \frac{c^2}{z}
\end{equation}
$$

where $c$ is a constant parameter. Before we discuss the Joukowski transformation, let's gain some practice with complex numbers in Python.

Using complex numbers, your function for the Joukowski transformation will look
very simple, and you won't have to calculate the real and imaginary parts separately.

Python (and therefore, NumPy) can deal with complex numbers right out of the box. But the imaginary number, $i=\sqrt{-1}$, is represented by `j`, *not* `i`, to avoid clashes with the common use of `i` in iterations.

If you haven't used them before, try some simple manipulations of complex numbers now. For example, enter the following in a cell block:

```Python
3 + 2j
```

Now try:

```Python
a = 3
b = 3
z = a + b * 1j
print('z = ', z)
print('The type of the variable is ', type(z))
```

### Exercise

Get familiar with complex-number operations in Python by answering the following:

1. $(2.75+3.69i)\times(8.55-6.13i)=$
2. $1.4\times e^{i5.32}=$
3. $\frac{7.51-9.15i}{4.43+9.64i}=$

Don't forget to enter your answers on [Open edX](https://openedx.seas.gwu.edu/courses/course-v1:MAE+MAE6226+2017/about).
(Notes: you will need to enter your results with a 2-digit precision after the decimal point in the auto-graded section.)

---

## 2. Shapes created by the Joukowski transformation

Start by writing a Python function that takes `z` and `c` as parameters, and returns  the Joukowski transformation of `z`.

We can generate several patterns using Joukowski transforms. Use your Python function to perform the calculations described below and answer the questions.

Just take $c=1$ here for simplicity.

1. On the $z$-plane, place a circle with radius $R$ larger than $c=1$, say $R=1.5$, centered at the origin. What does the shape look like when mapped on the $\xi$-plane?
    1. circle
    2. ellipse
    3. symmetric airfoil
    4. cambered airfoil
2. Now, place a circle on the $z$-plane with radius such that $c \lt R \lt 2c$ (e.g., $c=1$; $R=1.2$), centered at $(x_c,y_c)=(c-R, 0)$. What does the shape look like on the $\xi$-plane?
    1. circle
    2. ellipse
    3. symmetric airfoil
    4. cambered airfoil
3. Put a circle at $(x_c, y_c)=(-\Delta x, \Delta y)$, where $\Delta x$ and $\Delta y$ are small positive numbers, e.g. $\Delta x=0.1$ and $\Delta y=0.1$. The radius $R$ of the circle is $R = \sqrt{(c - x_c)^2 + y_c^2}$. What does the shape on $\xi$-plane look like?
    1. circle
    2. ellipse
    3. symmetric airfoil
    4. cambered airfoil
4. Consider the case when you obtain a symmetric airfoil. In polar coordinates, $(\theta, r=R)$, which point on the circle corresponds to the trailing edge of the airfoil?
    * $\theta=$?

Don't forget to enter your answers on [Open edX](https://openedx.seas.gwu.edu/courses/course-v1:MAE+MAE6226+2017/about).
(Notes: you will need to enter your results with a 2-digit precision after the decimal point in the auto-graded section.)

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

```python
R = 1.2
c = 1
x = np.linspace(-10,10,100)
```

```python
y = np.sqrt(R**2 - (x-c+R)**2)
y1 = -y
```

    D:\Anaconda\envs\new3\lib\site-packages\ipykernel_launcher.py:1: RuntimeWarning: invalid value encountered in sqrt
      """Entry point for launching an IPython kernel.

```python
xc = -0.1
yc = 0.1
R = np.sqrt((c-xc)**2 + yc**2)
y = np.sqrt(R**2 - (x-xc)**2) + yc
y1 = -y
```

    D:\Anaconda\envs\new3\lib\site-packages\ipykernel_launcher.py:4: RuntimeWarning: invalid value encountered in sqrt
      after removing the cwd from sys.path.

```python
z = x+y*1j
z1 = x+y1*1j
xi = z + c**2 / z
xi1=z1+c**2 / z1
```

    D:\Anaconda\envs\new3\lib\site-packages\ipykernel_launcher.py:3: RuntimeWarning: invalid value encountered in true_divide
      This is separate from the ipykernel package so we can avoid doing imports until
    D:\Anaconda\envs\new3\lib\site-packages\ipykernel_launcher.py:4: RuntimeWarning: invalid value encountered in true_divide
      after removing the cwd from sys.path.

```python
plt.plot(np.real(xi[:]),np.imag(xi[:]))
plt.plot(np.real(xi1[:]),np.imag(xi1[:]))
```

---

## 3. Grid points on the $z$-plane using polar coordinates

By the Joukowski transformation, a point on the $z$-plane corresponds to a point on the $\xi$-plane. As you saw in the previous section, this transformation sometimes gives a shape that looks quite a lot like an airfoil. _What's the use of this?_

It turns out that in complex analysis, when you have a solution to Laplace's equation on the complex plane and apply a [conformal mapping](http://en.wikipedia.org/wiki/Conformal_map), the transformed function is still a solution of the Laplace equation.

This means that we can map the potential function and the streamlines of flow over a cylinder on the $z$-plane to the $\xi$-plane, and obtain the streamlines of flow over an airfoil. The stream function around the airfoil will be given by:

$$
\begin{equation}
\psi(\xi_x, \xi_y) = \psi(\xi_x(x, y), \xi_y(x, y))
\end{equation}
$$

where the complex coordinates of $\xi$, $\xi_x$ and $\xi_y$, are obtained from the Joukowski transformation of $z=x+iy$.

In this exercise, you will obtain the flow over a symmetric Joukowski airfoil with zero and non-zero angle of attack. We obtain the shape of our target airfoil by placing a cylinder centered at $(x_c, y_c)=(-0.15, 0)$ with radius $R = 1.15$ and parameter $c=1$. You'll reach the goal step-by-step with problems (3) to (6).

First, build a set of grid points on the $z$-plane and see how these points look like on the $\xi$-plane. Use polar coordinates to build your grid on the $z$-plane. If you place grid points inside the cylinder, they will end up outside the airfoil after a Joukowski transformation (try it for yourself!). This is a problem. The streamlines inside the cylinder are not physical anyway, so we'll just ignore this region.

### Exercise

1. Build a grid in polar coordinates in the $z$-plane. Use $N_r = 100$ points in the radial direction, within the range $R \le r \le 5$, and $N_\theta = 145$ points in the axial direction, within the range $0 \le \theta \le 2\pi$.
2. Use the Joukowski transformation to map the grid into the $\xi$-plane.
3. Plot the grid points in both planes using the function `pyplot.scatter()`.

You should obtain figures that look like this:

```python
import numpy as np
from matplotlib import pyplot as plt
Nr=100
Ntheta=145
R=1.15
c=1
xc=-0.15
yc=0
r=np.linspace(R,5,Nr)
theta=np.linspace(0,2*np.pi,Ntheta)
r_mesh,theta_mesh=np.meshgrid(r,theta)
# z plane
zx=r_mesh*np.cos(theta_mesh)+xc
zy=r_mesh*np.sin(theta_mesh)

fg, (ax1,ax2)=plt.subplots(1,2,figsize=(10,5))
ax1.figsize=(5,5)
ax1.scatter(0,0,s=1,c='red')
ax1.scatter(zx,zy,s=1,color='#000000')

z=zx+1j*zy
xi=z+c**2/z
xix=np.real(xi)
xiy=np.imag(xi)
ax2.figsize=(5,5)
ax2.scatter(xix,xiy,s=1,color='#000000')
```

---

## 4. Flow over a symmetric Joukowski airfoil with zero angle of attack

### Stream function and streamlines

Now, you will evaluate potential flow over the cylinder on the $z$-plane. As mentioned above, $\psi(\xi) = \psi(\xi(z))$. This means that, after evaluating the stream function at a certain point on the $z$-plane, the corresponding point on the $\xi$-plane has the same value of the stream function. We can plot the streamlines on both planes using the function `pyplot.contour()`, because the stream function is a scalar function.

Use $1$ as the free stream velocity, i.e., $U_{\infty}=1$. You have to calculate the strength of the doublet first in order to have a cylinder with radius $R=1.15$.

You should obtain streamline patterns similar to those shown in the following figures.

```python
U=1
k=1.15**2*2*np.pi
zpsi=U*zy - k / (2 * np.pi) * (zy - yc) / ((zx - xc)**2 + (zy - yc)**2)

xiyc = 0
xixc = xc + 1/xc

xipsi=U*xiy - k / (2 * np.pi) * (xiy - xiyc) / ((xix - xixc)**2 + (xiy - xiyc)**2)

fg,(ax1,ax2)=plt.subplots(1,2,figsize=(10,5))

z0=-R-0.15
xi0=z0 + 1/z0
ax1.contour(zx,zy,zpsi,levels=50,colors='gray',linestyles='solid')
ax1.scatter(np.real(z0),np.imag(z0),c='red')
ax2.contour(xix,xiy,zpsi,levels=50,colors='gray',linestyles='solid')
ax2.scatter(np.real(xi0),np.imag(xi0),c='red')
```

### Velocity vector and pressure coefficient

To get the pressure coefficients, we need to compute the velocity fields.
We can simply obtain the velocity field on the $z$-plane using the coordinates of the grid points on the $z$-plane.
But can we just say that the velocity at the corresponding points on the $\xi$-plane is the same as that on the $z$-plane, just like what we did for the stream function?
_The answer is no._

The values of the stream function remain the same on the original and mapped points because the stream function is a scalar solution of Laplace's equation!

However, the velocity is a vector and is not a solution of Laplace's equation.
When the coordinate system changes by the conformal map, the values of the vector in the new system are different.

Now, let's go back to our problem: the $z$-plane and the $\xi$-plane are two different coordinate systems.
The velocity at a specific point on the $z$-plane is not the same as that at the corresponding point on the $\xi$-plane.
Some manipulation must be done.

We recall that the velocity potential, $\phi$, is also a solution of Laplace's equation; therefore, the potential values remain the same at a point under the conformal transformation.
The same applies to the complex potential, $F(z) = F(\xi(z)) = \phi + i \psi$.

The complex velocity in the $z$-plane is defined by:

$$
\begin{equation}
W_z = \frac{dF}{dz} = u_z - i v_z
\end{equation}
$$

where $u_z = \frac{\partial \psi}{\partial y}$ and $v_z = -\frac{\partial \psi}{\partial x}$.

We can obtain the complex velocity in the $\xi$-plane, $W_\xi = u_\xi - i v_\xi$, by applying a chain rule:

$$
\begin{equation}
W_\xi = \frac{dF}{d\xi} = \frac{dF}{dz} \times \frac{dz}{d\xi} = \frac{dF}{dz} / \frac{d\xi}{dz} = W_z / \frac{d\xi}{dz}
\end{equation}
$$

where

$$
\begin{equation}
\frac{d\xi}{dz} = \frac{d\left(z + \frac{c^2}{z}\right)}{dz} = 1 - \left(\frac{c}{z}\right)^2
\end{equation}
$$

Once we have the velocity fields, we can get the pressure coefficient on each plane.

The velocity field on each plane should look like this (using the function `pyplot.quiver()`):

The pressure coefficient on each plane should look like this (using the function `pyplot.contourf()`):

### Exercise

* Write the Python code to obtain the figures shown above (streamlines, velocity field, and pressure coefficient in both planes).
* Answer the following questions:
    1. What is the strength of the doublet?
    2. What is the velocity at the $62^\text{nd}$ point on the airfoil surface? (We assume that the trailing edge is the $1^\text{st}$ point and that we index the points counterclockwise.)
    3. What is the minimum value of the pressure coefficient on the airfoil surface?

Don't forget to enter your answers on [Open edX](https://openedx.seas.gwu.edu/courses/course-v1:MAE+MAE6226+2017/about).
(Notes: you will need to enter your results with a 2-digit precision after the decimal point in the auto-graded section.)

```python
def get_velocity_doublet(strength, xd, yd, X, Y):
    """
    Returns the velocity field generated by a doublet.

    Parameters
    ----------
    strength: float
        Strength of the doublet.
    xd: float
        x-coordinate of the doublet.
    yd: float
        y-coordinate of the doublet.
    X: 2D Numpy array of floats
        x-coordinate of the mesh points.
    Y: 2D Numpy array of floats
        y-coordinate of the mesh points.

    Returns
    -------
    u: 2D Numpy array of floats
        x-component of the velocity vector field.
    v: 2D Numpy array of floats
        y-component of the velocity vector field.
    """
    u = (- strength / (2 * math.pi) *
         ((X - xd)**2 - (Y - yd)**2) /
         ((X - xd)**2 + (Y - yd)**2)**2)
    v = (- strength / (2 * math.pi) *
         2 * (X - xd) * (Y - yd) /
         ((X - xd)**2 + (Y - yd)**2)**2)

    return u, v
```

```python
zu= 2 - np.ones((Ntheta,Nr)) - k / (2 * np.pi) * (zx**2 - zy**2) / (zx**2 + zy**2)**2
zv= - k / (2 * np.pi) * (2 * zx * zy) / (zx**2 + zy**2)**2
fg, (ax1,ax2) = plt.subplots(1,2,figsize=(10,5))

ax1.set_xlim((-3,3))
ax1.set_ylim((-3,3))
step = (slice(None,None,10))
zu2 = zu / 10
zv2 = zv / 1000000
ax1.quiver(zx,zy,zu2,zv2,scale=10)
ax2.quiver(zx,zy,zu2,zv2,scale=10)

```

```python

```

```python

```

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

X, Y = np.meshgrid(np.arange(0, 2 * np.pi, .2), np.arange(0, 2 * np.pi, .2))
U = np.cos(X)
V = np.sin(Y)

fig1, ax1 = plt.subplots()
ax1.set_title('Arrows scale with plot width, not view')
Q = ax1.quiver(X, Y, U, V, units='width')

```

---

## 5. Flow over a symmetric Joukowski airfoil with non-zero angle of attack, without circulation

Now, we want to place the airfoil at an angle of attack (AoA) with respect to the free stream.
Of course we can use flow over a cylinder and the Joukowski transformation to do this.
*But how?*
Can we superpose a free stream with non-zero inlet angle and a doublet to obtain what we want?
Actually, we can't.
If we do so, we won't be able to obtain a closed streamline, like we did in the previous case (in which a closed circular streamline can be taken as a cylinder surface).

A way to achieve a uniform flow with an inlet angle is by simply rotating the complex plane $z$ to get a new complex plane $z'$, where the $x'$-axis (i.e., real part of $z'$) is parallel to the free-stream direction, with its origin located at the center of the cylinder $\left(x_c, y_c\right)$, as shown in the figure below.

The relationship between the $z'$-plane and the $z$-plane is:

$$
\begin{equation}
z'=\left[ z-(x_c+iy_c) \right]e^{-i\times AoA}
\end{equation}
$$

i.e.,

$$
\begin{equation}
\left\{
\begin{array}{lcr}
x' & = & (x-x_c)\cos(AoA) + (y-y_c)\sin(AoA) \\
y' & = & - (x-x_c)\sin(AoA) + (y-y_c)\cos(AoA)
\end{array}
\right.
\end{equation}
$$

Now, we can obtain the flow over a cylinder on the new plane $z'$ by adding a free stream with **zero inlet angle** to a doublet **centered at the origin**.
Then, we can obtain the flow on the $z$-plane and, finally, on the $\xi$-plane.
Again, the stream function remains the same at the same point under the three coordinate systems ($z'$, $z$, and $\xi$).

You should get streamlines in the $z$-plane and in the $\xi$-plane that look like this:

The velocity vector need to be rotated back from the $z'$-plane to the $z$-plane.

$$
\begin{equation}
W_z = \frac{dF}{dz} = \frac{dF}{dz'} \times \frac{dz'}{dz} = W_{z'} e^{-i \times \text{AoA}}
\end{equation}
$$

Once you have the velocity in the $z$-plane, you can use what you learned in the previous exercise to get the velocity in the $\xi$-plane.

You should get the velocity and pressure coefficient (in the $z$ and $\xi$ planes) that look like this:

### Exercise

* Write the Python code to obtain the figures shown above (streamlines, velocity field, and pressure coefficient in the $z$ and $\xi$ planes). Set the angle of attack to $\text{AoA} = 20^\circ$.
* Answer the following questions:
    1. Do you think the flow over the airfoil looks reasonable? Explain why.
    2. What are the index of the stagnation points on the airfoil? (We assume that the trailing edge is the $1^\text{st}$ point and that we index the points counterclockwise.)
    3. How much is the lift? The lift, $L=-\oint p \times \sin{\theta} dA$, is perpendicular to the free-stream direction.
    4. How much is the drag? The drag, $D=-\oint p \times \cos{\theta} dA$, is in the direction of the free-stream.
    5. What is the velocity at the $50^\text{th}$ point on the airfoil?
    6. What is the pressure coefficient at the $75^\text{th}$ point on the airfoil?

Don't forget to enter your answers on [Open edX](https://openedx.seas.gwu.edu/courses/course-v1:MAE+MAE6226+2017/about).
(Notes: you will need to enter your results with a 2-digit precision after the decimal point in the auto-graded section.)

---

## 6. Flow over a symmetric Joukowski airfoil with non-zero angle of attack, with circulation

The flow obtained in the last exercise is not physical.
What we need is a **vortex**.
In [Lesson 6](06_Lesson06_vortexLift.ipynb), we saw that, by adding a vortex (i.e., circulation) to a potential flow over a cylinder, we can modify the position of the stagnation points and generate lift.

To make the flow more physical, we need to satisfy the [Kutta condition](http://en.wikipedia.org/wiki/Kutta_condition),
>"A body with a sharp trailing edge which is moving through a fluid will create about itself a circulation of sufficient strength to hold the rear stagnation point at the trailing edge."

This condition helps us in choosing the strength of the vortex to add.
The strength of the vortex should be calculated so that the rear-stagnation point on the cylinder moves from $\theta=\text{AoA}$ to $\theta=0^\circ$ in the $z$-plane.
You should be able to calculate the strength with what you learned in [Lesson 6](06_Lesson06_vortexLift.ipynb).

The streamlines, velocity, and pressure coefficient in the $z$-plane and in the $\xi$-plane should look like this:

### Exercise

* Write the Python code to obtain the figures shown above (streamlines, velocity field, and pressure coefficient in the $z$ and $\xi$ planes). Set the angle of attack to $\text{AoA} = 20^\circ$.
* Answer the following questions:
    1. What is the strength of the vortex?
    2. How much is the lift and does it agree with the Kutta-Joukowski theorem? The lift, $L=-\oint p \times \sin{\theta} dA$, is perpendicular to the free-stream direction.
    3. How much is the drag? The drag, $D=-\oint p \times \cos{\theta} dA$, is in the direction of the free-stream.
    4. What are the index of the stagnation points on the airfoil? (We assume that the trailing edge is the $1^\text{st}$ point and that we index the points counterclockwise.)
    5. What is the velocity at the $92^\text{nd}$ point on the airfoil?
    6. What is the pressure coefficient at the $111^\text{th}$ point on the airfoil?
    7. Explain what is going on with the pressure coefficient at the trailing edge of the airfoil.

Don't forget to enter your answers on [Open edX](https://openedx.seas.gwu.edu/courses/course-v1:MAE+MAE6226+2017/about).
(Notes: you will need to enter your results with a 2-digit precision after the decimal point in the auto-graded section.)

---

```python
from IPython.core.display import HTML
def css_styling(filepath):
    styles = open(filepath, 'r').read()
    return HTML(styles)
css_styling('../styles/custom.css')
```

<link href='http://fonts.googleapis.com/css?family=Fenix' rel='stylesheet' type='text/css'>
<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=Source+Code+Pro:300,400' rel='stylesheet' type='text/css'>


```python
from logicqubit.logic import *
from cmath import *
import numpy as np
import sympy as sp
from scipy.linalg import expm, sinm, cosm, logm, sqrtm
```

```python
def qft(qr):
    for i in range(len(qr)):
        for j in range(i):
            qr[i].CU1(qr[j], pi/float(2**(i-j)))
        qr[i].H()

def iqft(qr):
    for i in range(len(qr)):
        for j in range(i):
            qr[i].CU1(qr[j], -pi/float(2**(i-j)))
        qr[i].H()

def swap(s1, s2):
    s2.CX(s1)
    s1.CX(s2)
    s2.CX(s1)

def ancilla_rotation(k, C, N, t):
    if k == 0:
        k = 3
    theta = 2*asin(C*N*t / (2*pi*k))
    return theta
```

```python
# Ax=b, x=3/8=0.375 e -1/8=-0.125

#A = np.matrix([[1.5,0.5],[0.5,1.5]])
A = np.matrix([[3,1],[1,3]])
t0 = 2*pi
#t0 = 0.358166*pi
register_size = 3

# Set C to be the smallest eigenvalue that can be represented by the
C = 2*pi / (2**register_size * t0)

U = np.matrix(expm(1j*A*t0/2))
U_A_div2 = np.matrix(expm(1j*A*t0/2))
U_A_div4 = np.matrix(expm(1j*A*t0/4))
U_A_div8 = np.matrix(expm(1j*A*t0/8))
U_A_inv_div2 = np.matrix(expm(-1j*A*t0/2))
U_A_inv_div4 = np.matrix(expm(-1j*A*t0/4))
U_A_inv_div8 = np.matrix(expm(-1j*A*t0/8))
print(U_A_div2)
```

    [[1.+4.4408921e-16j 0.+0.0000000e+00j]
     [0.+0.0000000e+00j 1.+4.4408921e-16j]]

```python
b = np.array([1,0])
x = np.linalg.solve(A, b)
print(x)
```

    [ 0.375 -0.125]

```python
logicQuBit  = LogicQuBit(5)

m = 1

a = Qubit()
c1 = Qubit()
c2 = Qubit()
c3 = Qubit()
b = Qubit()

c1.H()
c2.H()
c3.H()

#logicQuBit.PrintState() # a,c1,c2,c3,b
#b.X()
#b.H()

# phase estimation
b.CU(c3, U_A_div8)
b.CU(c2, U_A_div4)
b.CU(c1, U_A_div2)

#b.CU(c3, U_A_div8)
#c3.CU(c2, U_A_div4)
#b.CU(c1, U_A_div2)

b.CU(c1, U)

b.CU(c2, U)
b.CU(c2, U)

b.CU(c3, U)
b.CU(c3, U)
b.CU(c3, U)

iqft([c1,c2,c3])

#logicQuBit.PrintState()

swap(c1,c2)

#a.CRY(c1, ancilla_rotation(1,C,register_size,t0))
#a.CRY(c2, ancilla_rotation(2,C,register_size,t0))
#a.CRY(c3, ancilla_rotation(3,C,register_size,t0))

a.CRY(c1, 4*pi/(2**(m-1)))
a.CRY(c2, 2*pi/(2**(m-1)))
a.CRY(c3, 1*pi/(2**(m-1)))

swap(c1,c2)

qft([c1,c2,c3])

# reverse phase estimation
b.CU(c1, U_A_inv_div2)
b.CU(c2, U_A_inv_div4)
b.CU(c3, U_A_inv_div8)

#b.CU(c1, U_A_div2.conj().T)
#c3.CU(c2, U_A_div4.conj().T)
#b.CU(c3, U_A_div8.conj().T)

c1.H()
c2.H()
c3.H()
#logicQuBit.PrintState()
```

```python
logicQuBit.getDictPsi()
```

    {'00000': (0.2500000000000002+3.2131722973884535e-16j),
     '00001': (-0.2500000000000003-3.2550159356878817e-16j),
     '00010': (-7.321690718642148e-16+0.24999999999999983j),
     '00011': (-7.093930911542993e-17-0.25000000000000067j),
     '00100': (-1.1865859033808e-16-1.5047887589474974e-16j),
     '00101': (1.3947527204980168e-16+1.377916140651098e-16j),
     '00110': (3.478463511321582e-16-9.11918847638287e-17j),
     '00111': (7.920201690584587e-17+1.358190860390403e-16j),
     '01000': (3.6672643306195197e-16-0.25000000000000017j),
     '01001': (-3.8635258879549916e-16+0.2500000000000002j),
     '01010': (0.2500000000000002+4.641375235333216e-16j),
     '01011': (-0.2500000000000002-3.4736685314507324e-16j),
     '01100': (5.341896931628643e-17+1.440331139973599e-16j),
     '01101': (-9.267128078338084e-17-1.4928834991657525e-16j),
     '01110': (-3.6825450891360043e-16+3.0221235648215795e-17j),
     '01111': (-2.229607773127381e-17-1.5799550988141224e-16j),
     '10000': (0.1249999999999997+0.30177669529663737j),
     '10001': (0.12499999999999983+0.30177669529663737j),
     '10010': (-6.138599005560863e-17-8.902191178233562e-17j),
     '10011': (-1.0432680771909032e-17+5.312328226519268e-17j),
     '10100': (0.3017766952966376-0.12499999999999997j),
     '10101': (0.30177669529663725-0.12499999999999967j),
     '10110': (-1.6276443080382638e-16-1.1159322553555473e-16j),
     '10111': (1.512540609329286e-16+1.602141626950017e-16j),
     '11000': (0.1250000000000003-0.05177669529663665j),
     '11001': (0.1250000000000001-0.051776695296636414j),
     '11010': (-2.3186449980152103e-16-9.676033064046987e-17j),
     '11011': (2.503711036136993e-16+1.0218867159906065e-16j),
     '11100': (-0.05177669529663725-0.1250000000000003j),
     '11101': (-0.05177669529663658-0.12500000000000014j),
     '11110': (-7.214302789729803e-17+3.2662796249987326e-16j),
     '11111': (9.395712073440904e-17-3.2550841057850427e-16j)}

```python
#res_a = logicQuBit.Measure_One(a)
#print(res_a)
res = logicQuBit.Measure([b], True)
logicQuBit.Plot()
print(res)
```

```python
def Text2List(table):
    list = [int(i, base=2) for i in table]
    size = len(table[0])
    tmp = sorted(list, key=int, reverse=False)  # values in ascending order
    result = [[int(bin(j)[2:].zfill(size)[i]) for i in range(size)] for j in tmp]
    return result

def BinList(n):
    blist = []
    for i in iter(range(2 ** n)):
        b = bin(i)[2:].zfill(n)  # value in binary, ex: i=1, n=4 -> '0001'
        blist.append(b)
    return Text2List(blist)

#size=3
#blist = [value for i,value in enumerate(BinList(size)) if res[i]>0.1]
#results = [sum([value/2**(i+1) for i,value in enumerate(j)]) for j in blist]
#print(results)
```

```python
# todos possíveis valores
size = 3
blist = [[i >> bl & 0x1 for bl in range(size)][::-1] for i in range(2**size)]
results = [2*pi*sum([value/2**(i+1) for i,value in enumerate(j)]) for j in blist]
results
```

    [0.0,
     0.7853981633974483,
     1.5707963267948966,
     2.356194490192345,
     3.141592653589793,
     3.9269908169872414,
     4.71238898038469,
     5.497787143782138]

```python

```


# Test of particle pusher (Boris method) in electromagnetic field
The Boris method for particle positions and velocities reads as follows:

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

As an example the method is applied to some particles in a constant magnetic field $\textbf{B}_0=B_{0z}\textbf{e}_z$ and electric field $\textbf{E}_0=E_{0x}\textbf{e}_x+E_{0y}\textbf{e}_y$.

This leads to an $\textbf{E}\times\textbf{B}$-drift.

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

# ... parameters
q = -1                          # ... charge of particle
m = 1                           # ... mass of particle
dt = 0.05                       # ... time step
Nt = 8000                       # ... number of time steps
T = dt*Nt                       # ... simulation time
dt = T/Nt                       # ... time step
Omega = q*B0z/m                 # ... cyclotron frequency
B0 = np.array([0,0,1.0])        # ... constant background magnetic field (for ExB-drift)
E0 = np.array([0.05,0.05,0])    # ... constant background electric field (for ExB-drift)
Np = 3                          # ... number of particles
Lz = 600                        # ... length of z-domain

B0z = 1.0                       # ... field at x = 0 for magnetic mirror
xi = 0.862                      # ... inhomogeneity factor of magnetic mirror
# ...

# ... define magnetic mirror
def Bfield3d3v(x,B0z,xi):

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

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

def Bfield1d3v(z,v,B0z,xi):

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

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

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

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

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

# ... set initial values for ExB-particles
particlesExB[0,0,3:6] = np.array([0.,0.14,2.])
particlesExB[0,1,3:6] = np.array([0.,1.,2.])
particlesExB[0,2,3:6] = np.array([1.,1.,2.])

rl1 = np.abs(m*particlesExB[0,0,4]/(q*np.linalg.norm(B0)))
rl2 = np.abs(m*particlesExB[0,1,4]/(q*np.linalg.norm(B0)))

particlesExB[0,0,0] = 4.
particlesExB[0,1,0] = -4.
particlesExB[0,2,0] = 0.
# ...

# ... set initial values for magnetic mirror particles
particles3d3v[0,0,3:6] = np.array([0.117,0,0.0795])
particles3d3v[0,1,3:6] = np.array([0.167,0,0.0595])

particles3d3v[0,0,0:3] = np.array([0,np.abs(particles3d3v[0,0,3]*m/(q*B0[2])),0])
particles3d3v[0,1,0:3] = np.array([0,np.abs(particles3d3v[0,1,3]*m/(q*B0[2])),0])

particles1d3v[0,0,1:4] = np.array([0.117,0,0.0795])
particles1d3v[0,1,1:4] = np.array([0.167,0,0.0595])

particles1d3v[0,0,0] = 0.0
particles1d3v[0,1,0] = -1.6
# ...

# ... analytical solution for ExB-drift
v0 = deepcopy(particlesExB[0,:,3:6])
x0 = deepcopy(particlesExB[0,:,0:3])

def analytical(x0,v0,t):

    x = x0[0] + 1/Omega*(E0[0]/B0z + v0[1])*(1 - np.cos(Omega*t)) + 1/Omega*(v0[0] - E0[1]/B0z)*np.sin(Omega*t) + E0[1]/B0z*t
    y = x0[1] + 1/Omega*(E0[1]/B0z - v0[0])*(1 - np.cos(Omega*t)) + 1/Omega*(v0[1] + E0[0]/B0z)*np.sin(Omega*t) - E0[0]/B0z*t
    z = x0[2] + v0[2]*t

    return np.array([x,y,z])
# ...

# ... fields at particle positions
BpExB = np.zeros((3,3))
EpExB = np.zeros((3,3))

Bpmirror = np.zeros((2,3))
Epmirror = np.zeros((2,3))
# ...

# ... analytical solution for magnetic mirror
z0mirror = deepcopy(particles1d3v[0,0,0])
v0mirror = deepcopy(particles1d3v[0,0,1:4])

def zana1(t):

    Bz = B0z*(1 + xi*z0mirror**2)
    Omega = q*Bz/m
    rho = -np.cross(v0mirror,np.array([0,0,1]))/Omega

    omegab = v0mirror[0]*np.sqrt(xi/np.sqrt((1 + xi*z0mirror**2)**2 + (rho[0]*B0z*xi*z0mirror)**2 + (rho[1]*B0z*xi*z0mirror)**2))

    return z0mirror*np.cos(omegab*t) + v0mirror[2]/omegab*np.sin(omegab*t)

# ...

# ... define Boris method
def pushBoris3d3v(particles,q,m,dt,B,E):

    qprime = dt*q/(2*m)
    h = qprime*B
    s = 2*h/(1 + np.linalg.norm(h,axis = 1)**2)[:,None]
    u = particles[:,3:6] + qprime*E
    uprime = u + np.cross(u + np.cross(u,h),s)
    vnew = uprime + qprime*E
    xnew = particles[:,0:3] + dt*vnew

    return xnew,vnew

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

    qprime = dt*q/(2*m)
    h = qprime*B
    s = 2*h/(1 + np.linalg.norm(h,axis = 1)**2)[:,None]
    u = particles[:,1:4] + qprime*E
    uprime = u + np.cross(u + np.cross(u,h),s)
    vnew = uprime + qprime*E
    znew = particles[:,0] + dt*vnew[:,2]

    return znew,vnew
# ...

# ... initialize velocities by pushing velocities back by -dt/2
particlesExB[0,:,3:6] = pushBoris3d3v(particlesExB[0],q,m,-dt/2,B0 + BpExB,E0 + EpExB)[1]
particles3d3v[0,:,3:6] = pushBoris3d3v(particles3d3v[0],q,m,-dt/2,Bfield3d3v(particles3d3v[0,:,0:3],B0z,xi),Epmirror)[1]
particles1d3v[0,:,1:4] = pushBoris1d3v(particles1d3v[0],q,m,-dt/2,Bfield1d3v(particles1d3v[0,:,0],particles1d3v[0,:,1:4],B0z,xi),Epmirror)[1]
# ...

# ... time integration
for n in range(0,Nt):
    particlesExB[n+1,:,0:3],particlesExB[n+1,:,3:6] = pushBoris3d3v(particlesExB[n],q,m,dt,B0 + BpExB,E0 + EpExB)
    particles3d3v[n+1,:,0:3],particles3d3v[n+1,:,3:6] = pushBoris3d3v(particles3d3v[n],q,m,dt,Bfield3d3v(particles3d3v[n,:,0:3],B0z,xi),Epmirror)
    particles1d3v[n+1,:,0],particles1d3v[n+1,:,1:4] = pushBoris1d3v(particles1d3v[n],q,m,dt,Bfield1d3v(particles1d3v[n,:,0],particles1d3v[n,:,1:4],B0z,xi),Epmirror)
# ...

# ... analytical orbits
test0 = analytical(x0[0],v0[0],tn)
test1 = analytical(x0[1],v0[1],tn)
test2 = analytical(x0[2],v0[2],tn)
# ...

# ... plot results
f1 = plt.figure()
f1.set_figheight(12)
f1.set_figwidth(16)
ax = f1.add_subplot(111,projection = '3d')
ax.plot(particlesExB[:,0,0],particlesExB[:,0,1],particlesExB[:,0,2])
ax.plot(particlesExB[:,1,0],particlesExB[:,1,1],particlesExB[:,1,2])
ax.plot(particlesExB[:,2,0],particlesExB[:,2,1],particlesExB[:,2,2])
ax.plot(test0[0,:],test0[1,:],test0[2,:],'k--')
ax.plot(test1[0,:],test1[1,:],test1[2,:],'k--')
ax.plot(test2[0,:],test2[1,:],test2[2,:],'k--')
plt.xlabel('x')
plt.ylabel('y')
ax.set_zlabel('z')
plt.title('Verification of particle mover')
# ...
```

```python
plt.plot(tn[::200],particles1d3v[::200,0,0],'k+',label = 'Simulation1d3v')
plt.plot(tn[100::200],particles3d3v[100::200,0,2],'r+',label = 'Simulation3d3v')
plt.plot(tn,zana(tn),label = 'Theory')
plt.xlabel('t')
plt.ylabel('z')
plt.ylim((-1,1))
plt.legend()
```