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

```python
ds = xr.open_dataset("/Volumes/Alsakan/hoth_archive/old_documents_12-01-2018/NCL_SOURCE/ncl_ncarg-6.2.0/ni/src/examples/data/cdf/chi200_ud_smooth.nc", decode_times=False)
```

```python
ds
```

    <xarray.Dataset>
    Dimensions:  (lat: 64, lon: 128, time: 182)
    Coordinates:
      * lat      (lat) float32 -87.8638 -85.09653 -82.31291 ... 85.09653 87.8638
      * lon      (lon) float32 0.0 2.8125 5.625 8.4375 ... 351.5625 354.375 357.1875
      * time     (time) float64 0.0 1.0 2.0 3.0 4.0 ... 178.0 179.0 180.0 181.0
    Data variables:
        gw       (lat) float32 ...
        date     (time) float64 ...
        CHI      (time, lon) float32 ...
    Attributes:
        title:    Time Filtered CHI for Hovmueller Demo
        source:   /fs/cgd/home0/shea/ncld/hovmu/chi2chiFilternc.ncl

```python
# same as in indices_nino_1.ipynb
def printVarSummary(da):
    n = da.name
    if hasattr(da, 'units'):
        u = da.units
    else:
        u = None
    if hasattr(da, 'long_name'):
        lng = da.long_name
    else:
        lng = "NO LONG NAME"
    c = []
    for i in da.dims:
        if i in da.coords:
            c.append(f"\t dim: {i} with coordinate of shape {da.coords[i].shape}")
        else:
            c.append(f"\t dim: {i} with no coordinate variable")
    mn = da.min().values.item()
    mx = da.max().values.item()
    print(f"{n} ({u})\n  {lng}\n  minimum: {mn}\n  maximum: {mx}")
    for i in c:
        print(i)

```

```python
scale = 1.e6                                     # create scale factor
tmp   = ds['CHI']                                # get chi
tmp   = tmp/scale                                # scale for convenience
printVarSummary(tmp)
#===============================
# filter over time dimension
#===============================
wgt  = np.array([1., 3., 5., 6., 5., 3., 1.])            # weights
wgt  = wgt/sum(wgt)                              # normalize wgts
print(wgt)
wgt = xr.DataArray(wgt, dims=['window'])

#===============================
# return variable to original dimension  order for plotting
#===============================
#chov      = yhov(time|:,lon|:)                   # move to 2D array for plot
#chov&time = tmp&time                                # set coordinate
#chov&lon  = tmp&lon
#copy_VarAtts(tmp,chov)                           # copy attributes

```

    CHI (None)
      NO LONG NAME
      minimum: -8.177284240722656
      maximum: 10.425189018249512
    	 dim: time with coordinate of shape (182,)
    	 dim: lon with coordinate of shape (128,)
    [0.04166667 0.125      0.20833333 0.25       0.20833333 0.125
     0.04166667]
    None (None)
      NO LONG NAME
      minimum: -7.98805848757426
      maximum: 9.950583299001059
    	 dim: time with coordinate of shape (182,)
    	 dim: lon with coordinate of shape (128,)

# Filter: weighted rolling average

We are applying `wgt` to the data along the time dimension as a weighed rolling average. That essentially means that the solution at some time, `t` should be

\begin{equation}
y(t) = \sum_{i = t-(m/2)}^{i = t + (m/2)} w_i x_i
\end{equation}

where $m$ is the size of the filter (`len(wgt)`). And I'm assuming normalized weights, so $\sum_i w_i = 1$.

## methods

There are numerous ways to do this operation. I will show three of them in the next cell that all give the same answer. Really, I think these all actually boil down to even running the same underlying code, or nearly so. The first uses just xarray; it makes the rolling object that is then operated on with construct and the dot product with `wgt`. I find this method to be a little hard to write and to understand. The other two methods use numpy's convolve function. The complication is that convolve works only on 1D arrays, so we have to loop over all the longitudes. The first approach uses `apply_along_axis`  and an anonymous function (`lambda`) to do that. The second approach (commented) uses a loop over longitudes to do the same operation. The two approaches give the same answer because they do the same convolution, but the first one is faster.

The xarray version keeps the coordinates in the result, but the numpy ways both result in a plain numpy array. So I show how to copy the coordinate information from the original array to the result.

```python
%%time

#
# Apply weighed rolling mean with xarray:
#
yhov0 = tmp.rolling(time=len(wgt), center=True).construct("window").dot(wgt)
printVarSummary(yhov0)

#
# using numpy only
#
yhov_np = np.apply_along_axis(lambda m: np.convolve(m, wgt, mode='same'), axis=0, arr=tmp)

# Equivalent to this loop (but a bit faster):
# yhov_np = np.zeros(tmp.shape)
# for x in range(len(tmp.lon)):
#     yhov_np[:,x] = np.convolve(tmp[:,x].values, wgt.values, mode='same')

f"Min: {np.nanmin(yhov_np)}, Max: {np.nanmax(yhov_np)}" # Matches NCL values

# copy coordinates from tmp:
yhov = xr.DataArray(yhov_np, dims=tmp.dims, coords=tmp.coords)
yhov.name = "CHI"
yhov.attrs["long_name"] = "smoothed CHI"
printVarSummary(yhov)
```

    None (None)
      NO LONG NAME
      minimum: -7.873470683892568
      maximum: 9.543959220250448
    	 dim: time with coordinate of shape (182,)
    	 dim: lon with coordinate of shape (128,)
    CHI (None)
      smoothed CHI
      minimum: -7.873470683892568
      maximum: 9.543959220250448
    	 dim: time with coordinate of shape (182,)
    	 dim: lon with coordinate of shape (128,)
    CPU times: user 18.6 ms, sys: 1.36 ms, total: 19.9 ms
    Wall time: 18.9 ms

```python
#
# create plot (mimic the NCL one)
#
fig, ax = plt.subplots(figsize=(6,8))

ax.set_title("Pacific Region")
levels = np.arange(-10, 12, 2)
cnFillPalette        = "bwr"
ax.set_yticks([0. ,  30.,  61.,   89., 120., 150.])
ax.set_yticklabels(["DEC","JAN","FEB","MAR" ,"APR","MAY"])
ax.invert_yaxis()

meshlon, meshtime = np.meshgrid(yhov0['lon'].sel(lon=slice(100,220)).values, yhov0['time'].values, )
img = ax.contourf(meshlon, meshtime, yhov0.sel(lon=slice(100,220)), cmap=cnFillPalette, levels=levels)

fig.colorbar(img, orientation='horizontal')

fig.savefig("../../docs/assets/filters_1_py.png")
```

```python

```


# 量子近似优化算法

<em> Copyright (c) 2021 Institute for Quantum Computing, Baidu Inc. All Rights Reserved. </em>

## 概览

量子近似优化算法（quantum approximate optimization algorithm, QAOA）是可以在近期的有噪中等规模量子（noisy intermediate-scale quantum, NISQ）设备上运行且具有广泛应用前景的量子算法。QAOA 由 Edward Farhi 等人于 2014 年提出 [1]，其目的是近似地求解组合优化问题（combinatorial optimization problems）。

### 组合优化问题

在应用数学和理论计算机科学的领域中，组合优化是在一个有限的对象集中找出最优对象的一类课题。简单来说，组合优化问题是指问题的所有解是由离散变量组成的，然后在离散的解集中寻找最优解。组合优化问题涉及的范围很广，且常见于实际生活中，例如飞机航线的设计、快递物流路线的规划等。

具体来说，一个组合优化问题可以由 $n$ 个比特（bit）和 $m$ 个子句（clause）描述。每个比特是一个二进制变量，其取值为 $0$ 或 $1$，我们用 $z_j$ 表示第 $j$ 个比特的取值。因此，这 $n$ 个比特的取值可以由比特串 $z=z_1z_2\dots z_n$ 表示。每个子句都是对部分比特的一个限制条件，例如一个子句可以要求第 $2$ 个比特的取值为 $0$，或者可以要求第 $3$ 个比特和第 $5$ 个比特的取值相同，等等。对于第 $j$ 个子句，我们定义一个与之相关的函数

$$
C_j(z)=
\begin{cases}
1 & \text{如果 $n$ 个比特的取值 $z$ 满足子句 $j$ 表明的条件}\\
0 & \text{如果 $n$ 个比特的取值 $z$ 不满足子句 $j$ 表明的条件}
\end{cases}.
\tag{1}
$$

例如，如果第一个子句要求第二个比特的取值为 0，那么我们有 $C_1(z_10z_3\dots z_n)=1$ 和 $C_1(z_11z_3\dots z_n)=0$。

由公式（1）中我们对于每个子句定义的函数 $C_j$，我们可以定义该组合优化问题的目标函数（objective function）

$$
C(z)=\sum_{j=1}^m w_jC_j(z),
\tag{2}
$$

其中 $w_j$ 是第 $j$ 个子句的权重（weight）。组合优化问题就是要找到一个取值 $z$ 使得目标函数 $C(z)$ 的值最大，即

$$
\underset{z}{\operatorname{argmax}} C(z).
\tag{3}
$$

在理论计算机科学中，有一个著名的问题叫作布尔可满足性问题（Boolean satisfiability problem, SAT），它是指对于输入的一组 $n$ 个比特和 $m$ 个子句，判断是否存在一种 $n$ 个比特的取值同时满足所有 $m$ 个子句。而一个与之相关的优化问题叫作最大布尔可满足性问题（MAX-SAT），该问题是要寻找一种 $n$ 个比特的取值以同时满足尽可能多的子句。值得注意的是，上面定义组合优化问题的方式其实就是将其抽象为了 MAX-SAT，更准确地说，是 MAX-SAT 的一个推广版本，即加权 MAX-SAT（weighted MAX-SAT）。

## 量子近似优化算法

在实际生活中，许多组合优化问题都属于 NP 完全（NP-complete）问题甚至是 NP 困难（NP-hard）问题，这意味着计算机很可能无法高效地解决这样的问题。此时，一种替代方案便是寻找这类问题的近似最优解，而这样的任务通常是可以高效完成的。QAOA 就是一个可以寻找到一个组合优化问题的近似最优解的量子算法。

### 编码组合优化问题

对于上述的一个组合优化问题，有 $n$ 个比特和 $m$ 个子句。QAOA 算法将这个问题转化为了在 $n$ 个量子比特系统上的优化问题，该量子系统的每个计算基态 $|z\rangle \in \{0,1\}^n$ 对应着原问题中 $n$ 个比特的一种取值 $z$。同时，对于原问题中的第 $j$ 个子句，我们定义一个对角（diagonal）哈密顿量 $H_{C_j}$ 使其满足

$$
H_{C_j}|z\rangle = C_j(z)|z\rangle.
\tag{4}
$$

具体我们可以通过下式来构造哈密顿量 $H_{C_j}$：

$$
H_{C_j} = \sum_{z\in\{0,1\}^n} C_j(z)|z\rangle\langle z|.
\tag{5}
$$

例如，假设满足第 $j$ 个子句的取值有 $z^{(1)}$ 和 $z^{(2)}$，那么我们可以定义

$$
H_{C_j} = |z^{(1)}\rangle\langle z^{(1)}| + |z^{(2)}\rangle\langle z^{(2)}|.
\tag{6}
$$

由此，QAOA 将组合优化问题的目标函数 $C$ 编码成了 $n$ 个量子比特系统上的哈密顿量

$$
H_C = \sum_{j=1}^m w_jH_{C_j},
\tag{7}
$$

并且

$$
H_C|z\rangle = \sum_{j=1}^m w_jH_{C_j}|z\rangle = \sum_{j=1}^m w_jC_j(z)|z\rangle = C(z)|z\rangle.
\tag{8}
$$

值得注意的是，假设原问题的一个最优解是 $z_\text{opt}$，那么我们有

$$
\langle z_\text{opt}|H_C|z_\text{opt}\rangle = \langle z_\text{opt}|C(z_\text{opt})|z_\text{opt}\rangle = C(z_\text{opt})\langle z_\text{opt}|z_\text{opt}\rangle = C(z_\text{opt}).
\tag{9}
$$

因此，原组合优化问题的最优解是哈密顿量 $H_C$ 的一个拥有最大本征值（eigenvalue）的本征态（eigenstate）。此外，对于任意量子态 $|\psi\rangle$，

$$
\langle\psi|H_C|\psi\rangle \leq C(z_\text{opt}),
\tag{10}
$$

且该式取等号当且仅当 $|\psi\rangle$ 是几个最优解的叠加态。由式（9）和式（10）可以得到，

$$
\max_{|\psi\rangle} \langle\psi|H_C|\psi\rangle = C(z_\text{opt})，
\tag{11}
$$

并且寻找原组合优化问题的最优解等同于寻找哈密顿量 $H_C$ 的一个拥有最大本征值的本征态，即寻找一个量子态 $|\psi\rangle$ 使得

$$
H_C|\psi\rangle = C(z_\text{opt})|\psi\rangle.
\tag{12}
$$

找到这样一个量子态 $|\psi\rangle$ 后，它很可能并不是一个计算基态，而是几个计算基态的叠加，这其中的每个计算基态都是原组合优化问题的一个最优解。因此，我们对 $|\psi\rangle$ 进行计算基上的测量，便能得到原组合优化问题的一个最优解。

### 寻找近似最优解

尽管将要解决的组合优化问题的目标函数编码成一个量子系统的哈密顿量 $H_C$ 较为简单，但想要根据式（11）从偌大的希尔伯特空间中找到代表最优解的量子态 $|\psi\rangle$ 并不容易。为了找到这样一个量子态，我们需要借助另一个哈密顿量

$$
H_B = \sum_{j=1}^n X_j,
\tag{13}
$$

其中 $X_j$ 表示作用在第 $j$ 个量子比特上的 Pauli $X$ 门。Pauli $X$ 门的两个本征态分别是 $|+\rangle$ 和 $|-\rangle$，它们对应的本征值分别是 $1$ 和 $-1$：

$$
\begin{align}
X|+\rangle &=
\begin{bmatrix}
0&1\\1&0
\end{bmatrix}
\frac{1}{\sqrt{2}}
\begin{bmatrix}
1\\1
\end{bmatrix}
= \frac{1}{\sqrt{2}}
\begin{bmatrix}
1\\1
\end{bmatrix}
= |+\rangle,\tag{14}\\
X|-\rangle &=
\begin{bmatrix}
0&1\\1&0
\end{bmatrix}
\frac{1}{\sqrt{2}}
\begin{bmatrix}
1\\-1
\end{bmatrix}
= \frac{1}{\sqrt{2}}
\begin{bmatrix}
-1\\1
\end{bmatrix}
= -|-\rangle.\tag{15}
\end{align}
$$

因此，$H_B$ 的拥有最大本征值的本征态为

$$
|s\rangle \equiv \underbrace{|+\rangle\otimes\cdots\otimes|+\rangle}_{\text{共 $n$ 个 }|+\rangle} = |+\rangle^{\otimes n}.
\tag{16}
$$

我们将把这个量子态 $|s\rangle$ 作为算法的初始态。构造出哈密顿量 $H_C$ 和 $H_B$ 后，我们就可以开始寻找原组合优化问题的一个近似最优解了。根据哈密顿量 $H_C$ 和 $H_B$，分别定义酉变换

$$
U_C(\gamma) = e^{-i\gamma H_C}
\tag{17}
$$

和酉变换

$$
U_B(\beta) = e^{-i\beta H_B},
\tag{18}
$$

其中 $\gamma$ 和 $\beta$ 是实数，为可调节的参数。给定任意一个整数 $p\geq1$ 以及参数 $\vec{\gamma}=(\gamma_1,\dots,\gamma_p)$ 和 $\vec{\beta}=(\beta_1,\dots,\beta_p)$，我们定义

$$
|\vec{\gamma},\vec{\beta}\rangle = U_B(\beta_p)U_C(\gamma_p)\cdots U_B(\beta_1)U_C(\gamma_1)|s\rangle.
\tag{19}
$$

可以看到，整数 $p$ 表示用到的 $U_C,U_B$ 的层数，即分别将 $U_C$ 和 $U_B$ 交替地作用在初始态 $|s\rangle$ 上 $p$ 次。我们记 $F_p(\vec{\gamma},\vec{\beta})$ 为哈密顿量 $H_C$ 在式（19）的量子态下的期望值，

$$
F_p(\vec{\gamma},\vec{\beta}) = \langle\vec{\gamma},\vec{\beta}|H_C|\vec{\gamma},\vec{\beta}\rangle.
\tag{20}
$$

通过调整参数 $\vec{\gamma},\vec{\beta}$，我们可以得到

$$
M_p = \max_{\vec{\gamma},\vec{\beta}} F_p(\vec{\gamma},\vec{\beta})
\tag{21}
$$

作为给定层数 $p$ 下的近似最优解。至此，我们将寻找量子态 $|\psi\rangle$ 的问题转变为了搜寻参数 $\vec{\gamma},\vec{\beta}$ 的问题。值得注意的是，$p$ 层酉变换 $U_C,U_B$ 的表达能力强于 $p-1$ 层的表达能力，因此

$$
M_p \geq M_{p-1}.
\tag{22}
$$

事实上，当 $p$ 足够大时，

$$
\lim_{p\to\infty} M_p = \max_z C(z).
\tag{23}
$$

### 解码量子答案

在上一段中，尽管我们将寻找量子态 $|\psi\rangle$ 转变为了寻找参数化量子态 $|\vec{\gamma},\vec{\beta}\rangle$ 以方便我们的搜索，但同时我们也缩小了搜索的空间，也就是说，在给定层数 $p$ 的情况下，可能并不存在参数 $\vec{\gamma},\vec{\beta}$ 使得 $F_p(\vec{\gamma},\vec{\beta}) = C(z_\text{opt})$。假设参数 $\vec{\gamma}^*$ 和 $\vec{\beta}^*$ 使得 $F_p$ 最大，即 $F_p(\vec{\gamma}^*,\vec{\beta}^*) = M_p$，那么在理想情况下量子态 $|\vec{\gamma}^*,\vec{\beta}^*\rangle$ 包含了最优解的信息。但要注意的是，这里只使用了 $p$ 层，因此很可能 $M_p < C(z_\text{opt})$。因此，一般情况下 $|\vec{\gamma}^*,\vec{\beta}^*\rangle$ 只包含了近似最优解的信息。进一步地，我们假设量子态 $|\vec{\gamma}^*,\vec{\beta}^*\rangle$ 是 $l$ 个计算基态的叠加态，即

$$
|\vec{\gamma}^*,\vec{\beta}^*\rangle = c_1|z^{(1)}\rangle + \cdots + c_l|z^{(l)}\rangle.
\tag{24}
$$

通常情况下，一个态 $|z^{(j)}\rangle$ 在计算基上测量得到的概率 $|c_j|^2$ 越大，意味着其对应的比特串 $z^{(j)}$ 是最优解的可能性越大。那么我们制备 $|\vec{\gamma}^*,\vec{\beta}^*\rangle$ 并测量，得到一个比特串 $z$ 并计算 $C(z)$ 的值。重复多次这个过程，便能得到一个 $z$ 使得 $C(z)$ 接近甚至超过 $M_p$。

### 绝热定理

为什么可以用上述的方法来构造量子态 $|\vec{\gamma},\vec{\beta}\rangle$？ QAOA 试图找到一个优化问题的近似最优解，一个与之类似的算法是量子绝热算法 [2]（quantum adiabatic algorithm, QAA）。但不同的是，QAA 是为了找到优化问题的最优解而非近似最优解。与 QAOA 类似，QAA 将一个优化问题转化为了求一个哈密顿量的基态的问题，并利用绝热定理（adiabatic theorem）对其求解。考虑一个量子系统的哈密顿量

$$
H(t) = (1-\frac tT)H_B + \frac tT H_C,
\tag{25}
$$

初始时，时间 $t=0$，该系统的哈密顿量为 $H(0) = H_B$。随着时间的流逝，该系统的哈密顿量逐渐由 $H_B$ 变为 $H_C$。当 $t=T$ 时，该系统的哈密顿量变为 $H(T) = H_C$。量子力学中的绝热定理告诉我们，如果初始时该系统处于 $H_B$ 的一个本征态，那么只要时间 $T$ 足够长，当系统的哈密顿量完全演化为 $H_C$ 时，该系统会处于 $H_C$ 的对应能级的本征态。因此，如果初始时该系统处于 $|s\rangle$，即 $H_B$ 拥有最大本征值的本征态，经过足够长的演化时间 $T$，该系统的量子态会变为 $H_C$ 拥有最大本征值的本征态。[3]

一种模拟哈密顿量 $H(t)$ 随着时间 $t$ 演化的方法便是交替地在该量子系统上作用酉变换 $U_C(\gamma)$ 和 $U_B(\beta)$，而模拟的精度取决于 $\gamma,\beta$ 的取值。另外，为了让系统的演化遵循绝热定理，需要足够长的演化时间，所以要求 $p$ 的取值足够大。因此，结合公式（22）可以推出公式（23）中的结论。

_______

## 参考文献

[1] Farhi, E., Goldstone, J. & Gutmann, S. A Quantum Approximate Optimization Algorithm. [arXiv:1411.4028 (2014).](https://arxiv.org/abs/1411.4028)

[2] Farhi, E., Goldstone, J., Gutmann, S. & Sipser, M. Quantum computation by adiabatic evolution. [arXiv:quant-ph/0001106 (2000).](https://arxiv.org/abs/quant-ph/0001106)

[3] Duan, R. Quantum Adiabatic Theorem Revisited. [arXiv:2003.03063 (2020).](https://arxiv.org/abs/2003.03063)


Текст распространяется на условиях лицензии Creative Commons Attribution license CC-BY 4.0, код — на условиях лицензии MIT license.  (c)2015 L.A. Barba, Pi-Yueh Chuang.
Перевод (c) 2018 Innokentiy Kursakov
# Преобразование Жуковского

Это задание ко второму модулю **"Потенциальные вихри и подъемная сила"** курса [_AeroPython_](https://github.com/barbagroup/AeroPython). Первый модуль курса, "Строительные элементы потенциальных течений", который состоял из трех блокнотов, закончился рассмотрением потенциального обтекания двумерного цилиндра, полученного путем суперпозиции диполя и равномерного потока. А во втором модуле вы узнали, что добавление еще одной особенности – вихря, позволяет получить цилиндр, на который действует подъемная сила. Может возникнуть вопрос: *есть ли польза от этих знаний?*

И вот как они станут полезными. Используя простые методы ТФКП, мы получим из обтекания цилиндра течение вокруг профиля. Фокус в том, чтобы использовать конформное [*отображение*](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%BD%D0%BE%D0%B5_%D0%BE%D1%82%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5) (комплексную функцию, сохраняющую углы) для перехода из плоскости цилиндра в плоскость профиля.

Давайте исследуем это классический подход!

## Введение

Вы узнали, как получить потенциальное обтекание цилиндра путем суперпозиции равномерного течения и [диполя](03_Lesson03_doublet.ipynb). Еще вы узнали, что при добавлении вихря возникает [подъемная сила](06_Lesson06_vortexLift.ipynb). Ну и что с того? Почему это так важно? Что нам делать с потенциальным течением вокруг цилиндра?

В те времена, когда еще не было компьютеров, ученые-аэродинамики и математики использовали мощный инструмент — ТФКП — для изучения потенциальных течений, не решая напрямую системы уравнений в частных производных. Пользуясь им и зная решение для потенциального обтекания цилиндра, они с легкостью получали решения для множества различных внешних потенциальных течений, включая обтекание нескольких видов профилей.

Однако сегодня мы больше не пользуемся этим волшебным инструментом. Тем не менее, по-прежнему полезно знать основной принцип, стоящий за этим волшебством — конформные отображения. В этом задании мы пройдем все шаги в процедуре  получения потенциального обтекания профиля из течения вокруг цилиндра при помощи знаменитого конформного преобразования — *функции Жуковского*. И вам станет понятно, какое важное значение в истории аэродинамики у потенциального обтекания цилиндра!

Не волнуйтесь. Математики будет немного. И вам, в отличие от пионеров аэродинамики, не придется ничего вычислять вручную. Просто следуйте инструкциям, а Питон сделает всю тяжелую работу за вас.

## 1. Комплексные числа в Питоне

Начнем с задания двух комплексных плоскостей: в одной заданы точки $z = x + iy$, в другой — точки $\xi = \xi_x+i\xi_y$. Функция Жуковского переводит точку из плоскости $z$ в точку на плоскости $\xi$:

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

где $c$ — константа. Прежде чем перейти к обсуждению формулы Жуковского, попрактикуемся немного с комплексными числами  в Питоне.

Если использовать комплексные числа, то формула  Жуковского приобретает простой вид,
при этом отпадает необходимость вычислять действительную и мнимую части по отдельности.

Питон, а следовательно и NumPy, умеет работать с комплексными числами, что называется, из коробки. Мнимая единица $i=\sqrt{-1}$ обозначается символом`j`, *а не* `i`, чтобы не возникало путаницы с итерационной переменной `i`.

Если вы впервые сталкиваетесь с комплексными переменными, попробуйте выполнить несколько простых операций. Например, введите в следующую ячейку код:

```Python
3 + 2j
```

А теперь попробуйте так:

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

### Упражнения:

Познакомьтесь с комплексными операциями в Питоне и ответьте на следующие вопросы:

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}=$

## 2. Фигуры, созданные при помощи формулы Жуковского

Начните с написания функции, которая принимает `z` и `c` в качестве параметров и возвращает преобразованное значение `z`.

При помощи такого преобразования можно получить несколько типов кривых. Используйте только что содзанную функцию чтобы проделать вычисления, описанные ниже и ответьте на вопросы.

Для простоты предположим, что $c=1$.

1. На плоскости $z$ расположите окружность радиусом $R$ больше $c=1$, скажем $R=1.5$, с центром в начале координат. Какой формы получится отображение этой окружности на полскость $\xi$?
    1. окружность
    2. эллипс
    3. симметричный профиль
    4. несимметричный профиль
2. Теперь поместите на плоскости $z$ окружность с центром в точке $(x_c,y_c)=(c-R, 0)$, радиус которой $c \lt R \lt 2c$ (например, $c=1$; $R=1.2$) Какой формы теперь получится отображение  на полскость $\xi$?
    1. окружность
    2. эллипс
    3. симметричный профиль
    4. несимметричный профиль
3. Поместите центр окружности в точку $(x_c, y_c)=(-\Delta x, \Delta y)$, где $\Delta x$ и $\Delta y$ — небольшие положительные значения, например, $\Delta x=0.1$ и $\Delta y=0.1$. Радиус окружности задайте как $R = \sqrt{(c - x_c)^2 + y_c^2}$. Что получилось на плоскости $\xi$?
    1. окружность
    2. эллипс
    3. симметричный профиль
    4. несимметричный профиль
4. Рассмотрим случай симметричного профиля. В полярных координатах $(\theta, r=R)$, какая точка на окружности соответствует задней кромке профиля?
    * $\theta=$?

## 3. Расчетная сетка в плоскости  $z$ в полярной системе координат

Формула Жуковского ставит в соответствие точке на плоскости $z$ точку на плоскости $\xi$. Как вы видели в предыдущем разделе, такое преобразование иногда дает фигуры, сильно похожие на аэродинамические профили. _Ну и что?_

Окажывается, согласно ТФКП, если применить [конформное отображение](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BD%D1%84%D0%BE%D1%80%D0%BC%D0%BD%D0%BE%D0%B5_%D0%BE%D1%82%D0%BE%D0%B1%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5) к решению уравнения Лапласа, то получившаяся функция тоже будет решением уравнения Лапласа.

Значит, можно отобразить потенциал и функцию тока для обтекания цилиндра из плоскости $z$ в $\xi$ и получить течение вокруг профиля. Функция тока для обтекания профиля задается уравнением

$$
\psi(\xi_x, \xi_y) = \psi(\xi_x(x, y), \xi_y(x, y))
$$

в котором комплексные координаты $\xi$, $\xi_x$ и $\xi_y$ получены путем преобразования по формуле Жуковского из  $z=x+iy$.

Выполнив это задание, вы получите обтекание симметричного профиля Жуковского под нулевым и ненулевым углами атаки. Форму профиля можно получить, задав в плоскости $z$ окружность с центром в точке $(x_c, y_c)=(-0.15, 0)$ и радиусом $R = 1.15$ (параметр $c=1$). Для того, чтобы достичь поставленной цели, нужно последовательно решить задачи с (3) по (6).

Сначала построим расчетную сетку в плоскости $z$ и посмотрим, как ее узлы будут выглядеть на плоскости $\xi$ после преобразования. Используйте в плоскости $z$ полярные координаты. Если поместить узлы расчетной сетки внутрь окружности, то после отображения они окажутся снаружи профиля (убедитесь в этом сами!) Это, вроде бы, проблема. С другой стороны, на окружности ставится граничное условие непротекания, поэтому линии тока внутри окружности нет, и эту область можно просто игнорировать.

### Упражнения:

Постройте расчетную сетку в плоскости $z$ в полярных координатах и при помощи формулы Жуковского отразите ее на плоскость $\xi$.

Поместите $N_r = 100$ узлов сетки в радиальном направлении при $R \le r \le 5$ и $N_{\theta}=145$ узлов в трансверсальном. В этом направлении $\theta$ меняется от 0 до $2\pi$. Теперь, если нарисовать что получилось при помощи функции `pyplot.scatter()`, должно получиться что-то такое:

## 4. Обтекание симметричного профиля Жуковского под нулевым углом атаки

### Функция и линии тока

Рассмотрим потенциальное обтекание цилиндра в плоскости $z$.  Как отмечалось выше, $\psi(\xi) = \psi(\xi(z))$. Значит, значение функции тока в точке на плоскости $z$ будет таким же, как и в соответствующей ей точке плоскости $\xi$. Мы можем построить линии тока в обеих плоскостях с помощью функции `pyplot.contour()`, поскольку функция тока является скалярной функцией.

В качестве скорости на бесконечности используйте $1$, т. е. $U_{\infty}=1$. Для того чтобы получить цилиндр с радиусом $R=1.15$, сначала нужно вычислить интенсивность диполя.

У вас должны получиться картины линий тока, похожие на те, что представлены на рисунках ниже.

### Векторы скорости и коэффициент давления

Чтобы вычислить коэффициент давления, нам потребуется поле скорости. Поле скорости в плоскости $z$ можно легко получить,  используя координаты узлов расчетной сетки. Но можно ли утверждать, что скорости в соответствующих точках плоскости $\xi$ будут такими же, как на плосокти $z$, по анлогии с функцией тока? _Правильный ответ: нет._

Значения функции тока остаются неизменными в исходных и отображенных точках, поскольку функция потока является решением скалярного уравнения Лапласа!

Однако, скорость-это вектор и он не является решением уравнения Лапласа. Координаты вектора изменяются при смене системы координат путем конформного преобразования.

В нашем случае плоскости $z$ и $\xi$ будут двумя различными системами координат. И скорость в заданной точке плоскости $z$ будет отличаться от скорости в соответствующей точке на плоскости $\xi$. Нужно выполнить некоторые манипуляции.

Скорости в плосокостях $z$ и $\xi$ записываются в следующем виде:

\begin{equation}
\left\{
\begin{array}{l}
u_z = \frac{\partial \psi}{\partial y} \\
v_z = -\frac{\partial \psi}{\partial x}
\end{array}
\right.
\text{  и  }
\left\{
\begin{array}{l}
u_\xi = \frac{\partial \psi}{\partial \xi_y} \\
v_\xi = - \frac{\partial \psi}{\partial \xi_x}
\end{array}
\right.
\end{equation}

Как получить $u_\xi$ и $v_\xi$, имея $u_z$ и  $v_z$, ? Это можно сделать, используя правило дифференцирования сложной функции. Самое время вспомнить о функции потенциала $\phi$, которая тоже является решением уравнения Лапласа.  Таким образом, значения потенциала и функции тока остаются неизменными при конформном отображении. То же самое справедливо для комплексного потенциала $F(\xi)  = F(\xi(z))= \phi + i\psi$.

По правилу диффернцирования сложной функции, $dF/d\xi=dF/dz\times dz/d\xi$. Таким образом,

\begin{equation}
W_\xi = u_\xi - iv_\xi = \frac{d F}{d \xi} = \frac{d F}{d z}\times\frac{d z}{d \xi} = \frac{d F}{d z}/\frac{d \xi}{d z} = (u_z-iv_z) / \frac{d \xi}{d z}
\end{equation}

И

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

Теперь, используя два полученных выше соотношения, можно получить скорости $u_\xi$ и $v_\xi$ на плоскости $\xi$.

Если воспользоваться функцией `pyplot.quiver()` для визуализации векторных полей, результат должен быть таким:

А поле коэффициента давления в расчетной области — так:

### Упражнения:

* Напишите код на Python для получения этих картин: линий тока, векторов скорости и распределения коэффициента давления.
* Ответьте на следующие вопросы:
    1. Какова интенсивность диполя?
    2. Чему равна скорость в 62-й точке на поверхности профиля? Предположим, что задняя кромка имеет индекс 1 и что значения индекса возрастает при движении против часовой стрелки.
    3. Каково минимальное значение коэффициента давления на поверхности профиля?

## 5. Обтекание симметричного профиля Жуковского под ненулевым углом атаки, без циркуляции

Теперь мы хотим расположить профиль под углом атаки (AoA — angle of attack) относительно набегающего потока. Конечно, для этого можно использовать преобразование Жуковского, применив его к обтеканию цилиндра. *Но как это сделать?* Можно ли достичь желаемого результата, сложив безграничное течение и диполь? На самом деле, нет. Если пойти этим путем, нам не удастся выделить замкнутую линию тока, как мы делали это раньше — а такая замкнутая линия тока выступает в качестве поверхности цилиндра

Для того, чтобы получить равномерный поток под углом атаки, нужно просто повернуть систему координат. Сначала создадим новую систему координат (или еще одну комплексную плоскость) $z'$ с началом в цетре цилиндра, в которой ось $x'$ (действительная часть $z'$) параллельна потоку, как показано на картинке.

Взаимосвязь между плоскостями $z'$ $z$:

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

Или, для координат $x$, $y$, $x'$ и $y'$:

\begin{equation}
\left\{
\begin{array}{l}
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}

где $(x_c, y_c)$ — положение центра цилиндра, а $AoA$ — угол атаки.

Теперь в новой плоскости $z'$  можно получить обтекание цилиндра, сложив безграничный поток с *нулевым углом атаки* и диполь, *расположенный в начале системы координат*. Затем, получим течение в плоскостях $z$ и $\xi$. Опять же, функция потока остается той же самой в заданной точке во всех трех различных системах координат ($z'$, $z$ и $\xi$). В плоскостях $z$ и $\xi$ у вас должны получиться такие линии тока:

Векторы скоростей нужно развернуть обратно при переходе от плоскости $z'$ к $z$.

\begin{equation}
u-iv=\frac{d F}{d z}=\frac{d F}{d z'}\times\frac{d z'}{d z}=(u'-iv')e^{-i\times AoA}
\end{equation}

Конечно же, можно использовать и явную запись для компонент $x$, $y$, $x'$ и $y'$. Выведите соответствующие соотношения самостоятельно, если вам удобнее пользоваться явной формой записи. Получив скорости в плоскости $z$, вы можете воспользоваться навыками, приобретенными при выполнении предыдущего упражнения, для того, чтобы выписать скорости в плоскости $\xi$. Итоговые поля скорости и коэффициента давления должны выглядеть следующим образом:

### Упражнения:

* Напишите код на Python, чтобы получть показанные выше картины. Используйте угол атаки $AoA=20^\circ$.
* Ответьте на следующие вопросы:
    1. Как вы думаете, физично ли полученное решение? Обоснуйте свой ответ
    2. Где на профиле расположены точки торможения? Предположим, что задняя кромка имеет индекс 1 и что значения индекса возрастает при движении против часовой стрелки.
    3. Каково значение подъемной силы?
    4. Каково значение коэффициента сопротивления?
    5. Чему равна скорость в 50-й точке на поверхности профиля?
    6. Чему равен коэффициент давления в 75-й точке на поверхности профиля?

## 6. Обтекание симметричного профиля Жуковского под ненулевым углом атаки при наличии циркуляции

Результат, полученный в предыдущем упражнении, не имеет физического смысла. Нам нужен **вихрь**. Как известно из [Занятия 6: Подъемная сила цилиндра](06_Lesson06_vortexLift.ipynb), добавление вихря (другими словами циркуляции) к потенциальному обтеканию цилиндра приводит к изменению положения точек торможения, а также создает подъемную силу.

Для того, чтобы сделать решение более физичным, нужно, чтобы выполнялось [условие Кутты-Жуковского](https://ru.wikipedia.org/wiki/%D0%9F%D0%BE%D1%81%D1%82%D1%83%D0%BB%D0%B0%D1%82_%D0%96%D1%83%D0%BA%D0%BE%D0%B2%D1%81%D0%BA%D0%BE%D0%B3%D0%BE_%E2%80%94_%D0%A7%D0%B0%D0%BF%D0%BB%D1%8B%D0%B3%D0%B8%D0%BD%D0%B0),
>"Из всех возможных обтеканий крыла с задней острой кромкой в природе реализуется только то, в котором скорость в заднем острие конечна."

Это утверждение позволит нам подобрать нужную интенсивность вихря. Она должна быть такой, чтобы задняя точка торможения на цилиндре переместилась из положения $\theta=$AoA в положение $\theta=0^\circ$ на плоскости $z$. Знаний, полученных на Занятии 6, должно быть достаточно для того, чтобы самостоятельно рассчитать необходимую интенсивность.

Линии тока, поля скоростей и коэффициента давления в плоскостях $z$ и $\xi$ должны выглядеть так:

### Упражнения

* Напишите код на Python, чтобы получть показанные выше картины.
* Ответьте на следующие вопросы:
    1. Какова интенсивность вихря?
    2. Каково значение подъемной силы? (Подсказка: подъемная сила, как нам известно из Занятия 6, действует в направлении, нормальном к набегающему потоку, в нашем случае в направлении оси $y'$. )
    3. Попробуйте вычислить значения подъемной силы и сопротивления напрямую, по формулам $L=-\oint p \times \sin{\theta} dA$ и $D=\oint p \times \cos{\theta} dA$. Удовлетворяет ли полученное значение подъемной силы теореме Кутты–Жуковского? Каково значение коэффициента сопротивления?
    4. Где на профиле расположены точки торможения? Предположим, что задняя кромка имеет индекс 1 и что значения индекса возрастает при движении против часовой стрелки.
    5. Чему равна скорость в 92-й точке на поверхности профиля?
    6. Чему равен коэффициент давления в 111-й точке на поверхности профиля?
    7. Что происходит с коэффициентом давления на задней кромке профиля?

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

<link href='http://fonts.googleapis.com/css?family=PT+Serif' 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'>
<style>

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

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

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

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

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

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

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

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

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Alegreya Sans', sans-serif;
    font-style:regular;
    font-weight: 200;
    font-size: 50pt;
    line-height: 100%;
    color:#CD2305;
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}
.text_cell_render h2 {
    font-family: 'PT Serif', serif;
    font-size: 22pt;
    line-height: 100%;
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'PT Serif', serif;
    margin-top:12px;
	font-size: 16pt;
    margin-bottom: 3px;
    font-style: regular;
}

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

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Alegreya Sans', sans-serif;
    font-weight: 300;
    font-size: 16pt;
    color: #CD2305;
    font-style: italic;
    margin-bottom: .5em;
    margin-top: 0.5em;
    display: block;
}

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

    .CodeMirror{
            font-family: "Consolas";
			font-size: 110%;
    }
/*    .prompt{
        display: None;
    }*/

    .warning{
        color: rgb( 240, 20, 20 )
        }
</style>