Skip to content

Commit

Permalink
Merge 2f0e15f into 0cf9aba
Browse files Browse the repository at this point in the history
  • Loading branch information
BoxiLi committed Sep 13, 2021
2 parents 0cf9aba + 2f0e15f commit 4e7687d
Show file tree
Hide file tree
Showing 6 changed files with 369 additions and 108 deletions.
27 changes: 27 additions & 0 deletions doc/source/qip-processor.rst
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ To let it find the optimal pulses, we need to give the parameters for :func:`~qu
Compiler and scheduler
======================

Compiler
--------

In order to simulate quantum circuits at the level of time evolution.
We need to first compile the circuit into the Hamiltonian model, i.e.
the control pulses.
Expand Down Expand Up @@ -236,6 +239,9 @@ The second one is turned on from ``t=1`` to ``t=2`` with the same strength.
The compiled pulse here is different from what is shown in the plot
in the previous subsection because the scheduler is turned off by default.

Scheduler
---------

The scheduler is implemented in the class :class:`.compiler.Scheduler`,
based on the idea of https://doi.org/10.1117/12.666419.
It schedules the order of quantum gates and instructions for the
Expand Down Expand Up @@ -287,6 +293,27 @@ one will need to wrap the :class:`.Gate` object with :class:`.compiler.instructi
with a parameter `duration`.
The result will then be the start time of each instruction.

Pulse shape
-----------

Apart from square pulses, compilers also support different pulse shapes.
All pulse shapes from `SciPy window functions <https://docs.scipy.org/doc/scipy/reference/signal.windows.html>`_ that do not require additional parameters are supported.
The method :obj:`.GateCompiler.generate_pulse_shape` allows one to generate pulse shapes that fulfil the given maximum intensity and the total integral area.

.. plot::

from qutip_qip.compiler import GateCompiler
compiler = GateCompiler()
coeff, tlist = compiler.generate_pulse_shape(
"hann", 1000, maximum=2., area=1.)
fig, ax = plt.subplots(figsize=(4,2))
ax.plot(tlist, coeff)
ax.set_xlabel("Time")
fig.show()

For predefined compilers, the compiled pulse shape can also be configured by the key word ``"shape"`` and ``"num_samples"`` in the dictionary attribute :attr:`.GateCompiler.args`
or the ``args`` parameter of :obj:`.GateCompiler.compile`.

Noise Simulation
================

Expand Down
143 changes: 86 additions & 57 deletions src/qutip_qip/compiler/cavityqedcompiler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from functools import partial
import numpy as np
import scipy

from ..circuit import QubitCircuit
from ..operations import Gate
Expand All @@ -10,8 +12,22 @@

class CavityQEDCompiler(GateCompiler):
"""
Decompose a :class:`.QubitCircuit` into
the pulse sequence for the processor.
Compiler for :obj:`.DispersiveCavityQED`.
Compiled pulse strength is in the unit of GHz.
Supported native gates: "RX", "RY", "RZ", "ISWAP", "SQRTISWAP",
"GLOBALPHASE".
Default configuration (see :obj:`.GateCompiler.args` and
:obj:`.GateCompiler.compile`):
+-----------------+--------------------+
| key | value |
+=================+====================+
| ``shape`` | ``rectangular`` |
+-----------------+--------------------+
|``params`` | Hardware Parameters|
+-----------------+--------------------+
Parameters
----------
Expand Down Expand Up @@ -52,9 +68,10 @@ def rz_compiler(self, gate, args):
Compiler for the RZ gate
"""
targets = gate.targets
g = self.params["sz"][targets[0]]
coeff = np.sign(gate.arg_value) * g
tlist = abs(gate.arg_value) / (2*g) / np.pi / 2
coeff, tlist = self.generate_pulse_shape(
args["shape"], args["num_samples"],
maximum=self.params["sz"][targets[0]],
area=gate.arg_value / 2. / np.pi * 0.5) # operator is Z, not Z/2
pulse_info = [("sz" + str(targets[0]), coeff)]
return [Instruction(gate, tlist, pulse_info)]

Expand All @@ -63,28 +80,20 @@ def rx_compiler(self, gate, args):
Compiler for the RX gate
"""
targets = gate.targets
g = self.params["sx"][targets[0]]
coeff = np.sign(gate.arg_value) * g
tlist = abs(gate.arg_value) / (2*g) / np.pi / 2
coeff, tlist = self.generate_pulse_shape(
args["shape"], args["num_samples"],
maximum=self.params["sx"][targets[0]],
area=gate.arg_value / 2. / np.pi * 0.5) # operator is X, not X/2
pulse_info = [("sx" + str(targets[0]), coeff)]
return [Instruction(gate, tlist, pulse_info)]

def sqrtiswap_compiler(self, gate, args):
"""
Compiler for the SQRTISWAP gate
Notes
-----
This version of sqrtiswap_compiler has very low fidelity, please use
iswap
"""
# FIXME This decomposition has poor behaviour
def _two_qubit_compiler(self, gate_name, gate, args):
q1, q2 = gate.targets
pulse_info = []
pulse_name = "sz" + str(q1)
coeff = self.wq[q1] - self.params["w0"]
pulse_info += [(pulse_name, coeff)]
pulse_name = "sz" + str(q1)
pulse_name = "sz" + str(q2)
coeff = self.wq[q2] - self.params["w0"]
pulse_info += [(pulse_name, coeff)]
pulse_name = "g" + str(q1)
Expand All @@ -95,55 +104,75 @@ def sqrtiswap_compiler(self, gate, args):
pulse_info += [(pulse_name, coeff)]

J = self.params["g"][q1] * self.params["g"][q2] * (
1 / self.Delta[q1] + 1 / self.Delta[q2]) / 2
tlist = (4 * np.pi / abs(J)) / 8 / np.pi / 2
1. / self.Delta[q1] + 1. / self.Delta[q2]) / 2.
if gate_name == "ISWAP":
area = 1. / 2.
correction_angle = -np.pi / 2.
elif gate_name == "SQRTISWAP":
area = 1. / 4.
correction_angle = -np.pi / 4.
else:
raise ValueError(f"Gate {gate.name} cannot not be compiled.")
coeff, tlist = self.generate_pulse_shape(
args["shape"], args["num_samples"], maximum=J, area=area)
instruction_list = [Instruction(gate, tlist, pulse_info)]

# corrections
gate1 = Gate("RZ", [q1], None, arg_value=-np.pi/4)
compiled_gate1 = self.rz_compiler(gate1, args)
gate1 = Gate("RZ", [q1], None, arg_value=correction_angle)
compiled_gate1 = self.gate_compiler["RZ"](gate1, args)
instruction_list += compiled_gate1
gate2 = Gate("RZ", [q2], None, arg_value=-np.pi/4)
compiled_gate2 = self.rz_compiler(gate2, args)
gate2 = Gate("RZ", [q2], None, arg_value=correction_angle)
compiled_gate2 = self.gate_compiler["RZ"](gate2, args)
instruction_list += compiled_gate2
gate3 = Gate("GLOBALPHASE", None, None, arg_value=-np.pi/4)
gate3 = Gate("GLOBALPHASE", None, None, arg_value=correction_angle)
self.globalphase_compiler(gate3, args)
return instruction_list

def iswap_compiler(self, gate, args):
"""
Compiler for the ISWAP gate
def sqrtiswap_compiler(self, gate, args):
"""
q1, q2 = gate.targets
pulse_info = []
pulse_name = "sz" + str(q1)
coeff = self.wq[q1] - self.params["w0"]
pulse_info += [(pulse_name, coeff)]
pulse_name = "sz" + str(q2)
coeff = self.wq[q2] - self.params["w0"]
pulse_info += [(pulse_name, coeff)]
pulse_name = "g" + str(q1)
coeff = self.params["g"][q1]
pulse_info += [(pulse_name, coeff)]
pulse_name = "g" + str(q2)
coeff = self.params["g"][q2]
pulse_info += [(pulse_name, coeff)]
Compiler for the SQRTISWAP gate.
Parameters
----------
gate : :obj:`.Gate`:
The quantum gate to be compiled.
args : dict
The compilation configuration defined in the attributes
:obj:`.GateCompiler.args` or given as a parameter in
:obj:`.GateCompiler.compile`.
Returns
-------
A list of :obj:`.Instruction`, including the compiled pulse
information for this gate.
J = self.params["g"][q1] * self.params["g"][q2] * (
1 / self.Delta[q1] + 1 / self.Delta[q2]) / 2
tlist = (4 * np.pi / abs(J)) / 4 / np.pi / 2
instruction_list = [Instruction(gate, tlist, pulse_info)]
Notes
-----
This version of sqrtiswap_compiler has very low fidelity, please use
iswap
"""
# FIXME This decomposition has poor behaviour.
return self._two_qubit_compiler("SQRTISWAP", gate, args)

# corrections
gate1 = Gate("RZ", [q1], None, arg_value=-np.pi/2.)
compiled_gate1 = self.rz_compiler(gate1, args)
instruction_list += compiled_gate1
gate2 = Gate("RZ", [q2], None, arg_value=-np.pi/2)
compiled_gate2 = self.rz_compiler(gate2, args)
instruction_list += compiled_gate2
gate3 = Gate("GLOBALPHASE", None, None, arg_value=-np.pi/2)
self.globalphase_compiler(gate3, args)
return instruction_list
def iswap_compiler(self, gate, args):
"""
Compiler for the ISWAP gate.
Parameters
----------
gate : :obj:`.Gate`:
The quantum gate to be compiled.
args : dict
The compilation configuration defined in the attributes
:obj:`.GateCompiler.args` or given as a parameter in
:obj:`.GateCompiler.compile`.
Returns
-------
A list of :obj:`.Instruction`, including the compiled pulse
information for this gate.
"""
return self._two_qubit_compiler("ISWAP", gate, args)

def globalphase_compiler(self, gate, args):
"""
Expand Down
83 changes: 63 additions & 20 deletions src/qutip_qip/compiler/circuitqedcompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,34 @@ class SCQubitsCompiler(GateCompiler):
"""
Compiler for :class:`.SCQubits`.
Compiled pulse strength is in the unit of GHz.
Supported native gates: "RX", "RY", "CNOT".
Default configuration (see :obj:`.GateCompiler.args` and
:obj:`.GateCompiler.compile`):
+-----------------+-----------------------+
| key | value |
+=================+=======================+
| ``shape`` | ``hann`` |
+-----------------+-----------------------+
|``num_samples`` | 1000 |
+-----------------+-----------------------+
|``params`` | Hardware Parameters |
+-----------------+-----------------------+
"""
def __init__(self, num_qubits, params):
super(SCQubitsCompiler, self).__init__(num_qubits, params=params)
self.gate_compiler.update({
"RX": self.single_qubit_compiler,
"RY": self.single_qubit_compiler,
"RY": self.ry_compiler,
"RX": self.rx_compiler,
"CNOT": self.cnot_compiler,
})
self.args = { # Default configuration
"shape": "hann",
"num_samples": 1000,
"params": self.params,
}

def _normalized_gauss_pulse(self):
"""
Expand All @@ -32,37 +52,58 @@ def _normalized_gauss_pulse(self):
# td normalization so that the total integral area is 1
td = 2.4384880692912567
sigma = 1/6 * td # 3 sigma
tlist = np.linspace(0, td, 100)
tlist = np.linspace(0, td, 1000)
max_pulse = 1 - np.exp(-(0-td/2)**2/2/sigma**2)
coeff = (np.exp(-(tlist-td/2)**2/2/sigma**2)
- np.exp(-(0-td/2)**2/2/sigma**2)) / max_pulse
coeff = (
np.exp(-(tlist-td/2)**2/2/sigma**2)
- np.exp(-(0-td/2)**2/2/sigma**2)
) / max_pulse
return tlist, coeff

def single_qubit_compiler(self, gate, args):
def ry_compiler(self, gate, args):
"""
Compiler for the RX and RY gate.
Compiler for the RZ gate
"""
targets = gate.targets
omega_single = self.params["omega_single"][targets[0]]
tlist, coeff = self._normalized_gauss_pulse()
sign = np.sign(omega_single) * np.sign(gate.arg_value)
tlist = tlist / omega_single * gate.arg_value/np.pi/2 * sign
coeff = coeff * omega_single * sign
if gate.name == "RY":
pulse_prefix = "sy"
elif gate.name == "RX":
pulse_prefix = "sx"
else:
raise ValueError(f"Gate {gate.name} cnot not be compiled.")
pulse_info = [(pulse_prefix + str(targets[0]), coeff)]
coeff, tlist = self.generate_pulse_shape(
args["shape"], args["num_samples"],
maximum=self.params["omega_single"][targets[0]],
area=gate.arg_value / 2. / np.pi)
pulse_info = [("sy" + str(targets[0]), coeff)]
return [Instruction(gate, tlist, pulse_info)]

def rx_compiler(self, gate, args):
"""
Compiler for the RX gate
"""
targets = gate.targets
coeff, tlist = self.generate_pulse_shape(
args["shape"], args["num_samples"],
maximum=self.params["omega_single"][targets[0]],
area=gate.arg_value / 2. / np.pi)
pulse_info = [("sx" + str(targets[0]), coeff)]
return [Instruction(gate, tlist, pulse_info)]

def cnot_compiler(self, gate, args):
"""
Compiler for CNOT gate using the cross resonance iteraction.
See
https://journals.aps.org/prb/abstract/10.1103/PhysRevB.81.134507
for reference
for reference.
Parameters
----------
gate : :obj:`.Gate`:
The quantum gate to be compiled.
args : dict
The compilation configuration defined in the attributes
:obj:`.GateCompiler.args` or given as a parameter in
:obj:`.GateCompiler.compile`.
Returns
-------
A list of :obj:`.Instruction`, including the compiled pulse
information for this gate.
"""
result = []
q1 = gate.controls[0]
Expand All @@ -78,6 +119,8 @@ def cnot_compiler(self, gate, args):
sign = np.sign(amplitude) * np.sign(area)
tlist = tlist / amplitude * area * sign
coeff = coeff * amplitude * sign
coeff, tlist = self.generate_pulse_shape(
args["shape"], args["num_samples"], maximum=zx_coeff, area=area)
pulse_info = [("zx" + str(q1) + str(q2), coeff)]
result += [Instruction(gate, tlist, pulse_info)]

Expand Down
Loading

0 comments on commit 4e7687d

Please sign in to comment.