# Notebook from RyAlAl/DMD
Path: Spin_chain.ipynb

_____no_output_____

!git clone --single-branch --branch 'simplified-version' 'https://github.com/LuchnikovI/Dynamic-mode-decomposition-for-open-quantum-systems-identification'
%cd 'Dynamic-mode-decomposition-for-open-quantum-systems-identification'
!pip install qutip

import math
import tensorflow as tf
import matplotlib.pyplot as plt
from qutip import *
import numpy as np

from nmd_finite_env import FiniteEnv
from embedding import Embedding

#from mixed_dynamics import generate_dynamicsCloning into 'Dynamic-mode-decomposition-for-open-quantum-systems-identification'...
remote: Enumerating objects: 173, done.
remote: Counting objects: 100% (173/173), done.
remote: Compressing objects: 100% (132/132), done.
remote: Total 173 (delta 97), reused 103 (delta 41), pack-reused 0
Receiving objects: 100% (173/173), 833.30 KiB | 2.30 MiB/s, done.
Resolving deltas: 100% (97/97), done.
/content/Dynamic-mode-decomposition-for-open-quantum-systems-identification
Collecting qutip
  Downloading https://files.pythonhosted.org/packages/71/94/c79bd57137320657c3acc8e7a08d3c66dcf9d697e594e945fbc905dfec42/qutip-4.5.2.tar.gz (3.4MB)
     |████████████████████████████████| 3.4MB 5.3MB/s
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
    Preparing wheel metadata ... done
Requirement already satisfied: cython>=0.21 in /usr/local/lib/python3.6/dist-packages (from qutip) (0.29.21)
Requirement already satisfied: numpy>=1.12 in /usr/local/lib/python3.6/dist-packages (from qutip) (1.18.5)
Requirement already satisfied: scipy>=1.0 in /usr/local/lib/python3.6/dist-packages (from qutip) (1.4.1)
Building wheels for collected packages: qutip
  Building wheel for qutip (PEP 517) ... done
  Created wheel for qutip: filename=qutip-4.5.2-cp36-cp36m-linux_x86_64.whl size=12468130 sha256=d61f0151cfb3bd4f105e7acf8770591cfd82c6d92f13f91903c66fc35f529f82
  Stored in directory: /root/.cache/pip/wheels/ae/90/e9/f26fdecfb6c0e9d9d6f5fa564d16d26ff2bdfd8ad6e7a8a28a
Successfully built qutip
Installing collected packages: qutip
Successfully installed qutip-4.5.2

Hamiltonian:
$\displaystyle H = - \frac{1}{2}\sum_n^N h_n \sigma_z(n) - \frac{1}{2} \sum_n^{N-1} [ J_x^{(n)} \sigma_x(n) \sigma_x(n+1) + J_y^{(n)} \sigma_y(n) \sigma_y(n+1) +J_z^{(n)} \sigma_z(n) \sigma_z(n+1)]$

_____no_output_____

class SpinChain:
    ''' This class provides trajectories for spin chain evolution.
    Args:
        N: spins amount
        solver: 'me' - ODE solver, 'mc' - Monte-Carlo solver
        ntraj: number of trajectories for 'mc' solver
    '''
    def __init__(self, N=3, solver='me', ntraj=1):
        self.N = N
        self.solver = solver
        self.ntraj = ntraj
        self.h = None
        self.Jx = None
        self.Jy = None
        self.Jz = None
        self.gamma = None

    def pauli(self):
        '''
        Create Pauli matrices for the system.
        Output:
            sx, sy, sz with shapes (N, 2**N, 2**N)
        '''
        si = qeye(2)
        sx = sigmax()
        sy = sigmay()
        sz = sigmaz()

        sx_list = []
        sy_list = []
        sz_list = []

        for n in range(self.N):
            op_list = []
            for m in range(self.N):
                op_list.append(si)

            op_list[n] = sx
            sx_list.append(tensor(op_list))

            op_list[n] = sy
            sy_list.append(tensor(op_list))

            op_list[n] = sz
            sz_list.append(tensor(op_list))

        return sx_list, sy_list, sz_list

    def set_problem(self, **kwargs):
        '''
        Setting Hamiltonian and initial conditions.
        Args:
            Kwargs: 'h', 'Jx', 'Jy', 'Jz', 'gamma', 'psi0'
        '''
        # array of spin energy splittings and coupling strengths. here we use
        # uniform parameters, but in general we don't have too
        self.h  = 1.0 * 2 * np.pi * np.ones(self.N)
        self.Jz = 0.1 * 2 * np.pi * np.ones(self.N)
        self.Jx = 0.1 * 2 * np.pi * np.ones(self.N)
        self.Jy = 0.1 * 2 * np.pi * np.ones(self.N)

        # dephasing rate
        self.gamma = 0.01 * np.ones(self.N)

        # intial state, first spin in state |1>, the rest in state |0>
        psi_list = []
        psi_list.append(basis(2,1))
        for n in range(self.N-1):
            psi_list.append(basis(2,0))
        self.psi0 = tensor(psi_list)

        # kwargs parsing
        for key, value in kwargs.items():
            if key == 'h':
                self.h = value
            if key == 'Jx':
                self.Jx = value
            if key == 'Jy':
                self.Jy = value
            if key == 'Jz':
                self.Jz = value
            if key == 'gamma':
                self.gamma = value
            if key == 'psi0':
                self.psi0 = value

    def solve(self, total_time=50, steps=200):
        '''
        Calculation trajectories for spin chain
        Input:
            total_time: simulation time, ns
            steps: number of time steps
        Output:
            density matrices of shape (ntraj, steps, 2**N, 2**N)

        '''
        # array of timesteps
        tlist = np.linspace(0, total_time, steps)

        # compute Pauli matrices
        sx_list, sy_list, sz_list = self.pauli()
        # construct the hamiltonian
        H = 0
        # energy splitting terms
        for n in range(self.N):
            H += - 0.5 * self.h[n] * sz_list[n]

        # interaction terms
        for n in range(self.N-1):
            H += - 0.5 * self.Jx[n] * sx_list[n] * sx_list[n+1]
            H += - 0.5 * self.Jy[n] * sy_list[n] * sy_list[n+1]
            H += - 0.5 * self.Jz[n] * sz_list[n] * sz_list[n+1]

        # collapse operators
        c_op_list = []

        # spin dephasing
        for n in range(self.N):
            if self.gamma[n] > 0.0:
                c_op_list.append(np.sqrt(self.gamma[n]) * sz_list[n])

        # evolve and calculate expectation values
        if self.solver == "me":
            kets = mesolve(H, self.psi0, tlist, c_op_list)
            states = kets.states
            dens = []
            for t in range(steps):
                ket = states[t].full()
                bra = states[t].dag().full()
                dens.append(bra*ket)
            dens = np.array(dens)[tf.newaxis]
        elif self.solver == "mc":
            kets = mcsolve(H, self.psi0, tlist, c_op_list, ntraj=self.ntraj)
            states = kets.states
            dens = []
            for traj in range(self.ntraj):
                arr = []
                for t in range(steps):
                    ket = states[traj, t].full()
                    bra = states[traj, t].dag().full()
                    arr.append(bra*ket)
                dens.append(arr)
            dens = np.array(dens)
        return dens
    def sigma(self, dens, axis='z'):
        '''
        Returns product of density matrix over Pauli matrix.
        Args:
            density: density matrixes of shape (ntraj, steps, 2**N, 2**N)
            axis: given Pauli matrix ('x', 'y', 'z')
        Output:
            sigma_{'axis'} of shape (ntraj, steps, N)
        '''
        sx_list, sy_list, sz_list = self.pauli()
        if axis == 'x':
            A = tf.concat([sx_list[i].full()[tf.newaxis]
                           for i in range(self.N)], axis=0)
        if axis == 'y':
            A = tf.concat([sy_list[i].full()[tf.newaxis]
                           for i in range(self.N)], axis=0)
        if axis == 'z':
            A = tf.concat([sz_list[i].full()[tf.newaxis]
                           for i in range(self.N)], axis=0)

        return tf.tensordot(dens, A, [[2, 3], [2, 1]])

_____no_output_____

Spin chain evolution_____no_output_____

#------------------------Parameters--------------------------#
N = 3 # number of spins
#------------------------------------------------------------#
# Problem setting
prob = SpinChain(N=N)
# Setting Hamiltonian and inital state
prob.set_problem()
# Integration
dens = prob.solve()_____no_output_____

DMD_____no_output_____

#------------------------Parameters--------------------------#
K = 10  # memory depth
eps = 1e-6  # tolerance
#------------------------------------------------------------#
# embedding
emb = Embedding()
# learn embedding
emb.learn(dens, K, eps)
# min rank
rank = emb.rank_____no_output_____

Performance of DMD compression_____no_output_____

# Calculation sigma_z for true trajectories
sigma_z_train = prob.sigma(dens)
# Calculation sigma_z for DMD reconstructed trajectories
sigma_z_train_DMD = prob.sigma(tf.concat(
    [dens[0,:K-1], emb.predict(dens[0,:K], dens.shape[1])],
    axis=0)[tf.newaxis])
# Plotting
plt.plot(sigma_z_train[0,:,0])
plt.plot(sigma_z_train_DMD[0,:,0],'x')/usr/local/lib/python3.6/dist-packages/numpy/core/_asarray.py:85: ComplexWarning: Casting complex values to real discards the imaginary part
  return array(a, dtype, copy=False, order=order)
/usr/local/lib/python3.6/dist-packages/numpy/core/_asarray.py:85: ComplexWarning: Casting complex values to real discards the imaginary part
  return array(a, dtype, copy=False, order=order)

Testing DMD performance: trajectory based on another initial data_____no_output_____

# intial state, second spin in state |1>, the rest in state |0>
psi_list = []
psi_list.append(basis(2,0)) # state |0>
psi_list.append(basis(2,1)) # state |1>
for n in range(N-2):
    psi_list.append(basis(2,0))
psi0 = tensor(psi_list)

# calculation trajectory with new initial data psi0
prob.set_problem(psi0=psi0) # update of problem initial state
dens_test = prob.solve() # integration

# Calculation sigma_z for true trajectories
sigma_z_test = prob.sigma(dens_test)
# Calculation sigma_z for DMD reconstructed trajectories
sigma_z_test_DMD = prob.sigma(tf.concat(
    [dens_test[0,:K-1], emb.predict(dens_test[0,:K], dens_test.shape[1])],
    axis=0)[tf.newaxis])
# Plotting
plt.plot(sigma_z_test[0,:,0])
plt.plot(sigma_z_test_DMD[0,:,0],'x')/usr/local/lib/python3.6/dist-packages/numpy/core/_asarray.py:85: ComplexWarning: Casting complex values to real discards the imaginary part
  return array(a, dtype, copy=False, order=order)
/usr/local/lib/python3.6/dist-packages/numpy/core/_asarray.py:85: ComplexWarning: Casting complex values to real discards the imaginary part
  return array(a, dtype, copy=False, order=order)


# Notebook from mlamoureux/CallystoJBook
Path: _sources/curriculum-notebooks/Science/ClimateChange/climate-change.ipynb

![Callysto.ca Banner](https://github.com/callysto/curriculum-notebooks/blob/master/callysto-notebook-banner-top.jpg?raw=true)

_____no_output_____# Climate Change

## What do we know?

The Earth's climate has been subject to changes throughout history and most of these can be attributed to variations in Earth's orbit resulting in the amount of solar energy that is received. The reason behind significant increase in warming trend since the industrial revolution is extremely likely to be the result of human activity.

Earth is a complex intergrated system of components and processes. A disturbance in one component can have unprecedented and irreversible effects over another component.

## How do we know it?

Earth-orbiting satellites and other scientific advancements have made it possible to understand Earth on a global scale and have enabled scientists to collect many different types of information about our planet, its climate and use it to analyze how it has changed over the years.

It is important to understand the evidence available for global climate change and the potential impacts it can cause in order to address the challenges that we may face. Knowing how these Earth systems have changed in the past and how they are changing in response to climate change now will help us understand how they will likely change in the future.

Scientists gather data on global temperature, Carbon dioxide in the atmosphere, Arctic sea ice, land ice and sea level on a regular basis to analyze and understand how climate is changing over the years._____no_output_____## Evidence for rapid climate change

### Evidence 1 : Global Land-Ocean Termperature index

Data Source & Inspiration: [NASA Global Climate Change](https://climate.nasa.gov/vital-signs/global-temperature/).

An increase in carbon dioxide and other human-made emissions into the atmosphere has caused our planet's average surface temperature to rise by 1.1 degrees Celsius since the late nineteenth century.

`▶Run` the following code cell to see a visualization created by NASA's scientific visualization studio showing the Global temperature anomalies from 1880 to 2017._____no_output_____

from IPython.display import YouTubeVideo
YouTubeVideo('xlrFFiSROmg')_____no_output_____

#### Code Libraries

The next code cell imports a number of code libraries that we will use in this notebook._____no_output_____

import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.animation as animation
%matplotlib inline
import plotly.graph_objs as go
import cartopy.crs as ccrs
import imageio
import os
import numpy as np
from datetime import timedelta, datetime
from IPython.display import Image

print('Code libraries imported')_____no_output_____climate_data_url = 'https://climate.nasa.gov/system/internal_resources/details/original/647_Global_Temperature_Data_File.txt'
global_temperature_df = pd.read_csv(climate_data_url, sep='\s+', skiprows=[0, 1, 2, 4])
global_temperature_df.rename(columns={'No_Smoothing':'Annual Mean', 'Lowess(5)':'Lowess Smoothing'}, inplace=True)

trace0 = go.Scatter(
    x = global_temperature_df['Year'],
    y = global_temperature_df['Annual Mean'],
    mode = 'lines+markers',
    name = 'Annual Mean'
)

trace1 = go.Scatter(
    x = global_temperature_df['Year'],
    y = global_temperature_df['Lowess Smoothing'],
    mode = 'lines+markers',
    name = 'Lowess Smoothing'
)

data = [trace0,trace1]
layout = go.Layout(dict(title='Global Land-Ocean Temperature Index'),
                   xaxis=dict(title='Year'),
                   yaxis=dict(title='Temperature Anomaly')
)
fig = go.Figure(data=data,layout=layout)
fig.show()_____no_output_____

This graph illustrates the change in global surface temperature relative to 1951-1980 average temperatures. Seventeen of the 18 warmest years in the 136-year record all have occurred since 2001, with the exception of 1998. The year 2016 ranks as the warmest on record.

By looking at the graph above and the video, one can see that 2016 was the warmest year since modern record keeping began._____no_output_____### Evidence 2: Decline in Extent and Thickness of Arctic Sea Ice

Scientists keep a close eye on Arctic sea ice as it warms through the years and it is found to be shrinking by about 13.2% per decade, relative to the 1981 to 2010 average.

Sea ice *area* and *extent* are two different measuers and they provide different information. **Sea ice extent** is a measure of edges of sea ice and all of the space inside it, while **sea ice area** is a measure of where there is ice only, not including the areas that doesn't have ice. Extent is always a larger number than area when compared at the same period of time.

Decreases in both sea ice extent and area impact a wide variety of marine life that sustain in the Arctic right from single-celled algae to bowhead whales, polar bears, and other mammals, as well as indigenous people who rely on sea ice for transportation and hunting.

The data used below are curated by NASA's global climate change intiative, originally pulled from the [National Snow and Ice Data Center](https://nsidc.org/)._____no_output_____

url = 'https://climate.nasa.gov/system/internal_resources/details/original/1270_minimum_extents_and_area_north_SBA_reg_20171001_2_.txt'
arctic_ice_df = pd.read_csv(url, sep='\s+', skiprows=[0,1,2,3],header=None, names=['Year', 'MN', 'DY', 'Ice Extent', 'Year1', 'MN1', 'DY1', 'Ice Area'])
arctic_ice_df.head(10)_____no_output_____trace0 = go.Scatter(
    x = arctic_ice_df['Year'],
    y = arctic_ice_df['Ice Extent'],
    mode = 'lines+markers',
    name = 'Ice Extent'
)

trace1 = go.Scatter(
x = arctic_ice_df['Year'],
y = arctic_ice_df['Ice Area'],
mode = 'lines+markers',
#opacity = 0.5,
name='Ice Area',
#hoverinfo = 'none'
)
data = [trace0, trace1]
layout = go.Layout(dict(title='Arctic Sea Ice Index'),
                   xaxis=dict(title='Year'),
                   yaxis=dict(title='Million Square Kilometers')
)
fig = go.Figure(data=data,layout=layout)
fig.show()_____no_output_____

The code below generates an animation that represent sea ice concentration data from the [NASA Distributed Active Archive Center](https://nsidc.org/daac) at [National Snow & Ice Data Center](https://nsidc.org/).

*Data Source: Maslanik, J. and J. Stroeve. 1999, updated daily. Near-Real-Time DMSP SSMIS Daily Polar Gridded Sea Ice Concentrations, Version 1. Boulder, Colorado USA. NASA National Snow and Ice Data Center Distributed Active Archive Center. doi: https://doi.org/10.5067/U8C09DWVX9LM. [April 2018]*_____no_output_____

sea_ice = ['./data/nt_20150418_f17_nrt_n.bin','./data/nt_20160331_f17_nrt_n.bin','./data/nt_20170418_f18_nrt_n.bin','./data/nt_20180423_f18_nrt_n.bin']

def prepare_data(ice_data):
    with open(ice_data, 'rb') as fr:
        hdr = fr.read(300)
        ice = np.fromfile(fr, dtype=np.uint8)

    ice = ice.reshape(448, 304)

    ice = ice / 250.

    ice = np.ma.masked_greater(ice, 1.0)
    dx = dy = 25000
    x = np.arange(-3850000, +3750000, +dx)
    y = np.arange(+5850000, -5350000, -dy)
    return x, y, ice

fig = plt.figure(figsize=(9,9))
ax = plt.axes(projection=ccrs.NorthPolarStereo(central_longitude=0))
i=2015
for ice_data in sea_ice:
    x, y, ice = prepare_data(ice_data)
    cs = ax.coastlines(resolution='110m', linewidth=0.5)
    ax.gridlines()
    ax.set_extent([-180, 180, 40, 90], crs=ccrs.PlateCarree())
    kw = dict(central_latitude=90, central_longitude=-45, true_scale_latitude=70)
    cs = ax.pcolormesh(x, y, ice, cmap=plt.cm.Blues,
                   transform=ccrs.Stereographic(**kw))
    fig.suptitle("April %s" %(i))
    fig.savefig(f'frames/frame_{i}.png', dpi=100, facecolor='white')
    i+=1
plt.close(fig)

png_dir = "./frames/"
images = []
for subdir, dirs, files in os.walk(png_dir):
    files = sorted(files)
    for file in files:
        file_path = os.path.join(subdir, file)
        if file_path.endswith(".png"):
            images.append(imageio.imread(file_path))
kargs = { 'duration': 2 }
imageio.mimsave('./gif/ice_concentration.gif', images, **kargs)

image_animation = Image('gif/ice_concentration.gif')
image_animation_____no_output_____

The animation shows visualization of sea ice concentration data taken in the month of April in the years 2015 to 2018. We can see the reduction in extent of sea ice over these years._____no_output_____### Evidence 3: Sea Level Rise

The Global Mean Sea Level (GMSL) increases due to thermal expansion of water and by melting of ice sheets and glaciers on land. As sea water levels increase and reach inland, it can cause erosion, more flooding, lost of habitat for fish, birds, and plants. It also affects lives of millions of people who live in sea water flood prone low lying islands and other coastal regions.

The GMSL has risen about 80 mm since 1992 at an annual rate of about 2.9 mm. Below you will see [National Oceanic and Atmospheric Administration (NOAA)](https://www.noaa.gov) data that illustrate changes in the Global mean sea level since 1992.

Data are acquired from satellite radar altimeter measuresements from the TOPEX - Poseidon (TP), Jason-1, Jason-2, and Jason-3 Satellite missions. Altimetry data are provided by the [NOAA Laboratory for Satellite Altimetry](https://www.star.nesdis.noaa.gov/socd/lsa/)._____no_output_____

sea_level_url = 'https://www.star.nesdis.noaa.gov/sod/lsa/SeaLevelRise/slr/slr_sla_gbl_keep_all_66.csv'
sea_level_df = pd.read_csv(sea_level_url, sep=',', skiprows=[0,1,2,3,4,5],header=None, names=['Year', 'TP', 'Jason-1', 'Jason-2', 'Jason-3'])

def convert_partial_year(number):
    year = int(number)
    d = timedelta(days=(number - year)*365)
    day_one = datetime(year,1,1)
    date = d + day_one
    return date

sea_level_df['Date'] = sea_level_df['Year'].apply(convert_partial_year)

trace0 = go.Scatter(
    x = sea_level_df['Date'],
    y = sea_level_df['TP'],
    mode = 'lines+markers',
    name = 'TOPEX-Poseiden'
)

trace1 = go.Scatter(
    x = sea_level_df['Date'],
    y = sea_level_df['Jason-1'],
    mode = 'lines+markers',
    name = 'Jason-1'
)

trace2 = go.Scatter(
    x = sea_level_df['Date'],
    y = sea_level_df['Jason-2'],
    mode = 'lines+markers',
    name = 'Jason-2'
)

trace3 = go.Scatter(
    x = sea_level_df['Date'],
    y = sea_level_df['Jason-3'],
    mode = 'lines+markers',
    name = 'Jason-3'
)

data = [trace0,trace1,trace2,trace3]
layout = go.Layout(dict(title='Change in Global Mean Sea Level from Satellite Altimetry'),
                   xaxis=dict(title='Date'),
                   yaxis=dict(title='Change in Mean Sea Level (mm)')
)
fig = go.Figure(data=data,layout=layout)
fig.show()_____no_output_____

### Some Questions to Consider_____no_output_____#### Temperature Anomalies Recorded in Canada:

The graph below illustrates the change in annual average temperature compared to the 30 year average reference value (measured between 1961 - 1990). The data were obtained from Government of Canada's [Open Data Portal](https://open.canada.ca/data/en/dataset/49118c5d-850c-40bd-af75-4f47650a9800).

Take a look at the graph and answer the questions that follow._____no_output_____

ca_url = 'https://www.canada.ca/content/dam/eccc/documents/csv/cesindicators/temperature-change/2019/Temperature-change-annual-en.csv'
ca_df = pd.read_csv(ca_url, encoding='iso-8859-1', skiprows=[0,1,74,75,76,77])

trace0 = go.Scatter(
    x = ca_df['Year'],
    y = ca_df['Temperature departure (degree Celsius)'],
    mode = 'lines+markers',
    name = 'Annual Mean'
)

trace1 = go.Scatter(
x = ca_df['Year'],
y = [0]*len(ca_df['Temperature departure (degree Celsius)']),
mode = 'lines',
opacity = 0.5,
name='Reference value',
hoverinfo = 'none'
)
data = [trace0, trace1]
layout = go.Layout(dict(title='Change in Annual Average Temperature in Canada'),
                   xaxis=dict(title='Year'),
                   yaxis=dict(title='Temperature Departure (Celsius)')
)
fig = go.Figure(data=data,layout=layout)
fig.show()_____no_output_____

1. What was the warmest and coldest year in Canada and by how much was it above or below the reference value?_____no_output______Type answers here.______no_output_____2. Do you see a trend in the annual average temperature departure? Is it an upward or downward trend and what does it signify?_____no_output______Type answers here.______no_output_____## Conclusion

This notebook used data about global temperatures, arctic sea ice, and global sea level to demonstrate evidence for climate change due to human impact._____no_output_____[![Callysto.ca License](https://github.com/callysto/curriculum-notebooks/blob/master/callysto-notebook-banner-bottom.jpg?raw=true)](https://github.com/callysto/curriculum-notebooks/blob/master/LICENSE.md)_____no_output_____


# Notebook from luke7mm/qiskit-textbook
Path: content/ch-labs/Lab07_QuantumSimulationSearchAlgorithm.ipynb

Lab 7 Quantum Simulation as a Search Algorithm
_____no_output_____Prerequisites:
- [Ch.3.8 Grover's Algorithm](https://qiskit.org/textbook/ch-algorithms/grover.html)
- [Ch.2.5 Proving Universality](https://qiskit.org/textbook/ch-gates/proving-universality.html#2.2-Unitary-and-Hermitian-matrices-)

Other relevant materials:
- [Ch 6.2 in QCQI] Michael A. Nielsen and Isaac L. Chuang. Quantum Computation and Quantum Information, p255_____no_output_____

from qiskit import *
from qiskit.quantum_info import Statevector, partial_trace
from qiskit.visualization import plot_state_qsphere, plot_histogram

import numpy as np
import matplotlib.pyplot as plt
import scipy.linalg as la_____no_output_____sim = Aer.get_backend('aer_simulator')_____no_output_____

Part 1: Hamiltonian Simulation

Goal

 In this lab, we consider changes to a quantum state viewed as an evolution process generated by a given Hamiltonian. For a specified Hamiltonian, there is a corresponding unitary operator that determines the final state for any given initial state.

For an initial state, $|\psi(0)\rangle$ and a time independent Hamiltonian $H$ , the final state $|\psi(t)\rangle$ is $|\psi(t)\rangle = e^{-iHt}|\psi(0)\rangle$. Therefore, by constructing an appropriate gate for the unitary operator $e^{-iHt}$, we can build a quantum circuit that simulates the evolution of the quantum state $|\psi\rangle$._____no_output_____
1. Build a quantum circuit for a given Hamiltonian.
_____no_output_____When the Hamiltonian $H$ and the initial state of the system, $|\psi(0)\rangle$, are given by

$H = |0\rangle\langle0| + |+\rangle\langle+|, ~~~~ |\psi(0)\rangle = |+\rangle = \frac{1}{\sqrt 2}(|0\rangle + |1\rangle)$.

Build the circuit with two qubits to evolve the state, $|\psi(0\rangle$, by $H$ for a time $\Delta t = \theta$, where the state of the system is encoded on the 0th qubit and the 1st qubit is an auxiliary.  Then, the final state $|\psi(\theta)\rangle$ is $|\psi(\theta)\rangle = e^{-i\theta ~ ( |0\rangle\langle0| ~ + ~ |+\rangle\langle+| )}~|\psi(0)\rangle$._____no_output_____
📓Step A. Show that the gate H1 from the following circuit performs the operation $e^{-i\frac{\pi}{9}|0\rangle\langle0|}$ on the 0th qubit when the state of the system is encoded on the 0th qubit and the 1st qubit, auxiliary, is set to the $|0\rangle$ state.
_____no_output_____

h1 = QuantumCircuit(2, name = 'H1')

h1.cnot(0, 1)
h1.p(np.pi/9, 1)
h1.cnot(0, 1)

H1 = h1.to_gate()

h1.draw()_____no_output_____

**Your Solution**:

_____no_output_____
📓Step B. Construct the gate H2 by completing the following code for the circuit `h2` to performs the operation $e^{-i\frac{\pi}{9}|+\rangle\langle+|}$ on the 0th qubit when the state of the system is encoded on the 0th qubit and the 1st qubit, auxiliary, is set to the $|0\rangle$ state.
_____no_output_____

h2 = QuantumCircuit(2, name='H2')

#### Your code goes here ###

#############################

H2 = h2.to_gate()

h2.draw()_____no_output_____

2. Execute the cell below to generate the state of the 0th qubit after every iteration._____no_output_____The circuit performs $(H1H2)^7|+\rangle = (~ e^{-i\frac{\pi}{9} ~ |0\rangle\langle0|}e^{-i\frac{\pi}{9}~|+\rangle\langle+|} ~)^7~|+\rangle$ on the 0th qubit.  The state of the 0th qubit after each `H1H2` operation is stored in the list variable 'myst'._____no_output_____

from qiskit.quantum_info import Statevector, partial_trace

def st_out(qc):
    out = Statevector.from_instruction(qc)
    out_red = partial_trace(out, [1])
    prob, st_all = la.eig(out_red.data)
    cond = (prob>0.99) & (prob<1.01)
    st = st_all[:, cond].ravel()

    return(st)

myst = []

circ = QuantumCircuit(2)
circ.h(0)
st = st_out(circ)
myst.append(Statevector(st))

for _ in range(7):
    circ.append(H1, range(2))
    circ.append(H2, range(2))
    st = st_out(circ)
    myst.append(Statevector(st))

circ.draw() _____no_output_____

The following Bloch sphere picture shows the evolution of the 0th qubit state. As it shows, the state starts from the $|+\rangle$ state rotate toward to and passes the $|0\rangle$ state.  Therefore, with appropriate the angle of the `H1` and `H2` operations, $|+\rangle$ state evolves to $|0\rangle$ state by applying $H1H2 = e^{-i\theta ~ |0\rangle\langle0|}e^{-i\theta~|+\rangle\langle+|}$ proper number of times. _____no_output__________no_output_____If you have installed `kaleidoscope` or run this lab on [IQX](https://quantum-computing.ibm.com), you can execute the cell below to visualize the state evolution through the interactive Bloch sphere. _____no_output_____

from kaleidoscope import bloch_sphere
from matplotlib.colors import LinearSegmentedColormap, rgb2hex

cm = LinearSegmentedColormap.from_list('graypurple', ["#999999", "#AA00FF"])
vectors_color = [rgb2hex(cm(kk)) for kk in np.linspace(-1,1,len(myst))]
bloch_sphere(myst, vectors_color = vectors_color)_____no_output_____

Part 2: Quantum Search as a Quantum Simulation

Goal

 In this part of the lab, we solve a search problem through quantum simulation.

In Part1, we showed that the Hamiltonian, $H$, transforms the state, $|\psi_i\rangle$,  to $|\psi_j\rangle$ when its structure depends on both states as $ H =|\psi_j\rangle\langle\psi_j| + |\psi_i\rangle\langle\psi_i| $ with a proper time duration.

Considering a search problem with a unique solution, we should be able to find the solution with the form of the Hamiltonian, $ H = |x\rangle\langle x| + |\psi\rangle\langle\psi|, $ when all possible items are encoded in a superposition state $|\psi\rangle$ and given as the initial state, same as in Grover's algorithm, while $|x\rangle$ represents the unknown solution.

Applying the unitary operator, $U = e^{-iH\Delta t}$ on the initial state, $|\psi\rangle$, right number of times with the properly chosen $\Delta t$, should evolve the state $|\psi\rangle$ into the solution $|x\rangle$ or close enough to it. The following code constructs the oracle gate for the search problem.  Execute the cell below. _____no_output_____

n = 5
qc = QuantumCircuit(n+1, name='Oracle')
qc.mct(list(range(n)), n)

Oracle = qc.to_gate()_____no_output_____

The following circuit encodes the phase $\pi$ on the solution state and zero on the other items through phase kickback with the 5th qubit as an auxiliary. Therefore, the output state of the circuit is $(|\psi\rangle - |x\rangle) + e^{i\pi}|x\rangle$, which can be confirmed visually using a qsphere plot where the color indicates the phase of each basis state. Run the following two cells. _____no_output_____

test = QuantumCircuit(n+1)
test.x(n)
test.h(range(n+1))
test.append(Oracle, range(n+1))
test.h(n)

test.draw()_____no_output_____st = Statevector.from_instruction(test)
st_red = partial_trace(st, [5])

plot_state_qsphere(st_red)_____no_output_____

1. Construct a circuit to approximate the Hamiltonian, $H = |x\rangle\langle x| + |\psi\rangle\langle\psi|$, when all possible items are encoded in a superposition state $|\psi\rangle$ and given as the initial state while $|x\rangle$ represents the unique unknown solution.
_____no_output_____As we did in the Part1, we build the circuit for the simulation with the Hamiltonian, but with more qubits to examine all the items in the question. Regard the search problem having one solution out of 32 items.  _____no_output_____
📓Step A. Construct the gate H1 performing the operation $e^{-i\Delta t|\psi\rangle\langle\psi|}$ by completing the following code.
_____no_output_____

def H1(delt, n=5):

    h1 = QuantumCircuit(n+1, name='H1')

    #### Your code goes here ######

    ###############################

    return h1.to_gate()_____no_output_____

📓Step B. Construct the gate H2 performing the operation $e^{-i\Delta t|x\rangle\langle x|}$ by completing the following code.
_____no_output_____

def H2(delt, n=5):

    h2 = QuantumCircuit(n+1, name='H2')

    #### Your code goes here ######

    ###############################

    return h2.to_gate()_____no_output_____

📓Step C. Create the circuit, 'sim_h', to compute $e^{-i \pi H_{app}}|\psi\rangle = (~e^{-i\pi~|x\rangle\langle x|}e^{-i\pi~|\psi\rangle\langle\psi|}~)|\psi\rangle $ which evolves the state $|\psi\rangle$ under the Hamiltonian $H = |x\rangle\langle x| + |\psi\rangle\langle\psi|$ approximately over the time duration $\Delta t = \pi$.
_____no_output_____Th state $|\psi\rangle$ represents the superposition state of all possible items.

Utilize the gates `H1` and `H2`. _____no_output_____

#### Your code goes here ####

############

sim_h.draw()_____no_output_____

2. Show that the search problem can be solved through quantum simulation with  $H_{appr}$ by verifying the two operations, Grover's algorithm and $U = e^{-i\Delta t~H_{appr}}$ with $\Delta t = \pi$, are equivalent.
_____no_output_____
Step A. The following circuit, `grover`, runs the Grover's algorithm for the problem to find a solution for the oracle that we built above. Run the cell below.
 _____no_output_____

qc = QuantumCircuit(n+1, name='Amp')
qc.h(range(n))
qc.x(range(n))
qc.mct(list(range(n)), n)
qc.x(range(n))
qc.h(range(n))

Amp = qc.to_gate()

grover = QuantumCircuit(n+1)
grover.x(n)
grover.h(range(n+1))
grover.append(Oracle, range(n+1))
grover.append(Amp, range(n+1))
grover.h(n)
grover.x(n)

grover.draw()_____no_output_____

Step B. Upon executing the cells below, the result shows that the circuits, 'grover' and 'sim_h' are identical up to a global phase.
_____no_output_____

st_simh = Statevector.from_instruction(sim_h)
st_grover = Statevector.from_instruction(grover)
print('grover circuit and sim_h circuit genrate the same output state: ' ,st_simh == st_grover)_____no_output_____plot_state_qsphere(st_simh)_____no_output_____plot_state_qsphere(st_grover)_____no_output_____

📓Step C. Find the number of the Grover iterations, R, needed to find the solutions of the Oracle that we built.
_____no_output_____

#### your code goes here ####

######
print(R)_____no_output_____

Step D. Find the solution to the search problem, for the Oracle that we built,  through Grover's algorithm and the simulation computing $e^{-i R\pi H_{app}}|\psi\rangle = (~e^{-i\pi~|x\rangle\langle x|}e^{-i\pi~|\psi\rangle\langle\psi|}~)^R|\psi\rangle $ where R is the number of iterations.
_____no_output_____

## The circuit to solve the search problem through Grover's algorithm.
n = 5

qc_grover = QuantumCircuit(n+1, n)
qc_grover.x(n)
qc_grover.h(range(n+1))
for _ in range(int(R)):
    qc_grover.append(Oracle, range(n+1))
    qc_grover.append(Amp, range(n+1))

qc_grover.h(n)
qc_grover.x(n)
qc_grover.barrier()
qc_grover.measure(range(n), range(n))

qc_grover.draw()_____no_output_____

📓 Complete the code to build the circuit, `qc_sim`,  to solve the search problem through the simulation._____no_output_____

qc_sim = QuantumCircuit(n+1, n)
qc_sim.h(range(n))

#### Your code goes here ####

_____no_output_____

Run the following cell to simulate both circuits, `qc_grover` and `qc_sim` and compare their solutions._____no_output_____

circ_trans = transpile([qc_grover, qc_sim], sim)
counts = sim.run(circ_trans).result().get_counts()
plot_histogram(counts, legend=['Grover', 'Hamiltonian'])_____no_output_____

3. The following result shows an example where the solution can be found with probability exactly equal to one through quantum simulation by the choosing the proper time duration $\Delta t$.
_____no_output_____

n = 5

qc = QuantumCircuit(n+1, n)
qc.h(range(n))

delt, R = np.pi/2.1, 6

for _ in range(int(R)):
    qc.append(H1(delt), range(n+1))
    qc.append(H2(delt), range(n+1))

qc.measure(range(n) ,range(n))
qc.draw()_____no_output_____qc_trans = transpile(qc, sim)
count = sim.run(qc_trans).result().get_counts()
plot_histogram(count)_____no_output_____