Skip to content

Commit

Permalink
Separate the gate definition from the QubitCircuit.propagators method (
Browse files Browse the repository at this point in the history
…#83)

- Merge the duplicated `QubitCircuit.propagators` methods.
- Move the definition for gate operators to the `Gate` class: one method defining the compact gate operator as Qobj, while the other one expands it to the full Hilbert space. This makes it easier to define subclasses for each gate later.
- Rename a previously undocumented `QubitCircuit.get_inds` method to `get_all_qubits` and add documentation. It was added recently but never documented.

There are two new method for `Gate`: `get_qobj` and `get_compact_qobj`. Although they could be merged into one, I made them two different methods because for custom gate, I wish that the user only need to define `get_compact_qobj`, without having to consider permutation and expanding etc. In `get_qobj`, we take care of that.
  • Loading branch information
BoxiLi authored Aug 3, 2021
1 parent 88cc63c commit 8b49a89
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 223 deletions.
4 changes: 2 additions & 2 deletions doc/source/qip-basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ full dimension of the circuit:

.. testcode::

U_list = qc.propagators()
U_list = qc.propagators(ignore_measurement=True)
print(U_list)

**Output**:
Expand Down Expand Up @@ -103,7 +103,7 @@ can be achieved with the argument ``expand=False`` specified to the

.. testcode::

U_list = qc.propagators(expand=False)
U_list = qc.propagators(expand=False, ignore_measurement=True)
print(U_list)

**Output**:
Expand Down
253 changes: 39 additions & 214 deletions src/qutip_qip/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
from collections.abc import Iterable
from itertools import product
import numbers

import warnings
import inspect

import numpy as np
Expand Down Expand Up @@ -202,7 +202,7 @@ def __init__(self, N, input_states=None, output_states=None,
self.N = N
self.reverse_states = reverse_states
self.gates = []
self.dims = dims
self.dims = dims if dims is not None else [2] * N
self.num_cbits = num_cbits

if input_states:
Expand Down Expand Up @@ -1247,7 +1247,7 @@ def adjacent_gates(self):

return temp

def propagators(self, expand=True):
def propagators(self, expand=True, ignore_measurement=False):
"""
Propagator matrix calculator returning the individual
steps as unitary matrices operating from left to right.
Expand All @@ -1262,217 +1262,36 @@ def propagators(self, expand=True):
list of unitaries will need to be combined with the list of
gates in order to determine which qubits the unitaries should
act on.
Returns
-------
U_list : list
Return list of unitary matrices for the qubit circuit.
"""

if not expand:
return self._propagators_no_expand()

U_list = []

gates = filter(lambda x: isinstance(x, Gate), self.gates)

for gate in gates:
if gate.name == "RX":
U_list.append(rx(
gate.arg_value, self.N, gate.targets[0]))
elif gate.name == "RY":
U_list.append(ry(
gate.arg_value, self.N, gate.targets[0]))
elif gate.name == "RZ":
U_list.append(rz(
gate.arg_value, self.N, gate.targets[0]))
elif gate.name == "X":
U_list.append(x_gate(self.N, gate.targets[0]))
elif gate.name == "Y":
U_list.append(y_gate(self.N, gate.targets[0]))
elif gate.name == "CY":
U_list.append(cy_gate(
self.N, gate.controls[0], gate.targets[0]))
elif gate.name == "Z":
U_list.append(z_gate(self.N, gate.targets[0]))
elif gate.name == "CZ":
U_list.append(cz_gate(
self.N, gate.controls[0], gate.targets[0]))
elif gate.name == "T":
U_list.append(t_gate(self.N, gate.targets[0]))
elif gate.name == "CT":
U_list.append(ct_gate(
self.N, gate.controls[0], gate.targets[0]))
elif gate.name == "S":
U_list.append(s_gate(self.N, gate.targets[0]))
elif gate.name == "CS":
U_list.append(cs_gate(
self.N, gate.controls[0], gate.targets[0]))
elif gate.name == "SQRTNOT":
U_list.append(sqrtnot(self.N, gate.targets[0]))
elif gate.name == "SNOT":
U_list.append(snot(self.N, gate.targets[0]))
elif gate.name == "PHASEGATE":
U_list.append(phasegate(gate.arg_value, self.N,
gate.targets[0]))
elif gate.name == "QASMU":
U_list.append(qasmu_gate(gate.arg_value, self.N,
gate.targets[0]))
elif gate.name == "CRX":
U_list.append(controlled_gate(rx(gate.arg_value),
N=self.N,
control=gate.controls[0],
target=gate.targets[0]))
elif gate.name == "CRY":
U_list.append(controlled_gate(ry(gate.arg_value),
N=self.N,
control=gate.controls[0],
target=gate.targets[0]))
elif gate.name == "CRZ":
U_list.append(controlled_gate(rz(gate.arg_value),
N=self.N,
control=gate.controls[0],
target=gate.targets[0]))
elif gate.name == "CPHASE":
U_list.append(cphase(gate.arg_value, self.N,
gate.controls[0], gate.targets[0]))
elif gate.name == "CNOT":
U_list.append(cnot(self.N,
gate.controls[0], gate.targets[0]))
elif gate.name == "CSIGN":
U_list.append(csign(self.N,
gate.controls[0], gate.targets[0]))
elif gate.name == "BERKELEY":
U_list.append(berkeley(self.N, gate.targets))
elif gate.name == "SWAPalpha":
U_list.append(swapalpha(gate.arg_value, self.N,
gate.targets))
elif gate.name == "SWAP":
U_list.append(swap(self.N, gate.targets))
elif gate.name == "ISWAP":
U_list.append(iswap(self.N, gate.targets))
elif gate.name == "SQRTSWAP":
U_list.append(sqrtswap(self.N, gate.targets))
elif gate.name == "SQRTISWAP":
U_list.append(sqrtiswap(self.N, gate.targets))
elif gate.name == "FREDKIN":
U_list.append(fredkin(self.N, gate.controls[0],
gate.targets))
elif gate.name == "TOFFOLI":
U_list.append(toffoli(self.N, gate.controls,
gate.targets[0]))
elif gate.name == "GLOBALPHASE":
U_list.append(globalphase(gate.arg_value, self.N))
elif gate.name == "IDLE":
U_list.append(qeye(self.N * [2]))
elif gate.name in self.user_gates:
if gate.controls is not None:
raise ValueError("A user defined gate {} takes only "
"`targets` variable.".format(gate.name))
func_or_oper = self.user_gates[gate.name]
if inspect.isfunction(func_or_oper):
func = func_or_oper
para_num = len(inspect.getfullargspec(func)[0])
if para_num == 0:
oper = func()
elif para_num == 1:
oper = func(gate.arg_value)
else:
raise ValueError(
"gate function takes at most one parameters.")
elif isinstance(func_or_oper, Qobj):
oper = func_or_oper
else:
raise ValueError("gate is neither function nor operator")
U_list.append(expand_operator(
oper, N=self.N, targets=gate.targets, dims=self.dims))
else:
raise NotImplementedError(
"{} gate is an unknown gate.".format(gate.name))

return U_list

def _propagators_no_expand(self):
"""
Propagator matrix calculator for N qubits returning the individual
steps as unitary matrices operating from left to right.
ignore_measurement: bool, optional
Whether :class:`.Measurement` operators should be ignored.
If set False, it will raise an error
when the circuit has measurement.
Returns
-------
U_list : list
Return list of unitary matrices for the qubit circuit.
Notes
-----
If ``expand=False``, the global phase gate only returns a number.
Also, classical controls are be ignored.
"""
U_list = []

gates = filter(lambda x: isinstance(x, Gate), self.gates)
gates = [g for g in self.gates if not isinstance(g, Measurement)]
if len(gates) < len(self.gates) and not ignore_measurement:
raise TypeError(
"Cannot compute the propagator of a measurement operator."
"Please set ignore_measurement=True.")

for gate in gates:
if gate.name == "RX":
U_list.append(rx(gate.arg_value))
elif gate.name == "RY":
U_list.append(ry(gate.arg_value))
elif gate.name == "RZ":
U_list.append(rz(gate.arg_value))
elif gate.name == "X":
U_list.append(x_gate())
elif gate.name == "Y":
U_list.append(y_gate())
elif gate.name == "CY":
U_list.append(cy_gate())
elif gate.name == "Z":
U_list.append(z_gate())
elif gate.name == "CZ":
U_list.append(cz_gate())
elif gate.name == "T":
U_list.append(t_gate())
elif gate.name == "CT":
U_list.append(ct_gate())
elif gate.name == "S":
U_list.append(s_gate())
elif gate.name == "CS":
U_list.append(cs_gate())
elif gate.name == "SQRTNOT":
U_list.append(sqrtnot())
elif gate.name == "SNOT":
U_list.append(snot())
elif gate.name == "PHASEGATE":
U_list.append(phasegate(gate.arg_value))
elif gate.name == "QASMU":
U_list.append(qasmu_gate(gate.arg_value))
elif gate.name == "CRX":
U_list.append(controlled_gate(rx(gate.arg_value)))
elif gate.name == "CRY":
U_list.append(controlled_gate(ry(gate.arg_value)))
elif gate.name == "CRZ":
U_list.append(controlled_gate(rz(gate.arg_value)))
elif gate.name == "CPHASE":
U_list.append(cphase(gate.arg_value))
elif gate.name == "CNOT":
U_list.append(cnot())
elif gate.name == "CSIGN":
U_list.append(csign())
elif gate.name == "BERKELEY":
U_list.append(berkeley())
elif gate.name == "SWAPalpha":
U_list.append(swapalpha(gate.arg_value))
elif gate.name == "SWAP":
U_list.append(swap())
elif gate.name == "ISWAP":
U_list.append(iswap())
elif gate.name == "SQRTSWAP":
U_list.append(sqrtswap())
elif gate.name == "SQRTISWAP":
U_list.append(sqrtiswap())
elif gate.name == "FREDKIN":
U_list.append(fredkin())
elif gate.name == "TOFFOLI":
U_list.append(toffoli())
elif gate.name == "GLOBALPHASE":
U_list.append(globalphase(gate.arg_value, self.N))
elif gate.name == "IDLE":
U_list.append(qeye(2))
elif gate.name in self.user_gates:
if gate.name == "GLOBALPHASE":
qobj = gate.get_qobj(self.N)
U_list.append(qobj)
continue

if gate.name in self.user_gates:
if gate.controls is not None:
raise ValueError("A user defined gate {} takes only "
"`targets` variable.".format(gate.name))
Expand All @@ -1481,21 +1300,26 @@ def _propagators_no_expand(self):
func = func_or_oper
para_num = len(inspect.getfullargspec(func)[0])
if para_num == 0:
oper = func()
qobj = func()
elif para_num == 1:
oper = func(gate.arg_value)
qobj = func(gate.arg_value)
else:
raise ValueError(
"gate function takes at most one parameters.")
elif isinstance(func_or_oper, Qobj):
oper = func_or_oper
qobj = func_or_oper
else:
raise ValueError("gate is neither function nor operator")
U_list.append(oper)
if expand:
all_targets = gate.get_all_qubits()
qobj = expand_operator(
qobj, N=self.N, targets=all_targets, dims=self.dims)
else:
raise NotImplementedError(
"{} gate is an unknown gate.".format(gate.name))

if expand:
qobj = gate.get_qobj(self.N, self.dims)
else:
qobj = gate.get_compact_qobj()
U_list.append(qobj)
return U_list

def compute_unitary(self):
Expand Down Expand Up @@ -1824,9 +1648,10 @@ def __init__(self, qc, state=None, cbits=None,
if U_list:
self.U_list = U_list
elif precompute_unitary:
self.U_list = qc.propagators(expand=False)
self.U_list = qc.propagators(
expand=False, ignore_measurement=True)
else:
self.U_list = qc.propagators()
self.U_list = qc.propagators(ignore_measurement=True)

self.ops = []
self.inds_list = []
Expand Down Expand Up @@ -1881,7 +1706,7 @@ def _process_ops_precompute(self):
if isinstance(gate, Measurement):
continue
else:
self.inds_list.append(gate.get_inds(self.qc.N))
self.inds_list.append(gate.get_all_qubits())

for operation in self.qc.gates:
if isinstance(operation, Measurement):
Expand Down Expand Up @@ -2073,7 +1898,7 @@ def step(self):
if apply_gate:
if self.precompute_unitary:
U = expand_operator(U, self.qc.N,
operation.get_inds(self.qc.N))
operation.get_all_qubits())
self._evolve_state(U)
else:
self._evolve_state(op)
Expand Down
Loading

0 comments on commit 8b49a89

Please sign in to comment.