Skip to content

Commit

Permalink
Merge pull request #111 from BoxiLi/qutip-qip-0.2.X
Browse files Browse the repository at this point in the history
Prepare the qutip-qip-0.2.0 release
  • Loading branch information
BoxiLi committed Nov 24, 2021
2 parents 60fe50e + eda8e43 commit 7d286ee
Show file tree
Hide file tree
Showing 25 changed files with 438 additions and 253 deletions.
1 change: 0 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
test:
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.0.dev
0.2.0
9 changes: 5 additions & 4 deletions doc/pulse-paper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ The following table summarizes the sections in the paper and the corresponding c

| Section | Code example |
| ----------- | ----------- |
| Appendix A | `pulse_simulation.py` |
| Appendix B | `dj_algorithm.py` |
| Section 4 | `main_example.py`|
| Appendix A | `dj_algorithm.py` |
| Fig.4 and Appendix C | `customize.py` |
| Fig.5 in Section 4 | `decoherence.py` |
| Appendix D | `deutsch_jozsa.qasm` and `deutsch_jozsa-qasm.py` |
| Fig.5 | `decoherence.py` |
| Section 5 | `deutsch_jozsa.qasm` and `deutsch_jozsa-qasm.py` |
| Appendix B | `qft.py` |
101 changes: 46 additions & 55 deletions doc/pulse-paper/customize.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,37 @@
global_setup(fontsize=10)
except:
pass
plt.rcParams.update({"text.usetex": False, "font.size": 10})
from joblib import Parallel, delayed # for parallel simulations

import numpy as np
from qutip import sigmax, sigmay, sigmaz, basis, qeye, tensor, Qobj, fock_dm
from qutip_qip.circuit import QubitCircuit, Gate
from qutip_qip.device import ModelProcessor
from qutip_qip.device import ModelProcessor, Model
from qutip_qip.compiler import GateCompiler, Instruction
from qutip import Options
from qutip_qip.noise import Noise


class MyProcessor(ModelProcessor):
"""Custom processor built using ModelProcessor as the base class.
This custom processor will inherit all the methods of the base class
such as setting up of the T1 and T2 decoherence rates in the simulations.
In addition, it is possible to write your own functions to add control
pulses.
Args:
num_qubits (int): Number of qubits in the processor.
t1, t2 (float or list): The T1 and T2 decoherence rates for the
qubit. If it is a list, then it is assumed that
each element of the list corresponds to the rates
for each of the qubits.
"""

def __init__(self, num_qubits, t1=None, t2=None):
super().__init__(num_qubits, t1=t1, t2=t2)
self.pulse_mode = "discrete" # set the control pulse as discrete or continuous.
self.set_up_ops() # set up the available Hamiltonians.
self.dims = [2] * num_qubits # dimension of the quantum system.
self.num_qubits = num_qubits
self.native_gates = ["RX", "RY"]
class MyModel(Model):
"""A custom Hamiltonian model with sigmax and sigmay control."""
def get_control(self, label):
"""
Get an avaliable control Hamiltonian.
For instance, sigmax control on the zeroth qubits is labeld "sx0".
def set_up_ops(self):
"""Sets up the single qubit control operators for each qubit."""
for m in range(self.num_qubits):
self.add_control(2 * np.pi * sigmax() / 2, m, label="sx" + str(m))
for m in range(self.num_qubits):
self.add_control(2 * np.pi * sigmay() / 2, m, label="sy" + str(m))
Args:
label (str): The label of the Hamiltonian
Returns:
The Hamiltonian and target qubits as a tuple (qutip.Qobj, list).
"""
targets = int(label[2:])
if label[:2] == "sx":
return 2 * np.pi * sigmax() / 2, [targets]
elif label[:2] == "sy":
return 2 * np.pi * sigmax() / 2, [targets]
else:
raise NotImplementError("Unknown control.")

class MyCompiler(GateCompiler):
"""Custom compiler for generating pulses from gates using the base class
Expand Down Expand Up @@ -87,10 +75,11 @@ def generate_pulse(self, gate, tlist, coeff, phase=0.0):
to implement a gate containing the control pulses.
"""
pulse_info = [
# (control label, coeff)
("sx" + str(gate.targets[0]), np.cos(phase) * coeff),
("sy" + str(gate.targets[0]), np.sin(phase) * coeff),
]
return [Instruction(gate, tlist, pulse_info)]
return [Instruction(gate, tlist=tlist, pulse_info=pulse_info)]

def single_qubit_gate_compiler(self, gate, args):
"""Compiles single qubit gates to pulses.
Expand Down Expand Up @@ -133,17 +122,19 @@ def rotation_with_phase_compiler(self, gate, args):
circuit.add_gate("RX", targets=0, arg_value=np.pi / 2)
circuit.add_gate("Z", targets=0)

myprocessor = MyProcessor(1)
myprocessor = ModelProcessor(model=MyModel(num_qubits))
myprocessor.native_gates = ["RX", "RY"]

mycompiler = MyCompiler(num_qubits, {"pulse_amplitude": 0.02})

myprocessor.load_circuit(circuit, compiler=mycompiler)
result = myprocessor.run_state(basis(2, 0))

fig, ax = myprocessor.plot_pulses(
figsize=(LINEWIDTH * 0.7, LINEWIDTH / 2 * 0.7), dpi=200
figsize=(LINEWIDTH * 0.7, LINEWIDTH / 2 * 0.7), dpi=200,
use_control_latex=False
)
ax[-1].set_xlabel("Time")
ax[-1].set_xlabel("$t$")
fig.tight_layout()
fig.savefig("custom_compiler_pulse.pdf")
fig.show()
Expand All @@ -166,8 +157,8 @@ def get_noisy_dynamics(self, dims=None, pulses=None, systematic_noise=None):
systematic_noise: A Pulse object (not used in this example).
"""
for i, pulse in enumerate(pulses):
if "sx" or "sy" not in pulse.label:
pass # filter out other pulses, e.g. drift
if "sx" not in pulse.label and "sy" not in pulse.label:
continue # filter out other pulses, e.g. drift
target = pulse.targets[0]
if target != 0: # add pulse to the left neighbour
pulses[i].add_control_noise(
Expand Down Expand Up @@ -197,7 +188,7 @@ def single_crosstalk_simulation(num_gates):
solver methods such as mesolve.
"""
num_qubits = 2 # Qubit-0 is the target qubit. Qubit-1 suffers from crosstalk.
myprocessor = MyProcessor(num_qubits)
myprocessor = ModelProcessor(model=MyModel(num_qubits))
# Add qubit frequency detuning 1.852MHz for the second qubit.
myprocessor.add_drift(2 * np.pi * (sigmaz() + 1) / 2 * 1.852, targets=1)
myprocessor.native_gates = None # Remove the native gates
Expand Down Expand Up @@ -228,7 +219,6 @@ def single_crosstalk_simulation(num_gates):
return result


num_qubits = 2
num_sample = 2
# num_sample = 1600
fidelity = []
Expand All @@ -247,31 +237,32 @@ def single_crosstalk_simulation(num_gates):
# Recorded result
num_gates_list = [250, 500, 750, 1000, 1250, 1500]
data_y = [
0.9577285560461476,
0.9384849070716464,
0.9230217713086177,
0.9062344084919285,
0.889009550855518,
0.8749290612064392,
0.9566768747558925,
0.9388905075892828,
0.9229470389282218,
0.9075513000339529,
0.8941659320508855,
0.8756519016627652
]

data_y_error = [
0.000431399017208067,
0.0008622091914303468,
0.0012216267555118497,
0.001537120153687202,
0.0018528957172559654,
0.0020169257334183596,
0.00042992029265330223,
0.0008339882813741004,
0.0012606632769758602,
0.0014643550337816722,
0.0017695604671714809,
0.0020964978542167617
]


def linear(x, a):
def rb_curve(x, a):
return (1 / 2 + np.exp(-2 * a * x) / 2) * 0.975


pos, cov = curve_fit(linear, num_gates_list, data_y, p0=[0.001])
pos, cov = curve_fit(rb_curve, num_gates_list, data_y, p0=[0.001])

xline = np.linspace(0, 1700, 200)
yline = linear(xline, *pos)
yline = rb_curve(xline, *pos)

fig, ax = plt.subplots(figsize=(LINEWIDTH, 0.65 * LINEWIDTH), dpi=200)
ax.errorbar(
Expand Down
116 changes: 82 additions & 34 deletions doc/pulse-paper/decoherence.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,99 @@
TEXTWIDTH = 7.1398920714
LINEWIDTH = 3.48692403487
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
try:
from quantum_plots import global_setup
global_setup(fontsize = 10)
except:
pass

import matplotlib.pyplot as plt

plt.rcParams.update({"text.usetex": False, "font.size": 10})
import numpy as np
from qutip import sigmaz, basis
import scipy
from qutip import sigmaz, basis, sigmax, fidelity
from qutip_qip.operations import hadamard_transform
from qutip_qip.pulse import Pulse
from qutip_qip.device import LinearSpinChain
from qutip_qip.circuit import QubitCircuit

# define a circuit with only idling
t = 30.
circuit = QubitCircuit(1)
circuit.add_gate("IDLE", 0, arg_value=t)
# define a processor with t2 relaxation
pi = np.pi
num_samples = 500
amp = 0.1
f = 0.5
t2 = 10 / f
processor = LinearSpinChain(1, t2=t2)
processor.add_drift(
2*np.pi*sigmaz()/2*f, targets=[0])
processor.load_circuit(circuit)
# Record the expectation value
plus_state = \
(basis(2,1) + basis(2,0)).unit()
result = processor.run_state(
init_state=plus_state,
tlist = np.linspace(0., t, 1000),
# observable
e_ops=[plus_state*plus_state.dag()])

tlist = np.linspace(0., t, 1000)
fig, ax = plt.subplots(figsize = (LINEWIDTH, LINEWIDTH*0.65), dpi=200)
# detail about lenght of tlist needs to be fixed
ax.plot(tlist[:-1], result.expect[0][:-1], '-', label="Simulation", color="slategray")
ax.plot(tlist[:-1], np.exp(-1./t2 * tlist[:-1])*0.5 + 0.5, '--', label="Theory", color="slategray")
ax.set_xlabel(r"Time [$\mu$s]")
ax.set_ylabel("Ramsey signal")
ax.legend()

# Define a processor.
proc = LinearSpinChain(
num_qubits=1, sx=amp/2, t2=t2)
ham_idle = 2*pi * sigmaz()/2 * f
resonant_sx = 2*pi * sigmax() - \
ham_idle / (amp/2)
proc.add_drift(ham_idle, targets=0)
proc.add_control(
resonant_sx, targets=0, label="sx0")

# Define a Ramsey experiment.
def ramsey(t, proc):
qc = QubitCircuit(1)
qc.add_gate("RX", 0, arg_value=pi/2)
qc.add_gate("IDLE", 0, arg_value=t)
qc.add_gate("RX", 0, arg_value=pi/2)
proc.load_circuit(qc)
result = proc.run_state(
init_state=basis(2, 0),
e_ops = sigmaz()
)
return result.expect[0][-1]

idle_tlist = np.linspace(0., 30., num_samples)
measurements = np.asarray([ramsey(t, proc) for t in idle_tlist])

fig, ax = plt.subplots(figsize = (LINEWIDTH, LINEWIDTH*0.60), dpi=200)

rx_gate_time = 1/4/amp # pi/2
total_time = 2*rx_gate_time + idle_tlist[-1]

tlist = np.linspace(0., total_time, num_samples)
ax.plot(idle_tlist[:], measurements[:], '-', label="Simulation", color="slategray")

peak_ind = scipy.signal.find_peaks(measurements)[0]
decay_func = lambda t, t2, f0: f0 * np.exp(-1./t2 * t)
(t2_fit, f0_fit), _ = scipy.optimize.curve_fit(decay_func, idle_tlist[peak_ind], measurements[peak_ind])
print("T2:", t2)
print("Fitted T2:", t2_fit)

ax.plot(idle_tlist, decay_func(idle_tlist, t2_fit, f0_fit), '--', label="Theory", color="slategray")
ax.set_xlabel(r"Idling time $t$ [$\mu$s]")
ax.set_ylabel("Ramsey signal", labelpad=2)
ax.set_ylim((ax.get_ylim()[0], ax.get_ylim()[1]))
ax.set_position([0.18, 0.2, 0.75, 0.75])
ax.grid()
fig.tight_layout()

fig.savefig("fig5_decoherence.pdf")
fig.show()
fig.show()


circuit = QubitCircuit(1)
circuit.add_gate("RX", 0, arg_value=pi/2)
circuit.add_gate("IDLE", 0, arg_value=15.)
circuit.add_gate("RX", 0, arg_value=pi/2)
proc.load_circuit(circuit)
fig2, axis = proc.plot_pulses(figsize=(LINEWIDTH, LINEWIDTH*0.15), use_control_latex=False, dpi=200)
axis[0].set_ylim((0. - axis[0].get_ylim()[1] * 0.05, axis[0].get_ylim()[1]))
axis[0].set_position([0.18, 0.39, 0.75, 0.60])
axis[0].set_ylabel("sx0", labelpad=25)
axis[0].yaxis.set_label_coords(-0.13, 0.25)
axis[0].set_xlabel("Ramsey pulse")
fig2.savefig("fig5_decoherence_pulse.pdf")
fig2.show()

# Test for time-dependent decoherence
from qutip_qip.noise import DecoherenceNoise
from qutip import sigmam
tlist = np.linspace(0, 30., 100)
coeff = tlist * 0.01
proc.add_noise(
DecoherenceNoise(sigmam(), targets=0, coeff=coeff, tlist=tlist))
result = proc.run_state(
init_state=basis(2, 0),
e_ops = sigmaz()
)
2 changes: 0 additions & 2 deletions doc/pulse-paper/deutsch_jozsa-qasm.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from qutip_qip.qasm import read_qasm

qc = read_qasm("deutsch-jozsa.qasm")

from qutip_qip.qasm import save_qasm

save_qasm(qc, "deutsch-jozsa-qutip.qasm")

0 comments on commit 7d286ee

Please sign in to comment.