# Calculating the time dependent solution for $\eta$ and $\theta$ domain Implicit Upwinding
\begin{equation}\label{p1introhpa} \Psi _{\eta \eta \tau }  = \frac{1}{{R_s }}\Psi _{\eta \eta \eta \eta }  + \Psi _{\eta \eta \eta } \Psi _\theta   - \Psi _{\eta \eta \theta } \Psi _\eta\\
-1 \le \eta \le1, \quad 0 \le \theta \le \pi
\end{equation}

The numerical solution found in this notebook is only stable for $0 < R_s \lesssim 55$

```julia
using DifferentialEquations
using Plots
using BenchmarkTools
using DelimitedFiles
using LaTeXStrings
using SparseArrays
using LinearAlgebra
```

In this code the vector $\Psi_j$ is used to store the solution at the current time step and $\Psi_{jnext}$ is psi value at the next time step

```julia
pyplot()
function Ψ_smallr(θ::Float64, η::Float64 ,r::Float64)
    return(-η*(η^2-1)*sin(θ)/2.0 + r*η*(η^2-1)^2.0*(2.0+η^2)*sin(2*θ)/560.0 +
        r^2.0*η*(η^2-1)^2*((-591 -2294*η^2 + 161*η^4 + 1428*η^6)*sin(θ) + 3.0*(423 + 166*η^2 - 553*η^4 - 84*η^6)sin(3*θ))/62092800)
end

function Ψ_smallr_d1(θ::Float64, η::Float64 ,r::Float64)
    return(-(3.0*η^2-1)*sin(θ)/2.0 + r*(7*η^6-9*η^2+2)*sin(2*θ)/560.0 +
        r^2.0*(3*(-197 - 1112*η^2 + 6930*η^4 - 2772*η^6 - 8085*η^8 + 5236*η^10)*sin(θ) + (1269 - 6120*η^2 - 6930*η^4 + 24948*η^6 - 10395*η^8 - 2772*η^10)*sin(3*θ))/62092800.0)
end

function  Ψ_smallr_d2(θ::Float64, η::Float64 ,r::Float64)
    return(-3.0*η*sin(θ) + r*(42*η^5-18*η)*sin(2*θ)/560.0 +
        r^2.0*(3*( - 2224*η + 27720*η^3 - 16632*η^5 - 64680*η^7 + 52360*η^9)*sin(θ) + (- 12240*η - 27720*η^3 + 149688*η^5 - 83160*η^7 - 27720*η^9)*sin(3*θ))/62092800.0)
end

function  Ψ_smallr_d3(θ::Float64, η::Float64 ,r::Float64)
    return(-3.0*sin(θ) + r*(210*η^4-18)*sin(2*θ)/560.0 +
        r^2.0*(3*( - 2224 + 83160*η^2 - 83160*η^4 - 452760*η^6 + 471240*η^8)*sin(θ) + (- 12240 - 83160*η^2 + 748440*η^4 - 582120*η^6 - 249480*η^8)*sin(3*θ))/62092800.0)
end

function Ψ_bigr(θ::Float64, η::Float64)
    return(sin(pi*η)*sin(θ)/pi)
end

function Ψ_bigr_d1(θ::Float64, η::Float64)
    return(cos(pi*η)*sin(θ))
end

function  Ψ_bigr_d2(θ::Float64, η::Float64)
    return(-sin(pi*η)*sin(θ)*pi)
end

function  Ψ_bigr_d3(θ::Float64, η::Float64)
     return(-cos(pi*η)*sin(θ)*pi^2)
end

function ψ_ηηθ_core_wind_highorder(ψ,n, m, M)

    if (ψ[n+1,m] < ψ[n-1,m] && m < M-2   )
    forward = -25*(-ψ[n-2,m]/12 +  4*ψ[n-1,m]/3  -5*ψ[n,m]/2 + 4*ψ[n+1,m]/3 -ψ[n+2,m]/12)/12+
              +4*( -ψ[n-2,m+1]/12 +  4*ψ[n-1,m+1]/3  -5*ψ[n,m+1]/2 + 4*ψ[n+1,m+1]/3 -ψ[n+2,m+1]/12)+
              -3*( -ψ[n-2,m+2]/12 +  4*ψ[n-1,m+2]/3  -5*ψ[n,m+2]/2 + 4*ψ[n+1,m+2]/3 -ψ[n+2,m+2]/12)+
              +4*( -ψ[n-2,m+3]/12 +  4*ψ[n-1,m+3]/3  -5*ψ[n,m+3]/2 + 4*ψ[n+1,m+3]/3 -ψ[n+2,m+3]/12)/3+
                -( -ψ[n-2,m+4]/12 +  4*ψ[n-1,m+4]/3  -5*ψ[n,m+4]/2 + 4*ψ[n+1,m+4]/3 -ψ[n+2,m+4]/12)/4
        return(forward)
    elseif(ψ[n+1,m] > ψ[n-2,m] && m > 4 )
    backward =     (-ψ[n-2,m-4]/12 +  4*ψ[n-1,m-4]/3 -5*ψ[n,m-4]/2 + 4*ψ[n+1,m-4]/3 -ψ[n+2,m-4]/12)/4+
              -4*(  -ψ[n-2,m-3]/12 +  4*ψ[n-1,m-3]/3 -5*ψ[n,m-3]/2 + 4*ψ[n+1,m-3]/3 -ψ[n+2,m-3]/12)/3+
              +3*(  -ψ[n-2,m-2]/12 +  4*ψ[n-1,m-2]/3 -5*ψ[n,m-2]/2 + 4*ψ[n+1,m-2]/3 -ψ[n+2,m-2]/12)+
              -4*(  -ψ[n-2,m-1]/12 +  4*ψ[n-1,m-1]/3 -5*ψ[n,m-1]/2 + 4*ψ[n+1,m-1]/3 -ψ[n+2,m-1]/12)+
             +25*(  -ψ[n-2,m]/12 +    4*ψ[n-1,m]/3   -5*ψ[n,m]/2 + 4*ψ[n+1,m]/3     -ψ[n+2,m]/12)/12

        #println("B",backward)

        return (backward)
    else
    central = (-ψ[n-2,m-2]/12 +  4*ψ[n-1,m-2]/3  -5*ψ[n,m-2]/2 + 4*ψ[n+1,m-2]/3 -ψ[n+2,m-2]/12)/12-
           2*( -ψ[n-2,m-1]/12 +  4*ψ[n-1,m-1]/3  -5*ψ[n,m-1]/2 + 4*ψ[n+1,m-1]/3 -ψ[n+2,m-1]/12)/3+
           2*( -ψ[n-2,m+1]/12 +  4*ψ[n-1,m+1]/3  -5*ψ[n,m+1]/2 + 4*ψ[n+1,m+1]/3 -ψ[n+2,m+1]/12)/3-
             ( -ψ[n-2,m+2]/12 +  4*ψ[n-1,m+2]/3  -5*ψ[n,m+2]/2 + 4*ψ[n+1,m+2]/3 -ψ[n+2,m+2]/12)/12
        return(central)
    end
end

function ψ_ηηθ_core_wind(ψ,n, m, M)

    if (ψ[n+1,m] < ψ[n-1,m] && m < M-2   )
    forward =   -( -ψ[n-2,m  ]/12 +  4*ψ[n-1,m  ]/3  -5*ψ[n,m  ]/2 + 4*ψ[n+1,m  ]/3 -ψ[n+2,m  ]/12)+
                 ( -ψ[n-2,m+1]/12 +  4*ψ[n-1,m+1]/3  -5*ψ[n,m+1]/2 + 4*ψ[n+1,m+1]/3 -ψ[n+2,m+1]/12)
        #println("F",forward)
        return(forward)
    elseif(ψ[n+1,m] > ψ[n-1,m] && m > 4 )
    backward =  -(  -ψ[n-2,m-1]/12 +  4*ψ[n-1,m-1]/3 -5*ψ[n,m-1]/2 + 4*ψ[n+1,m-1]/3 -ψ[n+2,m-1]/12)+
                (  -ψ[n-2,m]/12 +    4*ψ[n-1,m]/3   -5*ψ[n,m]/2 + 4*ψ[n+1,m]/3     -ψ[n+2,m]/12)
        #println("B",backward)
        return (backward)
    else
    central = (-ψ[n-2,m-2]/12 +  4*ψ[n-1,m-2]/3  -5*ψ[n,m-2]/2 + 4*ψ[n+1,m-2]/3 -ψ[n+2,m-2]/12)/12-
           2*( -ψ[n-2,m-1]/12 +  4*ψ[n-1,m-1]/3  -5*ψ[n,m-1]/2 + 4*ψ[n+1,m-1]/3 -ψ[n+2,m-1]/12)/3+
           2*( -ψ[n-2,m+1]/12 +  4*ψ[n-1,m+1]/3  -5*ψ[n,m+1]/2 + 4*ψ[n+1,m+1]/3 -ψ[n+2,m+1]/12)/3-
             ( -ψ[n-2,m+2]/12 +  4*ψ[n-1,m+2]/3  -5*ψ[n,m+2]/2 + 4*ψ[n+1,m+2]/3 -ψ[n+2,m+2]/12)/12
        #println("C",central)
        return(central)
    end
end

function ψ_θ_core_wind(ψ, n, m, M)
    #If \psi_eta_eta_eta > 0 then upwind with forward difference
    ψ_ηηη= -ψ[n-2,m]/2 +     ψ[n-1,m]      -ψ[n+1,m]   + ψ[n+2,m]/2

    if (ψ_ηηη > 0 && m < M-2   )
    forward =   -ψ[n,m] + ψ[n,m+1]
        #println("F",forward)
        return(forward)
    elseif(ψ_ηηη < 0  && m > 1 )
    backward =  -ψ[n,m-1] + ψ[n,m]
        #println("B",backward)
        return (backward)
    else
    central = ψ[n,m-2]/12 -  2*ψ[n,m-1]/3  +2*ψ[n,m+1]/3 - ψ[n,m+2]/12
        #println("C",central)
        return(central)
    end
end

```

    ψ_θ_core_wind (generic function with 1 method)

```julia
#Change these to constants after
const R = Float64(250.0) # Reynolds number
const N = 200  # Total number of intervals (steps-1) in the eta direction
const M = 256  # Total number of intervals (steps-1) theta direction
const J = 5000    # Total number of time steps
ψ_current = zeros(N+1,M+1)
ψ_next =zeros(N+1,M+1)
ψ_j = ψ_current
ψ_jnext = ψ_next
const Δη = 2.0/N
const Δθ = pi/M
const Δt = 0.001
const α = 0
const c = Δt/Δη^2
const d3 =      -1.0/12.0 - c/R
const d2 =      4.0/3.0   + 4*c/R
const d1 =     -5.0/2.0   -6*c/R

A =spdiagm(-2=>ones(N-3)*d3 ,-1 => ones(N-2)*d2, 0 => ones(N-1)*d1, 1 => ones(N-2)*d2,2 => ones(N-3)*d3)

A[1,1] = d1 + d3 #Boundary condition psi@i=2 = 0, psi_eta@i=2 = -sin(theta) add -2d3\delta\eta\sine(theta) to RHS
A[end,end] = d1 + d3 #Boundary condition psi@i=N+1 = 0, psi_eta@i=N+1 = -sin(theta) add 2d3\delta\eta\sine(theta) to RHS
LUA = lu(A)
varinfo(r"A")
```

| name                    |       size | summary                                       |
|:----------------------- | ----------:|:--------------------------------------------- |
| A                       | 17.172 KiB | 199×199 SparseMatrixCSC{Float64, Int64}       |
| LUA                     | 17.195 KiB | SuiteSparse.UMFPACK.UmfpackLU{Float64, Int64} |
| matrix_A_at_boundaries! |    0 bytes | typeof(matrix_A_at_boundaries!)               |
| matrix_A_for_coreflow!  |    0 bytes | typeof(matrix_A_for_coreflow!)                |

```julia
ψ_current[:,1] .=0.0
ψ_current[:,M+1] .=0.0
ψ_current[1,:] .= 0.0
ψ_current[N+1,:] .= 0.0
for n in 2:N
    for m in 2:M
        ψ_current[n,m] = Ψ_smallr(Δθ*(m-1),-1.0+Δη*(n-1),R)
        #ψ_current[n,m] = 0
    end
end
ψ_current
#n=1 is ficticous point at eta=-1-deltaeta, n=2 is at eta=-1 and n=N+2 and n=N+3 is ficticious at eta=1+deltaeta

```

    201×257 Matrix{Float64}:
     0.0   0.0           0.0          …   0.0           0.0          0.0
     0.0  -0.000126119  -0.000252214     -0.000201029  -0.000100518  0.0
     0.0  -0.000261402  -0.000522742     -0.000323425  -0.000161713  0.0
     0.0  -0.000408992  -0.000817864     -0.000381343  -0.000190666  0.0
     0.0  -0.000571538  -0.00114287      -0.000387604  -0.00019379   0.0
     0.0  -0.000751229  -0.00150214   …  -0.00035377   -0.00017687   0.0
     0.0  -0.000949824  -0.00189919      -0.000290224  -0.0001451    0.0
     0.0  -0.00116869   -0.00233673      -0.000206239  -0.000103119  0.0
     0.0  -0.00140881   -0.00281677      -0.00011005   -5.50453e-5   0.0
     0.0  -0.00167086   -0.00334062      -8.91939e-6   -4.51224e-6   0.0
     0.0  -0.00195518   -0.00390896   …   9.07962e-5    4.53012e-5   0.0
     0.0  -0.00226183   -0.00452193       0.000183585   9.16382e-5   0.0
     0.0  -0.00259062   -0.00517915       0.00026472    0.000132135  0.0
     ⋮                                ⋱                 ⋮
     0.0   0.00226183    0.00452193      -0.000183585  -9.16382e-5   0.0
     0.0   0.00195518    0.00390896   …  -9.07962e-5   -4.53012e-5   0.0
     0.0   0.00167086    0.00334062       8.91939e-6    4.51224e-6   0.0
     0.0   0.00140881    0.00281677       0.00011005    5.50453e-5   0.0
     0.0   0.00116869    0.00233673       0.000206239   0.000103119  0.0
     0.0   0.000949824   0.00189919       0.000290224   0.0001451    0.0
     0.0   0.000751229   0.00150214   …   0.00035377    0.00017687   0.0
     0.0   0.000571538   0.00114287       0.000387604   0.00019379   0.0
     0.0   0.000408992   0.000817864      0.000381343   0.000190666  0.0
     0.0   0.000261402   0.000522742      0.000323425   0.000161713  0.0
     0.0   0.000126119   0.000252214      0.000201029   0.000100518  0.0
     0.0   0.0           0.0          …   0.0           0.0          0.0

```julia
#wireframe(range(0,stop = pi,length = M+1),range(-1,stop = 1,length = N+1),ψ_j)

```

```julia
function make_ψ_jnext_on_boundaries!(ψ_j, ψ_jnext, parameters)
    (Δθ, Δη, R, N, M, rc, rd1, rd2, rd3, d1, d2, d3, A, LUA) = parameters
    for m = 2:M-2:M #For the first theta profile and last profile use second order difference formula for theta near boundaries

    #at n=1 psi[1,:] = 0 and we are only calculating the interior points from n=2 to n=N
    n=2
    bc=2*Δη*sin(Δθ*(m-1))
    bcp1=2*Δη*sin(Δθ*m)
    bcm1=2*Δη*sin(Δθ*(m-2))
    rhs = @view ψ_jnext[2:N,m]

    #Remember to fix the RHS so that the bc terms are still included

    rhs[1] = rd3*(ψ_j[2,m]+bc)    +    rd2*ψ_j[n-1,m] + rd1*ψ_j[n,m]+rd2*ψ_j[n+1,m] + rd3*ψ_j[n+2,m]+
                 rc*(-bc/2)*(-ψ_j[n,m-1]/2 + ψ_j[n,m+1]/2)-
                 rc*(-(-(ψ_j[2,m-1]+bcm1)/12 +   4*ψ_j[n-1,m-1]/3     -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -ψ_j[n+2,m-1]/12)/2+
                      (-(ψ_j[2,m+1]+bcp1)/12 +   4*ψ_j[n-1,m+1]/3     -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -ψ_j[n+2,m+1]/12)/2)*
                       (bc/12)- d3*bc

        for n = 3:N-1
            rhs[n-1] = rd3*ψ_j[n-2,m]    +    rd2*ψ_j[n-1,m] + rd1*ψ_j[n,m]+rd2*ψ_j[n+1,m] + rd3*ψ_j[n+2,m]
        end#End of the n loop

    #at n=N psi[N+1,:] = 0
    n=N
    rhs[N-1] = rd3*ψ_j[n-2,m]    +    rd2*ψ_j[n-1,m] + rd1*ψ_j[n,m]+rd2*ψ_j[n+1,m] + rd3*(ψ_j[N,m]-bc)+
                 rc*(-bc/2)*(-ψ_j[n,m-1]/2 + ψ_j[n,m+1]/2)-
                 rc*(-(-ψ_j[n-2,m-1]/12 + 4*ψ_j[n-1,m-1]/3  -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -(ψ_j[N,m-1]-bcm1)/12)/2+
                      (-ψ_j[n-2,m+1]/12 + 4*ψ_j[n-1,m+1]/3  -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -(ψ_j[N,m+1]-bcp1)/12)/2)*
                    (bc/12) + d3*bc

    matrix_A_at_boundaries!(ψ_j, m, parameters)
    lu!(LUA,A)
    ldiv!(LUA,rhs)
    end#End of the m loop
end

function make_ψ_jnext_in_core_region!(ψ_j, ψ_jnext, parameters)
    (Δθ, Δη, R, N, M, rc, rd1, rd2, rd3, d1, d2, d3, A, LUA) = parameters
    for m = 3:M-1

    #at n=1 psi[1,:] = 0 and we are only calculating the interior points from n=2 to n=N
    n=2
    bc=2*Δη*sin(Δθ*(m-1))
    bcp1=2*Δη*sin(Δθ*m)
    bcp2=2*Δη*sin(Δθ*(m+1))
    bcm1=2*Δη*sin(Δθ*(m-2))
    bcm2=2*Δη*sin(Δθ*(m-3))

    rhs = @view ψ_jnext[2:N,m]

    #Remember to fix the RHS so that the bc terms are still included

    rhs[1] = rd3*(ψ_j[2,m]+bc)    +    rd2*ψ_j[n-1,m] + rd1*ψ_j[n,m]+rd2*ψ_j[n+1,m] + rd3*ψ_j[n+2,m]+
             rc*(-bc/2)*( ψ_j[n,m-2]/12 -  2*ψ_j[n,m-1]/3  +2*ψ_j[n,m+1]/3 - ψ_j[n,m+2]/12)-
             rc*((-(ψ_j[2,m-2]+bcm2)/12 +  4*ψ_j[n-1,m-2]/3 -5*ψ_j[n,m-2]/2 + 4*ψ_j[n+1,m-2]/3 -ψ_j[n+2,m-2]/12)/12-
               2*(-(ψ_j[2,m-1]+bcm1)/12 +  4*ψ_j[n-1,m-1]/3 -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -ψ_j[n+2,m-1]/12)/3+
               2*(-(ψ_j[2,m+1]+bcp1)/12 +  4*ψ_j[n-1,m+1]/3 -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -ψ_j[n+2,m+1]/12)/3-
                 (-(ψ_j[2,m+2]+bcp2)/12 +  4*ψ_j[n-1,m+2]/3 -5*ψ_j[n,m+2]/2 + 4*ψ_j[n+1,m+2]/3 -ψ_j[n+2,m+2]/12)/12)*
                (bc/12) - d3*bc

        for n = 3:N-1
            rhs[n-1] = rd3*ψ_j[n-2,m] + rd2*ψ_j[n-1,m] + rd1*ψ_j[n,m]+rd2*ψ_j[n+1,m] + rd3*ψ_j[n+2,m]
        end#End of the n loop

    #at n=N psi[N+1,:] = 0
    n=N
    rhs[N-1] = rd3*ψ_j[n-2,m]    +    rd2*ψ_j[n-1,m] + rd1*ψ_j[n,m]+rd2*ψ_j[n+1,m] + rd3*(ψ_j[N,m]-bc)+
            rc*(-bc/2)*( ψ_j[n,m-2]/12 -  2*ψ_j[n,m-1]/3  +2*ψ_j[n,m+1]/3 - ψ_j[n,m+2]/12)-
            rc*((-ψ_j[n-2,m-2]/12 + 4*ψ_j[n-1,m-2]/3 -5*ψ_j[n,m-2]/2 + 4*ψ_j[n+1,m-2]/3 -(ψ_j[N,m-2]-bcm2)/12)/12-
              2*(-ψ_j[n-2,m-1]/12 + 4*ψ_j[n-1,m-1]/3 -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -(ψ_j[N,m-1]-bcm1)/12)/3+
              2*(-ψ_j[n-2,m+1]/12 + 4*ψ_j[n-1,m+1]/3 -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -(ψ_j[N,m+1]-bcp1)/12)/3-
                (-ψ_j[n-2,m+2]/12 + 4*ψ_j[n-1,m+2]/3 -5*ψ_j[n,m+2]/2 + 4*ψ_j[n+1,m+2]/3 -(ψ_j[N,m+2]-bcp2)/12)/12)*
                (bc/12) + d3*bc

    matrix_A_for_coreflow!(ψ_j, m, parameters)
    lu!(LUA,A)
    ldiv!(LUA,rhs)
    end#End of the m loop
end

function matrix_A_at_boundaries!( ψ_j, m, parameters)
    (Δθ, Δη, R, N, M, rc, rd1, rd2, rd3, d1, d2, d3, A, LUA) = parameters
    #This code should only be used to calculate matrix A for m= 2 or m = M because one sided theta derivatives are used at the boundaries
    bc=2*Δη*sin(Δθ*(m-1))
    bcp1=2*Δη*sin(Δθ*m)
    bcm1=2*Δη*sin(Δθ*(m-2))

    n=2
    ψ_θ = -ψ_j[n,m-1]/2 + ψ_j[n,m+1]/2
    ψ_ηηθ =  -(-(ψ_j[2,m-1]+bcm1)/12 +   4*ψ_j[n-1,m-1]/3  -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -ψ_j[n+2,m-1]/12)/2+
              (-(ψ_j[2,m+1]+bcp1)/12 +   4*ψ_j[n-1,m+1]/3  -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -ψ_j[n+2,m+1]/12)/2

    A[1,1]= d1+d3 -rc*(-ψ_θ/2 -  ψ_ηηθ/12)#-ve
    A[1,2]= d2    -rc*(-ψ_θ   -2*ψ_ηηθ/3)#-ve
    A[1,3]= d3    -rc*( ψ_θ/2 +  ψ_ηηθ/12)#-ve

    n=3
    ψ_θ = -ψ_j[n,m-1]/2 + ψ_j[n,m+1]/2
    ψ_ηηθ =  -(-ψ_j[n-2,m-1]/12 +   4*ψ_j[n-1,m-1]/3  -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -ψ_j[n+2,m-1]/12)/2+
              (-ψ_j[n-2,m+1]/12 +   4*ψ_j[n-1,m+1]/3  -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -ψ_j[n+2,m+1]/12)/2

    A[2,1]= d2 -rc*( ψ_θ   + 2*ψ_ηηθ/3)#-ve
    A[2,2]= d1
    A[2,3]= d2 -rc*(-ψ_θ   - 2*ψ_ηηθ/3)#-ve
    A[2,4]= d3 -rc*( ψ_θ/2 +   ψ_ηηθ/12)#-ve

    for n = 4:N-2
        ψ_θ = -ψ_j[n,m-1]/2 + ψ_j[n,m+1]/2
        ψ_ηηθ =  -(-ψ_j[n-2,m-1]/12 +  4*ψ_j[n-1,m-1]/3  -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -ψ_j[n+2,m-1]/12)/2+
                  (-ψ_j[n-2,m+1]/12 +  4*ψ_j[n-1,m+1]/3  -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -ψ_j[n+2,m+1]/12)/2

        A[n-1,n-3] = d3 -rc*(-ψ_θ/2 -  ψ_ηηθ/12)#-ve
        A[n-1,n-2] = d2 -rc*( ψ_θ   + 2*ψ_ηηθ/3)#-ve
        A[n-1,n-1] = d1
        A[n-1,n]   = d2 -rc*(-ψ_θ   - 2*ψ_ηηθ/3)#-ve
        A[n-1,n+1] = d3 -rc*( ψ_θ/2 +  ψ_ηηθ/12)#-ve

    end

    n=N-1
    ψ_θ = -ψ_j[n,m-1]/2 + ψ_j[n,m+1]/2
    ψ_ηηθ =  -(-ψ_j[n-2,m-1]/12 +  4*ψ_j[n-1,m-1]/3  -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -ψ_j[n+2,m-1]/12)/2+
              (-ψ_j[n-2,m+1]/12 +  4*ψ_j[n-1,m+1]/3  -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -ψ_j[n+2,m+1]/12)/2

    A[N-2,N-4]= d3 -   rc*(-ψ_θ/2 -  ψ_ηηθ/12)#-ve
    A[N-2,N-3]= d2    -rc*( ψ_θ   + 2*ψ_ηηθ/3)#-ve
    A[N-2,N-2]= d1
    A[N-2,N-1]= d2 -   rc*(-ψ_θ   - 2*ψ_ηηθ/3)#-ve

    n=N
    ψ_θ = -ψ_j[n,m-1]/2 + ψ_j[n,m+1]/2
    ψ_ηηθ =  -(-ψ_j[n-2,m-1]/12 + 4*ψ_j[n-1,m-1]/3  -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -(-ψ_j[N,m-1]-bcm1)/12)/2+
              (-ψ_j[n-2,m+1]/12 + 4*ψ_j[n-1,m+1]/3  -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -(-ψ_j[N,m+1]-bcp1)/12)/2

    A[N-1,N-3]= d3    -   rc*(-ψ_θ/2 -  ψ_ηηθ/12)#-ve
    A[N-1,N-2]= d2    -   rc*( ψ_θ   +2*ψ_ηηθ/3)#-ve
    A[N-1,N-1]= d1+d3 -   rc*( ψ_θ/2   +ψ_ηηθ/12)#-ve

end

function matrix_A_for_coreflow!(ψ_j, m, parameters)
    (Δθ, Δη, R, N, M, rc, rd1, rd2, rd3, d1, d2, d3, A, LUA) = parameters

    bc  =2*Δη*sin(Δθ*(m-1))
    bcp1=2*Δη*sin(Δθ*m)
    bcp2=2*Δη*sin(Δθ*(m+1))
    bcm1=2*Δη*sin(Δθ*(m-2))
    bcm2=2*Δη*sin(Δθ*(m-3))

    n=2
    ψ_θ =  ψ_j[n,m-2]/12 -  2*ψ_j[n,m-1]/3  +2*ψ_j[n,m+1]/3 - ψ_j[n,m+2]/12
    ψ_ηηθ =  (-(ψ_j[2,m-2]+bcm2)/12 +  4*ψ_j[n-1,m-2]/3  -5*ψ_j[n,m-2]/2 + 4*ψ_j[n+1,m-2]/3 -ψ_j[n+2,m-2]/12)/12-
           2*(-(ψ_j[2,m-1]+bcm1)/12 +  4*ψ_j[n-1,m-1]/3  -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -ψ_j[n+2,m-1]/12)/3+
           2*(-(ψ_j[2,m+1]+bcp1)/12 +  4*ψ_j[n-1,m+1]/3  -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -ψ_j[n+2,m+1]/12)/3-
             (-(ψ_j[2,m+2]+bcp2)/12 +  4*ψ_j[n-1,m+2]/3  -5*ψ_j[n,m+2]/2 + 4*ψ_j[n+1,m+2]/3 -ψ_j[n+2,m+2]/12)/12

    A[1,1]= d1+d3 -rc*(-ψ_θ/2 -  ψ_ηηθ/12)#-ve
    A[1,2]= d2    -rc*(-ψ_θ   -2*ψ_ηηθ/3)#-ve
    A[1,3]= d3    -rc*( ψ_θ/2 +  ψ_ηηθ/12)#-ve

    n=3
    ψ_θ =  ψ_j[n,m-2]/12 -  2*ψ_j[n,m-1]/3  +2*ψ_j[n,m+1]/3 - ψ_j[n,m+2]/12
    ψ_ηηθ =  (-ψ_j[n-2,m-2]/12 +  4*ψ_j[n-1,m-2]/3  -5*ψ_j[n,m-2]/2 + 4*ψ_j[n+1,m-2]/3 -ψ_j[n+2,m-2]/12)/12-
           2*(-ψ_j[n-2,m-1]/12 +  4*ψ_j[n-1,m-1]/3  -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -ψ_j[n+2,m-1]/12)/3+
           2*(-ψ_j[n-2,m+1]/12 +  4*ψ_j[n-1,m+1]/3  -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -ψ_j[n+2,m+1]/12)/3-
             (-ψ_j[n-2,m+2]/12 +  4*ψ_j[n-1,m+2]/3  -5*ψ_j[n,m+2]/2 + 4*ψ_j[n+1,m+2]/3 -ψ_j[n+2,m+2]/12)/12

    A[2,1]= d2 -rc*( ψ_θ   + 2*ψ_ηηθ/3)#-ve
    A[2,2]= d1
    A[2,3]= d2 -rc*(-ψ_θ   - 2*ψ_ηηθ/3)#-ve
    A[2,4]= d3 -rc*( ψ_θ/2 +   ψ_ηηθ/12)#-ve

    for n = 4:N-2
        ψ_θ = ψ_j[n,m-2]/12 -  2*ψ_j[n,m-1]/3  +2*ψ_j[n,m+1]/3 - ψ_j[n,m+2]/12
        ψ_ηηθ =  ψ_ηηθ_core_wind(ψ_j, n, m, M)

        A[n-1,n-3]= d3 -rc*(-ψ_θ/2 -  ψ_ηηθ/12)#-ve
        A[n-1,n-2]= d2 -rc*( ψ_θ   + 2*ψ_ηηθ/3)#-ve
        A[n-1,n-1]= d1
        A[n-1,n]  = d2 -rc*(-ψ_θ   - 2*ψ_ηηθ/3)#-ve
        A[n-1,n+1]= d3 -rc*( ψ_θ/2 +  ψ_ηηθ/12)#-ve

    end

    n=N-1
    ψ_θ =  ψ_j[n,m-2]/12 -  2*ψ_j[n,m-1]/3  +2*ψ_j[n,m+1]/3 - ψ_j[n,m+2]/12
    ψ_ηηθ =  (-ψ_j[n-2,m-2]/12 +  4*ψ_j[n-1,m-2]/3  -5*ψ_j[n,m-2]/2 + 4*ψ_j[n+1,m-2]/3 -ψ_j[n+2,m-2]/12)/12-
           2*(-ψ_j[n-2,m-1]/12 +  4*ψ_j[n-1,m-1]/3  -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -ψ_j[n+2,m-1]/12)/3+
           2*(-ψ_j[n-2,m+1]/12 +  4*ψ_j[n-1,m+1]/3  -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -ψ_j[n+2,m+1]/12)/3-
             (-ψ_j[n-2,m+2]/12 +  4*ψ_j[n-1,m+2]/3  -5*ψ_j[n,m+2]/2 + 4*ψ_j[n+1,m+2]/3 -ψ_j[n+2,m+2]/12)/12

    A[N-2,N-4]= d3 -   rc*(-ψ_θ/2 -  ψ_ηηθ/12)#-ve
    A[N-2,N-3]= d2    -rc*( ψ_θ   + 2*ψ_ηηθ/3)#-ve
    A[N-2,N-2]= d1
    A[N-2,N-1]= d2 -   rc*(-ψ_θ   - 2*ψ_ηηθ/3)#-ve

    n=N
    ψ_θ =  ψ_j[n,m-2]/12 -  2*ψ_j[n,m-1]/3  +2*ψ_j[n,m+1]/3 - ψ_j[n,m+2]/12
    ψ_ηηθ =  (-ψ_j[n-2,m-2]/12 + 4*ψ_j[n-1,m-2]/3 -5*ψ_j[n,m-2]/2 + 4*ψ_j[n+1,m-2]/3 -(ψ_j[N,m-2]-bcm2)/12)/12-
           2*(-ψ_j[n-2,m-1]/12 + 4*ψ_j[n-1,m-1]/3 -5*ψ_j[n,m-1]/2 + 4*ψ_j[n+1,m-1]/3 -(ψ_j[N,m-1]-bcm1)/12)/3+
           2*(-ψ_j[n-2,m+1]/12 + 4*ψ_j[n-1,m+1]/3 -5*ψ_j[n,m+1]/2 + 4*ψ_j[n+1,m+1]/3 -(ψ_j[N,m+1]-bcp1)/12)/3-
             (-ψ_j[n-2,m+2]/12 + 4*ψ_j[n-1,m+2]/3 -5*ψ_j[n,m+2]/2 + 4*ψ_j[n+1,m+2]/3 -(ψ_j[N,m+2]-bcp2)/12)/12

    A[N-1,N-3]= d3    -   rc*(-ψ_θ/2 -  ψ_ηηθ/12)#-ve
    A[N-1,N-2]= d2    -   rc*( ψ_θ   +2*ψ_ηηθ/3)#-ve
    A[N-1,N-1]= d1+d3 -   rc*( ψ_θ/2   +ψ_ηηθ/12)#-ve

end
```

    matrix_A_for_coreflow! (generic function with 1 method)

```julia
const rd3 =      -1.0/12.0
const rd2 =       4.0/3.0
const rd1 =      -5.0/2.0
const rc    =   Δt/(Δη*Δθ)

parameters =  (Δθ, Δη, R, N, M, rc, rd1, rd2, rd3, d1, d2, d3, A, LUA);

```

```julia
#Forming the righthand side vector

percent = 5.0
θstep_of_norm=5
old_norm = 0.0
expected_norm = round(norm([Ψ_smallr(Δθ*θstep_of_norm,-1.0+Δη*i,R) for i = 0:N]); digits=6)

#Only theta profiles from m =2 to m = M need to be calculated with one sided theta derivatives second order.
#11111111111111111111111111111111111111111111111111111111
@time for j =1:J

    make_ψ_jnext_on_boundaries!(ψ_j, ψ_jnext, parameters)
    make_ψ_jnext_in_core_region!(ψ_j, ψ_jnext, parameters)

    if( mod(j,2) == 1)
        ψ_j    = ψ_next
        ψ_jnext=ψ_current
    else
        ψ_j    = ψ_current
        ψ_jnext=ψ_next
    end #End of if statement

    if( j> round(percent*J/100))
        #Calculate a simple norm of psi
        psi_norm = norm(ψ_j[:,θstep_of_norm+1])
        println(string(percent)*"% compelete t="*string(round(j*Δt; digits=2))*" Core flow norm = "*string(round(psi_norm; digits=6))*" Expected norm= "*string(expected_norm))
        percent+=5
        if isnan(psi_norm )
           println("Psi is no longer a number, exiting... ")
           exit()
        elseif(abs(psi_norm-old_norm)<0.00001)
            println("Psi has converged, exiting... ")
            break
       end
    end

end #End of time loop j
println(string(percent)*"% compelete      Core flow norm = "*string(round(norm(ψ_j[:,θstep_of_norm+1]); digits=6))*" Expected norm= "*string(expected_norm))
println("Reynolds number= "*string(R))
#At this point inverse of A should be applied to the rhs vector to produce the next iterate of psi at the j+1 time step

```

    5.0% compelete t=0.25 Core flow norm = 0.449984 Expected norm= 0.893505
    10.0% compelete t=0.5 Core flow norm = 0.313851 Expected norm= 0.893505
    15.0% compelete t=0.75 Core flow norm = 0.275539 Expected norm= 0.893505
    20.0% compelete t=1.0 Core flow norm = 0.255197 Expected norm= 0.893505
    25.0% compelete t=1.25 Core flow norm = 0.238508 Expected norm= 0.893505
    30.0% compelete t=1.5 Core flow norm = 0.225166 Expected norm= 0.893505
    35.0% compelete t=1.75 Core flow norm = 0.228144 Expected norm= 0.893505
    40.0% compelete t=2.0 Core flow norm = 0.283854 Expected norm= 0.893505
    45.0% compelete t=2.25 Core flow norm = 0.328786 Expected norm= 0.893505
    50.0% compelete t=2.5 Core flow norm = 0.221795 Expected norm= 0.893505
    55.0% compelete t=2.75 Core flow norm = 0.198039 Expected norm= 0.893505
    60.0% compelete t=3.0 Core flow norm = 0.346006 Expected norm= 0.893505
    65.0% compelete t=3.25 Core flow norm = 0.67141 Expected norm= 0.893505
    70.0% compelete t=3.5 Core flow norm = 0.042079 Expected norm= 0.893505
    75.0% compelete t=3.75 Core flow norm = 4.161913 Expected norm= 0.893505
    80.0% compelete t=4.0 Core flow norm = 316.226285 Expected norm= 0.893505
    85.0% compelete t=4.25 Core flow norm = 163.366609 Expected norm= 0.893505
    90.0% compelete t=4.5 Core flow norm = 82548.396315 Expected norm= 0.893505
    95.0% compelete t=4.75 Core flow norm = 1828.508758 Expected norm= 0.893505
    146.959581 seconds (50.64 M allocations: 231.313 GiB, 2.97% gc time, 0.21% compilation time)
    100.0% compelete      Core flow norm = 2.080313 Expected norm= 0.893505
    Reynolds number= 250.0

```julia
open("psi_solution.txt", "w") do io
           writedlm(io, ψ_current)
       end
#wireframe(range(0,stop = pi,length = M+1),range(-1,stop = 1,length = N+1),ψ_j)
```

```julia
m=30
MatrixPsi=readdlm("psifin.m",Float64,);
xinterval=range(-1,stop=1,length=101)
size(MatrixPsi)
eta=range(-1, stop=1,length= N+1)
plot(eta,ψ_j[:,m],linecolor=:orange, label="Julia", linestyle=:solid)
plot!(x->Ψ_smallr(Δθ*(m-1),x,R),-1,1, label="Small R analytical", linestyle=:dot, linecolor=:blue,xlabel = L"\eta", ylabel = L"\Psi",size=(800,400))
plot!(xinterval,MatrixPsi[2:102,m], label="Fortran winding", linestyle=:dash, linecolor=:black,xlabel = L"\eta", ylabel = L"\Psi")
plot!(x->Ψ_bigr((m-1)*Δθ,x),-1,1, label="Large R analytical",  linestyle=:dash, linecolor=:red,xlabel = L"\eta", ylabel = L"\Psi",size=(800,400),yguidefontrotation=-90, legend=:outertopright)
```

```julia
eta_profile =0.6
eta_steps = size(MatrixPsi)[1]
delta_eta_steps = 2/(eta_steps-3)
theta=range(0, stop=pi,length= M+1)
plot(theta,ψ_j[round(Int,(eta_profile+1)/Δη)+1,:],linecolor=:orange, label="Julia", linestyle=:solid)
plot!(x->Ψ_smallr(x,eta_profile,R),0,pi, label="Small R analytical", linestyle=:dot, linecolor=:blue,xlabel = L"\theta", ylabel = L"\Psi",size=(800,400))
#plot!(x->Ψ_bigr(x,eta_profile),0,pi, label="Large R analytical",  linestyle=:dash, linecolor=:red,xlabel = L"\theta", ylabel = L"\Psi",size=(800,400),yguidefontrotation=-90, legend=:outertopright)
plot!(theta,MatrixPsi[round(Int,(eta_profile+1)/delta_eta_steps)+2,1:256], linestyle=:dash, label="Fortran winding",linecolor=:black)
```

```julia
round(Int,(eta_profile+1)/Δη)+1
```

    161

```julia
for n =2:N
    for m =2:M
            η = -1 + Δη*(n-1)
            θ = -1 + Δθ*(m-1)
            ψ_jnext[n,m] = ψ_j[n, m] - Ψ_smallr(θ,η,R)
    end
end

```

```julia
wireframe(range(0,stop = pi,length = M+1),range(-1,stop = 1,length = N+1),ψ_jnext)
```

```julia
expected_norm = round(norm([Ψ_smallr(Δθ*θprofile_of_norm,-1.0+Δη*i,R) for i = 0:N]); digits=6)
```

```julia
norm(ψ_j[:,θstep_of_norm+1])
```

    0.20183324053963236

```julia

```


# AxiScan Example

Here we show an example of the AxiScan analysis pipeline.

## Import Code and Setup Plotting Defaults

```python
# Import basics
import numpy as np
import scipy.stats
import matplotlib as mpl
import matplotlib.pyplot as plt
import pymultinest
import corner

# Plotting Settings
mpl.rcParams['figure.figsize'] = 20, 14 # default figure size
%matplotlib inline
%load_ext autoreload
%autoreload 2

# Import MC generation and data analysis code The necessary modules
from AxiScan import mc_gen # MC Generation
from AxiScan import scan # Data Analysis
import analysis_utilities as au # Convenient Utility Methods
```

# Step 1: Generate Monte Carlo

## Set the Parameters

First we generate Monte Carlo data for a scenario in which the majority of the dark matter is contained within a bulk halo following the Standard Halo Model parameters with a subdominant fraction contained within the Sagitarrius Stream. Although we have chosen to illustrate the analysis with a large signal strength, this can be easily adjusted.

This is accomplished by seeding an instance (`generator`) of the Generator class in `mc_gen` with arguments detailed below. Data on the $i^\text{th}$ day of data collection is generated by calling `generator.makePSD(i)`. The arguments for the Generator class are

| Argument | Purpose |
| ------------- | ------------- |
| ma | ma/2pi is the axion mass [Hz] |
| A | Proxy for the axion-photon coupling,  $A \propto g_{a \gamma \gamma}^2$ |
| v0_Halo | Velocity dispersion of the bulk halo [km/s] |
| vDotMag_Halo | Speed of the sun with respect to the bulk halo [km/s]|
| alpha_Halo | Bulk halo annual modulation scale, $\alpha \in [0, 1]$|
| tbar_Halo | Date parameter for the bulk halo annual modultion [days] |
| v0_Sub | Speed dispersion of the substructure halo [km/s] |
| vDotMag_Sub | Speed of the sun with respect to the substructure halo [km/s]|
| alpha_Sub | Substructure halo annual modulation scale, $\alpha \in [0, 1]$|
| tbar_Sub | $\qquad$ Date parameter for the substructure halo annual modultion [days] |
| frac_Sub | Fraction of the axion DM in the substructure |
| PSDback | Mean expected background Power Spectral Density |
| freqs | Array of frequencies to calculate the PSD at [Hz] |

The code generates data in the form of Power Spectral Densities (PSD).

```python
########################
###    Seed Values   ###
########################
c = 299798.452

# Physics Parameters
ma = 5.5e5*2*np.pi
A = 10000.0
PSDback= 163539.36

# Bulk SHM Parameters
v0_Halo = 220.0
vDotMag_Halo = 232.36
alpha_Halo = .49
tbar_Halo = 72.40

# Sagitarrius Stream Parameters
v0_Sub = 10.0
vDotMag_Sub = 418.815
alpha_Sub = .65903
tbar_Sub = 279.51
frac_Sub = 0.0

# Data Output Size
freqs = np.linspace(.99999, 1.00001, 10000)*5.5e5
PSD_Data = np.zeros((365, len(freqs)))

collectionTime = 1/(freqs[1] - freqs[0])
stacked_per_day = 86400 / collectionTime
num_stacked = 365*stacked_per_day

# Instantiate the data generator
generator = mc_gen.Generator(ma, A, PSDback, v0_Halo, vDotMag_Halo, alpha_Halo, tbar_Halo,
                             v0_Sub, vDotMag_Sub, alpha_Sub, tbar_Sub, frac_Sub, freqs)
```

## Generate the Data

Here we fill the `PSD_Data` array with each day of collected data. Data is generated assuming that that the entire 24 hours is used for data collection. If the collection time $T$ as inferred from the user-defined frequency resolution in `freqs` is less than 24 hours, then the data generated for each day is constructed as $24$ hours / $T$ stacked copies of data collections of duration $T$.

We then stack data over the course of the year. The data stacked on the duration of a year is used for simple scans for an axion signal. The data stacked on the duration of the year may be used for more sophisticated scans and parameter estimation.

```python
# Fill the PSD_Data array
for i in range(365):
    PSD_Data[i] = np.array(generator.makePSD(i))

# Average over the days in the PSD_Data array for the simple scan
Stacked_PSD_Data = np.mean(PSD_Data, axis = 0)

plt.plot(freqs, Stacked_PSD_Data)
plt.xlabel('Frequency [Hz]')
plt.ylabel('PSD')
plt.show()
```

# Step 2: The Simple Scan

## Calculating the Test Statistic

Next we analyze the MC data when stacked over the duration of a year. In this analysis, we only scan over values of A and ma, and we will assume the Axion DM to follow a bulk Standard Halo Model profile with no substructure present. These steps can be repeated on real data.

The anlysis is performed using `scan.TS_Scan`, which has the following arguments:

| Argument | Purpose |
| ------------- | ------------- |
| Stacked_PSD_Data | Array of PSD data associated with the measurements when stacked over the duration of a year|
| freqs | Array of frequencies associated with the data points [Hz] |
|mass_TestSet | Range of axion masses scanned for in the analysis|
| A_TestSet| Range of values of the A parameter scanned for at each mass|
| PSDback | Mean expected background Power Spectral Density |
| v0_Exp | Expected value of the SHM velocity dispersion [km/s]|
| vObs_Exp | Expected value of the sun's speed with respect to the bulk SHM Halo [km/s]|
| num_stacked | Total number of collections of duration T contained in the stacked data |

The output of `scan.TS_Scan` is `TS_Array`, the value of the test statistic TS(`ma`, `A`) at each value of `ma` and `A` in `mass_TestSet` and `A_TestSet`.

## Defining the Scan Parameters

Since we expect to be searching for a bulk SHM distribution, we take SHM parameters `v0_Exp = 220.0` and `vObs_Exp = 232.36`.

The set of masses in `mass_TestSet` is taken to be points on a log-spaced grid beginning at the mass corresponding to the minimum frequency for which we have data with a spacing factor of `1 + v0_Exp**2 /(2 c**2)`.

The set of `A` in `A_TestSet` is determined by the necessary value of `A` of an injected signal expected to produce a 5$\sigma$ detection. At a given mass-point, this value of A can be computed using [57] and [60] of 1711.10489. To ensure a sufficiently large range, we compute the maximum value of such an `A` over all mass, denoting this `A_max`. Then at each mass-point, we scan over values `-A_max` to `5 * A_max`

```python
# Expectation Parameters
v0_Exp = 220.0
vObs_Exp = 232.36

# Construct the range of masses to scan over
N_testMass = int(np.log(freqs[-50] / freqs[0])  / np.log(1. + v0_Exp**2. / 2. / c**2.))
mass_TestSet = (freqs[0]*(1. + v0_Exp**2. / 2. / c**2.)**np.arange(N_testMass) * 2*np.pi)

# Construct the range of signal strengths to scan over
Sigma_A = au.getSigma_A(mass_TestSet, 365, 86400, v0_Exp, vObs_Exp, PSDback)
N_indMasses = 4 * c**2 / (3 * v0_Exp**2) * np.log(np.amax(freqs)/np.amin(freqs))
TS_Thresh = scipy.stats.norm.ppf(1 - (1-scipy.stats.norm.cdf(5))/N_indMasses)**2
detection_Threshold = np.sqrt(TS_Thresh)*Sigma_A

A_TestSet = np.linspace(-1.0, 5.0, 101)*np.amax(detection_Threshold)

# Run the Scan
TS_Array = np.array(scan.TS_Scan(Stacked_PSD_Data, freqs, mass_TestSet, A_TestSet, PSDback, v0_Exp, vObs_Exp, num_stacked))
```

# Extracting Scan Values and Limits

Now that we have obtained `TS_Array`, we can extract our maximum-likelihood estimates and the 95% limits of `A` at each `ma`.

At a given `ma`, the maximum-likelihood estimate of A is given by
\begin{equation}
\hat A = \text{argmax}_{A} TS(m_a, A)
\end{equation}

At a given `ma`, the 95% limit on `A` is given by solving
\begin{equation}
TS(m_a, A_{95\%}) - TS(m_A, \hat A) = 2.71, \qquad A_{95\%} \geq \hat A
\end{equation}

```python
A_Limits = np.zeros(mass_TestSet.shape) # The expected 95% constraint
A_Scans = np.zeros((mass_TestSet.shape)) # The TS maximizing value

for i in range(len(A_Limits)):
    # Naive TS maximizing value
    A_Scans[i] = A_TestSet[np.argmax(TS_Array[i])]

    # Extracting the 95% constraint by a shift in the TS of 2.71
    temp = np.copy(TS_Array[i])
    temp[0:np.nanargmax(temp)] = float('nan')
    temp -= np.nanmax(temp)
    A_Limits[i] = A_TestSet[np.nanargmin(np.abs(temp+2.706))]

A_Limits = np.maximum(A_Limits, au.zScore(-1)*Sigma_A)
A_Scans = np.maximum(0, A_Scans)
```

```python
plt.subplot(2, 2, 1)
plt.title('Limits', size = 20)
plt.plot(mass_TestSet, A_Limits)
plt.fill_between(mass_TestSet, au.zScore(-1)*Sigma_A, au.zScore(2)*Sigma_A, color = 'yellow')
plt.fill_between(mass_TestSet, au.zScore(-1)*Sigma_A, au.zScore(1)*Sigma_A, color = 'limegreen')
plt.axvline(x=ma, ls = '--', c = 'black')

plt.subplot(2, 2, 2)
plt.title('MLE Values', size = 20)
plt.plot(mass_TestSet, A_Scans)
plt.plot(mass_TestSet, detection_Threshold)
plt.axvline(x=ma, ls = '--', c = 'black')
plt.show()
```

Above, we plot the results of the simple scan for an axion signal. In the left panel, we plot the resulting 95% constraints (solid black) against the expected 95% constraints (dashed black) and 1$\sigma$ (green) and 2$\sigma$ (yellow) containment determined by the Asimov dataset according to [56] of 1711.10489. In the right panel, we plot at each mass-point the MLE of `A` (solid black) and the value of A at the threshold of a 5$\sigma$ detection (dashed black).

# Step 3: The MultiNest Scan

Now that we have discovered a well-localized axion signal, we proceed to perform a MultiNest Scan over the data stacked at the level of a day. This will allow us to perform more detailed analysis of the signal parameters. For example, a MultiNest scan could be used to gain a more accurate estimate of `A` or `ma`, to study the annual modulation parameters, or to search for substructure. With sufficient computational resources, these could all be accomplished simultaneously.

In the example below, we will perform a very basic MultiNest scan to gain a more accurate estimate of the `A` parameter under the assumption that all other signal parameters are known with perfect accuracy.

```python
# Basic Settings
nlive = 500
chains_dir = '/nfs/turbo/bsafdi/fosterjw/github/AxiScan/examples/chains/'

pymultinest_options = {'importance_nested_sampling': False,
                        'resume': False, 'verbose': True,
                        'sampling_efficiency': 'model',
                        'init_MPI': False, 'evidence_tolerance': 0.5,
                        'const_efficiency_mode': False}
```

## A-Parameter Scan

```python
# Parameter to Scan Over
A_Prior = [.5*np.amax(A_Scans), 10*np.amax(A_Scans)]

# Formatting the prior cube as required by MultiNest
theta_min = [A_Prior[0]]
theta_max = [A_Prior[1]]

theta_interval = list(np.array(theta_max) - np.array(theta_min))
n_params = len(theta_min) # number of parameters to fit for

def prior_cube(cube, ndim=1, nparams=1):
    """ Cube of priors - in the format required by MultiNest
    """

    for i in range(ndim):
        cube[i] = cube[i] * theta_interval[i] + theta_min[i]
    return cube

# Defining the likelihood function in terms of fixed and floated parameters
def LL_Multinest(theta, ndim = 1, nparams = 1):
    return scan.SHM_AnnualMod_ll(freqs, PSD_Data, ma, theta[0], v0_Halo, vDotMag_Halo,
                                 alpha_Halo, tbar_Halo, PSDback, stacked_per_day)

# Run the MultiNest Scan
pymultinest.run(LL_Multinest, prior_cube, n_params,
                outputfiles_basename=chains_dir,
                n_live_points=nlive, **pymultinest_options)

# Plot the posteriors found by the MultiNest Scan
chain_file = '/nfs/turbo/bsafdi/fosterjw/github/AxiScan/examples/chains/post_equal_weights.dat'
chain = np.array(np.loadtxt(chain_file))[:, :-1]
# Now make a triangle plot using corner
corner.corner(chain, smooth=1.5,
              labels = ['$A$'], truths = [A],
              smooth1d=1, quantiles=[0.16, 0.5, 0.84], show_titles=True,
              title_fmt='.2f', title_args={'fontsize': 14},
              range=[1 for _ in range(chain.shape[1])],
              plot_datapoints=False, verbose=False)

plt.show()
```

# Alpha_Halo Scan

```python
# Parameter to Scan Over
alpha_Prior = [0.0, 1.0]

# Formatting the prior cube as required by MultiNest
theta_min = [alpha_Prior[0]]
theta_max = [alpha_Prior[1]]

theta_interval = list(np.array(theta_max) - np.array(theta_min))
n_params = len(theta_min) # number of parameters to fit for

def prior_cube(cube, ndim=1, nparams=1):
    """ Cube of priors - in the format required by MultiNest
    """

    for i in range(ndim):
        cube[i] = cube[i] * theta_interval[i] + theta_min[i]
    return cube

# Defining the likelihood function in terms of fixed and floated parameters
def LL_Multinest(theta, ndim = 1, nparams = 1):
    return scan.SHM_AnnualMod_ll(freqs, PSD_Data, ma, A, v0_Halo, vDotMag_Halo,
                                 theta[0], tbar_Halo, PSDback, stacked_per_day)

# Run the MultiNest Scan
pymultinest.run(LL_Multinest, prior_cube, n_params,
                outputfiles_basename=chains_dir,
                n_live_points=nlive, **pymultinest_options)

# Plot the posteriors found by the MultiNest Scan
chain_file = '/nfs/turbo/bsafdi/fosterjw/github/AxiScan/examples/chains/post_equal_weights.dat'
chain = np.array(np.loadtxt(chain_file))[:, :-1]
# Now make a triangle plot using corner
corner.corner(chain, smooth=1.5,
              labels = ['alpha_Halo'], truths = [alpha_Halo],
              smooth1d=1, quantiles=[0.16, 0.5, 0.84], show_titles=True,
              title_fmt='.2f', title_args={'fontsize': 14},
              range=[1 for _ in range(chain.shape[1])],
              plot_datapoints=False, verbose=False)

plt.show()
```

# tbar_Halo Scan

```python
# Parameter to Scan Over
tbar_Prior = [0, 365.0]

# Formatting the prior cube as required by MultiNest
theta_min = [tbar_Prior[0]]
theta_max = [tbar_Prior[1]]

theta_interval = list(np.array(theta_max) - np.array(theta_min))
n_params = len(theta_min) # number of parameters to fit for

def prior_cube(cube, ndim=1, nparams=1):
    """ Cube of priors - in the format required by MultiNest
    """

    for i in range(ndim):
        cube[i] = cube[i] * theta_interval[i] + theta_min[i]
    return cube

# Defining the likelihood function in terms of fixed and floated parameters
def LL_Multinest(theta, ndim = 1, nparams = 1):
    return scan.SHM_AnnualMod_ll(freqs, PSD_Data, ma, A, v0_Halo, vDotMag_Halo,
                                 alpha_Halo, theta[0], PSDback, stacked_per_day)

# Run the MultiNest Scan
pymultinest.run(LL_Multinest, prior_cube, n_params,
                outputfiles_basename=chains_dir,
                n_live_points=nlive, **pymultinest_options)

# Plot the posteriors found by the MultiNest Scan
chain_file = '/nfs/turbo/bsafdi/fosterjw/github/AxiScan/examples/chains/post_equal_weights.dat'
chain = np.array(np.loadtxt(chain_file))[:, :-1]
# Now make a triangle plot using corner
corner.corner(chain, smooth=1.5,
              labels = ['tbar_Halo'], truths = [tbar_Halo],
              smooth1d=1, quantiles=[0.16, 0.5, 0.84], show_titles=True,
              title_fmt='.2f', title_args={'fontsize': 14},
              range=[1 for _ in range(chain.shape[1])],
              plot_datapoints=False, verbose=False)

plt.show()
```

```python

```


# Pre-requisite Material for Foundations of Machine Learning

* The following includes of list a topics that you should know to succeed in this course.
* This covers material from calculus, linear algebra, statistics, and programming in Python.
* Please review the following material carefully.
* If you do not feel confident in all of the following material, you may want to reconsider taking the course at this time.

# Python Review

Some Python tutorials and references to go over are:

* Learn Python the hard way: https://learnpythonthehardway.org/python3/
* Python for absolute beginners: https://www.youtube.com/playlist?list=PLS1QulWo1RIaJECMeUT4LFwJ-ghgoSH6n
* Python Docs tutorial: https://docs.python.org/3/tutorial/
* Numpy quickstart tutorial: https://docs.scipy.org/doc/numpy/user/quickstart.html

- Python For Data Science - A Cheat Sheet For Beginners: https://www.datacamp.com/community/tutorials/python-data-science-cheat-sheet-basics
- Other Python Cheat Sheets: https://towardsdatascience.com/collecting-data-science-cheat-sheets-d2cdff092855 (Credits to Karlijn Willems)

# Calculus Review

Calculus topics to review include:

* Derivatives: https://www.youtube.com/watch?v=9vKqVkMQHKk&list=PLZHQObOWTQDMsr9K-rj53DwVRMYO3t5Yr&index=2
* Chain rule and product rule: https://www.youtube.com/watch?v=YG15m2VwSjA&list=PLZHQObOWTQDMsr9K-rj53DwVRMYO3t5Yr&index=4
* Integration: https://www.youtube.com/watch?v=rfG8ce4nNh0&list=PLZHQObOWTQDMsr9K-rj53DwVRMYO3t5Yr&index=8
* Taylor Series: https://www.youtube.com/watch?v=3d6DsjIBzJ4&list=PLZHQObOWTQDMsr9K-rj53DwVRMYO3t5Yr&index=11

Additional resources:
* Full 3Blue1Brown Calculus series: https://www.youtube.com/playlist?list=PLZHQObOWTQDMsr9K-rj53DwVRMYO3t5Yr
* ML-CheatSheet for Calculus: https://ml-cheatsheet.readthedocs.io/en/latest/calculus.html

## Linear Algebra Review

Topics and definitions to know include:
  * Vector: https://www.youtube.com/watch?v=fNk_zzaMoSs&list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab&index=2&t=0s
  \begin{equation} \mathbf{x} = \left[ \begin{array}{c} x_1 \\ x_2 \\ \vdots \\ x_D\end{array} \right] \end{equation}
  * Matrix:
  \begin{equation}
  \mathbf{X}  = \left[\!\begin{array}{c c c c}
    x_{11} & x_{12} & \cdots & x_{1n}\\
    x_{21} & x_{22} & \cdots & x_{2n}\\
    \vdots & \vdots & \ddots & \vdots\\
    x_{d1} & x_{d2} & \cdots & x_{dn}\end{array}\!\right]\! \!\in\! \mathcal{R}^{d \times n}
  \end{equation}
  * Transpose operation:
    \begin{equation}
		 \mathbf{x}^T = \left[  x_1,  x_2 , \cdots , x_D \right]
         \end{equation}

    \begin{equation}\left(\mathbf{A}^T\mathbf{B}\right)^T = \mathbf{B}^T\mathbf{A}\end{equation}

  * Vector/Matrix scaling:  Given a vector $\mathbf{x} \in \mathbb{R}^D$ and a scalar value $a$, *what is $a\mathbf{x}$?*  *What does this operation do geometrically?*
  * Vector/Matrix addition: Given $\mathbf{x} \in \mathbb{R}^D$ and $\mathbf{y} \in \mathbb{R}^D$, *what is $\mathbf{x} + \mathbf{y}$?  What is the geometric interpretation?*
  * Vector/Matrix subtraction: Given $\mathbf{x} \in \mathbb{R}^D$ and $\mathbf{y} \in \mathbb{R}^D$, *what is $\mathbf{x} - \mathbf{y}$? What is the geometric interpretation?*
  * Inner product: $\mathbf{x}^T\mathbf{y} = \mathbf{y}^T\mathbf{x} = \sum_{i=1}^D x_iy_i$
  * Outer product: $xy^\top \!=\! \left[\!\begin{array}{c}
x_1\\
x_2\\
\vdots\\
x_d\end{array}\!\right]\!\!
\left[\!\begin{array}{c}
y_1\\
y_2\\
\vdots\\
y_n\end{array}\!\right]^\top \!\!=\! \left[\!\begin{array}{c c c c}
x_1y_1 & x_1y_2 & \cdots & x_1y_n\\
x_2y_1 & x_2y_2 & \cdots & x_2y_n\\
\vdots & \vdots & \ddots & \vdots\\
x_dy_1 & x_dy_2 & \cdots & x_dy_n\end{array}\!\right]\! \!\in\! \mathcal{R}^{d \times n}.
$
* Linear transformations: https://www.youtube.com/watch?v=kYB8IZa5AuE&list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab&index=3
* Inverse: https://www.youtube.com/watch?v=uQhTuRlWMxw&list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab&index=7

* L-p norm: Given a vector, $\mathbf{x}$ and a $p$-value, the $l_p$ norm is defined as:
\begin{eqnarray}
\left\|\mathbf{x}\right\|_p = \left( \sum_{d=1}^D |x_d|^p \right)^{\frac{1}{p}}
\end{eqnarray}

So, if $p=2$, then the $l_2$ norm of a vector is:
\begin{eqnarray}
\left\|\mathbf{x}\right\|_2 = \left( \sum_{d=1}^D |x_d|^2 \right)^{\frac{1}{2}}
\end{eqnarray}

* Eigenvectors and Eigenvalues:  https://www.youtube.com/watch?v=PFDu9oVAE-g&list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab&index=14

```python
# Associated python code to review some of the concepts listed above.

import numpy as np
a = 2
x = np.array([[1],[2],[3]])
y = np.array([[4],[5],[6]])

#Print Vector x
print('x:',x)

#Transpose Vector x
print('x.T:',x.T)

#Scale Vector x with scalar a
print('a*x:', a*x)

#Vector addition
print('x+y:', x+y)

#Vector subtraction
print('y-x:',y-x)
```

```python
#Several ways to compute the inner product of vectors x and y in python/numpy

#First by using matrix multiplication operator '@' that numpy supports:
print(x.T@y)
print(y.T@x)

#Second with numpy.matmul function for matrix maultiplication:
print(np.matmul(x.T,y))
print(np.matmul(y.T,x))

#Third with numpy.inner function:
print(np.inner(x.T,y.T))

#Fourth with numpy.dot function, note that numpy.dot acts the same as '@ ' or 'np.matmul' for 2D arrays:
print(x.T.dot(y))
print(y.T.dot(x))

#for 1D arrays, acts similar to numpy.inner function:
print(np.dot([1,2,3],[4,5,6]))
```

```python
#Compute the outer product of vectors x and y
print(np.outer(x,y))
```

```python
print(np.outer(x,y).T)
```

```python
#Compute l-p norm for p = 2, 3, 1
x = np.array([1, 2, 3])
print(np.linalg.norm(x,ord=2))
print((x@x)**(1/2))
print(np.linalg.norm(x,ord=3))
print(np.linalg.norm(x,ord=1))
```

```python
#Compute l-p norm for p = 2, 3, 1, 0
x = np.array([-1, -2, -3])
print(np.linalg.norm(x,ord=2))
print((x@x)**(1/2))
print(np.linalg.norm(x,ord=3))
print(np.linalg.norm(x,ord=1))
print(np.linalg.norm(x,ord=0))
```

```python
#Compute l-p norm for p = 2, 3, 1, 0
x = np.array([-1, 0, 3])
print(np.linalg.norm(x,ord=2))
print((x@x)**(1/2))
print(np.linalg.norm(x,ord=3))
print(np.linalg.norm(x,ord=1))
print(np.linalg.norm(x,ord=0))
```

### Note the notation:
   * scalar values are unbolded (e.g., $N$, $x$)
   * vectors are lower case and bolded (e.g., $\mathbf{x}$)
   * matrices are uppercase and bolded (e.g., $\mathbf{A} \in \mathbb{R}^{D \times N}$)
   * vectors are generally assumed to be column vectors (e.g., $\mathbf{x}^T = \left(x_1, \ldots, x_N\right)$ and $\mathbf{x} =  \left(x_1, \ldots, x_N\right)^T$ )

### Additional reading and videos to review linear algebra concepts:

* Strang, Gilbert, et al. Introduction to linear algebra. Vol. 4. Wellesley, MA: Wellesley-Cambridge Press, 2009.
    Chapters 1-7

* Lay, David C. "Linear Algebra and its Applications, 3rd updated Edition." (2005).

* MITOpenCourseWare Linear Algebra:  https://ocw.mit.edu/courses/mathematics/18-06-linear-algebra-spring-2010/video-lectures/

* 3Blue1Brown Linear Algebra Review:  https://www.youtube.com/playlist?list=PLZHQObOWTQDPD3MizzM2xVFitgF8hE_ab

* SciPy Cheat Sheet: Linear Algebra in Python: https://www.datacamp.com/community/blog/python-scipy-cheat-sheet

# Statistics Review

Topics and definitions to know include:

- Likelihood and Probability
- Expected Value: https://www.youtube.com/watch?v=j__Kredt7vY
- Variance and covariance: https://www.youtube.com/watch?v=ualmyZiPs9w
- Random variables: https://www.youtube.com/watch?v=3v9w79NhsfI
- Probability density functions: https://www.youtube.com/watch?v=Fvi9A_tEmXQ
- Marginal and conditional probability: https://www.youtube.com/watch?v=CAXQvTKP8sg
- Independence and conditional independence: https://www.youtube.com/watch?v=uzkc-qNVoOk
- Normal/Gaussian distribution: https://www.youtube.com/watch?v=hgtMWR3TFnY
- Central Limit Theorem: https://www.youtube.com/watch?v=JNm3M9cqWyc
- Bayes' Rule: https://www.youtube.com/watch?v=XQoLVl31ZfQ

Additional Reading: Goodfellow, I. et al. "Deep Learning", MIT Press, 2016. Chapter 3: Probability and Information Theory, Pages 51-70. http://www.deeplearningbook.org/contents/prob.html


# Calculation of control fields for QFT gate on two qubits using L-BFGS-B algorithm

Alexander Pitchford (agp1@aber.ac.uk)

```python
cd /Users/benrosand/oct-qiskit-pulse
```

    /Users/benrosand/oct-qiskit-pulse

### Imports

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

```python
from qutip import Qobj, identity, sigmax, sigmay, sigmaz, tensor, hadamard_transform, basis
from qutip.qip.algorithms import qft
import qutip.logging_utils as logging
logger = logging.get_logger()
#Set this to None or logging.WARN for 'quiet' execution
log_level = logging.INFO
#QuTiP control modules
import qutip.control.pulseoptim as cpo
import qutip.control.pulsegen as pulsegen
from src.qutip_helper import convert_qutip_ham
from src.helper import *
from qiskit import IBMQ
from qutip.qip.device import Processor
from qiskit.pulse import Play, SamplePulse

from qiskit.providers.aer.pulse.system_models.hamiltonian_model import HamiltonianModel

example_name = 'QFT'
```

```python

```

### Defining the physics

Note here that there are two controls acting on each qubit.

```python
# subsystem_list=[0,1]
subsystem_list=[0,1]
single_q = True
IBMQ.load_account()

# provider = IBMQ.get_provider(hub='ibm-q', group='open', project='main')
provider = IBMQ.get_provider(hub='ibm-q-internal', group='deployed', project='default')
backend = provider.get_backend('ibmq_athens')

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
# HamiltonianModel.from_dict(backend.configuration().hamiltonian, subsystem_list=[0])._system[1][0].full()
print(backend.configuration().hamiltonian['h_latex'])

```

    \begin{align} \mathcal{H}/\hbar = & \sum_{i=0}^{4}\left(\frac{\omega_{q,i}}{2}(\mathbb{I}-\sigma_i^{z})+\frac{\Delta_{i}}{2}(O_i^2-O_i)+\Omega_{d,i}D_i(t)\sigma_i^{X}\right) \\ & + J_{1,2}(\sigma_{1}^{+}\sigma_{2}^{-}+\sigma_{1}^{-}\sigma_{2}^{+}) + J_{3,4}(\sigma_{3}^{+}\sigma_{4}^{-}+\sigma_{3}^{-}\sigma_{4}^{+}) + J_{0,1}(\sigma_{0}^{+}\sigma_{1}^{-}+\sigma_{0}^{-}\sigma_{1}^{+}) + J_{2,3}(\sigma_{2}^{+}\sigma_{3}^{-}+\sigma_{2}^{-}\sigma_{3}^{+}) \\ & + \Omega_{d,0}(U_{0}^{(0,1)}(t))\sigma_{0}^{X} + \Omega_{d,1}(U_{1}^{(1,0)}(t)+U_{2}^{(1,2)}(t))\sigma_{1}^{X} \\ & + \Omega_{d,2}(U_{3}^{(2,1)}(t)+U_{4}^{(2,3)}(t))\sigma_{2}^{X} + \Omega_{d,3}(U_{6}^{(3,4)}(t)+U_{5}^{(3,2)}(t))\sigma_{3}^{X} \\ & + \Omega_{d,4}(U_{7}^{(4,3)}(t))\sigma_{4}^{X} \\ \end{align}
    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

\begin{align} \mathcal{H}/\hbar = & \sum_{i=0}^{4}\left(\frac{\omega_{q,i}}{2}(\mathbb{I}-\sigma_i^{z})+\frac{\Delta_{i}}{2}(O_i^2-O_i)+\Omega_{d,i}D_i(t)\sigma_i^{X}\right) \\ & + J_{1,2}(\sigma_{1}^{+}\sigma_{2}^{-}+\sigma_{1}^{-}\sigma_{2}^{+}) + J_{3,4}(\sigma_{3}^{+}\sigma_{4}^{-}+\sigma_{3}^{-}\sigma_{4}^{+}) + J_{0,1}(\sigma_{0}^{+}\sigma_{1}^{-}+\sigma_{0}^{-}\sigma_{1}^{+}) + J_{2,3}(\sigma_{2}^{+}\sigma_{3}^{-}+\sigma_{2}^{-}\sigma_{3}^{+}) \\ & + \Omega_{d,0}(U_{0}^{(0,1)}(t))\sigma_{0}^{X} + \Omega_{d,1}(U_{1}^{(1,0)}(t)+U_{2}^{(1,2)}(t))\sigma_{1}^{X} \\ & + \Omega_{d,2}(U_{3}^{(2,1)}(t)+U_{4}^{(2,3)}(t))\sigma_{2}^{X} + \Omega_{d,3}(U_{6}^{(3,4)}(t)+U_{5}^{(3,2)}(t))\sigma_{3}^{X} \\ & + \Omega_{d,4}(U_{7}^{(4,3)}(t))\sigma_{4}^{X} \\ \end{align}

```python
using_simple_R=True
q_gate=True
if using_simple_R:
    backend.configuration().hamiltonian['vars']['wq0'] = 0
    backend.configuration().hamiltonian['vars']['wq1'] = 0
ham = convert_qutip_ham(backend, subsystem_list, False)
# ham['H_c'].pop('U2')
H_d = ham['H_d']
# ham['H_c'].pop('U2')
H_c = list(ham['H_c'].values())
if q_gate:
    H_c = H_c[0:2]

U_0 = Qobj(tensor(identity(3), identity(3)).full())
# U_0 = identity(3)

n_ctrls = len(H_c)
n_ctrls
```

    U0
    not using control
    U1
    not using control
    U2
    not using control
    D0
    NEED TO FINISH sigx1 or 2
    D1
    NEED TO FINISH sigx1 or 2
    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

    2

```python
H_c = H_c[0:2]
len(H_c)
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

    2

### Define the 3 level operators

```python
h = hadamard_transform(1).full()

hadamard_3 = Qobj([[h[0][0],h[0][1],0],
                   [h[1][0],h[1][1],0],
                   [0,      0,      1]])
sigmax_3 =  Qobj([[0,1,0],
                  [1,0,0],
                  [0,0,1]])

# cnot = Qobj(d_sum(identity(3), sigmax_3))

# cnot = Qobj(tensor(identity(3), sigmax_3)
cnot = d_sum(Qobj(d_sum(identity(3), sigmax_3)), identity(3))

# U_targ = Qobj(tensor(sigmax_3, identity(3)))
# U_targ = Qobj(tensor(identity(3), hadamard_3))
U_targ = Qobj(tensor(identity(3),sigmax_3))
U_0 = tensor(identity(3), identity(3))
# U_targ = Qobj(cnot)
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)
    <ipython-input-8-d9728022b02a>:1: DeprecationWarning: Importing functions/classes of the qip submodule directly from the namespace qutip is deprecated. Please import them from the submodule instead, e.g.
    from qutip.qip.operations import cnot
    from qutip.qip.circuit import QubitCircuit

      h = hadamard_transform(1).full()

### Defining the time evolution parameters

Multiple total evolution times will be tried. Using this approach, the minimum evolution time required to achieve the target fidelity could be determined (iteratively).

Note that the timeslot duration dt is fixed, and so the number of timeslots depends on the evo_time

```python
# Duration of each timeslot
dt = backend.configuration().dt * 1e9
# dt = 0.02
# n_ts_list = list(np.arange(0,800,100))
n_ts_list = [512]#, 480, 560,640]
evo_times = [dt * n for n in n_ts_list]
# List of evolution times to try
# evo_times = [1, 3, 6]
n_evo_times = len(evo_times)
evo_time = evo_times[0]
n_ts = int(float(evo_time) / dt)
#Empty list that will hold the results for each evolution time
# results = list()
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Set the conditions which will cause the pulse optimisation to terminate

### Set the initial pulse type

Here the linear initial pulse type is used, simply because it results in smooth final pulses

```python
# pulse type alternatives: RND|ZERO|LIN|SINE|SQUARE|SAW|TRIANGLE|
p_type = 'LIN'
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Give an extension for output files

```python
#Set to None to suppress output files
# f_ext = "{}_n_ts{}_ptype{}.txt".format(example_name, n_ts, p_type)
f_ext = None
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Create the optimiser objects

Here is the main difference between this and the Hadamard example. In this case we use a different pulseoptim function that just creates the objects that can be used to set the physics and configure the optimisation algorithm. This gives greater flexibility (shown here by seting different initial pulse parameters for each control) and is also more efficient when running multiple optimisations on the same system.

```python
# Fidelity error target
fid_err_targ = 1e-30
# Maximum iterations for the optisation algorithm
max_iter = 2000
# Maximum (elapsed) time allowed in seconds
max_wall_time = 120
# Minimum gradient (sum of gradients squared)
# as this tends to 0 -> local minima has been found
min_grad = 1e-30
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
optim = cpo.create_pulse_optimizer(H_d, H_c, U_0, U_targ, n_ts, evo_time,
                amp_lbound=-.7, amp_ubound=.7,
                fid_err_targ=fid_err_targ, min_grad=min_grad,
                max_iter=max_iter, max_wall_time=max_wall_time,
                optim_method='fmin_l_bfgs_b',
                method_params={'max_metric_corr':20, 'accuracy_factor':1e8},
                dyn_type='UNIT',
                fid_params={'phase_option':'PSU'},
                init_pulse_type=p_type,
                log_level=log_level, gen_stats=True)

# **** get handles to the other objects ****
optim.test_out_files = 0
dyn = optim.dynamics
dyn.test_out_files = 0
p_gen = optim.pulse_generator

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Optimise the pulse for each of the different evolution times

Here a loop is used to perform the optimisation for each of the evo_times given in the list above. The first optimisation is completed using the timeslot parameters passed when the optimisation objects are created. For the subsequent runs, the Dynamics object 'dyn' is used to set the timeslot parameters before the initial pulses are generated and optimisation is completed. Note that using this method, the dyn.initialize_controls method must be called with an array of the initial amplitudes before the optim.run_optimization method is called.

```python
dt
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

    0.2222222222222222

```python
results = []
for i in range(n_evo_times):
    # Generate the tau (duration) and time (cumulative) arrays
    # so that it can be used to create the pulse generator
    # with matching timeslots
    dyn.init_timeslots()
    if i > 0:
        # Create a new pulse generator for the new dynamics
        p_gen = pulsegen.create_pulse_gen(p_type, dyn)

    #Generate different initial pulses for each of the controls
    init_amps = np.zeros([n_ts, n_ctrls])
    if (p_gen.periodic):
        phase_diff = np.pi / n_ctrls
        for j in range(n_ctrls):
            init_amps[:, j] = p_gen.gen_pulse(start_phase=phase_diff*j)
    elif (isinstance(p_gen, pulsegen.PulseGenLinear)):
        for j in range(n_ctrls):
            p_gen.scaling = float(j) - float(n_ctrls - 1)/2
            init_amps[:, j] = p_gen.gen_pulse()
    elif (isinstance(p_gen, pulsegen.PulseGenZero)):
        for j in range(n_ctrls):
            p_gen.offset = sf = float(j) - float(n_ctrls - 1)/2
            init_amps[:, j] = p_gen.gen_pulse()
    else:
        # Should be random pulse
        for j in range(n_ctrls):
            init_amps[:, j] = p_gen.gen_pulse()

    dyn.initialize_controls(init_amps)

    # Save initial amplitudes to a text file
    if f_ext is not None:
        pulsefile = "ctrl_amps_initial_" + f_ext
        dyn.save_amps(pulsefile)
        print("Initial amplitudes output to file: " + pulsefile)

    print("***********************************")
    print("\n+++++++++++++++++++++++++++++++++++")
    print("Starting pulse optimisation for T={}".format(evo_time))
    print("+++++++++++++++++++++++++++++++++++\n")
    result = optim.run_optimization()
    # Save final amplitudes to a text file
    if f_ext is not None:
        pulsefile = "ctrl_amps_final_" + f_ext
        dyn.save_amps(pulsefile)
        print("Final amplitudes output to file: " + pulsefile)

    # Report the results
    result.stats.report()
    print("Final evolution\n{}\n".format(result.evo_full_final))
    print("********* Summary *****************")
    print("Final fidelity error {}".format(result.fid_err))
    print("Final gradient normal {}".format(result.grad_norm_final))
    print("Terminated due to {}".format(result.termination_reason))
    print("Number of iterations {}".format(result.num_iter))
    print("Completed in {} HH:MM:SS.US".format(
            datetime.timedelta(seconds=result.wall_time)))
    results.append(result)
    if i+1 < len(evo_times):
        # reconfigure the dynamics for the next evo time
        evo_time = evo_times[i+1]
        n_ts = int(float(evo_time) / dt)
        dyn.tau = None
        dyn.evo_time = evo_time
        dyn.num_tslots = n_ts
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)
    INFO:qutip.control.dynamics:Setting memory optimisations for level 0
    INFO:qutip.control.dynamics:Internal operator data type choosen to be <class 'numpy.ndarray'>
    INFO:qutip.control.dynamics:phased dynamics generator caching True
    INFO:qutip.control.dynamics:propagator gradient caching True
    INFO:qutip.control.dynamics:eigenvector adjoint caching True
    INFO:qutip.control.dynamics:use sparse eigen decomp False
    ***********************************

    +++++++++++++++++++++++++++++++++++
    Starting pulse optimisation for T=113.77777777777777
    +++++++++++++++++++++++++++++++++++

    INFO:qutip.control.optimizer:Optimising pulse(s) using GRAPE with 'fmin_l_bfgs_b' method

    ------------------------------------
    ---- Control optimisation stats ----
    **** Timings (HH:MM:SS.US) ****
    Total wall time elapsed during optimisation: 0:02:00.230211
    Wall time computing Hamiltonians: 0:00:02.757791 (2.29%)
    Wall time computing propagators: 0:01:50.375721 (91.80%)
    Wall time computing forward propagation: 0:00:00.580073 (0.48%)
    Wall time computing onward propagation: 0:00:00.523025 (0.44%)
    Wall time computing gradient: 0:00:05.475903 (4.55%)

    **** Iterations and function calls ****
    Number of iterations: 419
    Number of fidelity function calls: 437
    Number of times fidelity is computed: 437
    Number of gradient function calls: 437
    Number of times gradients are computed: 437
    Number of times timeslot evolution is recomputed: 437

    **** Control amplitudes ****
    Number of control amplitude updates: 436
    Mean number of updates per iteration: 1.0405727923627686
    Number of timeslot values changed: 223228
    Mean number of timeslot changes per update: 511.9908256880734
    Number of amplitude values changed: 446010
    Mean number of amplitude changes per update: 1022.9587155963303
    ------------------------------------
    Final evolution
    Quantum object: dims = [[3, 3], [3, 3]], shape = (9, 9), type = oper, isherm = False
    Qobj data =
    [[-2.15057579e-04-1.50324250e-04j -9.86940795e-01-1.61066733e-01j
      -2.38778409e-04+3.22507636e-04j -5.84742569e-04+2.85795099e-04j
       3.46295510e-04-4.38271266e-04j  3.19059744e-04-8.97865658e-04j
       1.67714094e-04+5.87213767e-04j -2.66947741e-04+7.45184141e-04j
      -8.12910749e-04+1.35687658e-03j]
     [-9.86929003e-01-1.61145071e-01j  2.52888269e-04-7.43591620e-05j
       1.45140122e-04+5.43143958e-04j -3.90763601e-04+9.17346764e-04j
       2.31013230e-04-5.33275322e-04j  5.67174385e-04-2.89304813e-04j
      -6.86440864e-05-5.50114249e-04j  8.34706950e-04-2.98810616e-04j
      -4.14902738e-04+1.44102706e-04j]
     [-3.11571318e-04+4.68522998e-04j  1.23451126e-04+3.81912264e-04j
      -9.87000368e-01-1.60709008e-01j  9.64148478e-04-7.43384618e-04j
       5.42673181e-04+1.95487944e-04j  1.20855838e-04-4.00440243e-04j
       3.19967953e-04+1.85486867e-04j -3.97545254e-04+8.08666624e-05j
       2.10638801e-04-2.73135500e-04j]
     [-4.87331565e-05-5.78556722e-04j -1.87502883e-04-5.24172139e-04j
      -5.78178687e-04+1.32289198e-05j -2.63494910e-04+4.70901994e-04j
      -9.87019239e-01-1.60592180e-01j  2.38500179e-04+2.30575632e-05j
      -4.21679392e-04-6.83019030e-04j -2.36843179e-04+4.66711138e-04j
      -7.63653099e-04+5.84162053e-04j]
     [ 7.85600692e-05+9.95005110e-04j  4.64313403e-04+4.56316874e-04j
      -6.77384797e-04-1.01204422e-03j -9.86944696e-01-1.61039250e-01j
       1.01125703e-04+5.30293430e-04j -2.04788676e-04-7.50058593e-04j
      -3.83731336e-04-8.10308300e-04j  5.88344149e-04+1.23529758e-03j
      -9.41439356e-05+2.08964383e-04j]
     [-4.47221529e-04-4.55049387e-04j -1.58373547e-05-9.54514586e-04j
       1.34601454e-05-4.17908361e-04j  4.31956803e-04-6.44933934e-04j
      -2.33806036e-04-5.43787367e-05j -9.86889338e-01-1.61383897e-01j
       1.03847565e-03+5.13560560e-04j  1.51897990e-05+5.05438493e-04j
       7.41857161e-04+6.09270316e-04j]
     [-6.95419951e-04-5.47640113e-04j  1.56115004e-05+7.91083289e-04j
       3.49831027e-04+2.03775315e-04j -9.52091612e-04+9.85020253e-04j
       7.61864187e-05+5.18139895e-04j -1.76503549e-04+4.73101356e-04j
       4.65221163e-04-1.74821337e-04j -9.86858681e-01-1.61572165e-01j
       3.84424494e-05-3.89481198e-04j]
     [ 2.38372911e-04-5.00616645e-04j -3.46137351e-04+5.02512800e-04j
      -3.61117854e-04+7.41005267e-05j  6.20567932e-04-6.47957437e-04j
       6.15924123e-04-5.14462467e-04j -1.14742111e-03+1.56906605e-04j
      -9.86977954e-01-1.60842765e-01j -3.86447429e-04-3.12706472e-04j
       4.71445340e-04+2.17763912e-04j]
     [ 3.46850392e-04+2.68499628e-04j  3.38456838e-04+1.54476323e-03j
      -1.12641346e-04-3.25470033e-04j  2.47836708e-05+2.26960638e-04j
       5.37793432e-04+7.96038615e-04j -8.99768678e-04+3.41817071e-04j
      -5.14707782e-04+5.75780571e-05j  8.89986334e-05-3.81717005e-04j
      -9.86980291e-01-1.60825280e-01j]]

    ********* Summary *****************
    Final fidelity error 2.250487713850724e-06
    Final gradient normal 0.00016032544244748523
    Terminated due to Max wall time exceeded
    Number of iterations 420
    Completed in 0:02:00.230211 HH:MM:SS.US

```python
evo = results[0].evo_full_final
evo.tidyup(1e-3)
# len(H_c)

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

Quantum object: dims = [[3, 3], [3, 3]], shape = (9, 9), type = oper, isherm = False\begin{equation*}\left(\begin{array}{*{11}c}0.0 & (-0.987-0.161j) & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.001j\\(-0.987-0.161j) & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & (-0.987-0.161j) & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & (-0.987-0.161j) & 0.0 & 0.0 & 0.0 & 0.0\\0.0 & 0.0 & -0.001j & (-0.987-0.161j) & 0.0 & 0.0 & 0.0 & 0.001j & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & (-0.987-0.161j) & 0.001 & 0.0 & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & (-0.987-0.162j) & 0.0\\0.0 & 0.0 & 0.0 & 0.0 & 0.0 & -0.001 & (-0.987-0.161j) & 0.0 & 0.0\\0.0 & 0.002j & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & 0.0 & (-0.987-0.161j)\\\end{array}\right)\end{equation*}

### Plot the initial and final amplitudes

```python
fig1 = plt.figure(figsize=(30,8))
for i in range(n_evo_times):
    #Initial amps
    ax1 = fig1.add_subplot(2, n_evo_times, i+1)
    ax1.set_title("Init amps T={}".format(evo_times[i]))
    # ax1.set_xlabel("Time")
    ax1.get_xaxis().set_visible(False)
    if i == 0:
        ax1.set_ylabel("Control amplitude")
    for j in range(n_ctrls):
        ax1.step(results[i].time,
             np.hstack((results[i].initial_amps[:, j],
                        results[i].initial_amps[-1, j])),
                 where='post')

    ax2 = fig1.add_subplot(2, n_evo_times, i+n_evo_times+1)
    ax2.set_title("Final amps T={}".format(evo_times[i]))
    ax2.set_xlabel("Time")
    #Optimised amps
    if i == 0:
        ax2.set_ylabel("Control amplitude")
    for j in range(n_ctrls):
        ax2.step(results[i].time,
             np.hstack((results[i].final_amps[:, j],
                        results[i].final_amps[-1, j])),
                 where='post')

plt.tight_layout()
plt.show()
```

### Versions

```python
pulse_seq = results[0].final_amps
pulse_seq
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

    array([[ 0.6728319 , -0.39643183],
           [ 0.68060756, -0.32376967],
           [ 0.61748569, -0.26208657],
           ...,
           [-0.14630335,  0.12922282],
           [-0.13461241,  0.28505606],
           [-0.19434586,  0.46958887]])

### Run QuTip pulse simulation

```python
processor = Processor(N=1, dims=[3 ** len(subsystem_list)])

dt = backend.configuration().dt * 1e9

# H_d = wq0 * (1 - sigmaz()) / 2
tlist = np.array([dt * i for i in range(len(pulse_seq) + 1)])
for i,control in enumerate(H_c):
    processor.add_control(control, targets=0)
    coef = [a[i] for a in pulse_seq]

    processor.pulses[i].coeff =  coef
    processor.pulses[i].tlist = tlist

# processor.pulses[0].coeff = seq_x
# processor.pulses[0].tlist = np.array([dt * i for i in range(len(seq_x)+1)])
# processor.add_control(H_c[0], targets=[0], label='sigmax')
# processor.add_control(H_d, targets=[0], label="drift")
processor.add_drift(H_d, targets=[0])

basis0 = basis(3 ** len(subsystem_list), 0)
# basis0 = basis(4,0) + basis(4,2)
result = processor.run_state(init_state=basis0)

result.states[-1].tidyup(1.e-3)
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

Quantum object: dims = [[9], [1]], shape = (9, 1), type = ket\begin{equation*}\left(\begin{array}{*{11}c}0.0\\(-0.987-0.161j)\\0.0\\0.0\\0.001j\\0.0\\0.0\\0.0\\0.0\\\end{array}\right)\end{equation*}

### Create Channels and convert qoc pulses to Qiskit pulses

```python
q1=0
q2=1

drive_chan1 = pulse.DriveChannel(q1)
meas_chan1 = pulse.MeasureChannel(q1)
acq_chan1 = pulse.AcquireChannel(q1)
con_chan1 = pulse.ControlChannel(q1)

drive_chan2 = pulse.DriveChannel(q2)
meas_chan2 = pulse.MeasureChannel(q2)
acq_chan2 = pulse.AcquireChannel(q2)
con_chan2 = pulse.ControlChannel(q2)

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
pulse_seqs = []
for i,control in enumerate(H_c):
    coef = [a[i] for a in pulse_seq]
    pulse_seqs.append(coef)
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
# pulse_seqs[2]
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
init_x = backend.defaults().instruction_schedule_map.get('x',[0])
init_x
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

    Schedule((0, Play(Drag(duration=160, amp=(0.198021137140145+0j), sigma=40, beta=0.8238197928261547), DriveChannel(0))), name="x")

```python
def_cx = backend.defaults().instruction_schedule_map.get('cx', [0,1])
# def_cx.draw()
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
# def_cnot = backend.defaults().instruction_schedule_map.get('', [0,1])
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
pulse_seq
D0 = []
D1 = []
for a in pulse_seq:
    # amp1 = derotate(a[0])
    # amp2 = derotate(a[1])
    D0.append(complex(a[0], a[1]))
    # D0.append(amp1, amp2)
    # D1.append(complex(a[2], a[3]))

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
schedule = pulse.Schedule(name='Frequency sweep')

# schedule += init_x

later = schedule.duration

# schedule += def_cx << later
if len(subsystem_list) == 1:
    schedule += Play(SamplePulse(pulse_seqs[0]), drive_chan1) << later
else:
    # schedule += Play(SamplePulse(pulse_seqs[0]), con_chan1) << later
    # schedule += Play(SamplePulse(pulse_seqs[1]), con_chan2) << later

    # schedule += Play(SamplePulse(pulse_seqs[2]), drive_chan1) << later

    # schedule += Play(SamplePulse(pulse_seqs[3]), drive_chan2)
    schedule += Play(SamplePulse(D0),drive_chan1) << later
    # schedule += Play(SamplePulse(D1),drive_chan1) << later
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)
    /Users/benrosand/oct-qiskit-pulse/qiskit-terra/qiskit/pulse/library/sample_pulse.py:40: DeprecationWarning: SamplePulse has been renamed to Waveform and is deprecated. Please replace SamplePulse(samples, channel) with Waveform(samples, channel).
      warnings.warn("SamplePulse has been renamed to Waveform and is deprecated. " +

```python
# Create the base schedule

from qiskit.pulse.macros import measure_all
# schedule = def_cx
schedule += measure_all(backend) << schedule.duration
# schedule.draw(plot_range=[0, 1000])

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

### Run the assembled pulse schedule

```python
from qiskit import assemble

num_shots_per_frequency = 1024
frequency_sweep_program = assemble(schedule,
                                   backend=backend,
                                   meas_level=2,
                                   meas_return='single',
                                #    qubit_lo_freq=backend.defaults().qubit_freq_est,
                                   shots=num_shots_per_frequency,
                                   )
                                #    schedule_los=schedule_frequencies)

```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
job = backend.run(frequency_sweep_program)
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
from qiskit.tools.monitor import job_monitor
job_monitor(job)

# job.result().get_counts()
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)
    Job Status: job has successfully run

```python
from src.helper import qubit_distribution
qubit_distribution(job.result().get_counts())
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

    {0: {0: 1013, 1: 11},
     1: {0: 1020, 1: 4},
     2: {0: 1021, 1: 3},
     3: {0: 997, 1: 27},
     4: {0: 294, 1: 730}}

```python
from qiskit import QuantumCircuit
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python
circ = QuantumCircuit(2)
circ.cnot(0,1)
```

    /Users/benrosand/anaconda3/envs/qoc/lib/python3.8/site-packages/ipykernel/ipkernel.py:287: DeprecationWarning: `should_run_async` will not call `transform_cell` automatically in the future. Please pass the result to `transformed_cell` argument and any exception that happen during thetransform in `preprocessing_exc_tuple` in IPython 7.17 and above.
      and should_run_async(code)

```python

transp_circ = qiskit.transpile(circ, backend=backend)
transp_circ.data[0][0]
```

```python
processor = Processor(N=1, dims=[9])

dt = backend.configuration().dt * 1e9

# H_d = wq0 * (1 - sigmaz()) / 2
tlist = np.array([dt * i for i in range(len(def_seq[0]) + 1)])
for i,control in enumerate(H_c):
    processor.add_control(control, targets=0)
    # coef = [a[i] for a in pulse_seq]

    processor.pulses[i].coeff = def_seq[i]
    processor.pulses[i].tlist = tlist

# processor.pulses[0].coeff = seq_x
# processor.pulses[0].tlist = np.array([dt * i for i in range(len(seq_x)+1)])
# processor.add_control(H_c[0], targets=[0], label='sigmax')
# processor.add_control(H_d, targets=[0], label="drift")
processor.add_drift(H_d, targets=[0])

basis0 = basis(9, 0)
# basis0 = basis(4,0) + basis(4,2)
result = processor.run_state(init_state=basis0)

result.states[-1].tidyup(1.e-3)
```


## EECS 445 - Introduction to Machine Learning
## Lecture 3: Convex Optimization and Review of Probability
### Date: September 14, 2016
### Instructor: Jacob Abernethy and Jia Deng

```python
from IPython.core.display import HTML, Image
from IPython.display import YouTubeVideo
from sympy import init_printing, Matrix, symbols, Rational
import sympy as sym
from warnings import filterwarnings
init_printing(use_latex = 'mathjax')
filterwarnings('ignore')

%pylab inline

import numpy as np
```

    Populating the interactive namespace from numpy and matplotlib

###### Some important notes
* HW1 is out! Due September 26th
* Homework will be submitted via *Gradescope*. Please see Piazza for precise instructions. Do it soon, not at the last minute!!

## Outline for this Lecture
- Convexity
    - Convex Set
    - Convex Function
- Introduction to Optimization
- Introduction to Lagrange Duality

> In this lecture, we will first introduce convex set, convex function and optimization problem. One approach to solving an optimization problem is to solve its dual problem. We will briefly cover some basics of duality in this lecture. More about optimization and duality will come when we study support vector machine (SVM).

## Convexity
### Convex Sets
- $C \subseteq \mathbb{R}^n$ is **convex** if
$$t x + (1-t)y \in C$$
for any $x, y \in C$ and $0 \leq t \leq 1$
- that is, a set is convex if the line connecting **any** two points in the set is entirely inside the set

(Left: Convex Set; Right: Non-convex Set)

### Convex Functions
* We say that a function $f$ is *convex* if, for any distinct pair of points $x_1,x_2$ we have
$$f(tx_1+(1-t)x_2) \leq tf(x_1)+(1-t)f(x_2) \quad \forall t \in[0,1]$$
* $f$ is *strictly convex* if strict inequality holds when $t \in (0,1)$
* A function $f$ is said to be *concave* if $-f$ is convex

### Fun Facts About Convex Functions
* If $f$ is differentiable, then $f$ is convex iff $f$ "lies above its linear approximation", i.e.:
$$ f(x + y) \geq f(x) + \nabla_x f(x) \cdot y \quad \forall x,y$$

   [Boyd and Vandenberghe]
* If $f$ is twice-differentiable, then $f$ is convex iff its hessian is always positive semi-definite!

## Introduction to Optimization
### The Most General Optimization Problem
Assume $f$ is some function, and $C \subset \mathbb{R}^n$ is some set. The following is an *optimization problem*:
$$
\begin{array}{ll}
\mbox{minimize} & f(x) \\
\mbox{subject to} & x \in C
\end{array}
$$
* How hard is it to find a solution that is (near-) optimal? This is one of the fundamental problems in Computer Science and Operations Research.
* A huge portion of ML relies on this task

### A Rough Optimization Hierarchy
$$
\mbox{minimize } \ f(x) \quad \mbox{subject to } x \in C
$$
* **[Really Easy]** $C = \mathbb{R}^n$ (i.e. problem is *unconstrained*), $f$ is convex, $f$ is differentiable, strictly convex, and "slowly-changing" gradients
* **[Easyish]** $C = \mathbb{R}^n$, $f$ is convex
* **[Medium]** $C$ is a convex set, $f$ is convex
* **[Hard]** $C$ is a convex set, $f$ is non-convex
* **[REALLY Hard]** $C$ is an arbitrary set, $f$ is non-convex

### Optimization Without Constraints
$$
\begin{array}{ll}
\mbox{minimize} & f(x) \\
\mbox{subject to} & x \in \mathbb{R}^n
\end{array}
$$
* This problem tends to be easier than constrained optimization
* If $f$ is convex, we just need to find an $x$ such that $\nabla f(x) = \vec{0}$ (necessary and sufficient)
* Techniques like *gradient descent* or *Newton's method* work in this setting. (More on this later)

### Optimization With Constraints
$$
\begin{aligned}
& {\text{minimize}} & & f(\mathbf{x})\\
& \text{subject to} & & g_i(\mathbf{x}) \leq 0, \quad i = 1, ..., m\\
& & & h_j(x) = 0, \quad j = 1, ..., n
\end{aligned}
$$
* Here $C = \{ x : g_i(x) \leq 0,\ h_j(x) = 0, \ i=1, \ldots, m,\ j = 1, ..., n \}$
* $C$ is convex as long as all $g_i(x)$ convex and all $h_j(x)$ affine (linear function + translation)
* The solution of this optimization may occur in the *interior* of $C$, in which case the optimal $x$ will have $\nabla f(x) = 0$
* But what if the solution occurs on the *boundary* of $C$?

## Introduction to Lagrange Duality
* In some cases original (**primal**) optimization problem can be hard to solve, solving a proxy problem sometimes can be easier
* The proxy problem could be **dual** problem which is transformed from primal problem
* Here is how to transform from primal to dual. For primal problem
$$
\begin{aligned}
& {\text{minimize}} & & f(\mathbf{x})\\
& \text{subject to} & & g_i(\mathbf{x}) \leq 0, \quad i = 1, ..., m\\
& & & h_j(x) = 0, \quad j = 1, ..., n
\end{aligned}
$$
Its Lagrangian is
$$L(x,\boldsymbol{\lambda}, \boldsymbol{\nu}) := f(x) + \sum_{i=1}^m \lambda_i g_i(x) + \sum_{j=1}^n \nu_j h_j(x)$$
of which $\boldsymbol{\lambda} \in \mathbb{R}^m$, $\boldsymbol{\nu} \in \mathbb{R}^n$ are **dual variables**

* The **Langragian dual function** is
$$L_D(\boldsymbol{\lambda}, \boldsymbol{\nu}) \triangleq \underset{x}{\inf}L(x,\boldsymbol{\lambda}, \boldsymbol{\nu}) = \underset{x}{\inf} \ \left[ f(x) + \sum_{i=1}^m \lambda_i g_i(x) + \sum_{j=1}^n \nu_j h_j(x) \right] $$
The minization is usually done by finding the stationary point of $L(x,\boldsymbol{\lambda}, \boldsymbol{\nu})$ with respect to $x$

### The Lagrange Dual Problem

* Then the **dual problem** is
$$
\begin{aligned}
& {\text{maximize}} & & L_D(\mathbf{\lambda}, \mathbf{\nu})\\
& \text{subject to} & & \lambda_i, \nu_j \geq 0 \quad i = 1, \ldots, m ,\ j = 1, ..., n\\
\end{aligned}
$$
Instead of solving primal problem with respect to $x$, we now need to solve dual problem with respect to $\boldsymbol{\lambda}$ and $\boldsymbol{\nu}$
* $L_D(\boldsymbol{\lambda}, \boldsymbol{\nu})$ is concave even if primal problem is not convex
* Let the $p^*$ and $d^*$ denote the optimal values of primal problem and dual problem, we always have *weak duality*: $p^* \geq d^*$
* Under nice conditions, we get *strong duality*: $p^* = d^*$
* Many details are ommited here and they will come when we study **support vector machine (SVM)**

## Recommended reading:
* Free online!
* Chapter 5 covers duality