Skip to content

Commit

Permalink
Add pulse shape
Browse files Browse the repository at this point in the history
Different pulse shapes are generated using window function from scipy.signal.windows.
A generate_pulse_shape method is defined and can be used to generate pulse shape with the arbitrary area and required maximum.
A default single qubit compiler is defined and simplifies the various compiler subclasses.
  • Loading branch information
BoxiLi committed Aug 18, 2021
1 parent 8b49a89 commit 2bf6d53
Show file tree
Hide file tree
Showing 6 changed files with 407 additions and 144 deletions.
26 changes: 26 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,26 @@ 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 does 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
155 changes: 80 additions & 75 deletions src/qutip_qip/compiler/cavityqedcompiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
###############################################################################
from functools import partial
import numpy as np
import scipy

from ..circuit import QubitCircuit
from ..operations import Gate
Expand All @@ -42,8 +44,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 @@ -71,52 +87,21 @@ def __init__(
self.gate_compiler.update({
"ISWAP": self.iswap_compiler,
"SQRTISWAP": self.sqrtiswap_compiler,
"RZ": self.rz_compiler,
"RX": self.rx_compiler,
"RZ": partial(self.default_single_qubit_compiler, scaling=0.5),
"RX": partial(self.default_single_qubit_compiler, scaling=0.5),
"GLOBALPHASE": self.globalphase_compiler
})
self.wq = np.sqrt(self.params["eps"]**2 + self.params["delta"]**2)
self.Delta = self.wq - self.params["w0"]
self.global_phase = global_phase

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
pulse_info = [("sz" + str(targets[0]), coeff)]
return [Instruction(gate, tlist, pulse_info)]

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
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 @@ -127,55 +112,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
73 changes: 53 additions & 20 deletions src/qutip_qip/compiler/circuitqedcompiler.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from functools import partial
import numpy as np

from ..circuit import Gate
Expand All @@ -11,14 +12,36 @@ 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,
"RX": partial(
self.default_single_qubit_compiler, param_name="omega_single"),
"RY": partial(
self.default_single_qubit_compiler, param_name="omega_single"),
"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 +55,41 @@ 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):
"""
Compiler for the RX and RY 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)]
return [Instruction(gate, tlist, pulse_info)]
return partial(
self.default_single_qubit_compiler, param_name="omega_single")

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 +105,12 @@ def cnot_compiler(self, gate, args):
sign = np.sign(amplitude) * np.sign(area)
tlist = tlist / amplitude * area * sign
coeff = coeff * amplitude * sign
from scipy.integrate import simps
print(amplitude)
print(simps(coeff, tlist))
coeff, tlist = self.generate_pulse_shape(
args["shape"], args["num_samples"], maximum=zx_coeff, area=area)
print(simps(coeff, tlist))
pulse_info = [("zx" + str(q1) + str(q2), coeff)]
result += [Instruction(gate, tlist, pulse_info)]

Expand Down

0 comments on commit 2bf6d53

Please sign in to comment.