<jupyter_start><jupyter_text>Assignment (note) The assignment idea and auxilary function for compute exact magneton comes from https://github.com/kuehnste/QiskitTutorial.We want to compute the real-time evolution of an initial state $|\psi_0\rangle$ under the Ising Hamiltonian$$H = \sum_{i=0}^{N-2} Z_iZ_{i+1} + h\sum_{i=0}^{N-1} X_i$$using a trotterized version of the time-evolution operator $$U(\Delta t)\approx \prod_{k=0}^{N-2} \exp\left(-i Z_kZ_{k+1} \Delta t\right) \prod_{k=0}^{N-1} \exp\left(-i hX_{k} \Delta t\right).$$Thus, we can obtain $$|\psi(n\Delta t)\rangle = U(\Delta t)^n|\psi_0\rangle.$$In the following we use $N=4$ and the initial state$$|\psi_0\rangle = |0010\rangle$$.<jupyter_code>import numpy as np
#from random import random
from scipy.linalg import expm
import matplotlib.pyplot as plt


from qiskit import *
#from qiskit.circuit.library.standard_gates import U2Gate
from qiskit.aqua.operators import WeightedPauliOperator
from qiskit.aqua.algorithms import NumPyEigensolver

#from qiskit.visualization.bloch import Bloch
import warnings
warnings.filterwarnings('ignore')<jupyter_output><empty_output><jupyter_text>Function for convenience which allows for running the simulator and extracting the results<jupyter_code># Function for convenience which allows for running the simulator and extracting the results
def run_on_qasm_simulator(quantum_circuit, num_shots):
    """Takes a circuit, the number of shots and a backend and returns the counts for running the circuit
    on the qasm_simulator backend."""
    qasm_simulator = Aer.get_backend('qasm_simulator')
    job = execute(quantum_circuit, backend=qasm_simulator, shots=num_shots)
    result = job.result()
    counts = result.get_counts()
    return counts<jupyter_output><empty_output><jupyter_text>Function providing the exact solution for the magnetization for comparison<jupyter_code>##Do not modify these functions#

def Op(M, n ,N):
    """Given a single site operator, provide the N-body operator 
    string obtained by tensoring identities"""
    d = M.shape[0]
    id_left = np.eye(d**n)
    id_right = np.eye(d**(N-n-1))
    res = np.kron(id_left,np.kron(M,id_right))
    return res

def IsingHamiltonian(N, h):
    """The Ising Hamiltonian for N sites with parameter h"""
    Z = np.array([[1., 0.],[0., -1.]])
    X = np.array([[0., 1.],[1., 0.]])
    H = np.zeros((2**N, 2**N))
    for i in range(N):
        if i<N-1:            
            H += Op(Z, i, N)@Op(Z, i+1, N)
        H += h*Op(X, i, N)
    return H            

# For reference, we provide a function computing the exact solution for
# the magnetization as a function of time
def get_magnetization_vs_time(h, delta_t, nsteps):
    """Compute the exact value of the magnetization"""
    Z = np.array([[1., 0.],[0., -1.]])
    X = np.array([[0., 1.],[1., 0.]])
    Id = np.eye(2)
    # The Ising Hamiltonian for 4 sites with parameter h
    H = IsingHamiltonian(4, h)
    # The time evolution operator for an interval \Delta t
    U = expm(-1.0j*delta_t*H)
    # The operator for the total magnetization
    M = Op(Z,0,4) + Op(Z,1,4) + Op(Z,2,4) + Op(Z,3,4)
    # Numpy array to hold the results
    magnetization = np.zeros(nsteps)
    # The initial wave function corresponding to |0010>
    psi = np.zeros(16)
    psi[int('0010', 2)] = 1
    # Evolve in steps of \Delta t and measure the magnetization
    for n in range(nsteps):
        psi = U@psi
        magnetization[n] = np.real(psi.conj().T@M@psi)
    return magnetization<jupyter_output><empty_output><jupyter_text>initial stateComplete the following function which provides a quantum circuit creating the initial state $|0010\rangle$ (all qubits in state zero, except for qubit 1) without measurement.<jupyter_code>def initial_state():
    # Create a quantum circuit qc for 4 qubits
    qc = QuantumCircuit(4)
    
    # Add the necessary gate(s) to provide the inital state |0010>

    
    return qc<jupyter_output><empty_output><jupyter_text>Compose Weighted Pauli operator from Paulis dictWe can compose problem hamiltonian by using [WeightedPauliOperator](https://qiskit.org/documentation/stubs/qiskit.aqua.operators.legacy.WeightedPauliOperator.html). To use this, we need to define paulis dict of the hamiltonian.For example, if we want to make a Paulis dict with the hamiltonian $H = I0I1 + X1 + X0X1 + Y0Y1 + Z0Z1$ then the paulis dict would be<jupyter_code>Paulis_example = {
    'paulis': [{"coeff": {"imag": 0.0, "real": 1}, "label": 'II'},
               {"coeff": {"imag": 0.0, "real": 1}, "label": "IX"},
               {"coeff": {"imag": 0.0, "real": 1}, "label": "XX"},
               {"coeff": {"imag": 0.0, "real": 1}, "label": "YY"},
               {"coeff": {"imag": 0.0, "real": 1}, "label": "ZZ"}
              ]}<jupyter_output><empty_output><jupyter_text>We can build Hamiltonian by using this<jupyter_code>H_example = WeightedPauliOperator.from_dict(Paulis_example)<jupyter_output><empty_output><jupyter_text>With this hamiltonian, we can easily compute the ground state energy with NumpyEigenSolver from Qiskit.<jupyter_code>np_res = NumPyEigensolver(H_example).run()
ground_energy = min(np.real(np_res.eigenvalues))
print('Ground state energy compute by numpy is: {}'.format(ground_energy))<jupyter_output><empty_output><jupyter_text>To get the time evolution of this hamiltonian with $dt = 0.001$ and it's quantum circuit, we can use "evolve" function for this. Below example show you how to get the time evolution of the example hamiltonian and get it's quantum circuit.<jupyter_code>H_example_circuit = H_example.evolve(evo_time=0.001)

backend = Aer.get_backend('qasm_simulator')
qc_t=transpile(H_example_circuit,backend)

qc_t.draw('mpl')<jupyter_output><empty_output><jupyter_text>With this ingredient, let's build a function which returns system's paulis which receive "h" as input and return hamiltonina of it.<jupyter_code>def hamiltonian(h):
#H = Z0Z1 + Z1Z2 + Z2Z3 + h*X0 + h*X1 + h*X2 + h*X3
    Paulis = {
        'paulis': [
            
            #build paulis of the hamiltonian here
            
                  ]}
    H = WeightedPauliOperator.from_dict(Paulis)
    return H<jupyter_output><empty_output><jupyter_text>Functions for the time evoltuonAfter building the circuit implementing the initial state and the parts of the time evolution hamiltonian, the first of following functions allows for building the total circuit evolving the initial state in time by N steps.The second function allows for computing the magnetization given the counts resulting from a measurement.<jupyter_code>def build_time_evolution_circuit(qc_init_state, qc_evolve, N):
    """Given the circuits implementing the initial state and the two parts
    of the trotterized time-evolution operator build the circuit evolving the 
    wave function N steps
    """
    # Generate an empty quantum circuit qc for 4 qubits
    qc = QuantumCircuit(4)
    # Add the inital state
    qc.compose(qc_init_state, inplace=True)
    
    # For each time step add qc_evolve
    for i in range(N):
        qc.compose(qc_evolve, inplace=True)

    # Add the final measurments
    qc.measure_all()
    return qc
def get_magnetization(counts):
    """Given the counts resulting form a measurement, compute the site
    resolved magnetization"""
    total_counts = sum(counts.values())
    res = np.zeros(4)
    for qubit in range(4):
        Z_expectation = 0.
        for key, value in counts.items():
            if key[qubit] == '0':
                Z_expectation += value
            else:
                Z_expectation -= value
        res[qubit] = Z_expectation/total_counts
    return res<jupyter_output><empty_output><jupyter_text>Run the evolution and visualize the results<jupyter_code>#set computational parameters

qc_init_state = initial_state()
h = 1.5
delta_t = 0.05
nsteps = 40
nshots = 1000

H = hamiltonian(h)
#Numpy array for expectation values of the magnetization
magnetization = np.zeros(nsteps)

# Numpy array for qubit configuration
configuration = np.zeros((4, nsteps))

# Run the time evolution
for n in range(1, nsteps+1):
    # Build the evolution circuit out of qc_init_state, time evolution circuit, n
    # n steps
    qc_evo = build_time_evolution_circuit(qc_init_state, H.evolve(evo_time=delta_t), n)
    qc_evo.draw()
    
    # Run the evolution circuit on the qasm_simulator
    res = run_on_qasm_simulator(qc_evo, nshots)
    
    # Compute the ovservables
    configuration[:,n-1] = get_magnetization(res)    
    magnetization[n-1] = sum(configuration[:,n-1])
# For reference we compute the exact solution
magnetization_exact = get_magnetization_vs_time(h, delta_t, nsteps)
# Plot the total magnetization as a function of time and compare to
# the exact result
plt.figure()
plt.plot(magnetization_exact, '--', label='exact')
plt.plot(magnetization, 'o', label='quantum circuit')
plt.xlabel('$t/\Delta t$')
plt.ylabel('$<\sum_i Z_i(t)>$')
plt.title('Total magnetization')
plt.legend()

# Plot the site resolved spin configuration as a function of time
plt.figure()
plt.imshow(configuration, aspect='auto')
plt.colorbar()
plt.xlabel('$t/\Delta t$')
plt.ylabel('$<Z_i(t)>$')
plt.title('Spatially resolved spin configuration')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11189341421143847, 'fraction_numerical': 0.015381282495667245, 'mean_word_length': 4.031607629427793, 'pattern_counts': {'":': 20, '<': 48, '<?xml version=': 0, '>': 49, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '110149', 'license': 'Apache-2.0', 'n_tokens_mistral': 3178, 'n_tokens_neox': 2989, 'n_words': 918, 'path': 'Lecture1/Assignment.ipynb', 'repo_name': '0sophy1/Oct2021HKUST', 'size': '9232'}