Skip to content

Commit

Permalink
Merge pull request #143 from CQCL/release/1.17.0
Browse files Browse the repository at this point in the history
Release/1.17.0
  • Loading branch information
cqc-melf authored Jul 6, 2023
2 parents 5c03735 + 88e6320 commit d7acc0a
Show file tree
Hide file tree
Showing 9 changed files with 171 additions and 27 deletions.
2 changes: 0 additions & 2 deletions .github/workflows/docs/build-docs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ def build_module_docs():
"-D",
f"project=pytket-{MODULE}",
"-D",
f"copyright={datetime.date.today().year} Cambridge Quantum Computing",
"-D",
f"version={'.'.join(v[:2])}",
"-D",
f"release={'.'.join(v)}",
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
# Configuration file for the Sphinx documentation builder.
# See https://www.sphinx-doc.org/en/master/usage/configuration.html

author = "Cambridge Quantum Computing Ltd"
copyright = "2023 Quantinuum"
author = "Quantinuum"

extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinx.ext.mathjax",
"sphinx_copybutton",
]

pygments_style = "borland"

html_theme = "sphinx_book_theme"

html_theme_options = {
Expand Down
2 changes: 1 addition & 1 deletion _metadata.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__extension_version__ = "0.40.0"
__extension_version__ = "0.41.0"
__extension_name__ = "pytket-qiskit"
8 changes: 8 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Changelog
~~~~~~~~~

0.41.0 (July 2023)
------------------

* Update pytket version requirement to 1.17.
* Fix conversion of qiskit `UnitaryGate` to and from pytket (up to 3 qubits).
* Fix handling of qiskit controlled gates in the :py:meth:`qiskit_to_tk` converter.
* Handle CCZ and CSX gates in circuit converters.

0.40.0 (June 2023)
------------------

Expand Down
1 change: 1 addition & 0 deletions pytket/extensions/qiskit/backends/aer.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def _tket_gate_set_from_qiskit_backend(
}
if "unitary" in config.basis_gates:
gate_set.add(OpType.Unitary1qBox)
gate_set.add(OpType.Unitary2qBox)
gate_set.add(OpType.Unitary3qBox)
# special case mapping TK1 to U
gate_set.add(OpType.TK1)
Expand Down
3 changes: 0 additions & 3 deletions pytket/extensions/qiskit/backends/ibm.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import itertools
import logging
from ast import literal_eval
Expand Down Expand Up @@ -125,8 +124,6 @@ def _save_ibmq_auth(qiskit_config: Optional[QiskitConfig]) -> None:
token = None
if qiskit_config is not None:
token = qiskit_config.ibmq_api_token
if token is None and os.getenv("PYTKET_REMOTE_QISKIT_TOKEN") is not None:
token = os.getenv("PYTKET_REMOTE_QISKIT_TOKEN")
try:
if token is not None:
IBMProvider.save_account(token, overwrite=True)
Expand Down
67 changes: 50 additions & 17 deletions pytket/extensions/qiskit/qiskit_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@
Node,
Op,
OpType,
Unitary1qBox,
Unitary2qBox,
Unitary3qBox,
UnitType,
CustomGateDef,
Bit,
Expand Down Expand Up @@ -122,6 +124,7 @@
qiskit_gates.CU1Gate: OpType.CU1,
qiskit_gates.CU3Gate: OpType.CU3,
qiskit_gates.CXGate: OpType.CX,
qiskit_gates.CSXGate: OpType.CSX,
qiskit_gates.CYGate: OpType.CY,
qiskit_gates.CZGate: OpType.CZ,
qiskit_gates.ECRGate: OpType.ECR,
Expand All @@ -137,6 +140,7 @@
qiskit_gates.C3XGate: OpType.CnX,
qiskit_gates.C4XGate: OpType.CnX,
qiskit_gates.CCXGate: OpType.CCX,
qiskit_gates.CCZGate: OpType.CnZ,
qiskit_gates.CSwapGate: OpType.CSWAP,
# Multi-controlled gates (qiskit expects a list of controls followed by the target):
qiskit_gates.MCXGate: OpType.CnX,
Expand All @@ -149,7 +153,6 @@
Gate: OpType.CustomGate,
Measure: OpType.Measure,
Reset: OpType.Reset,
UnitaryGate: OpType.Unitary2qBox,
Initialize: OpType.StatePreparationBox,
StatePreparation: OpType.StatePreparationBox,
}
Expand Down Expand Up @@ -196,8 +199,6 @@
name = gate(*([1] * n_params)).name
_gate_str_2_optype[name] = optype

# assumption that unitary will refer to unitary2qbox, even though it means any unitary
_gate_str_2_optype["unitary"] = OpType.Unitary2qBox
_gate_str_2_optype_rev = {v: k for k, v in _gate_str_2_optype.items()}
# the aliasing of the name is ok in the reverse map
_gate_str_2_optype_rev[OpType.Unitary1qBox] = "unitary"
Expand Down Expand Up @@ -351,8 +352,12 @@ def add_qiskit_data(self, data: "QuantumCircuitData") -> None:
pass
self.add_xs(num_ctrl_qubits, ctrl_state, qargs)
optype = None
if type(instr) == ControlledGate:
if type(instr.base_gate) == qiskit_gates.RYGate:
if isinstance(instr, ControlledGate):
if type(instr) in _known_qiskit_gate:
# First we check if the gate is in _known_qiskit_gate
# this avoids CZ being converted to CnZ
optype = _known_qiskit_gate[type(instr)]
elif type(instr.base_gate) == qiskit_gates.RYGate:
optype = OpType.CnRy
elif type(instr.base_gate) == qiskit_gates.YGate:
optype = OpType.CnY
Expand All @@ -366,22 +371,14 @@ def add_qiskit_data(self, data: "QuantumCircuitData") -> None:
f"qiskit ControlledGate with base gate {instr.base_gate}"
+ "not implemented"
)
elif type(instr) == PauliEvolutionGate:
elif type(instr) in [PauliEvolutionGate, UnitaryGate]:
pass # Special handling below
else:
optype = _known_qiskit_gate[type(instr)]
qubits = [self.qbmap[qbit] for qbit in qargs]
bits = [self.cbmap[bit] for bit in cargs]

if optype == OpType.Unitary2qBox:
u = instr.to_matrix()
ubox = Unitary2qBox(u)
# Note reversal of qubits, to account for endianness (pytket unitaries
# are ILO-BE == DLO-LE; qiskit unitaries are ILO-LE == DLO-BE).
self.tkc.add_unitary2qbox(
ubox, qubits[1], qubits[0], **condition_kwargs
)
elif optype == OpType.QControlBox:
if optype == OpType.QControlBox:
base_tket_gate = _known_qiskit_gate[type(instr.base_gate)]
params = [param_to_tk(p) for p in instr.base_gate.params]
n_base_qubits = instr.base_gate.num_qubits
Expand Down Expand Up @@ -432,6 +429,38 @@ def add_qiskit_data(self, data: "QuantumCircuitData") -> None:
circ = gen_term_sequence_circuit(qpo, empty_circ)
ccbox = CircBox(circ)
self.tkc.add_circbox(ccbox, qubits)
elif type(instr) == UnitaryGate:
# Note reversal of qubits, to account for endianness (pytket unitaries
# are ILO-BE == DLO-LE; qiskit unitaries are ILO-LE == DLO-BE).
params = instr.params
assert len(params) == 1
u = cast(np.ndarray, params[0])
assert len(cargs) == 0
n = len(qubits)
if n == 0:
assert u.shape == (1, 1)
self.tkc.add_phase(np.angle(u[0][0]) / np.pi)
elif n == 1:
assert u.shape == (2, 2)
ubox = Unitary1qBox(u)
self.tkc.add_unitary1qbox(ubox, qubits[0], **condition_kwargs)
elif n == 2:
assert u.shape == (4, 4)
ubox = Unitary2qBox(u)
self.tkc.add_unitary2qbox(
ubox, qubits[1], qubits[0], **condition_kwargs
)
elif n == 3:
assert u.shape == (8, 8)
ubox = Unitary3qBox(u)
self.tkc.add_unitary3qbox(
ubox, qubits[2], qubits[1], qubits[0], **condition_kwargs
)
else:
raise NotImplementedError(
f"Conversion of {n}-qubit unitary gates not supported"
)

elif optype == OpType.Barrier:
self.tkc.add_barrier(qubits)
elif optype in (OpType.CircBox, OpType.CustomGate):
Expand Down Expand Up @@ -572,7 +601,7 @@ def append_tk_command_to_qiskit(
else:
instruc = subqc.to_instruction()
return qcirc.append(instruc, qargs, cargs)
if optype == OpType.Unitary2qBox:
if optype in [OpType.Unitary1qBox, OpType.Unitary2qBox, OpType.Unitary3qBox]:
qargs = [qregmap[q.reg_name][q.index[0]] for q in args]
u = op.get_matrix()
g = UnitaryGate(u, label="unitary")
Expand Down Expand Up @@ -698,7 +727,11 @@ def append_tk_command_to_qiskit(
_additional_multi_controlled_gates = {OpType.CnY, OpType.CnZ, OpType.CnRy}

# tket gates which are protected from being decomposed in the rebase
_protected_tket_gates = _supported_tket_gates | _additional_multi_controlled_gates
_protected_tket_gates = (
_supported_tket_gates
| _additional_multi_controlled_gates
| {OpType.Unitary1qBox, OpType.Unitary2qBox, OpType.Unitary3qBox}
)


Param = Union[float, "sympy.Expr"] # Type for TK1 and U3 parameters
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
packages=find_namespace_packages(include=["pytket.*"]),
include_package_data=True,
install_requires=[
"pytket ~= 1.16",
"pytket ~= 1.17",
"qiskit ~= 0.43.1",
"qiskit-ibm-runtime ~= 0.11.1",
"qiskit-aer ~= 0.12.0",
Expand Down
107 changes: 107 additions & 0 deletions tests/qiskit_convert_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,14 @@
from qiskit.circuit import Parameter # type: ignore
from qiskit_aer import Aer # type: ignore
from qiskit.quantum_info import Statevector
from qiskit.extensions import UnitaryGate # type: ignore

from pytket.circuit import ( # type: ignore
Circuit,
CircBox,
Unitary1qBox,
Unitary2qBox,
Unitary3qBox,
OpType,
Qubit,
Bit,
Expand All @@ -60,6 +63,7 @@

REASON = "PYTKET_RUN_REMOTE_TESTS not set (requires IBM configuration)"


# helper function for testing
def _get_qiskit_statevector(qc: QuantumCircuit) -> np.ndarray:
"""Given a QuantumCircuit, use aer_simulator_statevector to compute its
Expand Down Expand Up @@ -229,6 +233,22 @@ def test_boxes() -> None:
assert d == d1


def test_Unitary1qBox() -> None:
c = Circuit(1)
u = np.asarray([[0, 1], [1, 0]])
ubox = Unitary1qBox(u)
c.add_unitary1qbox(ubox, 0)
# Convert to qiskit
qc = tk_to_qiskit(c)
# Verify that unitary from simulator is correct
back = Aer.get_backend("aer_simulator_unitary")
qc.save_unitary()
job = execute(qc, back).result()
a = job.get_unitary(qc)
u1 = np.asarray(a)
assert np.allclose(u1, u)


def test_Unitary2qBox() -> None:
c = Circuit(2)
u = np.asarray([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
Expand All @@ -245,6 +265,35 @@ def test_Unitary2qBox() -> None:
assert np.allclose(u1, u)


def test_Unitary3qBox() -> None:
c = Circuit(3)
u = np.asarray(
[
[0, 0, 0, 0, 0, 1, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 1],
[0, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 1, 0, 0, 0, 0],
]
)
ubox = Unitary3qBox(u)
c.add_unitary3qbox(ubox, 0, 1, 2)
# Convert to qiskit
qc = tk_to_qiskit(c)
# Verify that unitary from simulator is correct
back = Aer.get_backend("aer_simulator_unitary")
qc.save_unitary()
job = execute(qc, back).result()
a = job.get_unitary(qc)
u1 = permute_rows_cols_in_unitary(
np.asarray(a), (2, 1, 0)
) # correct for endianness
assert np.allclose(u1, u)


def test_gates_phase() -> None:
c = Circuit(4).SX(0).V(1).V(2).Vdg(3).Phase(0.5)
qc = tk_to_qiskit(c)
Expand Down Expand Up @@ -897,3 +946,61 @@ def test_conversion_to_tket_with_and_without_resets() -> None:
decomp_qc = qiskit_qc_sp.decompose(reps=5)
qiskit_state = _get_qiskit_statevector(decomp_qc)
assert compare_statevectors(tkc_sv, qiskit_state)


def test_unitary_gate() -> None:
# https://github.com/CQCL/pytket-qiskit/issues/122
qkc = QuantumCircuit(3)
for n in range(4):
u = np.eye(1 << n, dtype=complex)
gate = UnitaryGate(u)
qkc.append(gate, list(range(n)))
tkc = qiskit_to_tk(qkc)
cmds = tkc.get_commands()
assert len(cmds) == 3
assert cmds[0].op.type == OpType.Unitary1qBox
assert cmds[1].op.type == OpType.Unitary2qBox
assert cmds[2].op.type == OpType.Unitary3qBox


def test_ccz_conversion() -> None:
qc_ccz = QuantumCircuit(4)
qc_ccz.append(qiskit_gates.CCZGate(), [0, 1, 2])
qc_ccz.append(qiskit_gates.CCZGate(), [3, 1, 0])
tkc_ccz = qiskit_to_tk(qc_ccz)
assert tkc_ccz.n_gates_of_type(OpType.CnZ) == tkc_ccz.n_gates == 2
# bidirectional CnZ conversion already supported
qc_ccz2 = tk_to_qiskit(tkc_ccz)
assert qc_ccz2.count_ops()["ccz"] == 2
tkc_ccz2 = qiskit_to_tk(qc_ccz2)
assert compare_unitaries(tkc_ccz.get_unitary(), tkc_ccz2.get_unitary())


def test_csx_conversion() -> None:
qc_csx = QuantumCircuit(2)
qc_csx.append(qiskit_gates.CSXGate(), [0, 1])
qc_csx.append(qiskit_gates.CSXGate(), [1, 0])
converted_tkc = qiskit_to_tk(qc_csx)
assert converted_tkc.n_gates == 2
assert converted_tkc.n_gates_of_type(OpType.CSX) == 2
u1 = converted_tkc.get_unitary()
new_tkc_csx = Circuit(2)
new_tkc_csx.add_gate(OpType.CSX, [0, 1]).add_gate(OpType.CSX, [1, 0])
u2 = new_tkc_csx.get_unitary()
assert compare_unitaries(u1, u2)
converted_qc = tk_to_qiskit(new_tkc_csx)
assert converted_qc.count_ops()["csx"] == 2
qc_c3sx = QuantumCircuit(4)
qc_c3sx.append(qiskit_gates.C3SXGate(), [0, 1, 2, 3])
tkc_c3sx = qiskit_to_tk(qc_c3sx)
assert tkc_c3sx.n_gates == tkc_c3sx.n_gates_of_type(OpType.QControlBox) == 1


def test_CS_and_CSdg() -> None:
qiskit_qc = QuantumCircuit(2)
qiskit_qc.append(qiskit_gates.CSGate(), [0, 1])
qiskit_qc.append(qiskit_gates.CSdgGate(), [0, 1])
qiskit_qc.append(qiskit_gates.CSGate(), [1, 0])
qiskit_qc.append(qiskit_gates.CSdgGate(), [1, 0])
tkc = qiskit_to_tk(qiskit_qc)
assert tkc.n_gates_of_type(OpType.QControlBox) == 4

0 comments on commit d7acc0a

Please sign in to comment.