From 2bf6d5361d0ecfc70adaa7bd13200953d2f49f59 Mon Sep 17 00:00:00 2001 From: Boxi Li Date: Wed, 18 Aug 2021 15:02:04 +0200 Subject: [PATCH] Add pulse shape 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. --- doc/source/qip-processor.rst | 26 +++ src/qutip_qip/compiler/cavityqedcompiler.py | 155 +++++++++-------- src/qutip_qip/compiler/circuitqedcompiler.py | 73 +++++--- src/qutip_qip/compiler/gatecompiler.py | 169 ++++++++++++++++++- src/qutip_qip/compiler/spinchaincompiler.py | 109 +++++++----- tests/test_compiler.py | 19 +++ 6 files changed, 407 insertions(+), 144 deletions(-) diff --git a/doc/source/qip-processor.rst b/doc/source/qip-processor.rst index 934046ecc..2ff80563d 100644 --- a/doc/source/qip-processor.rst +++ b/doc/source/qip-processor.rst @@ -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. @@ -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 @@ -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 `_ 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 ================ diff --git a/src/qutip_qip/compiler/cavityqedcompiler.py b/src/qutip_qip/compiler/cavityqedcompiler.py index 564dcad79..fce68dee4 100644 --- a/src/qutip_qip/compiler/cavityqedcompiler.py +++ b/src/qutip_qip/compiler/cavityqedcompiler.py @@ -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 @@ -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 ---------- @@ -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) @@ -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): """ diff --git a/src/qutip_qip/compiler/circuitqedcompiler.py b/src/qutip_qip/compiler/circuitqedcompiler.py index 32af4f5e3..f9f09b3ed 100644 --- a/src/qutip_qip/compiler/circuitqedcompiler.py +++ b/src/qutip_qip/compiler/circuitqedcompiler.py @@ -1,3 +1,4 @@ +from functools import partial import numpy as np from ..circuit import Gate @@ -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): """ @@ -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] @@ -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)] diff --git a/src/qutip_qip/compiler/gatecompiler.py b/src/qutip_qip/compiler/gatecompiler.py index d575186e4..463a12d9a 100644 --- a/src/qutip_qip/compiler/gatecompiler.py +++ b/src/qutip_qip/compiler/gatecompiler.py @@ -30,12 +30,14 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ############################################################################### +import warnings import numpy as np +from scipy import signal + from .instruction import Instruction from .scheduler import Scheduler from ..circuit import QubitCircuit from ..operations import Gate -import warnings __all__ = ['GateCompiler'] @@ -68,8 +70,18 @@ class GateCompiler(object): Note that for continuous pulse, the first coeff should always be 0. args: dict - Arguments for individual compiling routines. - It adds more flexibility in customizing compiler. + The compilation configurations. + It will be passed to each compiling functions. + Available arguments: + + * ``shape``: The compiled pulse shape. ``rectangular`` or + one of the `SciPy window functions + `_. + * ``num_samples``: + Number of samples in continuous pulse. + It has not effect in rectangular pulse. + * ``params``: Hardware parameters computed in the :obj:`Processor`. + """ def __init__(self, num_qubits=None, params=None, pulse_dict=None, N=None): self.gate_compiler = {} @@ -80,8 +92,11 @@ def __init__(self, num_qubits=None, params=None, pulse_dict=None, N=None): "GLOBALPHASE": self.globalphase_compiler, "IDLE": self.idle_compiler } - self.args = {} - self.args.update({"params": self.params}) + self.args = { # Default configuration + "shape": "rectangular", + "num_samples": None, + "params": self.params, + } self.global_phase = 0. if pulse_dict is not None: warnings.warn( @@ -319,3 +334,147 @@ def _process_idling_tlist( # idling until the start time idling_tlist.append([start_time]) return np.concatenate(idling_tlist) + + def default_single_qubit_compiler( + self, gate, args, param_name=None, op_name=None, scaling=1.): + """ + Default compiler for single qubits gates. + It compiles ``"RY"`` ``"RX"`` and ``"RZ"`` gate, + assuming that the corresponding + operator is named ``"sy"``, ``"sx"``, and ``"sz"``. + + 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`. + param_name :str + The name of the coefficient saved in the attribute + :obj:`GateCompiler.params`. + The corresponding value sets the maximum of the pulse. + It should be calculated in the corresponding :class:`.Processor` + and passed to the compiler. + op_name : str + The name of the corresponding operator. + It will be one of the keys of the returned dictionary of + :obj:`.GateCompiler.compile`. + The name should match the label prefix of the control Hamiltonians + in :obj:`.Processor`. + E.g., if the label is "sx0" for the zeroth qubit, + set ``op_name="sx"``. + scaling : float + Scaling for the ``tlist``. + By default, the operator is assumed to be + ``2*pi*sigmax()/2`` (or y, z). + If operators with other factors are used, e.g., ``2*pi*sigmax()``, + choose ``scaling=0.5``. + It will be multiplied to the ``tlist`` to ensure that + the total area is desired. + + Returns + ------- + A list of :obj:`.Instruction`, including the compiled pulse + information for this gate. + """ + targets = gate.targets + if gate.name == "RY": + pulse_prefix = "sy" + elif gate.name == "RX": + pulse_prefix = "sx" + elif gate.name == "RZ": + pulse_prefix = "sz" + else: + raise ValueError(f"Gate {gate.name} cannot not be compiled.") + if param_name is None: + param_name = pulse_prefix + if op_name is None: + op_name = pulse_prefix + coeff, tlist = self.generate_pulse_shape( + args["shape"], args["num_samples"], + maximum=self.params[param_name][targets[0]], + area=gate.arg_value / 2. / np.pi) + pulse_info = [(op_name + str(targets[0]), coeff)] + return [Instruction(gate, tlist * scaling, pulse_info)] + + def generate_pulse_shape(self, window, num_samples, maximum=1., area=1.): + """ + Return a tuple consisting of a coeff list and a time sequence + according to a given window function. + + Parameters + ---------- + window : str + The name ``"rectangular"`` for constant pulse or + the name of a Scipy window function. + See + `the Scipy documentation + `_ + for detail. + num_samples : int + The number of the samples of the coefficients. + maximum : float + The maximum of the coefficients. + The absolute value will be used if negative. + area : float + The total area if one integrates coeff as a function of the time. + + Returns + ------- + coeff, tlist : + If the default window ``"shape"="rectangular"`` is used, + both are float numbers. + If Scipy window functions are used, both are a 1-dimensional numpy + array with the same size. + + Notes + ----- + If Scipy window functions are used, it is suggested to set + ``Processor.pulse_mode`` to ``"continuous"``. + Also, finite number of samples will also make + the total integral of the coefficients slightly deviate from ``area``. + """ + coeff, tlist = _normalized_window(window, num_samples) + sign = np.sign(area) + coeff *= np.abs(maximum) * sign + tlist *= abs(area) / np.abs(maximum) + return coeff, tlist + + +""" +Normalized scipy window functions. +The scipy implementation only makes sure that it is maximum is 1. +Here, we save a default t_max so that the integral is always 1. +""" +_default_window_t_max = { + "boxcar": 1., + "triang": 2., + "blackman": 1./0.42, + "hamming": 1./0.54, + "hann": 2., + "bartlett": 2., + "flattop": 1./0.21557897160000217, + "parzen": 1./0.375, + "bohman": 1./0.4052847750978287, + "blackmanharris": 1./0.35875003586900384, + "nuttall": 1./0.36358193632191405, + "barthann": 2., + "cosine": np.pi/2., + } + + +def _normalized_window(window, num_samples): + """ + Return a normalized window functions. + """ + if window == "rectangular": + return 1., 1. + if window not in _default_window_t_max.keys(): + raise RuntimeError(f"Window function {window} is not supported.") + coeff = signal.windows.get_window( + window, num_samples + ) + tlist = np.linspace(0, _default_window_t_max[window], num_samples) + return coeff, tlist diff --git a/src/qutip_qip/compiler/spinchaincompiler.py b/src/qutip_qip/compiler/spinchaincompiler.py index 2a7cd6e3f..30bb5b85b 100644 --- a/src/qutip_qip/compiler/spinchaincompiler.py +++ b/src/qutip_qip/compiler/spinchaincompiler.py @@ -30,6 +30,7 @@ # (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 from ..circuit import QubitCircuit @@ -42,8 +43,22 @@ class SpinChainCompiler(GateCompiler): """ - Compile a :class:`.QubitCircuit` into - the pulse sequence for the processor. + Compiler for :obj:`.SpinChain`. + Compiled pulse strength is in the unit of MHz. + + 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 ---------- @@ -96,43 +111,25 @@ 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.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 iswap_compiler(self, gate, args): - """ - Compiler for the ISWAP gate - """ + def _two_qubit_compiler(self, gate_name, gate, args): targets = gate.targets q1, q2 = min(targets), max(targets) g = self.params["sxsy"][q1] - coeff = -g - tlist = np.pi / (4 * g) / np.pi / 2 + maximum = g + if gate_name == "ISWAP": + area = -1 / 8 + elif gate_name == "SQRTISWAP": + area = -1 / 16 + else: + raise ValueError(f"Gate {gate.name} cannot not be compiled.") + coeff, tlist = self.generate_pulse_shape( + args["shape"], args["num_samples"], maximum, area) if self.N != 2 and q1 == 0 and q2 == self.N - 1: pulse_name = "g" + str(q2) else: @@ -140,21 +137,45 @@ def iswap_compiler(self, gate, args): pulse_info = [(pulse_name, coeff)] return [Instruction(gate, tlist, pulse_info)] + 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 sqrtiswap_compiler(self, gate, args): """ - Compiler for the SQRTISWAP gate + 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. """ - targets = gate.targets - q1, q2 = min(targets), max(targets) - g = self.params["sxsy"][q1] - coeff = -g - tlist = np.pi / (8 * g) / np.pi / 2 - if self.N != 2 and q1 == 0 and q2 == self.N - 1: - pulse_name = "g" + str(q2) - else: - pulse_name = "g" + str(q1) - pulse_info = [(pulse_name, coeff)] - return [Instruction(gate, tlist, pulse_info)] + return self._two_qubit_compiler("SQRTISWAP", gate, args) def globalphase_compiler(self, gate, args): """ diff --git a/tests/test_compiler.py b/tests/test_compiler.py index c227063de..7347ef3e2 100644 --- a/tests/test_compiler.py +++ b/tests/test_compiler.py @@ -33,6 +33,7 @@ import pytest import numpy as np from numpy.testing import assert_array_equal +from qutip_qip.compiler.gatecompiler import _default_window_t_max from qutip_qip.device import ( DispersiveCavityQED, CircularSpinChain, LinearSpinChain) @@ -183,3 +184,21 @@ def test_compiler_result_format(): processor.set_all_tlist(tlist) assert_array_equal(processor.pulses[0].coeff, coeffs[0]) assert_array_equal(processor.pulses[0].tlist, tlist[0]) + + +@pytest.mark.parametrize("shape", _default_window_t_max.keys()) +def test_pulse_shape(shape): + """Test different pulse shape functions""" + num_qubits = 1 + circuit = QubitCircuit(num_qubits) + circuit.add_gate("X", 0) + processor = LinearSpinChain(num_qubits) + compiler = SpinChainCompiler(num_qubits, processor.params) + compiler.args.update({"shape": shape, "num_samples": 100}) + processor.load_circuit(circuit, compiler=compiler) + processor.pulse_mode = "continuous" + init_state = basis(2, 0) + num_result = processor.run_state(init_state).states[-1] + ideal_result = circuit.run(init_state) + ifid = 1 - fidelity(num_result, ideal_result) + assert(pytest.approx(ifid, abs=0.01) == 0)