## check if PSE step has anticipated growth

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

```python
# display items
# view jupyter notebook wide screen
from IPython.core.display import display, HTML
display(HTML("<style>.container { width:90% !important; }</style>"))
# matplotlib items
%matplotlib inline
#%matplotlib notebook
# pdf and png output
from IPython.display import set_matplotlib_formats
set_matplotlib_formats('png', 'pdf')
```

<style>.container { width:90% !important; }</style>

### functions to read data

```python
# read in data and plot Aq and qp1-q from PSE vs OSS
def add_legend_outside(fig,ax, x0=1,y0=0.9, direction = "h", padpoints = 3,**kwargs):
    import matplotlib.transforms
    otrans = ax.figure.transFigure
    t = ax.legend(bbox_to_anchor=(x0,y0), loc=1, bbox_transform=otrans,**kwargs)
    plt.tight_layout()
    ax.figure.canvas.draw()
    plt.tight_layout()
    ppar = [0,-padpoints/72.] if direction == "v" else [-padpoints/72.,0]
    trans2=matplotlib.transforms.ScaledTranslation(ppar[0],ppar[1],fig.dpi_scale_trans)+\
             ax.figure.transFigure.inverted()
    tbox = t.get_window_extent().transformed(trans2 )
    bbox = ax.get_position()
    if direction=="v":
        ax.set_position([bbox.x0, bbox.y0,bbox.width, tbox.y0-bbox.y0])
    else:
        ax.set_position([bbox.x0, bbox.y0,tbox.x0-bbox.x0, bbox.height])
def read_q(fname_q):
    q = np.genfromtxt(fname_q, delimiter=',',dtype=str)
    todelete=[]
    for i in range(q.size):
        # remove various lines that match
        if 'Vec' in q[i]:
            todelete.append(i)
        if 'type' in q[i]:
            todelete.append(i)
        if 'Process' in q[i]:
            todelete.append(i)
        # get rid of whitespace and change i to j
        q[i] = q[i].replace(' ','').replace('i','j')
    q=np.delete(q,todelete)
    q = q.astype(complex)
    #print('q = ',q)
    #print('shape = ',q.shape)
    return q
```

#### read in q and qp1 and plot OSS vs PSE

```python
# read and plot printVecq, printVecqp1, and Aq.txt
fname_q='../PSE/printVecq.txt'
fname_qp1='../PSE/printVecqp1.txt'

q = read_q(fname_q)
qp1 = read_q(fname_qp1)
Aq = read_q('../PSE/Aq.txt')

# plot q
fig,ax=plt.subplots(figsize=(6,4))
ax.plot((q.real),'.-',label='q.real')
ax.plot((qp1.real),'.-',label='qp1.real')
ax.plot((q.imag),'.-',label='q.imag')
ax.plot((qp1.imag),'.-',label='qp1.imag')
#ax[0].plot(np.abs(q_ansatz),label='q_ansatz')
ax.set_ylabel('q')
#ax[0].legend(loc='best',numpoints=1)
add_legend_outside(fig,ax)
fig.show()

# plot error
fig,ax=plt.subplots()
ax.plot(np.abs(qp1-q),'.-',label=r'$|q_{i+1}-q_i|$')
ax.set_ylabel(r'$|q_{i+1}-q_i|$')
#ax[1].legend(loc='best',numpoints=1)
#add_legend_outside(fig,ax[1])

# plot Aq
fig,ax=plt.subplots()
ax.plot(Aq.real,'.-',label='Aq.real')
ax.plot(Aq.imag,'.-',label='Aq.imag')
ax.set_ylabel('Aq')
#ax[2].legend(loc='best',numpoints=1)
add_legend_outside(fig,ax)
fig.show()
```

### plot multiple closure steps, see what happens

```python
# multiple closure steps... see what happens
#fname_base = "../PSE/printVecqp1_"
fname_base = "../PSE/printVecq"
fname_orig = "../PSE/printVecq0.txt"
q0=read_q(fname_orig)
ny=101
y=np.linspace(-1,1,ny)
fig,ax=plt.subplots(figsize=(4,4))
#fig2,ax2=plt.subplots(figsize=(4,4))
q=[]
q.append(q0)
for i in range(1,15):
    q.append(read_q(fname_base+str(i)+".txt"))
ax.plot(q0.real[:ny],y,'o',label='orig.real')
#ax.plot(q0.imag[:ny],y,'o',label='orig.imag')
for i in range(1,15):
    ax.plot(q[i].real[:ny],y,label="%i.real"%i) # first five plots
    #ax.plot(q[i].imag[:ny],y,'--',label="%i.imag"%i) # first five plots
#ax2.plot(q0.real[:ny],y,'o',label='orig.real')
#ax2.plot(q0.imag[:ny],y,'o',label='orig.imag')
#for i in range(4,3):
    #ax2.plot(q[i].real[:ny],y,label="%i.real"%i) # all plots
    #ax2.plot(q[i].imag[:ny],y,'--',label="%i.imag"%i) # all plots
ax.set_xlabel(r'$\hat{u}$')
ax.set_ylabel('y')
#ax2.set_xlabel('u')
#ax2.set_ylabel('y')
#add_legend_outside(fig,ax)
#ax.legend(loc='best',numpoints=1)
#ax.set_xlim([-0.048,-0.015])
#ax.set_ylim([-1,-0.75])
#fig.show()
fig.tight_layout()
#add_legend_outside(fig2,ax2)

```

```python
# plot change in alpha as function of x
# copy and paste alpha values from alpha.txt
alpha=np.array([
    0.611665+0.140665j,
    0.612085+0.140278j,
    0.611991+0.141877j,
    0.611738+0.140831j,
    0.611392+0.142572j,
    0.611432+0.141595j,
    0.611405+0.143088j,
    0.611481+0.141848j,
    0.611411+0.143234j,
    0.611491+0.141981j,
    0.611493+0.143313j,
    0.61158+0.141979j,
    0.611537+0.143257j,
    0.611566+0.141938j,
    0.611501+0.143239j,])
x=np.arange(0,15*2.5,2.5)
fig,ax=plt.subplots(figsize=(4,3))
#ax.plot(x,alpha.real,'.',label=r'real($\alpha$)')
ax.plot(x,alpha.imag,'.',label=r'imag($\alpha$)')
ax.set_xlabel(r'$x$')
add_legend_outside(fig,ax,)
```

### plot q_physical before and after

```python
# now read q_physical
fname_orig = '../PSE/q_physical_before.txt'
fname_after = '../PSE/q_physical_after_'
q=[]
q_before = read_q(fname_orig)
for i in range(100):
    q.append(read_q(fname_after+str(i)+'.txt'))
#ax.legend(loc='best',numpoints=1)
#fig.tight_layout()
```

```python
# plot q and difference in before and after
fig,ax = plt.subplots()
fig2,ax2 = plt.subplots()
ax.plot(q_before.real[:ny],y,'o',label='before')
for i in range(0,100):
    ax.plot(q[i].real[:ny],y,label=i)
    ax2.plot(np.abs(q[i].real[:ny] - q_before.real[:ny]),y,label=i)
ax.set_xlabel(r'$u$')
ax.set_ylabel(r'$y$')
#ax.legend(loc='best',numpoints=1)
fig.tight_layout()
fig2.tight_layout()
```

```python
fname_base = "../PSE/printVecq"
fig,ax=plt.subplots()
q=[]
for i in range(15):
    q.append(read_q(fname_base+str(i)+".txt"))
    ax.plot(q[i],label=i)
ax.set_ylabel('q')
add_legend_outside(fig,ax)
fig.show()

```

```python
q[4]
```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

#### calc anticipated qp1

```python
# calc q's from ansatz
def ansatz(q,alpha,x,omega,t):
    return q*np.exp(1.j*(alpha*x-omega*t))
alpha=1.026869 - 8.454123e-4j
x=0.001
omega=0.27
t=0.

q_ansatz = ansatz(q,alpha,x,omega,t)
print(q_ansatz)
```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```

### Check Taylor Series expansions and order of accuracy

```python
from IPython.core.display import display, HTML
```

```python
# taylor series check
from sympy import *
x, h = symbols('x, h')
f = Function('f')
from IPython.display import display
init_printing()
def ft(x,h):
    return sum(h**i/factorial(i) * f(x).diff(x, i) for i in range(11))

# Check second order accurate central diff
print( 'central 2nd diff 2nd order')
display(
    (
        ft(x,-h)
        - 2*ft(x,0)
        + ft(x,h)
    )/h**2)

# check central 4th order second derivative
print( 'central 2nd diff 4th order')
display(
    (
        -1*ft(x,-2*h)
        +16 *ft(x,-h)
        -30 *ft(x,0)
        +16 *ft(x,h)
        -ft(x,2 *h)
    )/(12 *h**2))

# check shifted 4th order 2nd derivative
print( 'shifted 2nd diff 4th order')
display(
    (
         10 *ft(x,-1*h)
        -15 *ft(x,0   )
        -4  *ft(x,h   )
        +14 *ft(x,2*h )
        -6  *ft(x,3*h )
        +1  *ft(x,4*h )
    )/(12 *h**2))

# check central 4th order 4th derivative
print( 'central 4th diff 4th order')
display(
    (
        -1  *ft(x,-3*h)
        +12 *ft(x,-2*h)
        -39 *ft(x,-h  )
        +56 *ft(x,0*h )
        -39 *ft(x,h   )
        +12 *ft(x,2*h )
        -1  *ft(x,3*h )
    )/(6*h**4))

# check shifted 4th order 4th derivative
print( 'shifted 4th diff 4th order')
display(
    (
         21 *ft(x,-1*h)
        -112*ft(x,0   )
        +255*ft(x,h   )
        -324*ft(x,2*h )
        +251*ft(x,3*h )
        -120*ft(x,4*h )
        +33 *ft(x,5*h )
        -4  *ft(x,6*h )
    )/(6*h**4))

# check shifted 4th order 4th derivative
print('shifted 4th diff 4th order')
display(
    (
         4  *ft(x,-2*h)
        -11 *ft(x,-1*h)
        +0  *ft(x,0   )
        +31 *ft(x,h   )
        -44 *ft(x,2*h )
        +27 *ft(x,3*h )
        -8  *ft(x,4*h )
        +1  *ft(x,5*h )
    )/(6 *h**4))

```

### old useless code

```python
def set_D2(y,order=2):
    '''
    Input:
        y: array of y values of channel
    Output:
        D2: Second derivative of order O(h^2) or what specifide assuming uniform y spacing
    '''
    n = y.size
    h=y[1]-y[0]
    ones=np.ones(n)
    I=np.eye(n)
    # get coefficients for main diagonals
    d=2
    N=order+d # how many pts needed for order of accuracy
    if N>n:
        raise ValueError('You need more points in your domain, you need %i pts and you only gave %i'%(N,n))
    Nm1=N-1 # how many pts needed if using central difference is equal to N-1
    # stencil and get Coeffs for diagonals
    s = np.arange(Nm1)-(Nm1-1)/2 # stencil for central diff of order
    smax=s[-1] # right most stencil used (positive range)
    Coeffs = get_D_Coeffs(s,d=d)
    # loop over s and add coefficient matrices to D2
    D2 = np.zeros_like(I)
    si = np.nditer(s,('c_index',))
    while not si.finished:
        i = si.index
        if si[0]==0:
            diag_to_add = np.diag(Coeffs[i] * ones,k=si[0])
        else:
            diag_to_add = np.diag(Coeffs[i] * ones[:-abs(si[0])],k=si[0])

        D2 += diag_to_add
        si.iternext()
    # alter BC so we don't go out of range on bottom of channel
    for i in range(1,smax):
        # for ith row, set proper stencil coefficients
        s = np.arange(N)-i # stencil for shifted diff of order
        Coeffs = get_D_Coeffs(s,d=d)
        D2[i,:] = 0. # set row to zero
        D2[i,s+i] = Coeffs # set row to have proper coefficients

        # for -ith-1 row, set proper stencil coefficients
        s = -(np.arange(N)-i) # stencil for shifted diff of order
        Coeffs = get_D_Coeffs(s,d=d)
        D2[-i-1,:] = 0. # set row to zero
        D2[-i-1,s-i-1] = Coeffs # set row to have proper coefficients

    return (1./(h**2)) * D2[1:-1,:] # do not return the top or bottom row
def set_D4(y,order=2):
    '''
    Input:
        y: array of y values of channel
    Output:
        D4: Fourth derivative of order O(h^2) or what is specified assuming uniform y spacing and D1(y)=0 at walls
    '''
    h = y[1]-y[0] # uniform spacing
    n = y.size
    ones=np.ones(n)
    I = np.eye(n)
    # get coefficients for main diagonals
    d=4
    N=order+d # how many pts needed for order of accuracy
    if N>n:
        raise ValueError('You need more points in your domain, you need %i pts and you only gave %i'%(N,n))
    Nm1=N-1 # how many pts needed if using central difference is equal to N-1
    # stencil and get Coeffs for diagonals
    s = np.arange(Nm1)-(Nm1-1)/2 # stencil for central diff of order
    smax=s[-1] # right most stencil used (positive range)
    Coeffs = get_D_Coeffs(s,d=d)
    # loop over s and add coefficient matrices to D4
    D4 = np.zeros_like(I)
    si = np.nditer(s,('c_index',))
    while not si.finished:
        i = si.index
        if si[0]==0:
            diag_to_add = np.diag(Coeffs[i] * ones,k=si[0])
        else:
            diag_to_add = np.diag(Coeffs[i] * ones[:-abs(si[0])],k=si[0])

        D4 += diag_to_add
        si.iternext()
    # alter BC so we don't go out of range on bottom of channel
    for i in range(1,smax):
        # for ith row, set proper stencil coefficients
        s = np.arange(N)-i # stencil for shifted diff of order
        Coeffs = get_D_Coeffs(s,d=d)
        D4[i,:] = 0. # set row to zero
        D4[i,s+i] = Coeffs # set row to have proper coefficients

        # for -ith-1 row, set proper stencil coefficients
        s = -(np.arange(N)-i) # stencil for shifted diff of order
        Coeffs = get_D_Coeffs(s,d=d)
        D4[-i-1,:] = 0. # set row to zero
        D4[-i-1,s-i-1] = Coeffs # set row to have proper coefficients

    return (1./(h**4)) * D4[1:-1,:] # do not return the top or bottom row
```

```python

```

```python

```

```python

```

## Solve using primitive variables, but while using $\nabla^2$ in each equation

```python
# functions for primitive formulation u,v,w,P
def set_BCs_primitive_lap(y, Aus, Bus, Avs, Bvs, Aws, Bws, Acons, Bcons, order=2,large_multiple=0.):
    '''
    Set boundary condition values to As, Bs
    Modifies the (n-2 X n) matrices to be (n X n)
    Sets first/last row of Aos, Bos to have the BC v'=0 at walls
    Adds first/last rows to all matrices to have v=eta=0 at walls

    Inputs:
        y: y spactial array
        Aus: List of [Auu, Auv, Auw, Aup]
        Bus: List of [Buu, Buv, Buw, Bup]
        Avs: List of [Avu, Avv, Avw, Avp]
        Bvs: List of [Bvu, Bvv, Bvw, Bvp]
        Aws: List of [Awu, Awv, Aww, Awp]
        Bws: List of [Bwu, Bwv, Bww, Bwp]
        Acons: List of [Aconu, Aconv, Aconw, Aconp]
        Bcons: List of [Bconu, Bconv, Bconw, Bconp]
        order: order of derivative (O(h^order))
        large_multiple: used to set RHS to nonzero and kill spurious eigenmodes
    '''
    h=y[1]-y[0]
    # v'=0 at walls
    # alter first and last line of As and Bs to contain u'=v'=w'=0 BC
    d=1 # derivative
    N=order+d # how many pts needed for order of accuracy
    s = np.arange(N) # stencil for shifted diff of order for i=0
    sn = -1*np.arange(N) # stencil for shifted diff of order for i=-1
    Coeffs = get_D_Coeffs(s,d=d)
    Coeffsn = get_D_Coeffs(sn,d=d)
    # set u-mom u'=0 at walls
    for i in range(len(Aus)):
        Aus[i][(0,-1),:] = 0.
        Bus[i][(0,-1),:] = 0.
        if i==0:
            Aus[i][0,s] = Coeffs
            Bus[i][0,s] = large_multiple*Coeffs
            Aus[i][-1,sn-1] = Coeffsn
            Bus[i][-1,sn-1] = large_multiple*Coeffsn
    # set v-mom v'=0 at walls
    for i in range(len(Avs)):
        Avs[i][(0,-1),:] = 0.
        Bvs[i][(0,-1),:] = 0.
        if i==1:
            Avs[i][0,s] = Coeffs
            Bvs[i][0,s] = large_multiple*Coeffs
            Avs[i][-1,sn-1] = Coeffsn
            Bvs[i][-1,sn-1] = large_multiple*Coeffsn

    # set w-mom w'=0 at walls
    for i in range(len(Aws)):
        Aws[i][(0,-1),:] = 0.
        Bws[i][(0,-1),:] = 0.
        if i==2:
            Aws[i][0,s] = Coeffs
            Bws[i][0,s] = large_multiple*Coeffs
            Aws[i][-1,sn-1] = Coeffsn
            Bws[i][-1,sn-1] = large_multiple*Coeffsn

    # v=0 at walls
    # rows to add to As and Bs at top and bottom
    zero_row = np.zeros((1,Aus[0][0,:].size))
    v0_at_bottom = np.copy(zero_row)
    v0_at_bottom[0,0] = 1.
    v0_at_top = np.copy(zero_row)
    v0_at_top[0,-1] = 1.
    # add them to As and Bs
    # for Aus Bus u=0
    for i in range(len(Aus)):
        if i==0:
            Aus[i] = np.concatenate((v0_at_bottom,Aus[i],v0_at_top),axis=0)
            Bus[i] = np.concatenate((large_multiple*v0_at_bottom,Bus[i],large_multiple*v0_at_top),axis=0)
        else:
            Aus[i] = np.concatenate((zero_row,Aus[i],zero_row),axis=0)
            Bus[i] = np.concatenate((zero_row,Bus[i],zero_row),axis=0)
    # for Avs Bvs v=0
    for i in range(len(Avs)):
        if i==1:
            Avs[i] = np.concatenate((v0_at_bottom,Avs[i],v0_at_top),axis=0)
            Bvs[i] = np.concatenate((large_multiple*v0_at_bottom,Bvs[i],large_multiple*v0_at_top),axis=0)
        else:
            Avs[i] = np.concatenate((zero_row,Avs[i],zero_row),axis=0)
            Bvs[i] = np.concatenate((zero_row,Bvs[i],zero_row),axis=0)
    # for Aws Bws w=0
    for i in range(len(Aws)):
        if i==2:
            Aws[i] = np.concatenate((v0_at_bottom,Aws[i],v0_at_top),axis=0)
            Bws[i] = np.concatenate((large_multiple*v0_at_bottom,Bws[i],large_multiple*v0_at_top),axis=0)
        else:
            Aws[i] = np.concatenate((zero_row,Aws[i],zero_row),axis=0)
            Bws[i] = np.concatenate((zero_row,Bws[i],zero_row),axis=0)
    # for Acons Bcons P=0
    #for i in range(len(Acons)):
    #    if i==3:
    #        Acons[i] = np.concatenate((v0_at_bottom,Acons[i],v0_at_top),axis=0)
    #        Bcons[i] = np.concatenate((large_multiple*v0_at_bottom,Bcons[i],large_multiple*v0_at_top),axis=0)
    #    else:
    #        Acons[i] = np.concatenate((zero_row,Acons[i],zero_row),axis=0)
    #        Bcons[i] = np.concatenate((zero_row,Bcons[i],zero_row),axis=0)
    return (Aus,Bus,Avs, Bvs, Aws, Bws, Acons, Bcons)
def set_A_and_B_primitive_lap(y,alpha=1,beta=0.,Re=2000.,order=2,large_multiple=2.):
    '''
    Set A and B matrix for stated eigenvalue equation Aq=\omega Bq
    Input:
        y: array of y values of channel
        alpha=1 : alpha value for eigenvalue equation for channel flow
        Re=2000. : Reynolds number for flow
        order: order of accuracy of finite difference derivatives to use
    Output:
        A: matrix for LHS of eigenvalue equation
        B: matrix for RHS of eigenvalue equation
    '''
    #h=y[1]-y[0] # uniform spacing is assumed
    k2=alpha**2 + beta**2
    ialpha=1.j * alpha
    ibeta =1.j * beta

    # identity matrix
    I = np.eye(y.size)
    Im2=I[1:-1,:] # skip first and last rows (at walls)
    zero_mat=0.*Im2
    zero_mat_full = 0.*I
    # base flow
    U,Uy,Uyy = base_flow(y)

    # calculate derivatives
    D1 = set_D(y,order=order,d=1)
    D1_full = set_D(y,order=order,d=1,output_full=True)
    D2 = set_D(y,order=order,d=2)
    D3 = set_D(y,order=order,d=3)
    D4 = set_D(y,order=order,d=4)

    # for Orr-Sommerfeld eq. of the form
    # set constants and Au Bu
    Auu = ((ialpha*k2*U + ialpha*Uyy-1./Re * k2**2)*Im2
           + 2.*ialpha*Uy * D1
           + (1./Re*2.*k2 + ialpha*U)*D2
           - 1./Re * D4)
    Auv = ((Uy*k2+2.*Uyy)*Im2
           + Uy*D2)
    Auw = zero_mat
    Aup = (ialpha*k2*Im2
           + ialpha*D2)
    Aus = [Auu, Auv, Auw, Aup]
    Buu = (1.j*k2*Im2
           + 1.j*D2)
    Buv = zero_mat
    Buw = zero_mat
    Bup = zero_mat
    Bus = [Buu, Buv, Buw, Bup]

    # for constants and Av Bv
    Avu = zero_mat
    Avv = ((ialpha*U*k2 + ialpha*Uyy - 1./Re * k2**2)*Im2
           + (2.*ialpha*Uy*D1)
           + (ialpha*U + 1./Re * 2 * k2)*D2
           - 1./Re * D4)
    Avw = zero_mat
    Avp = (k2*D1 + D3)
    Avs = [Avu, Avv, Avw, Avp]
    Bvu = zero_mat
    Bvv = (1.j*k2 * Im2
          + 1.j*D2)
    Bvw = zero_mat
    Bvp = zero_mat
    Bvs = [Bvu, Bvv, Bvw, Bvp]

    # for constants and Aw Bw
    Awu = zero_mat
    Awv = zero_mat
    Aww = ((ialpha*k2*U + ialpha*Uyy - 1./Re * k2**2)*Im2
          + 2.*ialpha*Uy*D1
          + (ialpha*U + 1./Re * k2)*D2
          - 1./Re * D4)
    Awp = ((ibeta*k2)*Im2
           + ibeta*D2)
    Aws = [Awu, Awv, Aww, Awp]
    Bwu = zero_mat
    Bwv = zero_mat
    Bww = (1.j*k2*Im2
          + 1.j*D2)
    Bwp = zero_mat
    Bws = [Bwu, Bwv, Bww, Bwp]

    # for constants and Acon Bcon
    acon = ialpha
    bcon = 1.
    ccon = ibeta
    Aconu = acon*I #do full array, since no BCs to add
    Aconv = bcon*D1_full
    Aconw = ccon*I
    Aconp = zero_mat_full
    Acons = [Aconu, Aconv, Aconw, Aconp]
    Bconu = zero_mat_full
    Bconv = zero_mat_full
    Bconw = zero_mat_full
    Bconp = zero_mat_full
    Bcons = [Bconu, Bconv, Bconw, Bconp]

    # BCs
    Aus,Bus,Avs,Bvs,Aws,Bws,Acons,Bcons = set_BCs_primitive_lap(y, Aus, Bus, Avs, Bvs, Aws, Bws, Acons, Bcons, order=order,large_multiple=large_multiple)

    #combine to A and B for combined eqs.
    A = np.concatenate((
            np.concatenate(Aus,axis=1), # u-mom
            np.concatenate(Avs,axis=1), # v-mom
            np.concatenate(Aws,axis=1), # w-mom
            np.concatenate(Acons,axis=1))# continuity
        ,axis=0)
    B = np.concatenate((
            np.concatenate(Bus,axis=1), # u-mom
            np.concatenate(Bvs,axis=1), # v-mom
            np.concatenate(Bws,axis=1), # w-mom
            np.concatenate(Bcons,axis=1)) # continuity
        ,axis=0)

    return (A,B)
def set_and_solve_eig_primitive_lap(n=201,iBCs=True,iplot=True,**kwargs):
    '''
    Inputs:
        n=201:   number of pts in spatial y for finite difference scheme
        **kwargs: inputs for set_A_and_B and set_D and set_BCs
    Returns:
        eig:     eigenvalues of the equation
        evec:    eigenvectors of the equation
        eig_i:   index for eig and evec for max(eig) to smallest(eig) by using np.sort()
    '''
    print('inputs:')
    print('    n=%i'%(n),end='')
    for k in kwargs.items():
        print(k,end='')
    print('')

    # create y
    y = np.linspace(-1,1,n)
    # solve eigenvalue problem
    A,B = set_A_and_B_primitive_lap(y,**kwargs)
    eig,evec = linalg.eig(A,b=B)

    # sort in order of decending eigenvalues using argsort and print max,min,nans,inf
    eig_i = eig.argsort().imag[::-1]
    if iBCs:
        print( 'check max and min eig')
        print( '   ',eig[eig!=np.inf][np.nanargmax(np.abs(eig[eig!=np.inf]))])
        print( '   ',eig[eig!=np.inf][np.nanargmin(np.abs(eig[eig!=np.inf]))])
        print( 'check isnan and isinf')
        print( '   ',eig[np.isnan(eig)])
        print( '   ',eig[np.isinf(eig)])

        # check BCs
        print('check BCs u=u_y=v=v_y=w=w_y=0')
        # check values at walls
        BadBCun1 = evec[0,:]!=0
        BadBCup1 = evec[n-1,:]!=0
        BadBCvn1 = evec[n,:]!=0
        BadBCvp1 = evec[2*n-1,:]!=0
        BadBCwn1 = evec[2*n,:]!=0
        BadBCwp1 = evec[3*n-1,:]!=0
        #BadBCpn1 = evec[3*n,:]!=0
        #BadBCpp1 = evec[4*n-1,:]!=0

        print( '  bad boundary condition eigenfunctions satisfying u=0')
        print( '   ',evec[0,BadBCun1],'with associated eig-value of',eig[BadBCun1],' u(y=-1 )=0')
        print( '   ',evec[n-1,BadBCup1],'with associated eig-value of',eig[BadBCup1],' u(y=1 )=0')
        print( '  bad boundary condition eigenfunctions satisfying v=0')
        print( '   ',evec[n,BadBCvn1],'with associated eig-value of',eig[BadBCvn1],' v(y=-1 )=0')
        print( '   ',evec[2*n-1,BadBCvp1],'with associated eig-value of',eig[BadBCvp1],' v(y=1 )=0')
        print( '  bad boundary condition eigenfunctions satisfying w=0')
        print( '   ',evec[2*n,BadBCwn1],'with associated eig-value of',eig[BadBCwn1],' w(y=-1 )=0')
        print( '   ',evec[3*n-1,BadBCwp1],'with associated eig-value of',eig[BadBCwp1],' w(y=1 )=0')
        #print( '  bad boundary condition eigenfunctions satisfying P=0')
        #print( '   ',evec[3*n,BadBCpn1],'with associated eig-value of',eig[BadBCpn1],' p(y=-1 )=0')
        #print( '   ',evec[4*n-1,BadBCpp1],'with associated eig-value of',eig[BadBCpp1],' p(y=1 )=0')

        # now check u'=v'=w'=0 using forward and backward 1st diff 2nd order
        uy_bot = -3.*evec[0,:] + 4.*evec[1,:] - evec[2,:]
        vy_bot = -3.*evec[n,:] + 4.*evec[n+1,:] - evec[n+2,:]
        wy_bot = -3.*evec[2*n,:] + 4.*evec[2*n+1,:] - evec[2*n+2,:]
        # now at other wall
        uy_top = 1.*evec[n-3,:] -4.*evec[n-2,:] + 3.*evec[n-1,:]
        vy_top = 1.*evec[2*n-3,:] -4.*evec[2*n-2,:] + 3.*evec[2*n-1,:]
        wy_top = 1.*evec[3*n-3,:] -4.*evec[3*n-2,:] + 3.*evec[3*n-1,:]
        # plot derivative at walls for every eigenfunction for v=0
        fig=plt.figure(figsize=(5,4))
        plt.title('d/dy [u,v,w]=0 at wall check')
        plt.plot(uy_bot[eig_i],'o',label='u bot')
        plt.plot(uy_top[eig_i],'.',label='u top')
        plt.plot(vy_bot[eig_i],'o',label='v bot')
        plt.plot(vy_top[eig_i],'.',label='v top')
        plt.plot(wy_bot[eig_i],'o',label='w bot')
        plt.plot(wy_top[eig_i],'.',label='w top')
        plt.ylabel(r'$\frac{d\hat{v}}{dy}(y=\pm 1)$')
        plt.xlabel(r'$\omega_{max}$ to $\omega_{min}$')
        plt.legend(loc='best',numpoints=1,frameon=False)
        fig.tight_layout()

    if iplot:
        # plot Orr-Sommerfeld-Squire spectrum
        fig=plt.figure(figsize=(4,4))
        ax=plt.subplot(111)
        ax.plot(eig.real,eig.imag,'bo')
        ax.set_xlabel(r'$\omega_r$')
        ax.set_ylabel(r'$\omega_i$')
        ax.axis([0,1,-1.,0])
        ax.set_title('eigenvalues')
        fig.tight_layout()
        plt.show()
    return eig,evec,eig_i
```

```python
%%time
eig_p,evec_p,eig_i_p = set_and_solve_eig_primitive_lap(
    n=101,
    iBCs=False,
    iplot=True,
    alpha=1.,
    beta=0.,
    Re=10000.,
    order=4,
    large_multiple=6e1,)
eig_p,evec_p,eig_i_p = set_and_solve_eig_primitive_lap(
    n=101,
    iBCs=False,
    iplot=True,
    alpha=1.,
    beta=0.,
    Re=10000.,
    order=8,
    large_multiple=6e1,)
eig,evec,eig_i = set_and_solve_eig(
    n=101,
    iBCs=False,
    iplot=True,
    alpha=1.,
    beta=0.,
    Re=10000.,
    order=4,
    large_multiple=6e1,)
```

```python

```

```python

```

```python

```

## Check method with old code

```python
# functions for primitive formulation u,v,w,P
def set_BCs_primitive_check(y, Aus, Bus, Avs, Bvs, order=2,large_multiple=6e1):
    '''
    Set boundary condition values to As, Bs
    Modifies the (n-2 X n) matrices to be (n X n)
    Sets first/last row of Aos, Bos to have the BC v'=0 at walls
    Adds first/last rows to all matrices to have v=eta=0 at walls

    Inputs:
        y: y spactial array
        Aus: List of [Auu, Auv, Auw, Aup]
        Bus: List of [Buu, Buv, Buw, Bup]
        Avs: List of [Avu, Avv, Avw, Avp]
        Bvs: List of [Bvu, Bvv, Bvw, Bvp]
        order: order of derivative (O(h^order))
        large_multiple: used to set RHS to nonzero and kill spurious eigenmodes
    '''
    h=y[1]-y[0]
    # v'=0 at walls
    # alter first and last line of As and Bs to contain u'=v'=w'=0 BC
    d=1 # derivative
    N=order+d # how many pts needed for order of accuracy
    s = np.arange(N) # stencil for shifted diff of order for i=0
    sn = -1*np.arange(N) # stencil for shifted diff of order for i=-1
    Coeffs = get_D_Coeffs(s,d=d)
    Coeffsn = get_D_Coeffs(sn,d=d)
    # set v-mom v'=0 at walls
    for i in range(len(Aus)):
        Aus[i][(0,-1),:] = 0.
        Bus[i][(0,-1),:] = 0.
        if i==0:
            Aus[i][0,s] = Coeffs
            Bus[i][0,s] = large_multiple*Coeffs
            Aus[i][-1,sn-1] = Coeffsn
            Bus[i][-1,sn-1] = large_multiple*Coeffsn
    ## set v-mom v'=0 at walls
    #for i in range(len(Avs)):
    #    Avs[i][(0,-1),:] = 0.
    #    Bvs[i][(0,-1),:] = 0.
    #    if i==1:
    #        Avs[i][0,s] = Coeffs
    #        Bvs[i][0,s] = large_multiple*Coeffs
    #        Avs[i][-1,sn-1] = Coeffsn
    #        Bvs[i][-1,sn-1] = large_multiple*Coeffsn
    #
    ## set w-mom w'=0 at walls
    #for i in range(len(Aws)):
    #    Aws[i][(0,-1),:] = 0.
    #    Bws[i][(0,-1),:] = 0.
    #    if i==2:
    #        Aws[i][0,s] = Coeffs
    #        Bws[i][0,s] = large_multiple*Coeffs
    #        Aws[i][-1,sn-1] = Coeffsn
    #        Bws[i][-1,sn-1] = large_multiple*Coeffsn

    # v=0 at walls
    # rows to add to As and Bs at top and bottom
    zero_row = np.zeros((1,Aus[0][0,:].size))
    v0_at_bottom = np.copy(zero_row)
    v0_at_bottom[0,0] = 1.
    v0_at_top = np.copy(zero_row)
    v0_at_top[0,-1] = 1.
    # add them to As and Bs
    # for Aus Bus u=0
    for i in range(len(Aus)):
        if i==0:
            Aus[i] = np.concatenate((v0_at_bottom,Aus[i],v0_at_top),axis=0)
            Bus[i] = np.concatenate((large_multiple*v0_at_bottom,Bus[i],large_multiple*v0_at_top),axis=0)
        else:
            Aus[i] = np.concatenate((zero_row,Aus[i],zero_row),axis=0)
            Bus[i] = np.concatenate((zero_row,Bus[i],zero_row),axis=0)
    # for Avs Bvs v=0
    for i in range(len(Avs)):
        if i==1:
            Avs[i] = np.concatenate((v0_at_bottom,Avs[i],v0_at_top),axis=0)
            Bvs[i] = np.concatenate((large_multiple*v0_at_bottom,Bvs[i],large_multiple*v0_at_top),axis=0)
        else:
            Avs[i] = np.concatenate((zero_row,Avs[i],zero_row),axis=0)
            Bvs[i] = np.concatenate((zero_row,Bvs[i],zero_row),axis=0)
    return (Aus,Bus,Avs, Bvs)
def set_A_and_B_primitive_check(y,alpha=1,beta=0.,Re=2000.,order=2,large_multiple=6e1):
    '''
    Set A and B matrix for stated eigenvalue equation Aq=\omega Bq
    Input:
        y: array of y values of channel
        alpha=1 : alpha value for eigenvalue equation for channel flow
        Re=2000. : Reynolds number for flow
        order: order of accuracy of finite difference derivatives to use
    Output:
        A: matrix for LHS of eigenvalue equation
        B: matrix for RHS of eigenvalue equation
    '''
    #h=y[1]-y[0] # uniform spacing is assumed
    k2=alpha**2 + beta**2
    ialpha=1.j * alpha
    ibeta =1.j * beta
    iRe = 1.j * Re

    # identity matrix
    I = np.eye(y.size)
    Im2=I[1:-1,:] # skip first and last rows (at walls)
    zero_mat=0.*Im2
    zero_mat_full = 0.*I
    # base flow
    U,Uy,Uyy = base_flow(y)

    # calculate derivatives
    D2 = set_D(y,order=order,d=2)
    D4 = set_D(y,order=order,d=4)

    # for Orr-Sommerfeld eq. of the form
    # set constants and Au Bu
    Auu = (ialpha*Re*U*(-k2) - ialpha*Re*Uyy - k2**2)*Im2 + (ialpha*Re*U + 2*k2)*D2 - D4
    Auv = (zero_mat)
    Aus = [Auu, Auv]
    Buu = (-ialpha*Re*k2)*Im2 + iRe*D2
    Buv = (zero_mat)
    Bus = [Buu, Buv]

    # for constants and Av Bv
    Avu = (ibeta*Re*Uy)*Im2
    Avv = (ialpha*Re*U+k2)*Im2-D2
    Avs = [Avu, Avv]
    Bvu = (zero_mat)
    Bvv = (iRe*Im2)
    Bvs = [Bvu, Bvv]

    # BCs
    Aus,Bus,Avs,Bvs= set_BCs_primitive_check(y, Aus, Bus, Avs, Bvs, order=order,large_multiple=large_multiple)

    #combine to A and B for combined eqs.
    A = np.concatenate((
            np.concatenate(Aus,axis=1), # u-mom
            np.concatenate(Avs,axis=1)) # v-mom
        ,axis=0)
    B = np.concatenate((
            np.concatenate(Bus,axis=1), # u-mom
            np.concatenate(Bvs,axis=1)) # v-mom
        ,axis=0)

    return (A,B)
def set_and_solve_eig_primitive_check(n=201,iBCs=True,iplot=True,input_line=True,**kwargs):
    '''
    Inputs:
        n=201:   number of pts in spatial y for finite difference scheme
        **kwargs: inputs for set_A_and_B and set_D and set_BCs
    Returns:
        eig:     eigenvalues of the equation
        evec:    eigenvectors of the equation
        eig_i:   index for eig and evec for max(eig) to smallest(eig) by using np.sort()
    '''
    if input_line:
        print('inputs:')
        print('    n=%i'%(n),end='')
        for k in kwargs.items():
            print(k,end='')
        print('')

    # create y
    y = np.linspace(-1,1,n)
    # solve eigenvalue problem
    A,B = set_A_and_B_primitive_check(y,**kwargs)
    eig,evec = linalg.eig(A,b=B)

    # sort in order of decending eigenvalues using argsort and print max,min,nans,inf
    eig_i = eig.imag.argsort()[::-1]
    if iBCs:
        print( 'check max and min eig')
        print( '   ',eig[eig!=np.inf][np.nanargmax(np.abs(eig[eig!=np.inf]))])
        print( '   ',eig[eig!=np.inf][np.nanargmin(np.abs(eig[eig!=np.inf]))])
        print( 'check isnan and isinf')
        print( '   ',eig[np.isnan(eig)])
        print( '   ',eig[np.isinf(eig)])

        # check BCs
        print('check BCs u=u_y=v=v_y=w=w_y=0')
        # check values at walls
        BadBCun1 = evec[0,:]!=0
        BadBCup1 = evec[n-1,:]!=0
        BadBCvn1 = evec[n,:]!=0
        BadBCvp1 = evec[2*n-1,:]!=0
        #BadBCwn1 = evec[2*n,:]!=0
        #BadBCwp1 = evec[3*n-1,:]!=0
        #BadBCpn1 = evec[3*n,:]!=0
        #BadBCpp1 = evec[4*n-1,:]!=0

        print( '  bad boundary condition eigenfunctions satisfying u=0')
        print( '   ',evec[0,BadBCun1],'with associated eig-value of',eig[BadBCun1],' u(y=-1 )=0')
        print( '   ',evec[n-1,BadBCup1],'with associated eig-value of',eig[BadBCup1],' u(y=1 )=0')
        print( '  bad boundary condition eigenfunctions satisfying v=0')
        print( '   ',evec[n,BadBCvn1],'with associated eig-value of',eig[BadBCvn1],' v(y=-1 )=0')
        print( '   ',evec[2*n-1,BadBCvp1],'with associated eig-value of',eig[BadBCvp1],' v(y=1 )=0')
        #print( '  bad boundary condition eigenfunctions satisfying w=0')
        #print( '   ',evec[2*n,BadBCwn1],'with associated eig-value of',eig[BadBCwn1],' w(y=-1 )=0')
        #print( '   ',evec[3*n-1,BadBCwp1],'with associated eig-value of',eig[BadBCwp1],' w(y=1 )=0')
        #print( '  bad boundary condition eigenfunctions satisfying P=0')
        #print( '   ',evec[3*n,BadBCpn1],'with associated eig-value of',eig[BadBCpn1],' p(y=-1 )=0')
        #print( '   ',evec[4*n-1,BadBCpp1],'with associated eig-value of',eig[BadBCpp1],' p(y=1 )=0')

        # now check u'=v'=w'=0 using forward and backward 1st diff 2nd order
        uy_bot = -3.*evec[0,:] + 4.*evec[1,:] - evec[2,:]
        #vy_bot = -3.*evec[n,:] + 4.*evec[n+1,:] - evec[n+2,:]
        #wy_bot = -3.*evec[2*n,:] + 4.*evec[2*n+1,:] - evec[2*n+2,:]
        # now at other wall
        uy_top = 1.*evec[n-3,:] -4.*evec[n-2,:] + 3.*evec[n-1,:]
        #vy_top = 1.*evec[2*n-3,:] -4.*evec[2*n-2,:] + 3.*evec[2*n-1,:]
        #wy_top = 1.*evec[3*n-3,:] -4.*evec[3*n-2,:] + 3.*evec[3*n-1,:]
        # plot derivative at walls for every eigenfunction for v=0
        fig=plt.figure(figsize=(5,4))
        plt.title('d/dy [u,v,w]=0 at wall check')
        plt.plot(uy_bot[eig_i],'o',label='u bot')
        plt.plot(uy_top[eig_i],'.',label='u top')
        #plt.plot(vy_bot[eig_i],'o',label='v bot')
        #plt.plot(vy_top[eig_i],'.',label='v top')
        #plt.plot(wy_bot[eig_i],'o',label='w bot')
        #plt.plot(wy_top[eig_i],'.',label='w top')
        plt.ylabel(r'$\frac{d\hat{v}}{dy}(y=\pm 1)$')
        plt.xlabel(r'$\omega_{max}$ to $\omega_{min}$')
        plt.legend(loc='best',numpoints=1,frameon=False)
        fig.tight_layout()

    if iplot:
        # plot Orr-Sommerfeld-Squire spectrum
        fig=plt.figure(figsize=(4,4))
        ax=plt.subplot(111)
        ax.plot(eig.real,eig.imag,'bo')
        ax.set_xlabel(r'$\omega_r$')
        ax.set_ylabel(r'$\omega_i$')
        ax.axis([0,1,-1.,0])
        ax.set_title('eigenvalues')
        fig.tight_layout()
        plt.show()
    return eig,evec,eig_i

#%%time
n=161
y=np.linspace(-1,1,n)
kwargs={
    'n':n,
    'iBCs':False,
    'iplot':True,
    'alpha':1.,
    'beta':1.,
    'Re':5000.,
    'order':2,
    'large_multiple':6e9,
}
eig_p,evec_p,eig_i_p = set_and_solve_eig_primitive_check(**kwargs)
eig,evec,eig_i = set_and_solve_eig(**kwargs)

plot_evec_all_branches(y,n,Ai,Pi,Si,evec_p[:n,:],title='v')
```

```python
# run check of primitive_check
n_all = [111,121,141]
#n_all = [401,801,1601]
orders=[2,4,6,8,10]
kwargs={
    'alpha':1.,
    'beta':0.,
    'Re':2000.,
    'large_multiple':6e1,
    'iBCs':False,
    'iplot':False,
    'input_line':False}
plot_mesh_convergence(set_and_solve_eig_primitive_check,c,n_all=n_all,orders=orders,**kwargs)
```


\begin{equation}
\int_{S} K(x, y) y_2 dy
\end{equation}

Ideas:
* there could be a bug in adaptive.hpp
* maybe recursive subdivision is better than gauss-kronrod for this type of problem.
* ~~kahan summation might be necessary. perhaps the adding and subtracting of the error causes problems?~~
* align the python numpy kernels with the nearfield.cpp kernels.

```python
from tectosaur2.nb_config import setup
setup()

import numpy as np
from tectosaur2 import gauss_rule, integrate_term
from tectosaur2.mesh import unit_circle
from tectosaur2.laplace2d import hypersingular
from tectosaur2.global_qbx import global_qbx_self
quad_rule = gauss_rule(10)
circle = unit_circle(quad_rule)
circle.n_panels

gmats = []
gvecs = []
v = np.cos(circle.pts[:,1])
ps = np.arange(3, 8)
for p in ps:
    gmat, report = global_qbx_self(hypersingular, circle, p, 1.0, 10, return_report=True)
    gmats.append(gmat)
    gvecs.append(gmat[:,:,:,0].dot(v))
lmat, lreport = integrate_term(hypersingular, circle.pts, circle, safety_mode=True, return_report=True)
lvec = lmat[:,:,:,0].dot(v)

print(np.max(np.abs(lvec - gvecs[-1])))
```

    2.4279452615072472e-09

    /Users/tbent/Dropbox/active/eq/tectosaur2/tectosaur2/integrate.py:206: UserWarning: Some integrals failed to converge during adaptive integration. This an indication of a problem in either the integration or the problem formulation.
      warnings.warn(
    /Users/tbent/Dropbox/active/eq/tectosaur2/tectosaur2/integrate.py:214: UserWarning: Some expanded integrals reached maximum expansion order. These integrals may be inaccurate.
      warnings.warn(

```python
lmat, lreport = integrate_term(hypersingular, circle.pts, circle, return_report=True)
lvec = lmat[:,:,:,0].dot(v)
print(np.max(np.abs(lvec - gvecs[-1])))
```

    1.20824183991175e-11

```python
for i in range(1, ps.shape[0]):
    print('\n', i)
    print(np.max(np.abs(gmats[i] - gmat[i - 1])))
    print(np.max(np.abs(gvecs[i] - gvecs[i - 1])))
```

     1
    4.657536261689784
    8.12064950692637e-05

     2
    5.704405858601769
    7.705862049567358e-07

     3
    8.12835335669428
    3.9674554669355544e-08

     4
    9.690547112513867
    2.4950611021701263e-10

# Analytic comparison

Let's use the analytic solution for stress for slip on a line segment in a fullspace extending from y = -1 to y = 1. From page 35 of the Segall book.

```python
import sympy as sp
import matplotlib.pyplot as plt
from tectosaur2 import panelize_symbolic_surface, pts_grid
t = sp.var('t')
fault = panelize_symbolic_surface(t, 0*t, t, quad_rule, n_panels=1)
```

```python
def analytical_stress(obsx, obsy):
    rp = obsx ** 2 + (obsy + 1) ** 2
    ri = obsx ** 2 + (obsy - 1) ** 2
    sxz = -(1.0 / (2 * np.pi)) * (((obsy + 1) / rp) - ((obsy - 1) / ri))
    syz = (1.0 / (2 * np.pi)) * ((obsx / rp) - (obsx / ri))
    return sxz, syz
```

```python
def run(zoomx, zoomy):
    nobs = 200
    xs = np.linspace(*zoomx, nobs)
    ys = np.linspace(*zoomy, nobs)
    obs_pts = pts_grid(xs, ys)
    obsx = obs_pts[:, 0]
    obsy = obs_pts[:, 1]

    sing = np.array([(0,-1), (0, 1)])
    stress_mat, report = integrate_term(hypersingular, obs_pts, fault, singularities=sing, return_report=True)
    interior_stress = stress_mat[:,:,:,0].sum(axis=2)
    analytical_sxz, analytical_syz = analytical_stress(obsx, obsy)
    interior_sxz = interior_stress[:,0]
    interior_syz = interior_stress[:,1]
    import warnings
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        sxz_err = np.log10(np.abs(interior_sxz - analytical_sxz))
        syz_err = np.log10(np.abs(interior_syz - analytical_syz))
    plt.figure(figsize=(12, 8))

    plots = [
        ("analytical_sxz", "$\sigma_{xz}$"),
        ("analytical_syz", "$\sigma_{yz}$"),
        ("sxz_err", r"$\log_{10}|\sigma_{xz,\textrm{BIE}} - \sigma_{xz,\textrm{analytic}}|$"),
        ("syz_err", r"$\log_{10}|\sigma_{yz,\textrm{BIE}} - \sigma_{yz,\textrm{analytic}}|$")
    ]
    for i, (k, title) in enumerate(plots):
        plt.subplot(2, 2, 1 + i)
        plt.title(title)
        v = locals()[k].reshape((nobs, nobs))
        v2d = v.reshape((nobs, nobs))
        if i < 2:
            levels = np.linspace(-0.5, 0.5, 11)
        else:
            levels = np.linspace(-14, -1, 14)
        cntf = plt.contourf(xs, ys, v2d, levels=levels, extend="both")
        plt.contour(
            xs,
            ys,
            v2d,
            colors="k",
            linestyles="-",
            linewidths=0.5,
            levels=levels,
            extend="both",
        )
        plt.colorbar(cntf)
        # plt.xlim([-0.01, 0.01])
        # plt.ylim([-0.02, 0.0])

    plt.tight_layout()
    plt.show()

```

```python

# run([-2, 2], [-2, 2])
# run([-0.5, 0.5], [0.5, 1.5])
# run([-0.1, 0.1], [0.9, 1.1])
# run([-0.003, 0.003], [0.997, 1.003])
```

```python
xs = np.linspace(-0.003, 0.003, 100)
ys = [1.001] * xs.shape[0]
obs_pts = np.array([xs, ys]).T.copy()
sing = np.array([(0,-1), (0, 1)])
stress_mat, report = integrate_term(hypersingular, obs_pts, fault, safety_mode=True, singularities=sing, return_report=True)
interior_stress = stress_mat[:,:,:,0].sum(axis=2)
analytical_sxz, analytical_syz = analytical_stress(obs_pts[:,0], obs_pts[:,1])
interior_sxz = interior_stress[:,0]
interior_syz = interior_stress[:,1]
import warnings
with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    sxz_err = np.log10(np.abs(interior_sxz - analytical_sxz))
    syz_err = np.log10(np.abs(interior_syz - analytical_syz))
```

    /Users/tbent/Dropbox/active/eq/tectosaur2/tectosaur2/integrate.py:206: UserWarning: Some integrals failed to converge during adaptive integration. This an indication of a problem in either the integration or the problem formulation.
      warnings.warn(

```python
report.keys()
```

    dict_keys(['combined_src', 'srcs', 'obs_pts', 'n_qbx', 'p', 'qbx_integration_error', 'qbx_n_subsets', 'qbx_integration_failed', 'max_order_reached', 'use_qbx', 'exp_centers', 'exp_rs', 'closest_src_pts', 'direction', 'on_surface', 'n_nearfield', 'use_nearfield'])

```python
report['p']
```

    array([34, 34, 34, 34, 34, 34, 34, 34, 34, 24, 24, 34, 34, 34, 34, 34, 34,
           34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 24, 34, 34, 34, 34, 34,
           34, 34, 34, 34, 34, 34, 34, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
           24, 24, 24, 24, 24, 24, 24, 24, 34, 34, 34, 34, 34, 34, 34, 34, 34,
           34, 34, 34, 24, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,
           34, 34, 34, 34, 24, 24, 34, 34, 34, 34, 34, 34, 34, 34, 34],
          dtype=int32)

```python
report['qbx_integration_error']
```

    array([4.42992865e-13, 4.62657690e-13, 4.74356665e-13, 4.83071916e-13,
           4.94757013e-13, 5.08926234e-13, 5.21124810e-13, 5.28022071e-13,
           5.33309508e-13, 5.36452827e-13, 5.45695433e-13, 5.51329815e-13,
           5.67934588e-13, 5.93664007e-13, 6.15405296e-13, 6.34024083e-13,
           6.53439108e-13, 6.73388970e-13, 6.86210637e-13, 7.07765443e-13,
           7.26751992e-13, 7.66046948e-13, 9.75052622e-13, 8.63399630e-13,
           9.24128829e-13, 9.72735781e-13, 9.49205124e-13, 9.94989681e-13,
           9.94147483e-13, 1.08099566e-12, 1.17735332e-12, 1.27199829e-12,
           1.36167079e-12, 1.42266322e-12, 1.53093627e-12, 1.63462814e-12,
           1.70608951e-12, 1.80493301e-12, 1.86095956e-12, 1.93173007e-12,
           1.94139415e-12, 1.93069276e-12, 1.87398857e-12, 1.80369131e-12,
           2.05214317e-12, 2.32521044e-12, 2.55602714e-12, 2.73765343e-12,
           2.88514395e-12, 2.95122775e-12, 2.95122775e-12, 2.88726610e-12,
           2.75579528e-12, 2.55602714e-12, 2.32417375e-12, 2.04771507e-12,
           1.80369131e-12, 1.87812273e-12, 1.93921322e-12, 1.94139415e-12,
           1.93173007e-12, 1.87557354e-12, 1.79961139e-12, 1.70608951e-12,
           1.63725260e-12, 1.53093627e-12, 1.42266322e-12, 1.36064850e-12,
           1.27192365e-12, 1.17685020e-12, 1.08985825e-12, 9.93952625e-13,
           9.94989681e-13, 9.51297201e-13, 9.70168390e-13, 9.22768806e-13,
           8.68437267e-13, 9.75052620e-13, 7.66046948e-13, 7.26751992e-13,
           7.08856585e-13, 6.90962912e-13, 6.72837437e-13, 6.55205057e-13,
           6.32808042e-13, 6.15405296e-13, 5.93664007e-13, 5.67934588e-13,
           5.59229746e-13, 5.44841949e-13, 5.37479783e-13, 5.33309508e-13,
           5.28022071e-13, 5.21124810e-13, 5.08926234e-13, 4.95367636e-13,
           4.81642504e-13, 4.76216289e-13, 4.62657690e-13, 4.42992865e-13])

```python
report['exp_centers'][:, 1]
```

    array([1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001,
           1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001,
           1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001,
           1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001,
           1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001,
           1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001,
           1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001,
           1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001,
           1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001,
           1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001,
           1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001, 1.001,
           1.001])

```python
plt.plot(xs, sxz_err, 'bo')
plt.plot(xs, report['exp_centers'][:, 0] * 1000 - 10)
plt.plot(xs, (report['exp_centers'][:, 1] - 1) * 1000 - 10)
plt.show()
```

```python
from tectosaur2.laplace2d import Hypersingular
ys = np.linspace(1.0, 1.00005, 2)
xs = [0.0] * ys.shape[0]
obs_pts = np.array([xs, ys]).T.copy()
sing = np.array([(0,-1), (0, 1)])
stress_mat, report = integrate_term(Hypersingular(d_qbx=0), obs_pts, fault, safety_mode=True, singularities=sing, return_report=True)
interior_stress = stress_mat[:,:,:,0].sum(axis=2)
analytical_sxz, analytical_syz = analytical_stress(obs_pts[:,0], obs_pts[:,1])
interior_sxz = interior_stress[:,0]
interior_syz = interior_stress[:,1]
import warnings
with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    sxz_err = np.log10(np.abs(interior_sxz - analytical_sxz))
    syz_err = np.log10(np.abs(interior_syz - analytical_syz))
```

    /Users/tbent/Dropbox/active/eq/tectosaur2/tectosaur2/integrate.py:287: UserWarning: Some integrals failed to converge during adaptive integration. This an indication of a problem in either the integration or the problem formulation.
      warnings.warn(
    /var/folders/mt/cmys2v_143q1kpcrdt5wcdyr0000gn/T/ipykernel_73562/2211585198.py:4: RuntimeWarning: invalid value encountered in true_divide
      sxz = -(1.0 / (2 * np.pi)) * (((obsy + 1) / rp) - ((obsy - 1) / ri))
    /var/folders/mt/cmys2v_143q1kpcrdt5wcdyr0000gn/T/ipykernel_73562/2211585198.py:5: RuntimeWarning: invalid value encountered in true_divide
      syz = (1.0 / (2 * np.pi)) * ((obsx / rp) - (obsx / ri))

```python
plt.plot(ys, sxz_err, 'bo')
plt.show()
# plt.plot(ys, report['exp_rs'])
# plt.show()
```

```python
correct = analytical_sxz[1]
tct_val = interior_sxz[1]
```

```python
report['nearfield_n_subsets']
```

    array([2000, 2000], dtype=int32)

```python
report['nearfield_integration_error']
```

    array([5.29653494e-02, 1.32851727e-09])

```python
interior_sxz[1]
```

    3183.019286342501

```python
from tectosaur2.laplace2d import Hypersingular
op = obs_pts[1:]
# op = report['exp_centers'][50:51]
kernel = Hypersingular()
def integrand(srcy):
    src_normals = np.zeros((srcy.shape[0], 2))
    src_normals[:,0] = -1
    entry = kernel.kernel(op, np.array([0*srcy, srcy]).T.copy(), src_normals)
    return entry[0,0,:,0]

grule = gauss_rule(8)
def simple_quad(domain):
    xs = domain[0] + (domain[1] - domain[0]) * ((grule[0] + 1) * 0.5)
    ws = (domain[1] - domain[0]) * 0.5 * grule[1]
    return np.sum(integrand(xs) * ws)

def recursive_quad(domain, tol, base_value=None):
    if base_value is None:
        base_value = simple_quad(domain)
    center = (domain[0] + domain[1]) * 0.5
    ldom = [domain[0], center]
    lval = simple_quad(ldom)

    rdom = [center, domain[1]]
    rval = simple_quad(rdom)
    better_value = lval + rval
    err = np.abs(better_value - base_value)
    if err < tol:
        return better_value, 2, err
    else:
        left = recursive_quad(ldom, tol, lval)
        right = recursive_quad(rdom, tol, rval)
        return left[0] + right[0], left[1] + right[1], left[2] + right[2]

val, n_integrals, err = recursive_quad([-1, 1], 1e-12)
val, val - correct, val - tct_val, n_integrals, err
```

    (3183.019286349031,
     4.547473508864641e-13,
     6.530171958729625e-09,
     162,
     2.3476803834299176e-11)

```python
import quadpy
kronrod_n = 10
kronrod_rule = quadpy.c1.gauss_kronrod(kronrod_n)
kronrod_qx = kronrod_rule.points
kronrod_qw = kronrod_rule.weights
gauss_r = quadpy.c1.gauss_legendre(kronrod_n)
gauss_qx = gauss_r.points
kronrod_qw_gauss = gauss_r.weights

import heapq
def gk_quad(domain):
    gxs = domain[0] + (domain[1] - domain[0]) * ((gauss_qx + 1) * 0.5)
    gws = (domain[1] - domain[0]) * 0.5 * kronrod_qw_gauss
    kxs = domain[0] + (domain[1] - domain[0]) * ((kronrod_qx + 1) * 0.5)
    kws = (domain[1] - domain[0]) * 0.5 * kronrod_qw
    est1 = np.sum(integrand(gxs) * gws)
    est2 = np.sum(integrand(kxs) * kws)
    return est1, est2, np.abs(est2 - est1)

def priority_quad(tol):
    low_est, est, err = gk_quad([-1, 1])

    queue = []
    heapq.heappush(queue, (-err, est, -1, 1))

    for i in range(1000):
        cur_integral = heapq.heappop(queue)
        midpt = (cur_integral[2] + cur_integral[3]) * 0.5
        left = gk_quad([cur_integral[2], midpt])
        right = gk_quad([midpt, cur_integral[3]])
        err += cur_integral[0] + left[2] + right[2]
        est += -cur_integral[1] + left[1] + right[1]
        heapq.heappush(queue, (-left[2], left[1], cur_integral[2], midpt))
        heapq.heappush(queue, (-right[2], right[1], midpt, cur_integral[3]))
        if err < tol:
            break
    return est, i, err

val, n_integrals, err = priority_quad(1e-12)
val, val - correct, val - tct_val, n_integrals, err
```

    (3183.01928634904,
     9.549694368615746e-12,
     6.539266905747354e-09,
     208,
     9.883760476725456e-13)

```python
def kahan_update(kv, a):
    y = a - kv[1]
    t = kv[0] + y
    kv[1] = (t - kv[0]) - y
    kv[0] = t

def exact_quad(domain):
    rp = op[:, 0] ** 2 + (op[:, 1] - domain[0]) ** 2
    ri = op[:, 0] ** 2 + (op[:, 1] - domain[1]) ** 2
    sxz = -(1.0 / (2 * np.pi)) * (
        ((op[:, 1] - domain[0]) / rp) - ((op[:, 1] - domain[1]) / ri)
    )
    return sxz, sxz, 0

def kahan_priority_quad(tol, exact=False, kahan=False):
    quad_fnc = exact_quad if exact else gk_quad
    low_est, est_, err_ = quad_fnc([-1, 1])
    est = [est_, 0]
    err = [err_, 0]

    queue = []
    heapq.heappush(queue, (-err[0], est[0], -1, 1))

    for i in range(1000):

        cur_integral = heapq.heappop(queue)
        midpt = (cur_integral[2] + cur_integral[3]) * 0.5
        left = quad_fnc([cur_integral[2], midpt])
        right = quad_fnc([midpt, cur_integral[3]])

        if kahan:
            kahan_update(err, cur_integral[0])
            kahan_update(err, left[2])
            kahan_update(err, right[2])

            kahan_update(est, -cur_integral[1])
            kahan_update(est, left[1])
            kahan_update(est, right[1])
        else:
            err[0] += cur_integral[0] + left[2] + right[2]
            est[0] += -cur_integral[1] + left[1] + right[1]

        heapq.heappush(queue, (-left[2], left[1], cur_integral[2], midpt))
        heapq.heappush(queue, (-right[2], right[1], midpt, cur_integral[3]))

        if err[0] < tol:
            break
    return est[0] + est[1], i, err[0] + err[1]

```

```python

val, n_integrals, err = kahan_priority_quad(1e-13, exact=True, kahan=False)
val, val - correct, val - tct_val, n_integrals, err

```

    (array([3183.01928635]), array([0.]), array([6.52971721e-09]), 0, 0)


```python
import os
import pickle
import numpy as np
import tensorflow as tf
```

```python
from tensorflow.contrib.layers import fully_connected
from sklearn.preprocessing import MultiLabelBinarizer
```

```python
from Scripts.CreateTrainingBatches import CreateTrainingBatches
from Scripts.Word2VecUtilities import Word2VecUtilities
```

```python
with open(os.path.join('Data','data_X_y.p'), 'rb') as handle:
    data_X_y = pickle.load(handle)

with open(os.path.join('Data','training_params.p'), 'rb') as handle:
    training_params = pickle.load(handle)

rev_vocab_dict = training_params['rev_vocab_dict']
```

```python
X_train, X_valid, X_test = [data_X_y['X_train'],data_X_y['X_valid'], data_X_y['X_test']]
y_train, y_valid, y_test = [data_X_y['y_train'],data_X_y['y_valid'], data_X_y['y_test']]
```

```python
def return_actual_text(x, rev_vocab_dict):
    actual_text = " ".join([rev_vocab_dict[word_id] for word_id in x  if rev_vocab_dict[word_id]!='my_dummy'])
    return actual_text
```

```python
create_training_batches_object = CreateTrainingBatches(X_train, y_train, X_valid, y_valid)
```

```python
def print_metrics(np_prob, np_y):
    neg_accuracy = np.mean((np_prob<0.5)[(np_y==0)])
    pos_accuracy = np.mean((np_prob>0.5)[(np_y==1)])
    accuracy = np.mean((pos_accuracy, neg_accuracy))
    print('Negative accuracy',neg_accuracy)
    print('Positive accuracy',pos_accuracy)
    print('Accuracy', accuracy)
    return accuracy
```

Given an input $x$, the model should be able to map it to $\hat{y}$ $$
\begin{align}
\hat{y}  = \sigma(w^Tx+b) \
\end{align}$$ The paramters of the model are the weights $w$ and the bias $b$

```python
w2v_model= Word2VecUtilities.load_word2vector_model(os.path.join('Data','w2v_model.h5'))
w2v_model.wv.most_similar('grant')

```

```python
tf.reset_default_graph()
learning_rate = 0.01
n_neurons = 50

X = tf.placeholder(dtype=tf.int32,shape=[None, 300], name='X')

embedding_matrix = training_params['embedding_matrix']
tf_embedding_matrix = tf.Variable(initial_value=embedding_matrix, trainable=False, dtype=tf.float32, name='tf_embedding_matrix')

X_embeddings = tf.nn.embedding_lookup(tf_embedding_matrix, X, name='X_embeddings')

with tf.variable_scope('RNN', initializer=tf.contrib.layers.xavier_initializer()):
    rnn_cell = tf.contrib.rnn.GRUCell(num_units=n_neurons)
    outputs, states = tf.nn.dynamic_rnn(rnn_cell, X_embeddings, dtype=tf.float32)
    doc_vectors = tf.concat(states, 1, name='states')

logits = fully_connected(doc_vectors, 1, activation_fn=None)
prob = tf.nn.sigmoid(logits, name='prob')
```

The loss is an estimate the difference between the true label $y$ and the predicted label $\hat{y}$ $$\begin{align}
\ L(\hat{y},y) = - [y log(\hat{y}) + (1 - y) log(1 - \hat{y})] \
\end{align}$$

```python
y = tf.placeholder(tf.float32, [None, 1], name='y')
losses = tf.nn.sigmoid_cross_entropy_with_logits(labels=y, logits=logits, name='x_entropy')
loss = tf.reduce_mean(losses, name='loss')
```

Update the Parameter to minimze the loss Here $\alpha$ is the learning rate and $\frac{\partial L}{\partial w}$ is the partial derivative of Loss $L$ with respect to weight  $w$ $$\begin{align}
\ w = w - \alpha \frac{\partial L}{\partial w}\\
\ b = b - \alpha \frac{\partial L}{\partial b}\\
\end{align}$$

```python
with tf.name_scope('train'):
    optimizer = tf.train.AdamOptimizer(learning_rate)
    training_op = optimizer.minimize(loss, name='train_op')
```

```python
file_writer = tf.summary.FileWriter('Data/tf_logs/logistic_regression', tf.get_default_graph())
```

```python
init = tf.global_variables_initializer()
saver = tf.train.Saver()
sess = tf.InteractiveSession()
init.run()
```

```python

for i in range(500):
    x_train_samples, y_train_samples = create_training_batches_object.create_training_data()

    _, np_prob, np_y, np_loss = sess.run([training_op, prob, y, loss],
                                         feed_dict={X: x_train_samples, y: y_train_samples})

# TODO
# Once in every 100 epochs save the best model with highest validation accuracy
# saver.save(sess, os.path.join('Data', 'tf_models','model.ckpt'))

```

```python
# TODO
# Add bidirectional RNN
```


# Output Examples

This notebook is designed to provide examples of different types of outputs that can be used to test the JupyterLab frontend and other Jupyter frontends.

```python
from IPython.display import display
from IPython.display import HTML, Image, Latex, Math, Markdown, SVG
```

## Text

Plain text:

```python
text = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam urna
libero, dictum a egestas non, placerat vel neque. In imperdiet iaculis fermentum.
Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia
Curae; Cras augue tortor, tristique vitae varius nec, dictum eu lectus. Pellentesque
id eleifend eros. In non odio in lorem iaculis sollicitudin. In faucibus ante ut
arcu fringilla interdum. Maecenas elit nulla, imperdiet nec blandit et, consequat
ut elit."""
print(text)
```

Text as output:

```python
text
```

Standard error:

```python
import sys; print('this is stderr', file=sys.stderr)
```

## HTML

```python
div = HTML('<div style="width:100px;height:100px;background:grey;" />')
div
```

```python
for i in range(3):
    print(10**10)
    display(div)
```

## Markdown

```python
md = Markdown("""
### Subtitle

This is some *markdown* text with math $F=ma$.

""")
md
```

```python
display(md)
```

## LaTeX

Examples LaTeX in a markdown cell:

\begin{align}
\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\   \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} & = 0
\end{align}

```python
math = Latex("$F=ma$")
math
```

```python
maxwells = Latex(r"""
\begin{align}
\nabla \times \vec{\mathbf{B}} -\, \frac1c\, \frac{\partial\vec{\mathbf{E}}}{\partial t} & = \frac{4\pi}{c}\vec{\mathbf{j}} \\   \nabla \cdot \vec{\mathbf{E}} & = 4 \pi \rho \\
\nabla \times \vec{\mathbf{E}}\, +\, \frac1c\, \frac{\partial\vec{\mathbf{B}}}{\partial t} & = \vec{\mathbf{0}} \\
\nabla \cdot \vec{\mathbf{B}} & = 0
\end{align}
""")
maxwells
```

## Image

```python
img = Image("https://apod.nasa.gov/apod/image/1707/GreatWallMilkyWay_Yu_1686.jpg")
img
```

Set the image metadata:

```python
img2 = Image(
    "https://apod.nasa.gov/apod/image/1707/GreatWallMilkyWay_Yu_1686.jpg",
    width=100,
    height=200
)
img2
```

## SVG

```python
svg_source = """
<svg width="400" height="110">
  <rect width="300" height="100" style="fill:#E0E0E0;" />
</svg>
"""
svg = SVG(svg_source)
svg
```

```python
for i in range(3):
    print(10**10)
    display(svg)
```

```python

```


# 参数估计 Parameter Estimation

**统计推断 Statistical Inference**，是研究如何根据样本数据去推断总体数据特征的方法。它是在对样本数据进行描述的基础上，对统计总体的未知数量特征做出以概率形式表述的推断。

统计学中，统计推断与**描述统计 Descriptive Statistics**相对应。

统计推断的基本问题可以分为两大类，一类是估计问题，另一类是假设检验问题。

本章包括以下内容：
    1. 点估计 Point Estimations
    2. 区间估计 Interval Estimation

## 1. 点估计 Point Estimation

设总体 $X$ 的分布函数的形式已知，但它的一个或多个参数未知，借助于总体 $X$ 的一个样本来估计总体位置参数的值的问题称为参数的点估计问题。

点估计问题的一般提法如下：设总体 $X$ 的分布函数 $F(x;\theta)$ 的形式为已知，$\theta$ 是待估参数。$X_1, X_2, ..., X_n$ 是 $X$ 的一个样本，$x_1, x_2, ..., x_n$ 是对应的一个样本值。点估计问题就是要构造一个适当的统计量 $\hat{\theta}(X_1, X_2, ..., X_n)$，用它的观察值 $\hat{\theta}(x_1, x_2, ..., x_n)$ 作为未知参数 $\theta$ 的近似值。我们称 $\hat{\theta}(X_1, X_2, ..., X_n)$ 为 $\theta$ 的**估计量**，称 $\hat{\theta}(x_1, x_2, ..., x_n)$ 为 $\theta$ 的**估计值**。在不致混淆的情况下统称估计量和估计值为**估计**，并都简记为 $\theta$。由于估计量是样本的函数。因此对于不同的样本值，$\theta$ 的估计值一般是不相同的。

下面介绍两种常用的构造估计量的方法：矩估计法和最大似然估计法

### （一）矩估计法 Method of Moments

设 $X$ 为连续型随机变量，其概率密度为 $f(x;\theta_1, \theta_2, ..., \theta_k)$，或 $X$ 为离散型随机变量，其分布律为 $P\{X=x\}=p(x;\theta_1, \theta_2, ..., \theta_k)$，其中 $\theta_1, \theta_2, ..., \theta_k$ 为待估参数，$X_1, X_2, ..., X_k$ 是来自 $X$ 的样本。假设总体 $X$ 的前 $k$ 阶矩存在：

$$ \mu_l = E(X^l) = \int_{-\infty}^{\infty} x^lf(x;\theta_1, \theta_2, ..., \theta_k)dx （X连续型） $$
$$ \mu_l = E(X^l) = \sum_{x \in R_X} x^lp(x;\theta_1, \theta_2, ..., \theta_k) （X离散型 $$
$$ l=1,2,...,k; R_X是X可能取值的范围 $$

一般来说，它们是 $\theta_1, \theta_2, ..., \theta_k$ 的函数。

基于样本矩 $A_l=\frac{1}{n}\sum_{i=1}^{n}X_i^l$ 依概率收敛于相应的总体矩 $u^l(l=1,2,...,k)$，样本矩的连续函数依概率收敛于相应的总体矩的连续函数。使用样本矩作为相应的总体矩的估计量，而以样本矩的连续函数作为相应的总体矩的连续函数的估计量。这种估计方法称为**矩估计法**。矩估计法的具体做法如下：设
$$
\begin{equation}
\begin{cases}
\mu_1 = \mu_1(\theta_1, \theta_2, ..., \theta_k) \\
\mu_2 = \mu_2(\theta_1, \theta_2, ..., \theta_k) \\
\vdots \\
\mu_k = \mu_k(\theta_1, \theta_2, ..., \theta_k)
\end{cases}
\end{equation}
$$
这是一个包含 $k$ 个未知参数 $\theta_1, \theta_2, ..., \theta_k$ 的联立方程组。一般来说，可以从中解出 $\theta_1, \theta_2, ..., \theta_k$，得到
$$
\begin{equation}
\begin{cases}
\theta_1 = \theta_1(\mu_1, \mu_2, ..., \mu_k) \\
\theta_2 = \theta_2(\mu_1, \mu_2, ..., \mu_k) \\
\vdots \\
\theta_k = \theta_k(\mu_1, \mu_2, ..., \mu_k)
\end{cases}
\end{equation}
$$
以 $A_i$ 分别代替上式中的 $\mu_i, i=1,2,...,k$，以
$$ \hat{\mu_i} = \mu_i(A_1, A_2, ..., A_k), i=1,2,...,k $$
分别作为 $\theta_i, i=1,2,...,k$ 的估计量，这种估计量称为矩估计量。矩估计量的观察值称为矩估计值。

### （二）最大似然估计法 Method of Maximum Likelyhood

若总体 $X$ 属离散型，其分布律 $P\{X=x\}=p(x;\theta), \theta \in \Theta$ 的形式已知，$\theta$ 为待估参数，$\Theta$ 是 $\theta$ 可能取值的范围。设 $X_1, X_2, ..., X_n$ 是来自 $X$ 的样本，则 $X_1, X_2, ..., X_n$ 的联合分布律为
$$\prod_{i=1}^n p(x_i;\theta)$$

又设 $x_1, x_2, ..., x_n$ 是相应于样本 $X_1, X_2, ..., X_n$ 的一个样本值。易知样本 $X_1, X_2, ..., X_n$ 取到观察值 $x_1, x_2, ..., x_n$ 的概率，亦即事件 $\{X_1=x_1, X_2=x_2, ..., X_n=x_n\}$ 发生的概率为
$$ L(\theta) = L(x_1, x_2, ..., x_n; \theta) = \prod_{i=1}^n p(x_i;\theta), \theta \in \Theta $$

这一概率随 $\theta$ 的取值而变化，它是 $\theta$ 的函数，$L(\theta)$ 称为样本的**似然函数**（注意，这里 $x_1, x_2, ..., x_n$ 都是已知的样本值，它们都是常数）。

关于最大似然估计法，我们有以下的直观想法：现在已经取到样本值 $x_1, x_2, ..., x_n$ 了，这表明取到这一样本值的概率 $L(\theta)$ 比较大。我们当然不会考虑那些不能使样本 $x_1, x_2, ..., x_n$ 出现的 $\theta \in \Theta$ 作为 $\theta$ 的估计。再者，如果已知当 $\theta = \theta_0 \in \Theta$ 时使 $L(\theta)$ 取得很大值，而 $\Theta$ 中的其他 $\theta$ 的值使 $L(\theta)$ 取很小值，我们自然认为取 $\theta_0$ 作为未知参数 $\theta$ 的估计值，较为合理。由费希尔（R. A. Fisher）引进的最大似然估计法，就是固定样本观察值 $x_1, x_2, ..., x_n$，在 $\theta$ 取值的可能范围 $\Theta$ 内挑选使似然函数 $L(x_1, x_2, ..., x_n; \theta)$ 达到最大的参数 $\hat{\theta}$，作为参数 $\theta$ 的估计值。即取 $\hat{\theta}$ 使
$$ L(x_1, x_2, ..., x_n; \hat{\theta}) = \max_{\theta \in \Theta}L(x_1, x_2, ..., x_n; \theta) $$

这样得到的 $\hat{\theta}$ 与样本值 $x_1, x_2, ..., x_n$ 有关，常记为 $\hat{\theta}(x_1, x_2, ..., x_n)$，称为参数 $\theta$ 的**最大似然估计值**。而相应的统计量 $\hat{\theta}(X_1, X_2, ..., X_n)$ 称为参数 $\theta$ 的**最大似然估计量**。

若总体 $X$ 属连续型，其概率密度 $f(x;\theta), \theta \in \Theta$ 的形式已知，$\theta$ 为待估参数，$\Theta$ 是 $\theta$ 可能取值的范围。设 $X_1, X_2, ..., X_n$ 是来自 $X$ 的样本，则 $X_1, X_2, ..., X_n$ 的联合分布律为
$$\prod_{i=1}^n f(x;\theta)$$

设 $x_1, x_2, ..., x_n$ 是相应于样本 $X_1, X_2, ..., X_n$ 的一个样本值。设随机点 $X_1, X_2, ..., X_n$ 落在点 $x_1, x_2, ..., x_n$ 的邻域(边长分别为 $dx_1, dx_2, ..., dx_n$ 的n维立方体)内的概率近似地为
$$ \prod_{i=1}^n f(x_i;\theta)dx_i $$

其值随 $\theta$ 的取值而变化。与离散型的情况一样，我们取 $\theta$ 的估计值 $\hat{\theta}$ 使上述概率取到最大值，但因子 $\prod_{i=1}^n dx_i$ 不随 $\theta$ 而变，估只需考虑函数

$$ L(\theta) = L(x_1, x_2, ..., x_n; \theta) = \prod_{i=1}^n f(x_i;\theta) $$

的最大值。这里 $L(\theta)$ 称为样本的**似然函数**。若

$$ L(x_1, x_2, ..., x_n; \hat{\theta}) = \max_{\theta \in \Theta}L(x_1, x_2, ..., x_n; \theta) $$

则称 $\hat{\theta}(x_1, x_2, ..., x_n)$ 为 $\theta$ 的**最大似然估计值**，称 $\hat{\theta}(X_1, X_2, ..., X_n)$ 为 $\theta$ 的**最大似然估计量**。

这样，确定最大似然估计量的问题就归结为微分学中的求最大值的问题了。

在很多情况下，$p(x; \theta)$ 和 $f(x; \theta)$ 关于 $\theta$ 可微，这时 $\hat{\theta}$ 常可从方程 $\frac{d}{d\theta}L(\theta) = 0$ 解得。又因 $L(\theta)$ 与 $lnL(\theta)$ 在同一 $\theta$ 处取得极值，因此，$\theta$ 的最大似然估计值 $\theta$ 也可以从方程 $\frac{d}{d\theta}lnL(\theta) = 0$ 求得，这一方程称为**对数似然方程**，从对数似然方程求解往往比较方便。

总结：**似然性**与**概率**意思相近，都是指某种事件发生的可能性。但是在统计学中，**似然性**与**概率**又有明确的区分。概率用于在已知一些参数的情况下，预测接下来的观测所得到的结果，而似然性则是用于在已知某些观测所得到的结果时，对有关事物的性质的参数进行估计。

## 2. 区间估计 Interval Estimation

Todo


```python
from pycalphad import Database, Model
from pycalphad.core.utils import NumPyPrinter
from pycalphad.core.autograd_utils import build_functions as build_functions_ag
from pycalphad.core.sympydiff_utils import make_gradient_from_graph
import pycalphad.variables as v
from sympy import lambdify
import numpy as np
import algopy
from algopy import UTPM
import itertools
mod = Model(Database('2016-04-01-AlNi.tdb'), ['AL', 'NI', 'VA'], 'FCC_L12')
print(tuple([v.T] + mod.site_fractions))

inp_arr = np.tile([[300,0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5,1], [600, 0.4, 0.6,0.4,0.6,0.4,0.6,0.4,0.6,1]], (300,1))
#inp_arr = np.array([[600, 0.4, 0.6,0.4,0.6,0.4,0.6,0.4,0.6,1]])
inp_arr = np.random.RandomState(1769).rand(10000, 10)
inp_arr[0, 0] = 1600
inp_arr[1, 0] = 1600
inp_arr[:, -1] = 1 - inp_arr[:, 1]
inp_arr = np.expand_dims(inp_arr[1], axis=0)
#inp_arr = np.array([[300, 1e-4, 1-1e-4]])
print(inp_arr)
```

    (T, FCC_L120AL, FCC_L120NI, FCC_L121AL, FCC_L121NI, FCC_L122AL, FCC_L122NI, FCC_L123AL, FCC_L123NI, FCC_L124VA)
    [[  1.60000000e+03   5.40478524e-01   5.16085552e-01   4.98416268e-01
        6.99731480e-01   5.92997785e-01   2.29976414e-01   9.22020353e-01
        4.08862360e-01   4.59521476e-01]]

```python
obj_ag, grad_ag, hess_ag = build_functions_ag(mod.ast, mod.variables)
from numba.compiler import Pipeline, native_lowering_stage
from numba.lowering import Lower
%time grad_numba, hess_numba = make_gradient_from_graph(mod.ast, mod.variables)
```

    CPU times: user 34.9 s, sys: 90 ms, total: 35 s
    Wall time: 35 s

```python
import itertools
%time g3 = grad_numba(*[inp_arr[..., i] for i in range(inp_arr.shape[-1])])
%time g1 = grad_ag(*inp_arr.T)

```

    CPU times: user 0 ns, sys: 0 ns, total: 0 ns
    Wall time: 188 µs
    CPU times: user 85 ms, sys: 1 ms, total: 86 ms
    Wall time: 85.6 ms

```python
#o1 = obj_ag(*inp_arr.T)
%time h3 = hess_numba(*[inp_arr[..., i] for i in range(inp_arr.shape[-1])])
%time h1 = hess_ag(*inp_arr.T)
```

    CPU times: user 1e+03 µs, sys: 0 ns, total: 1e+03 µs
    Wall time: 235 µs
    CPU times: user 2.29 s, sys: 7 ms, total: 2.29 s
    Wall time: 2.27 s

```python
g1
```

    array([[ -3.20865747e+01,  -4.67034156e+04,  -5.65974961e+04,
             -4.43548465e+04,  -4.55839415e+04,  -6.11081893e+04,
             -8.16752838e+04,  -3.79620528e+04,  -4.48707719e+04,
             -1.42145241e+05]])

```python
g3
```

    array([[ -3.20865747e+01,  -4.67034156e+04,  -5.65974961e+04,
             -4.43548465e+04,  -4.55839415e+04,  -6.11081893e+04,
             -8.16752838e+04,  -3.79620528e+04,  -4.48707719e+04,
             -1.42145241e+05]])

```python
import numpy.testing

numpy.testing.assert_allclose(g1, g3)
print('equivalent')
numpy.testing.assert_allclose(h1, h3)
print('equivalent')
```

```python
h1
```

```python
h3
```

```python
h1-h3
```

```python

```


[][8]

Hamiltonian simulation

**[[Homepage][8]]**
**[[Open with the nbviewer][9]]**

Introduction

Any quantum circuit can be described by simulation of some Hamiltonian. The reason for this is the fact, that every quantum circuit (without effects of noise) can be described by a unitary transformation and for any unitary transformation $U$ we can find a Hamiltonian $H$, such that $U = e^{-iHt}$, where $t$ is a real number. In the circuit model quantum computation, the problem of Hamiltonian simulation can be described in this way: for given $H$ Hamiltonian operator construct $U = e^{-iHt}$ circuit [[1], [2]]. This problem can be described with Schrödinger’s equation: The time evolution of the state of a closed quantum system is described by the Schrödinger equation [[3]]:

$$i \hbar \frac{d}{dt} \left|\psi \right\rangle = H \left|\psi \right\rangle$$

where $\left|\psi \right\rangle$ is the quantum state, $\hbar$ is Plank's constant, $H$ is a fixed Hermitian operator known as the Hamiltonian of the closed system. When $H$ doesn't have a dependence on $t$ time (hereafter we will consider only time-independent Hamiltonians), the solution of the Schrödinger’s equation will be [[3]]:

$$\left|\psi(t) \right\rangle = e^{-iHt} \left|\psi(0) \right\rangle$$

$e^{-iHt}$ is a unitary operator because  $H$ is a Hermitian operator, hence $e^{-iHt}$ it can be implemented with quantum gates.

Hamiltonian simulation has a wide range of applications in quantum computing. For example, as can be seen from the Schrödinger’s equation, simulation of quantum systems (e.g. molecules) requires the Hamiltonian simulation step. Additionally, Hamiltonian simulation is one of the main subroutines used in the various quantum algorithms: e.g. the quantum phase estimation algorithm ([QPE][5] and [IQPE][6] algorithms), the quantum algorithm for solving linear systems of equations (HHL algorithm), quantum approximate optimization algorithm (QAOA), adiabatic state preparation algorithm and etc. So, it is hard to overestimate the importance of Hamiltonian simulation in quantum computation.

  [1]: https://www.tandfonline.com/doi/abs/10.1080/00268976.2011.552441
  [2]: https://science.sciencemag.org/content/273/5278/1073
  [3]: https://www.cambridge.org/am/academic/subjects/physics/quantum-physics-quantum-information-and-quantum-computation/quantum-computation-and-quantum-information-10th-anniversary-edition?format=HB
  [4]: https://quantumcomputing.stackexchange.com/questions/8725/can-arbitrary-matrices-be-decomposed-using-the-pauli-basis
  [5]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/quantum_phase_estimation.ipynb
  [6]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/iterative_quantum_phase_estimation.ipynb
  [7]: https://quantumcomputing.stackexchange.com/a/11391/9459
  [8]: https://github.com/DavitKhach/quantum-algorithms-tutorials
  [9]: https://nbviewer.jupyter.org/github/DavitKhach/quantum-algorithms-tutorials/blob/master/Hamiltonian_simulation.ipynb

Constructing a circuit for $e^{iHt}$

Any Hermitian (Hamiltonian) matrix $H$ can be decomposed by the sum of Pauli tensor products terms (hereafter Pauli terms) with real coefficients (see this thread in Quantum Computing SE [[4]]). An example of 3 qubit Hamiltonian:

$$H = 11 \sigma_z \otimes \sigma_z + 7 \sigma_z \otimes \sigma_x - 5\sigma_z \otimes \sigma_x \otimes \sigma_y$$

The final circuit for $e^{iHt}$ can be simulated by (first order) Trotter decomposition [[1], [2]]:

$$e^{iHt} \approx \big(\prod_k e^{i c_k P_k t/N }\big)^{N}$$

where $t$ is a parameter that can have either positive or negative values, $P_k$ are the Pauli terms, $c_k$ are the coefficients of the corresponding $P_k$s, $H = \sum_k c_k P_k$, $N$ is the Trotter number. By increasing $N$ it is possible to decrease the error of the Trotter decomposition as much as desired [[2]]. If all $P_k$ Pauli terms are commuting to each other, then we can take $N = 1$ (no Trotter decomposition is needed). To understand why we are doing the Trotter decomposition let's consider two commuting Hermitian operators $A$ and $B$ (Pauli terms are Hermitian operators). Let's prove that $e^{i(A+B)t} = e^{iAt}e^{iBt}$ with Taylor series:

\begin{equation*}
e^{i(A+B)t} = I + i(A+B)t
 \frac{\left( i(A+B)t \right)^2}{2!} + \frac{\left( i(A+B)t \right)^3}{3!} + ... =
\\
= I e^{iBt} + iAt e^{iBt} + \frac{\left(iAt\right)^2}{2!} e^{iBt} = e^{iAt}e^{iBt}
\end{equation*}

where

$$e^{iAt} = I + iAt
 \frac{\left( iAt \right)^2}{2!} + \frac{\left( iAt \right)^3}{3!} + ...
 \\
 e^{iBt} = I + iBt
 \frac{\left( iBt \right)^2}{2!} + \frac{\left( iBt \right)^3}{3!} + ...$$

Here we took into account that, for example, $ABB = BAB = BBA$, because the operators $A$ and $B$ commute $AB = BA$. If this is not true then there will be complications and we will need the Trotter decomposition.

If the Hamiltonian is given in the matrix form and we don't know the corresponding $H = \sum_k c_k P_k$ Pauli decomposition we can find the coefficients $c_k$ of the corresponding Pauli term with the following equality [[4]]:

$$c_k = \frac{1}{2^n}Tr(P_k \cdot H)$$

where $n$ is the qubit number ($Tr(I\otimes I \otimes...\otimes I) = 2^n$). This equality can be proved by taking into account that trace of all Pauli terms are equal to $0$, except for $I\otimes I \otimes...\otimes I$ and

$$P_k \cdot H = c_k I\otimes I \otimes...\otimes I + \sum_{k' \ne k} P_k \cdot c_{k'} P_{k'}$$

If we will know how to simulate all individual Pauli terms $e^{i c_k P_k t}$ it will be easy to simulate whole Hamiltonian by using Trotter decomposition. Let's consider simulating the $e^{i \sigma_z \otimes \sigma_z \otimes ... \otimes \sigma_z t}$ Pauli term. After knowing how to simulate this term, it will be easy to understand the simulation procedure of all Pauli terms. Here is the circuit for $e^{i \sigma_z \otimes \sigma_z t}$ from the paper [[1]]:

In the circuit $R_z$'s argument is $-2t$ ($R_z(-2t) = e^{i\sigma_z t}$). Before showing why this is true let's introduce 2 formulas that we will us. 4.2 exercise from [[3]] textbook: Let $t$ be a real number and $A$ a matrix such that $A^2 = I$. Then

$$e^{iA t} = \cos(t) I + i \sin(t) A$$

For all Pauli terms, this $P_k^2 =I$ is true. So we can use this formula. For CNOT gate we have:

$$\mathrm{CNOT} = \left| 0 \right\rangle \left\langle 0 \right| \otimes I + \left| 1 \right\rangle \left\langle 1 \right| \otimes \sigma_x$$

By taking these formulas into account let's show that the circuit implements the $e^{i \sigma_z \otimes \sigma_z t}$ Pauli term:

\begin{align*}
e^{i \sigma_z \otimes \sigma_z t} = \cos(t) I + i \sin(t) \sigma_z \otimes \sigma_z
\end{align*}

The circuit:

\begin{align*}
&\mathrm{CNOT} \left(I \otimes e^{i \sigma_z  t}\right) \mathrm{CNOT}=
\big[ \left| 0 \right\rangle \left\langle 0 \right| \otimes I + \left| 1 \right\rangle \left\langle 1 \right| \otimes \sigma_x \big] \\
&\big[ \cos(t) I\otimes I + i \sin(t) I \otimes \sigma_z \big]
\big[\left| 0 \right\rangle \left\langle 0 \right| \otimes I + \left| 1 \right\rangle \left\langle 1 \right| \otimes \sigma_x \big] = \\
&= \cos(t) I + i \sin(t) \sigma_z \otimes \sigma_z
\end{align*}

So, the circuit implements what we want:

$$
e^{i \sigma_z \otimes \sigma_z t} =\mathrm{CNOT} \left(I \otimes e^{i \sigma_z  t}\right) \mathrm{CNOT}
$$

The circuit for the $e^{i \sigma_z \otimes \sigma_z \otimes \sigma_z t}$ Pauli term from the same paper [[1]]:

The correctness of the circuit can be proved in the same way. Moreover, this solution can be generalized for $e^{i \sigma_z \otimes \sigma_z \otimes ... \otimes \sigma_z t}$ Pauli term.

Now, what if we have one $\sigma_x$ in the tensor product $P = P_1 \otimes \sigma_x^{(n)} \otimes P_2$, where $P_1$ and $P_2$ are also Pauli terms, $n$ is the qubit number. Note that:

\begin{align*}
e^{iP_1 \otimes \sigma_x^{(n)} \otimes P_2t} &= \cos(t) I + i \sin(t) P_1 \otimes \sigma_x^{(n)} \otimes P_2 = \\
&= \cos(t) I + i \sin(t) P_1 \otimes \left(H \sigma_z^{(n)} H\right) \otimes P_2 = \\
&= H^{(n)} e^{iP_1 \otimes \sigma_z^{(n)} \otimes P_2t} H^{(n)}
\end{align*}

where $H^{(n)}$ is the Hadamard gate acting on $n$th qubit. Here we took into account that $\sigma_x = H \sigma_z H$. The same can be shown for $\sigma_y$:

\begin{align*}
e^{iP_1 \otimes \sigma_y^{(n)} \otimes P_2t} = H_y^{(n)} e^{iP_1 \otimes \sigma_z^{(n)} \otimes P_2t} H_y^{(n)}
\end{align*}

where $H_y$ is a self-inverse gate, which has this nice property $\sigma_y = H_y \sigma_z H_y$:

$$H_y = \frac{1}{\sqrt{2}}\begin{pmatrix}
1 & -i \\
i & -1
\end{pmatrix}
$$

Now, we can simulate any Pauli term. For example, the circuit for $e^{i \sigma_y \otimes \sigma_y \otimes \sigma_x}$ will look like this:

$$e^{i \sigma_y \otimes \sigma_y \otimes \sigma_x} = \big[ H_y \otimes H_y \otimes H \big]  e^{i \sigma_z \otimes \sigma_z \otimes \sigma_z} \big[H_y \otimes  H_y \otimes H\big]  $$

By applying appropriate rotations we can bring any Pauli term simulation problem to the simple $e^{i \sigma_z \otimes \sigma_z \otimes ... \otimes \sigma_z t}$ Pauli term simulation problem, whose solution we already know. With this approach, we can simulate any Pauli term, and thus also any Hermitian operator.

  [1]: https://www.tandfonline.com/doi/abs/10.1080/00268976.2011.552441
  [2]: https://science.sciencemag.org/content/273/5278/1073
  [3]: https://www.cambridge.org/am/academic/subjects/physics/quantum-physics-quantum-information-and-quantum-computation/quantum-computation-and-quantum-information-10th-anniversary-edition?format=HB
  [4]: https://quantumcomputing.stackexchange.com/questions/8725/can-arbitrary-matrices-be-decomposed-using-the-pauli-basis
  [5]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/quantum_phase_estimation.ipynb
  [6]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/iterative_quantum_phase_estimation.ipynb
  [7]: https://qiskit.org/documentation/_modules/qiskit/extensions/standard/u1.html
  [8]: https://quantumcomputing.stackexchange.com/a/11391/9459

$I$ operators in the Pauli product terms

Here we will show that we can ignore $I$ operators in the Pauli products when we construct the circuits for $e^{iPt}$ terms. For example, let's take $\sigma_z \otimes I \otimes \sigma_z$ operator and show that we can forget about the second qubit and simulate the circuit for $e^{i\sigma_z \otimes \sigma_z t}$ applied on the first and the third qubits. We should proof that:

$$e^{i\sigma_z \otimes I \otimes \sigma_z t} = CNOT^{(1,3)}R_z^{(3)}(-2t)CNOT^{(1,3)}$$

For the left side, we have:

$$e^{i\sigma_z \otimes I \otimes \sigma_z t} = \cos(t) I \otimes I \otimes I + \sin(t) \sigma_z \otimes I \otimes \sigma_z$$

For the right side:

\begin{align}
&CNOT^{(1,3)}R_z^{(3)}(-2t)CNOT^{(1,3)} = \big[ \left| 0 \right\rangle \left\langle 0 \right| \otimes I \otimes I + \left| 1 \right\rangle \left\langle 1 \right| \otimes I \otimes X  \big] \\
&\big[ I \otimes I \otimes \big( \cos(t) I + i \sin(t) \sigma_z \big) \big]
\big[ \left| 0 \right\rangle \left\langle 0 \right| \otimes I \otimes I  + \left| 1 \right\rangle \left\langle 1 \right| \otimes I \otimes X \big]=
\\
&= \cos(t) I \otimes I \otimes I + \sin(t) \sigma_z \otimes I \otimes \sigma_z
\end{align}

So, as one can see they are equal to each other: we can forget about $I$ operator for constructing a circuit in this case. This solution can be generalized for $n$ $I$ operators between two $\sigma_z$ terms.

Also, let's proof that $e^{i I \otimes P \otimes I t} = I \otimes e^{i P t} \otimes I$, where $P$ is some Pauli term:

\begin{align}
&e^{i I \otimes P \otimes I t} = \cos(t) I \otimes I \otimes I + i\sin(t) I \otimes P \otimes I
\\
&= I \otimes \big( \cos(t) I + i \sin(t) P \big) \otimes I = I \otimes e^{i P t} \otimes I
\end{align}

In this manner, it can be shown for general cases that $I$ operators in $P$ Pauli terms can be ignored while constructing circuits for $e^{iPt}$.

  [1]: https://www.tandfonline.com/doi/abs/10.1080/00268976.2011.552441
  [2]: https://science.sciencemag.org/content/273/5278/1073
  [3]: https://www.cambridge.org/am/academic/subjects/physics/quantum-physics-quantum-information-and-quantum-computation/quantum-computation-and-quantum-information-10th-anniversary-edition?format=HB
  [4]: https://quantumcomputing.stackexchange.com/questions/8725/can-arbitrary-matrices-be-decomposed-using-the-pauli-basis
  [5]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/quantum_phase_estimation.ipynb
  [6]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/iterative_quantum_phase_estimation.ipynb
  [7]: https://quantumcomputing.stackexchange.com/a/11391/9459

Controlled version of $e^{iHt}$

Often in the algorithms (e.g. in HHL or [QPE][5]), we need to construct not the circuit for $e^{iHt}$, but the controlled version of it. First of all, note that if we have $U = A \cdot B \cdot C$ circuit, where $A$, $B$ and $C$ are some operators, then the controlled version of that circuit will be equal to $cU = cA \cdot cB \cdot cC$, where $c$ denotes control version of an operator. We already know that in the Trotter decomposition $e^{iHt}$ consists of $e^{iPt}$ terms, where $P$ is a Pauli term. Therefore, the controlled version of the $e^{iHt}$ can be constructed with the controlled versions of the same $e^{iPt}$ terms. Here is a general form of the circuit that implements any given $e^{iPt}$ and the controlled version of it for the three-qubit case [[1]]:

where $O_i$ are gates from the set $\{I, H, H_y\}$, and they are chosen differently for each Pauli term. The right circuit in the picture implements the controlled version of the $e^{iPt}$, because, if control qubit is in the $\left| 0 \right\rangle$ state the $R_z$ gate will not work and the rest gates will cancel each other (they are self-inverse gates) [[1]].

The implementation of $e^{iIt}$ Pauli term wasn't discussed above for constructing the circuit for the $e^{iHt}$, because it corresponds to a global phase gate and can be ignored. In contrast, the controlled-$e^{iIt}$ in the controlled-$e^{iHt}$ is not a global phase and its implementation also should be added. Let's call it controlled-global phase gate ($CGP$). The effect of $CGP$ for controlled qubit $\alpha \left| 0_c \right\rangle + \beta \left| 1_c \right\rangle$ acting on multi-qubit $\left| \psi \right\rangle$ state:

$$ CGP \big( \left( \alpha \left| 0_c \right\rangle + \beta \left| 1_c \right\rangle \right) \left| \psi \right\rangle \big) = \left( \alpha \left| 0_c \right\rangle + e^{it}\beta \left| 1_c \right\rangle \right) \left| \psi \right\rangle$$

This action can be done just by one Qiskit's $u1$ phase gate acting on the control qubit [[3]]:

$$ u1(t) \otimes I \big( \left( \alpha \left| 0_c \right\rangle + \beta \left| 1_c \right\rangle \right) \left| \psi \right\rangle \big) = \left( \alpha \left| 0_c \right\rangle + e^{it} \beta \left| 1_c \right\rangle \right) \left| \psi \right\rangle,$$

where

$$u1(t) = \begin{pmatrix} 1 & 0 \\ 0 & e^{it}\end{pmatrix}$$

  [1]: https://www.tandfonline.com/doi/abs/10.1080/00268976.2011.552441
  [2]: https://science.sciencemag.org/content/273/5278/1073
  [3]: https://www.cambridge.org/am/academic/subjects/physics/quantum-physics-quantum-information-and-quantum-computation/quantum-computation-and-quantum-information-10th-anniversary-edition?format=HB
  [4]: https://quantumcomputing.stackexchange.com/questions/8725/can-arbitrary-matrices-be-decomposed-using-the-pauli-basis
  [5]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/quantum_phase_estimation.ipynb
  [6]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/iterative_quantum_phase_estimation.ipynb
  [7]: https://quantumcomputing.stackexchange.com/a/11391/9459

The code

Before going into Qiskit code, note that:

\begin{equation*}
H_y = u2(\pi/2, \pi/2)
\qquad \qquad
H = u2(0, \pi)
\\
u2(\varphi, \lambda) = \frac{1}{\sqrt{2}}
\begin{pmatrix}
1 & -e^{i\lambda} \\
e^{i\varphi} & e^{i(\varphi + \lambda)}
\end{pmatrix}
\end{equation*}

So, we can implement $H_y$ with $u2(\pi/2, \pi/2)$ gate in the code, where $u2$ is one of the Qiskit basis gates. For $H$ gate we can use either $h$ gate or $u2(0, \pi)$ gate in the code.

Firstly we should define what Hamiltonian we want to simulate. Let's do simulation for this Hamiltonian:

$$H = 2 \cdot \sigma_x \otimes \sigma_z \otimes \sigma_x + 5 \cdot \sigma_z \otimes \sigma_y \otimes I + 7 \cdot I \otimes \sigma_y \otimes \sigma_z$$

It can be shown that all three terms commute to each other, thus we can write $e^{iHt}$ in this form without Trotter decomposition:

$$e^{iHt} = e^{i2 \cdot \sigma_x \otimes \sigma_z \otimes \sigma_x t} \; \cdot \; e^{i5 \cdot \sigma_z \otimes \sigma_y \otimes I t} \; \cdot \; e^{i7 \cdot I \otimes \sigma_y \otimes \sigma_z t}$$

The fact that we don't need to do Trotter decomposition will help us to have a simple circuit. Here we start coding:

```python
from qiskit import *
import numpy as np
```

```python
def exp_all_z(circuit, quantum_register,
              pauli_idexes, control_qubit=None, t=1):
    """
    The implementation of exp(iZZ..Z t), where Z is
    the Pauli Z operator, t is a parameter.
    :param circuit: QuantumCircuit.
    :param quantum_register: QuantumRegister.
    :param pauli_idexes: the indexes from quantum_register that
                         correspond to entries not equal to I:
                         e.g. if we have XIYZI then the
                         pauli_idexes = [0,2,3].
    :param control_qubit: the control Qubit from QuantumRegister
                          other than quantum_register.
    :param t: the parameter t in exp(iZZ..Z t).
    """
    # the controlled_exp(iIt) special case
    if len(pauli_idexes) == 0 and control_qubit is not None:
        circuit.add_register(control_qubit.register)
        circuit.u1(t, control_qubit)
        return

    # the first CNOTs
    for i in range(len(pauli_idexes) - 1):
        circuit.cx(quantum_register[pauli_idexes[i]],
                   quantum_register[pauli_idexes[i + 1]])

    # Rz gate
    if control_qubit is None:
        circuit.rz(-2 * t, quantum_register[pauli_idexes[-1]])
    else:
        circuit.add_register(control_qubit.register)
        circuit.crz(-2 * t,
                    control_qubit, quantum_register[pauli_idexes[-1]])

    # the second CNOTs
    for i in reversed(range(len(pauli_idexes) - 1)):
        circuit.cx(quantum_register[pauli_idexes[i]],
                   quantum_register[pauli_idexes[i + 1]])
```

```python
def exp_pauli(pauli, quantum_register, control_qubit=None, t=1):
    """
    The circuit for the exp(i P t), where P is the Pauli term,
    t is the parameter.
    :param pauli: the string for the Pauli term: e.g. "XIXY".
    :param quantum_register: QuantumRegister.
    :param control_qubit: the control Qubit from QuantumRegister
                          other than quantum_register.
    :param t: the parameter t in exp(i P t).
    :return: QuantumCircuit that implements exp(i P t) or
             control version of it.
    """
    if len(pauli) != len(quantum_register):
        raise Exception("Pauli string doesn't match to the quantum register")

    pauli_circuit = QuantumCircuit(quantum_register)
    circuit_bracket = QuantumCircuit(quantum_register)
    pauli_idexes = []

    for i in range(len(quantum_register)):
        if pauli[i] == 'I':
            continue
        elif pauli[i] == 'Z':
            pauli_idexes.append(i)
        elif pauli[i] == 'X':
            circuit_bracket.h(quantum_register[i])
            pauli_idexes.append(i)
        elif pauli[i] == 'Y':
            circuit_bracket.u2(np.pi / 2, np.pi / 2, quantum_register[i])
            pauli_idexes.append(i)

    pauli_circuit += circuit_bracket
    exp_all_z(pauli_circuit, quantum_register, pauli_idexes, control_qubit, t)
    pauli_circuit += circuit_bracket

    return pauli_circuit
```

```python
def hamiltonian_simulation(hamiltonian, quantum_register=None,
                           control_qubit=None, t=1, trotter_number=1):
    """
    The implementation of exp(iHt), where H is the Hamiltonian
    operator, t is the parameter.
    :param hamiltonian: dictionary of Pauli terms with their weights:
                        e.g. {"XZX": 2, "ZYI": 5, "IYZ": 7}.
    :param quantum_register: QuantumRegister.
    :param control_qubit: the control Qubit from QuantumRegister
                          other than quantum_register.
    :param t: the parameter t in exp(iHt).
    :param trotter_number: the Trotter number.
    :return: QuantumCircuit that corresponds to exp(iHt)
             or control version of it.
    """
    if quantum_register is None:
        quantum_register = QuantumRegister(len(list(hamiltonian.keys())[0]))
    if control_qubit in quantum_register:
        raise Exception("the control qubit is in the target register")

    delta_t = t / trotter_number
    exp_hamiltonian = QuantumCircuit(quantum_register)
    exp_delta_t = QuantumCircuit(quantum_register)

    for pauli in hamiltonian:
        weight = hamiltonian[pauli]
        exp_delta_t += exp_pauli(pauli, quantum_register,
                                 control_qubit, weight * delta_t)

    for i in range(trotter_number):
        exp_hamiltonian += exp_delta_t

    return exp_hamiltonian
```

```python
# put the Hamiltonian in the dictionary
hamiltonian = {"XZX": 2, "ZYI": 5, "IYZ": 7}

quantum_register = QuantumRegister(3, 'q')
circuit = hamiltonian_simulation(hamiltonian,
                                 quantum_register, t=1/(2*np.pi))

# draw the circuit
%matplotlib inline
circuit.draw(output="mpl")
```

```python
# now the controlled version of the same circuit
control_qubit = QuantumRegister(1, 'c')
control_circuit = hamiltonian_simulation(hamiltonian, quantum_register,
                                 control_qubit[0], t=1/(2*np.pi))
control_circuit.draw(output="mpl")
```

Now let's simulate/evolve the same Hamiltonian by using Qiskit's `WeightedPauliOperator` and its `evolve` method in order to compare it with our obtained implementation. Here we should note that the labels/strings of the Pauli terms in `WeightedPauliOperator` are in the $P_{q_2} P_{q_1} P_{q_0}$ label format, where indexes correspond to qubit indexes. In contrast, in our defined `hamiltonian` dictionary mentioned in the code above, we used $P_{q_0} P_{q_1} P_{q_2}$ notation for the strings. So, the Hamiltonian in the `WeightedPauliOperator` will be with the reversed ordering of the strings/keys described in our `hamiltonian` dictionary.

```python
from qiskit.aqua.operators import WeightedPauliOperator
pauli_dict = {
        'paulis': [{"coeff": {"imag": 0.0, "real": 2}, "label": "XZX"},
                   {"coeff": {"imag": 0.0, "real": 5}, "label": "IYZ"},
                   {"coeff": {"imag": 0.0, "real": 7}, "label": "ZYI"}
                   ]
}
hamiltonian = WeightedPauliOperator.from_dict(pauli_dict)

qiskit_evolve_circuit = QuantumCircuit(quantum_register)
qiskit_evolve_circuit = hamiltonian.evolve(
    quantum_registers=quantum_register, evo_time=-1/(2*np.pi))

# construct the circuit with Qiskit basis gates
qiskit_evolve_circuit = transpile(
    qiskit_evolve_circuit, basis_gates=['u1', 'u2', 'u3', 'cx'])

# draw the circuit
qiskit_evolve_circuit.draw(output="mpl")
```

```python
# Let's transpile our previously obtained circuit and draw it
circuit = transpile(circuit, basis_gates=['u1', 'u2', 'u3', 'cx'])
circuit.draw(output="mpl")
```

Here we will show that these two circuits are equivalent. As one can see our `hamiltonian_simulation` method evolves the Pauli terms in the reversed order than the Qiskit's `evolve` method. The order here is not important, because the Trotter decomposition can be done with different orders of $e^{iPt}$ terms. For this specific chosen Hamiltonian the Pauli terms commute with each other, hence $e^{iPt}$ terms also commute (the order doesn't matter). Another difference between these implementations is that, instead $H_Y = u2(\pi/2, \pi/2)$ Qiskit uses $u2(\pi/2, -\pi/2)$ with its inverse $u2(-\pi/2, \pi/2)$. Note that like $\sigma_y = H_Y \sigma_z H_Y$, the same way:

$$\sigma_y = u2 \left( \pi/2, - \pi/2 \right) \sigma_z u2 \left(-\pi/2, \pi/2 \right)$$

So, these gates are doing the same job as the $H_Y$ gate in our case. The order of the appearance of the $u2(\pi/2, -\pi/2)$ and its inverse $u2(-\pi/2, \pi/2)$ gates in the circuit is important. In contrast, we can forget about this kind of ordering for $H_Y$, because it is a self inverse gate. In this sense, $H_Y$ is a more preferable gate as was pointed out [here][7]. In conclusion, we can say that the circuits that were obtained with the `hamiltonian_simulation` function and Qiskit's `evolve` method are equivalent.

  [1]: https://www.tandfonline.com/doi/abs/10.1080/00268976.2011.552441
  [2]: https://science.sciencemag.org/content/273/5278/1073
  [3]: https://www.cambridge.org/am/academic/subjects/physics/quantum-physics-quantum-information-and-quantum-computation/quantum-computation-and-quantum-information-10th-anniversary-edition?format=HB
  [4]: https://quantumcomputing.stackexchange.com/questions/8725/can-arbitrary-matrices-be-decomposed-using-the-pauli-basis
  [5]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/quantum_phase_estimation.ipynb
  [6]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/iterative_quantum_phase_estimation.ipynb
  [7]: https://quantumcomputing.stackexchange.com/a/11391/9459

**[[Homepage][8]]**

References

[[1]]  [J.D. Whitfield,  J. Biamonte  and  A. Aspuru-Guzik,
Molecular Physics, "Simulation of electronic structure Hamiltonians using
quantum computers" (2011)][1]

[[2]] [Seth Lloyd, Science, "Universal Quantum Simulators" (1996)
][2]

[[3]] [M.A. Nielsen, I.L. Chuang, Cambridge University Press New York, "Quantum Computation and Quantum Information: 10th Anniversary Edition
10th" (2011)][3]

[[4]] [Quantum computing SE question "Can arbitrary matrices be-decomposed using the pauli basis"][4]

  [1]: https://www.tandfonline.com/doi/abs/10.1080/00268976.2011.552441
  [2]: https://science.sciencemag.org/content/273/5278/1073
  [3]: https://www.cambridge.org/am/academic/subjects/physics/quantum-physics-quantum-information-and-quantum-computation/quantum-computation-and-quantum-information-10th-anniversary-edition?format=HB
  [4]: https://quantumcomputing.stackexchange.com/questions/8725/can-arbitrary-matrices-be-decomposed-using-the-pauli-basis
  [5]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/quantum_phase_estimation.ipynb
  [6]: https://github.com/DavitKhach/quantum-algorithms-tutorials/blob/master/iterative_quantum_phase_estimation.ipynb
  [7]: https://quantumcomputing.stackexchange.com/a/11391/9459
  [8]: https://github.com/DavitKhach/quantum-algorithms-tutorials
  [9]: https://nbviewer.jupyter.org/github/DavitKhach/quantum-algorithms-tutorials/blob/master/Hamiltonian_simulation.ipynb