Skip to content

Commit

Permalink
Merge branch 'main' into Optimisation-level-3
Browse files Browse the repository at this point in the history
  • Loading branch information
sjdilkes committed Nov 25, 2024
2 parents 42d9329 + 55357b4 commit 031c601
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 8 deletions.
4 changes: 3 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@

## 0.60.0 (Unreleased)

- Revert a change made in release v0.59.0 where users are warned about implicit qubit permutations in {py:func}`tk_to_qiskit`. This avoids spamming the user with unhelpful warnings when using pytket-qiskit backends. These backends handle implicit permutations automatically.
- Fix an unhelpful warning message about implicit swaps when using optimisation level 2 with {py:class}`AerBackend`
- Add a boolean `perm_warning` argument to {py:func}`tk_to_qiskit` indicating whether to give a warning if the input {py:class}`Circuit` has an implicit qubit permutation.
- Add new level 3 optimisation that uses `GreedyPauliSimp`
- Fix `get_results()` for Aer backends when circuits with different numbers of qubits or postprocessing circuits submitted together.

## 0.59.0 (November 2024)

Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ Every {py:class}`~pytket.backends.backend.Backend` in pytket has its own {py:met
2 preserve barriers in a circuit, while optimisation level 3 will remove them.

:::{list-table} **Default compilation pass for the IBMQBackend and IBMQEmulatorBackend**
:widths: 25 25 25
:widths: 25 25 25 25
:header-rows: 1

* - optimisation_level = 0
Expand All @@ -208,7 +208,7 @@ Every {py:class}`~pytket.backends.backend.Backend` in pytket has its own {py:met
* - [AutoRebase [2]](inv:#*.AutoRebase)
- [SynthesiseTket](inv:#*.SynthesiseTket)
- [FullPeepholeOptimise](inv:#*.passes.FullPeepholeOptimise)
- [RemoveBarriers](inv:#*.passes.RmoveBarriers)
- [RemoveBarriers](inv:#*pytket._tket.passes.RemoveBarriers)
* - LightSabre [3]
- LightSabre [3]
- LightSabre [3]
Expand Down
12 changes: 10 additions & 2 deletions pytket/extensions/qiskit/backends/aer.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,12 @@ class _AerBaseBackend(Backend):
_has_arch: bool = False
_needs_transpile: bool = False

# Map from (job ID, circuit index) to (number of qubits, postprocessing circuit),
# i.e. from the first two components of the ResultHandle to the last two.
_circuit_data: dict[
tuple[int | float | complex | str | bool | bytes, int], tuple[int, str]
] = {}

@property
def required_predicates(self) -> list[Predicate]:
return self._required_predicates
Expand Down Expand Up @@ -365,7 +371,7 @@ def process_circuits(
else:
c0, ppcirc_rep = tkc, None

qc = tk_to_qiskit(c0, replace_implicit_swaps)
qc = tk_to_qiskit(c0, replace_implicit_swaps, perm_warning=False)

if self.supports_state:
qc.save_state()
Expand Down Expand Up @@ -396,6 +402,7 @@ def process_circuits(
for i, ind in enumerate(indices):
handle = ResultHandle(jobid, i, tkc_qubits_count[i], ppcirc_strs[i])
handle_list[ind] = handle
self._circuit_data[(jobid, i)] = (tkc_qubits_count[i], ppcirc_strs[i])
self._cache[handle] = {"job": job}
return cast(list[ResultHandle], handle_list)

Expand All @@ -415,7 +422,7 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul
try:
return super().get_result(handle)
except CircuitNotRunError:
jobid, _, qubit_n, ppc = handle
jobid = handle[0]
try:
job: AerJob = self._cache[handle]["job"]
except KeyError:
Expand All @@ -431,6 +438,7 @@ def get_result(self, handle: ResultHandle, **kwargs: KwargTypes) -> BackendResul
include_density_matrix=self._supports_density_matrix,
)
for circ_index, backres in enumerate(backresults):
qubit_n, ppc = self._circuit_data[(jobid, circ_index)]
self._cache[ResultHandle(jobid, circ_index, qubit_n, ppc)][
"result"
] = backres
Expand Down
27 changes: 25 additions & 2 deletions pytket/extensions/qiskit/qiskit_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

"""Methods to allow conversion between Qiskit and pytket circuit classes
"""
import warnings
from collections import defaultdict
from collections.abc import Iterable
from inspect import signature
Expand Down Expand Up @@ -861,8 +862,16 @@ def append_tk_command_to_qiskit(
supported_gate_rebase = AutoRebase(_protected_tket_gates)


def _has_implicit_permutation(circ: Circuit) -> bool:
"""Returns True if a Circuit has a non-trivial permutation
of qubits, false otherwise."""
return any(q0 != q1 for q0, q1 in circ.implicit_qubit_permutation().items())


def tk_to_qiskit(
tkcirc: Circuit, replace_implicit_swaps: bool = False
tkcirc: Circuit,
replace_implicit_swaps: bool = False,
perm_warning: bool = True,
) -> QuantumCircuit:
"""
Converts a pytket :py:class:`Circuit` to a qiskit :py:class:`qiskit.QuantumCircuit`.
Expand All @@ -871,19 +880,33 @@ def tk_to_qiskit(
If no exact replacement can be found for a part of the circuit then an equivalent
circuit will be returned using the tket gates which are supported in qiskit.
Please note that implicit swaps in a pytket Circuit are not handled by default.
Note that implicit swaps in a pytket Circuit are not handled by default.
Consider using the replace_implicit_swaps flag to replace these implicit swaps with
SWAP gates.
:param tkcirc: A :py:class:`Circuit` to be converted
:param replace_implicit_swaps: Implement implicit permutation by adding SWAPs
to the end of the circuit.
:param perm_warning: Warn if an input circuit has implicit qubit permutations,
and `replace_implicit_swaps` is `False`. True by default.
:return: The converted circuit
"""
tkc = tkcirc.copy() # Make a local copy of tkcirc
if replace_implicit_swaps:
tkc.replace_implicit_wire_swaps()

if (
_has_implicit_permutation(tkcirc)
and perm_warning
and not replace_implicit_swaps
):
warnings.warn(
"The pytket Circuit contains implicit qubit permutations"
+ " which aren't handled by default."
+ " Consider using the replace_implicit_swaps flag in tk_to_qiskit or"
+ " replacing them using Circuit.replace_implicit_swaps()."
)

qcirc = QuantumCircuit(name=tkc.name)
qreg_sizes: dict[str, int] = {}
for qb in tkc.qubits:
Expand Down
10 changes: 10 additions & 0 deletions tests/backend_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1545,3 +1545,13 @@ def test_optimisation_level_3_serialisation() -> None:
},
)
assert p_dict == passlist.to_dict()


def test_process_circuits_n_qubits() -> None:
# https://github.com/CQCL/pytket-qiskit/issues/420
circs = [Circuit(1).X(0).measure_all(), Circuit(2).X(0).measure_all()]
b = AerBackend()
hs = b.process_circuits(circs, n_shots=10)
rs = b.get_results(hs)
assert rs[0].get_counts() == Counter({(1,): 10})
assert rs[1].get_counts() == Counter({(1, 0): 10})
19 changes: 18 additions & 1 deletion tests/qiskit_convert_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import warnings
from collections import Counter
from math import pi

Expand Down Expand Up @@ -62,7 +63,10 @@
reg_eq,
)
from pytket.extensions.qiskit import IBMQBackend, qiskit_to_tk, tk_to_qiskit
from pytket.extensions.qiskit.backends import qiskit_aer_backend
from pytket.extensions.qiskit.backends import (
AerBackend,
qiskit_aer_backend,
)
from pytket.extensions.qiskit.qiskit_convert import _gate_str_2_optype
from pytket.extensions.qiskit.result_convert import qiskit_result_to_backendresult
from pytket.extensions.qiskit.tket_pass import TketAutoPass, TketPass
Expand Down Expand Up @@ -1166,6 +1170,19 @@ def test_symbolic_param_conv() -> None:
)


def test_implicit_swap_warning() -> None:
c = Circuit(2).H(0).SWAP(0, 1)
c.replace_SWAPs()
c.measure_all()
with pytest.warns(UserWarning, match="The pytket Circuit contains implicit qubit"):
tk_to_qiskit(c)

shots_backend = AerBackend()
with warnings.catch_warnings():
warnings.simplefilter("error")
shots_backend.run_circuit(c)


# https://github.com/CQCL/pytket-qiskit/issues/337
def test_nonregister_bits() -> None:
c = Circuit(1).X(0).measure_all()
Expand Down

0 comments on commit 031c601

Please sign in to comment.