# -*- coding: utf-8 -*-
"""
Created on Thu Jun 6 11:48:00 2024
Module for Generating Walsh Pulse Sequence
for Dynamical Decoupling in Long-range interactions
@author: Jessica
"""
import numpy as np
from numpy import linalg
from scipy import linalg as splinalg
import matplotlib.pyplot as plt
from scipy import sparse as sp
import scipy.sparse.linalg
from functools import reduce
import itertools
from scipy import linalg
from scipy.linalg import expm
Z = np.array([[1, 0], [0, -1]])
X = np.array([[0, 1], [1, 0]])
Y = np.array([[0, -1j], [1j, 0]])
I = np.array([[1, 0], [0, 1]])
H = np.array([[1, 1], [1, -1]])
[docs]
def WF_Conditions(tupleprdt, **kwargs):
"""
*args: tupleprdt is a list of tuple of
signs of wxi and wyi.
Returns: tupleprdt - a list of Pauli Ops
depending on eq 8 from the paper
"""
for i, tprdt in enumerate(tupleprdt):
if tprdt[0] == tprdt[1] == 1:
tupleprdt[i] = I
elif tprdt[0] == -tprdt[1] == 1:
tupleprdt[i] = X
elif -tprdt[0] == tprdt[1] == 1:
tupleprdt[i] = Y
elif tprdt[0] == tprdt[1] == -1:
tupleprdt[i] = Z
return tupleprdt
[docs]
def WF_Generate(params, **kwargs):
"""
**kwargs: W_x, W_y: Required - list of one element
consisting the index of x, y part of one
qubit. q: Optional - gives the number of times H has
to tensor product with itself. Useful to form WF based
on the highest index of decoupling lists of Wx, Wy.
Returns: lstPaulOp - a list of Pauli Ops
based on the W.I associated with that qubit.
"""
wx, wy, lst, q, signTuple, lstPaulOp = kwargs['wx'], kwargs['wy'], [], 0, [], []
H0, H1 = np.eye(1), H
if 'q' in kwargs:
q = kwargs['q']
else:
q = int(np.ceil(np.log2(max(wx, wy)+1)))
if q == 0:
lst = [H0]
else:
lst = [H1]
for i in range(q-1):
lst += [H1]
Hf = reduce(np.kron, lst)
wfx, wfy = Hf[wx], Hf[wy]
for i, wfx_k in enumerate(wfx):
signTuple += [(wfx_k, wfy[i])]
lstPaulOp = WF_Conditions(signTuple)
return lstPaulOp
[docs]
def WF_WIList(params, **kwargs):
"""
**kwargs: Wx, Wy - list consisting the
index of x, y part of each qubit.
Returns: Pseq - Pulse sequence.
"""
Wx, Wy, lstPaulOp, Pseq = kwargs['Wx'], kwargs['Wy'], [], []
q = int(np.ceil(np.log2(max(max(Wx, Wy))+1)))
for i, wx in enumerate(Wx):
lstPaulOp += [WF_Generate(params, wx = wx, wy = Wy[i], q = q)]
padded_lstPaulOp = list(zip(*itertools.zip_longest(*lstPaulOp, fillvalue=I)))
pseq_k = [[] for _ in range(len(padded_lstPaulOp[0]))]
for i, ps_k in enumerate(pseq_k):
for j, paulop in enumerate(padded_lstPaulOp):
pseq_k[i] += [paulop[i]]
for i, ps_k in enumerate(pseq_k):
Pseq += [reduce(np.kron, ps_k)]
return Pseq
[docs]
def WPSresource_Hamiltonian_TimeEvolOp_IsingType(params, **kwargs):
"""
Returns: Resource Hamiltonian (Hr) and
its time evolution for τ time.
"""
N, opH, unitary_timeOp = params['N'], params['opH'], 0
H_r, R, r, alpha = np.zeros((2**N, 2**N), dtype = complex), params['R'], params['r'], params['alpha']
lst = [I for _ in range(N)]
for op in opH:
for i in range(N):
for j in range(i+1, N, 1):
lst[i] = op
lst[j] = op
H_r += (np.abs(R[i]-R[j]))*reduce(np.kron, lst)/(np.power(np.abs(i-j), alpha))
lst = [I for _ in range(N)]
tau = params['tau']
unitary_timeOp = expm(-1j*tau*H_r/(params['n']))
return H_r, unitary_timeOp
[docs]
def WPSeq_TimeEvolOp(params, **kwargs):
"""
To input any Hamiltonian other than XY, use 'Hr' in kwargs.
**kwargs: Hr Optional
Returns: Unitary time evolution operator as per eq1
and time interval based on τ step.
"""
Pseq, unitary_timeOp, timeOpPHrP = params['pulses'], [], np.eye(2**(params['N']))
if 'Hr' in kwargs:
Hr = kwargs['Hr']
expHr = expm(-1j*params['tau']*Hr/(params['n']))
else:
Hr, expHr = WPSresource_Hamiltonian_TimeEvolOp_IsingType(params)
for i, p in enumerate(Pseq):
timeOpPHrP = np.linalg.inv(p) @ expHr @ p @ timeOpPHrP
t_list = np.arange(0, params['T'], params['tau'])
unitary_timeOp = [np.linalg.matrix_power(timeOpPHrP, i) for i, t in enumerate(t_list)]
return unitary_timeOp, t_list