From c007b96266830cd26d3d7666481366272090660c Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:08:32 +0000 Subject: [PATCH 01/17] add optimisation level 3 --- pytket/extensions/qiskit/backends/aer.py | 47 +++++++++++++++++++----- pytket/extensions/qiskit/backends/ibm.py | 24 ++++++++++-- 2 files changed, 57 insertions(+), 14 deletions(-) diff --git a/pytket/extensions/qiskit/backends/aer.py b/pytket/extensions/qiskit/backends/aer.py index 68807957..3f4054c4 100644 --- a/pytket/extensions/qiskit/backends/aer.py +++ b/pytket/extensions/qiskit/backends/aer.py @@ -163,8 +163,9 @@ def _arch_dependent_default_compilation_pass( self, arch: Architecture, optimisation_level: int = 2, + timeout: int = 300, ) -> BasePass: - assert optimisation_level in range(3) + assert optimisation_level in range(4) arch_specific_passes = [ AutoRebase({OpType.CX, OpType.TK1}), CustomPass(_gen_lightsabre_transformation(arch, optimisation_level)), @@ -190,30 +191,53 @@ def _arch_dependent_default_compilation_pass( ], False, ) + if optimisation_level == 2: + return SequencePass( + [ + DecomposeBoxes(), + FullPeepholeOptimise(), + *arch_specific_passes, + CliffordSimp(False), + SynthesiseTket(), + ], + False, + ) return SequencePass( [ - DecomposeBoxes(), - FullPeepholeOptimise(), + DecomposesBoxes(), + RemoveBarriers(), + AutoRebase({OpType.CX, OpType.H, OpType.Rz}), + GreedyPauliSimp(thread_timeout=timeout, no_reduce=True, trials=10), *arch_specific_passes, - CliffordSimp(False), SynthesiseTket(), - ], - False, + False, + ] ) def _arch_independent_default_compilation_pass( - self, optimisation_level: int = 2 + self, optimisation_level: int = 2, timeout: int = 300, ) -> BasePass: - assert optimisation_level in range(3) + assert optimisation_level in range(4) if optimisation_level == 0: return SequencePass([DecomposeBoxes(), self.rebase_pass()]) if optimisation_level == 1: return SequencePass([DecomposeBoxes(), SynthesiseTket()]) - return SequencePass([DecomposeBoxes(), FullPeepholeOptimise()]) + if optimisation_level == 2: + return SequencePass([DecomposeBoxes(), FullPeepholeOptimise()]) + if optimisation_level == 3: + return SequencePass( + [ + DecomposeBoxes(), + RemoveBarriers(), + AutoRebase({OpType.CX, OpType.H, OpType.Rz}), + GreedyPauliSimp(thread_timeout=timeout, no_reduce=True, trials=10), + ] + ) def default_compilation_pass( self, optimisation_level: int = 2, + timeout: int = 300, ) -> BasePass: """ See documentation for :py:meth:`IBMQBackend.default_compilation_pass`. @@ -227,9 +251,12 @@ def default_compilation_pass( return self._arch_dependent_default_compilation_pass( arch, # type: ignore optimisation_level, + timeout, ) - return self._arch_independent_default_compilation_pass(optimisation_level) + return self._arch_independent_default_compilation_pass( + optimisation_level, timeout + ) def process_circuits( self, diff --git a/pytket/extensions/qiskit/backends/ibm.py b/pytket/extensions/qiskit/backends/ibm.py index 83b8f5e9..1b6c6516 100644 --- a/pytket/extensions/qiskit/backends/ibm.py +++ b/pytket/extensions/qiskit/backends/ibm.py @@ -56,6 +56,7 @@ FullPeepholeOptimise, KAKDecomposition, NaivePlacementPass, + RemoveBarriers, RemoveRedundancies, SequencePass, SimplifyInitial, @@ -344,6 +345,7 @@ def required_predicates(self) -> list[Predicate]: def default_compilation_pass( self, optimisation_level: int = 2, + timeout: int = 300, ) -> BasePass: """ A suggested compilation pass that will will, if possible, produce an equivalent @@ -360,12 +362,15 @@ def default_compilation_pass( :param optimisation_level: The level of optimisation to perform during compilation. + :param timeout: Parameter for optimisation level 3, given in seconds. - Level 0 does the minimum required to solves the device constraints, without any optimisation. - Level 1 additionally performs some light optimisations. - Level 2 (the default) adds more computationally intensive optimisations that should give the best results from execution. + - Level 3 re-synthesises the circuit using the computationally intensive + `GreedyPauliSimp`. This will remove any barriers while optimising. :return: Compilation pass guaranteeing required predicates. @@ -373,7 +378,7 @@ def default_compilation_pass( config: PulseBackendConfiguration = self._backend.configuration() props: Optional[BackendProperties] = self._backend.properties() return IBMQBackend.default_compilation_pass_offline( - config, props, optimisation_level + config, props, optimisation_level, timeout ) @staticmethod @@ -381,12 +386,13 @@ def default_compilation_pass_offline( config: PulseBackendConfiguration, props: Optional[BackendProperties], optimisation_level: int = 2, + timeout: int = 300, ) -> BasePass: backend_info = IBMQBackend._get_backend_info(config, props) primitive_gates = _get_primitive_gates(_tk_gate_set(config)) supports_rz = OpType.Rz in primitive_gates - assert optimisation_level in range(3) + assert optimisation_level in range(4) passlist = [DecomposeBoxes()] # If you make changes to the default_compilation_pass, # then please update this page accordingly @@ -402,12 +408,21 @@ def default_compilation_pass_offline( passlist.append(SynthesiseTket()) elif optimisation_level == 2: passlist.append(FullPeepholeOptimise()) + elif optimisation_level == 3: + passlist.append(RemoveBarriers()) + passlist.append(AutoRebase({OpType.CX, OpType.H, OpType.Rz}).apply(c)) + passlist.append( + GreedyPauliSimp(thread_timeout=timeout, no_reduce=True, trials=10) + ) arch = backend_info.architecture assert arch is not None if not isinstance(arch, FullyConnected): passlist.append(AutoRebase(primitive_gates)) passlist.append( - CustomPass(_gen_lightsabre_transformation(arch, optimisation_level)) + CustomPass( + _gen_lightsabre_transformation(arch, optimisation_level), + "lightsabre", + ) ) passlist.append(NaivePlacementPass(arch)) if optimisation_level == 1: @@ -420,7 +435,8 @@ def default_compilation_pass_offline( SynthesiseTket(), ] ) - + if optimisation_level == 3: + passlist.append(SynthesiseTket()) passlist.extend( [IBMQBackend.rebase_pass_offline(primitive_gates), RemoveRedundancies()] ) From b96a6a8504f619f7b5426e0b4b9f219c62132d73 Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:02:48 +0000 Subject: [PATCH 02/17] add tests, fix new pass addition, remove strict false --- pytket/extensions/qiskit/backends/aer.py | 33 ++++++------- pytket/extensions/qiskit/backends/ibm.py | 6 +-- .../extensions/qiskit/backends/ibm_utils.py | 5 +- tests/backend_test.py | 47 ++++++++++++++++++- 4 files changed, 64 insertions(+), 27 deletions(-) diff --git a/pytket/extensions/qiskit/backends/aer.py b/pytket/extensions/qiskit/backends/aer.py index 3f4054c4..090448e4 100644 --- a/pytket/extensions/qiskit/backends/aer.py +++ b/pytket/extensions/qiskit/backends/aer.py @@ -38,7 +38,8 @@ CustomPass, DecomposeBoxes, FullPeepholeOptimise, - NaivePlacementPass, + GreedyPauliSimp, + RemoveBarriers, SequencePass, SynthesiseTket, ) @@ -168,8 +169,7 @@ def _arch_dependent_default_compilation_pass( assert optimisation_level in range(4) arch_specific_passes = [ AutoRebase({OpType.CX, OpType.TK1}), - CustomPass(_gen_lightsabre_transformation(arch, optimisation_level)), - NaivePlacementPass(arch), + CustomPass(_gen_lightsabre_transformation(arch), label="lightsabrepass"), ] if optimisation_level == 0: return SequencePass( @@ -179,7 +179,6 @@ def _arch_dependent_default_compilation_pass( *arch_specific_passes, self.rebase_pass(), ], - False, ) if optimisation_level == 1: return SequencePass( @@ -189,7 +188,6 @@ def _arch_dependent_default_compilation_pass( *arch_specific_passes, SynthesiseTket(), ], - False, ) if optimisation_level == 2: return SequencePass( @@ -200,22 +198,22 @@ def _arch_dependent_default_compilation_pass( CliffordSimp(False), SynthesiseTket(), ], - False, ) return SequencePass( [ - DecomposesBoxes(), + DecomposeBoxes(), RemoveBarriers(), AutoRebase({OpType.CX, OpType.H, OpType.Rz}), - GreedyPauliSimp(thread_timeout=timeout, no_reduce=True, trials=10), + GreedyPauliSimp(thread_timeout=timeout, only_reduce=True, trials=10), *arch_specific_passes, SynthesiseTket(), - False, - ] + ], ) def _arch_independent_default_compilation_pass( - self, optimisation_level: int = 2, timeout: int = 300, + self, + optimisation_level: int = 2, + timeout: int = 300, ) -> BasePass: assert optimisation_level in range(4) if optimisation_level == 0: @@ -230,8 +228,10 @@ def _arch_independent_default_compilation_pass( DecomposeBoxes(), RemoveBarriers(), AutoRebase({OpType.CX, OpType.H, OpType.Rz}), - GreedyPauliSimp(thread_timeout=timeout, no_reduce=True, trials=10), - ] + GreedyPauliSimp( + thread_timeout=timeout, only_reduce=True, trials=10 + ), + ], ) def default_compilation_pass( @@ -243,17 +243,12 @@ def default_compilation_pass( See documentation for :py:meth:`IBMQBackend.default_compilation_pass`. """ arch = self._backend_info.architecture - if ( - self._has_arch - and arch.coupling # type: ignore - and self._backend_info.get_misc("characterisation") - ): + if self._has_arch and arch.coupling: # type: ignore return self._arch_dependent_default_compilation_pass( arch, # type: ignore optimisation_level, timeout, ) - return self._arch_independent_default_compilation_pass( optimisation_level, timeout ) diff --git a/pytket/extensions/qiskit/backends/ibm.py b/pytket/extensions/qiskit/backends/ibm.py index 1b6c6516..41ca7ddb 100644 --- a/pytket/extensions/qiskit/backends/ibm.py +++ b/pytket/extensions/qiskit/backends/ibm.py @@ -55,7 +55,6 @@ DecomposeBoxes, FullPeepholeOptimise, KAKDecomposition, - NaivePlacementPass, RemoveBarriers, RemoveRedundancies, SequencePass, @@ -412,7 +411,7 @@ def default_compilation_pass_offline( passlist.append(RemoveBarriers()) passlist.append(AutoRebase({OpType.CX, OpType.H, OpType.Rz}).apply(c)) passlist.append( - GreedyPauliSimp(thread_timeout=timeout, no_reduce=True, trials=10) + GreedyPauliSimp(thread_timeout=timeout, only_reduce=True, trials=10) ) arch = backend_info.architecture assert arch is not None @@ -424,7 +423,6 @@ def default_compilation_pass_offline( "lightsabre", ) ) - passlist.append(NaivePlacementPass(arch)) if optimisation_level == 1: passlist.append(SynthesiseTket()) if optimisation_level == 2: @@ -440,7 +438,7 @@ def default_compilation_pass_offline( passlist.extend( [IBMQBackend.rebase_pass_offline(primitive_gates), RemoveRedundancies()] ) - return SequencePass(passlist, False) + return SequencePass(passlist) @property def _result_id_type(self) -> _ResultIdTuple: diff --git a/pytket/extensions/qiskit/backends/ibm_utils.py b/pytket/extensions/qiskit/backends/ibm_utils.py index 5d2850d8..4c63fcfb 100644 --- a/pytket/extensions/qiskit/backends/ibm_utils.py +++ b/pytket/extensions/qiskit/backends/ibm_utils.py @@ -103,14 +103,13 @@ def _architecture_to_couplingmap(architecture: Architecture) -> CouplingMap: def _gen_lightsabre_transformation( # type: ignore - architecture: Architecture, optimization_level: int = 2, seed=0, attempts=20 + architecture: Architecture, seed=0, attempts=20 ) -> Callable[[Circuit], Circuit]: """ Generates a function that can be passed to CustomPass for running LightSABRE routing. :param architecture: Architecture LightSABRE routes circuits to match - :param optimization_level: Corresponds to qiskit optmization levels :param seed: LightSABRE routing is stochastic, with this parameter setting the seed :param attempts: Number of generated random solutions to pick from. :return: A function that accepts a pytket Circuit and returns a new Circuit that @@ -147,7 +146,7 @@ def _gen_lightsabre_transformation( # type: ignore ) def lightsabre(circuit: Circuit) -> Circuit: - c: Circuit = qiskit_to_tk(sabre_pass.run(tk_to_qiskit(circuit))) + c: Circuit = qiskit_to_tk(sabre_pass.run(tk_to_qiskit(circuit, replace_implicit_swaps = True))) c.remove_blank_wires() c.rename_units({q: Node(q.index[0]) for q in c.qubits}) RebaseTket().apply(c) diff --git a/tests/backend_test.py b/tests/backend_test.py index 8dc4104c..ab09ce20 100644 --- a/tests/backend_test.py +++ b/tests/backend_test.py @@ -59,13 +59,14 @@ qiskit_to_tk, tk_to_qiskit, ) +from pytket.extensions.qiskit.backends.ibm_utils import _gen_lightsabre_transformation from pytket.extensions.qiskit.backends.crosstalk_model import ( CrosstalkParams, FractionalUnitary, NoisyCircuitBuilder, ) from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager -from pytket.passes import CliffordSimp, FlattenRelabelRegistersPass +from pytket.passes import CliffordSimp, FlattenRelabelRegistersPass, SequencePass from pytket.pauli import Pauli, QubitPauliString from pytket.predicates import ( CompilationUnit, @@ -1496,3 +1497,47 @@ def test_mc_gate_on_aer() -> None: c.measure_all() r = b.run_circuit(c, n_shots=10) assert r.get_counts() == Counter({(1, 1, 1): 10}) + + +def test_optimisation_level_3_compilation() -> None: + b = AerBackend() + a = Architecture([(0, 1), (0, 2), (0, 3), (3, 4), (4, 5), (4, 6), (4, 7)]) + b._has_arch = True + b._backend_info.architecture = a + + c = Circuit(6) + for _ in range(6): + for i in range(4): + for j in range(i + 1, 4): + c.CX(i, j) + c.Rz(0.23, j) + c.S(j) + c.H(i) + + compiled_2 = b.get_compiled_circuit(c, 2) + compiled_3 = b.get_compiled_circuit(c, 3) + + assert compiled_2.n_2qb_gates() == 78 + assert compiled_2.n_gates == 205 + assert compiled_2.depth() == 147 + assert compiled_3.n_2qb_gates() == 61 + assert compiled_3.n_gates == 164 + assert compiled_3.depth() == 114 + + +def test_optimisation_level_3_serialisation() -> None: + b = AerBackend() + a = Architecture([(0, 1), (0, 2), (0, 3), (3, 4), (4, 5), (4, 6), (4, 7)]) + b._has_arch = True + b._backend_info.architecture = a + + p_dict = b.default_compilation_pass(3).to_dict() + passlist = SequencePass.from_dict( + p_dict, + { + "lightsabrepass": _gen_lightsabre_transformation( + b._backend_info.architecture + ) + }, + ) + assert p_dict == passlist.to_dict() From 4445109aac2d1fb03238ff03ef3a0d6578efff4b Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:05:38 +0000 Subject: [PATCH 03/17] Update ibm_utils.py --- pytket/extensions/qiskit/backends/ibm_utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pytket/extensions/qiskit/backends/ibm_utils.py b/pytket/extensions/qiskit/backends/ibm_utils.py index 4c63fcfb..046d6092 100644 --- a/pytket/extensions/qiskit/backends/ibm_utils.py +++ b/pytket/extensions/qiskit/backends/ibm_utils.py @@ -146,7 +146,9 @@ def _gen_lightsabre_transformation( # type: ignore ) def lightsabre(circuit: Circuit) -> Circuit: - c: Circuit = qiskit_to_tk(sabre_pass.run(tk_to_qiskit(circuit, replace_implicit_swaps = True))) + c: Circuit = qiskit_to_tk( + sabre_pass.run(tk_to_qiskit(circuit, replace_implicit_swaps=True)) + ) c.remove_blank_wires() c.rename_units({q: Node(q.index[0]) for q in c.qubits}) RebaseTket().apply(c) From 75512135953d2311ce3e18eb2f2abc5224aa5540 Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:09:26 +0000 Subject: [PATCH 04/17] update chagelog and docs --- docs/changelog.md | 1 + docs/index.md | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index e6529ed1..bd6a54a6 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -10,6 +10,7 @@ - Update default compilation to use `Qiskit` `SabreLayoutPassManager` as a `CustomPass`. - Fix handling of non-default registers when selecting bits in results. - The {py:func}`tk_to_qiskit` converter gives a warning if the input {py:class}`~pytket.circuit.Circuit` contains [implicit qubit permutations](https://docs.quantinuum.com/tket/user-guide/manual/manual_circuit.html#implicit-qubit-permutations). +- Add new level 3 optimisation that uses `GreedyPauliSimp` ## 0.58.0 (October 2024) diff --git a/docs/index.md b/docs/index.md index 846f6fb6..0da12398 100644 --- a/docs/index.md +++ b/docs/index.md @@ -199,30 +199,39 @@ Every {py:class}`~pytket.backends.backend.Backend` in pytket has its own {py:met * - optimisation_level = 0 - optimisation_level = 1 - optimisation_level = 2 [1] + - optimisation_level = 3 * - [DecomposeBoxes](inv:#*.passes.DecomposeBoxes) - [DecomposeBoxes](inv:#*.passes.DecomposeBoxes) - [DecomposeBoxes](inv:#*.passes.DecomposeBoxes) + - [DecomposeBoxes](inv:#*.passes.DecomposeBoxes) * - [AutoRebase [2]](inv:#*.AutoRebase) - [SynthesiseTket](inv:#*.SynthesiseTket) - [FullPeepholeOptimise](inv:#*.passes.FullPeepholeOptimise) + - [AutoRebase [2]](inv:#*.AutoRebase) * - LightSabre [3] - LightSabre [3] - LightSabre [3] + - GreedyPauliSimp * - [AutoRebase [2]](inv:#*.AutoRebase) - [SynthesiseTket](inv:#*.SynthesiseTket) - [KAKDecomposition(allow_swaps=False)](inv:#*.passes.KAKDecomposition) + - LightSabre [3] * - [RemoveRedundancies](inv:#*.passes.RemoveRedundancies) - [AutoRebase [2]](inv:#*.AutoRebase) - [CliffordSimp(allow_swaps=False)](inv:#*.passes.CliffordSimp) + - [SynthesiseTket](inv:#*.SynthesiseTket) * - - [RemoveRedundancies](inv:#*.passes.RemoveRedundancies) - [SynthesiseTket](inv:#*.SynthesiseTket) + - [AutoRebase [2]](inv:#*.AutoRebase) * - - - [AutoRebase [2]](inv:#*.AutoRebase) + - * - - - [RemoveRedundancies](inv:#*.passes.RemoveRedundancies) + - [RemoveRedundancies](inv:#*.passes.RemoveRedundancies) ::: From c6b4143c80c01dc7aace85c5d9cbe6b2a030003b Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:13:00 +0000 Subject: [PATCH 05/17] Update ibm.py --- pytket/extensions/qiskit/backends/ibm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytket/extensions/qiskit/backends/ibm.py b/pytket/extensions/qiskit/backends/ibm.py index 41ca7ddb..f97313f1 100644 --- a/pytket/extensions/qiskit/backends/ibm.py +++ b/pytket/extensions/qiskit/backends/ibm.py @@ -54,6 +54,7 @@ CustomPass, DecomposeBoxes, FullPeepholeOptimise, + GreedyPauliSimp, KAKDecomposition, RemoveBarriers, RemoveRedundancies, @@ -409,7 +410,7 @@ def default_compilation_pass_offline( passlist.append(FullPeepholeOptimise()) elif optimisation_level == 3: passlist.append(RemoveBarriers()) - passlist.append(AutoRebase({OpType.CX, OpType.H, OpType.Rz}).apply(c)) + passlist.append(AutoRebase({OpType.CX, OpType.H, OpType.Rz})) passlist.append( GreedyPauliSimp(thread_timeout=timeout, only_reduce=True, trials=10) ) From dd7041653ca780ad4aa61c75f0c946ec562f2f6f Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:15:30 +0000 Subject: [PATCH 06/17] Update backend_test.py --- tests/backend_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/backend_test.py b/tests/backend_test.py index ab09ce20..d1d47e72 100644 --- a/tests/backend_test.py +++ b/tests/backend_test.py @@ -59,12 +59,12 @@ qiskit_to_tk, tk_to_qiskit, ) -from pytket.extensions.qiskit.backends.ibm_utils import _gen_lightsabre_transformation from pytket.extensions.qiskit.backends.crosstalk_model import ( CrosstalkParams, FractionalUnitary, NoisyCircuitBuilder, ) +from pytket.extensions.qiskit.backends.ibm_utils import _gen_lightsabre_transformation from pytket.mapping import LexiLabellingMethod, LexiRouteRoutingMethod, MappingManager from pytket.passes import CliffordSimp, FlattenRelabelRegistersPass, SequencePass from pytket.pauli import Pauli, QubitPauliString From b5842ee7aa13c591b8c23c3bce04409bf1ef9209 Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:06:22 +0000 Subject: [PATCH 07/17] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fe39443d..b3e7a71d 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ packages=find_namespace_packages(include=["pytket.*"]), include_package_data=True, install_requires=[ - "pytket >= 1.34.0", + "pytket == 1.35.0rc2", "qiskit >= 1.2.4", "qiskit-ibm-runtime >= 0.30.0", "qiskit-aer >= 0.15.1", From ec53bbb0029467713ba95224324e569e5a44d3e0 Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:14:17 +0000 Subject: [PATCH 08/17] Update aer.py --- pytket/extensions/qiskit/backends/aer.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/pytket/extensions/qiskit/backends/aer.py b/pytket/extensions/qiskit/backends/aer.py index 090448e4..5766377d 100644 --- a/pytket/extensions/qiskit/backends/aer.py +++ b/pytket/extensions/qiskit/backends/aer.py @@ -222,17 +222,16 @@ def _arch_independent_default_compilation_pass( return SequencePass([DecomposeBoxes(), SynthesiseTket()]) if optimisation_level == 2: return SequencePass([DecomposeBoxes(), FullPeepholeOptimise()]) - if optimisation_level == 3: - return SequencePass( - [ - DecomposeBoxes(), - RemoveBarriers(), - AutoRebase({OpType.CX, OpType.H, OpType.Rz}), - GreedyPauliSimp( - thread_timeout=timeout, only_reduce=True, trials=10 - ), - ], - ) + return SequencePass( + [ + DecomposeBoxes(), + RemoveBarriers(), + AutoRebase({OpType.CX, OpType.H, OpType.Rz}), + GreedyPauliSimp( + thread_timeout=timeout, only_reduce=True, trials=10 + ), + ], + ) def default_compilation_pass( self, From 58a24e772f4647641d78467c18a8e956eb014df9 Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:20:10 +0000 Subject: [PATCH 09/17] Update aer.py --- pytket/extensions/qiskit/backends/aer.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pytket/extensions/qiskit/backends/aer.py b/pytket/extensions/qiskit/backends/aer.py index 5766377d..61337201 100644 --- a/pytket/extensions/qiskit/backends/aer.py +++ b/pytket/extensions/qiskit/backends/aer.py @@ -227,9 +227,7 @@ def _arch_independent_default_compilation_pass( DecomposeBoxes(), RemoveBarriers(), AutoRebase({OpType.CX, OpType.H, OpType.Rz}), - GreedyPauliSimp( - thread_timeout=timeout, only_reduce=True, trials=10 - ), + GreedyPauliSimp(thread_timeout=timeout, only_reduce=True, trials=10), ], ) From 8ff847f3478365ed2a0ce505af19f01036073bac Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:11:37 +0000 Subject: [PATCH 10/17] Update index.md --- docs/index.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/index.md b/docs/index.md index 0da12398..59d1dece 100644 --- a/docs/index.md +++ b/docs/index.md @@ -207,30 +207,35 @@ 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) - - [AutoRebase [2]](inv:#*.AutoRebase) + - [RemoveBarriers](inv:#*.passes.RmoveBarriers) * - LightSabre [3] - LightSabre [3] - LightSabre [3] - - GreedyPauliSimp + - [AutoRebase [2]](inv:#*.AutoRebase) * - [AutoRebase [2]](inv:#*.AutoRebase) - [SynthesiseTket](inv:#*.SynthesiseTket) - [KAKDecomposition(allow_swaps=False)](inv:#*.passes.KAKDecomposition) - - LightSabre [3] + - [GreedyPauliSimp](inv:#*.passes.GreedyPauliSimp) * - [RemoveRedundancies](inv:#*.passes.RemoveRedundancies) - [AutoRebase [2]](inv:#*.AutoRebase) - [CliffordSimp(allow_swaps=False)](inv:#*.passes.CliffordSimp) - - [SynthesiseTket](inv:#*.SynthesiseTket) + - [AutoRebase [2]](inv:#*.AutoRebase) * - - [RemoveRedundancies](inv:#*.passes.RemoveRedundancies) - [SynthesiseTket](inv:#*.SynthesiseTket) - - [AutoRebase [2]](inv:#*.AutoRebase) + - LightSabre [3] * - - - [AutoRebase [2]](inv:#*.AutoRebase) - - + - [SynthesiseTket](inv:#*.SynthesiseTket) * - - - [RemoveRedundancies](inv:#*.passes.RemoveRedundancies) + - [AutoRebase [2]](inv:#*.AutoRebase) + +* - + - + - - [RemoveRedundancies](inv:#*.passes.RemoveRedundancies) ::: From 4c408eb9131b0d2ec1293161bc2160fe8f124083 Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:34:57 +0000 Subject: [PATCH 11/17] add note about barriers, address comments --- docs/changelog.md | 4 +++- docs/index.md | 3 ++- setup.py | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 90b1f077..f877868a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,9 @@ # Changelog +## 0.60.0 (November 2024) +- Add new level 3 optimisation that uses `GreedyPauliSimp` + ## 0.59.0 (November 2024) - Updated pytket version requirement to 1.34.0. @@ -11,7 +14,6 @@ - Update default compilation to use `Qiskit` `SabreLayoutPassManager` as a `CustomPass`. - Fix handling of non-default registers when selecting bits in results. - The {py:func}`tk_to_qiskit` converter gives a warning if the input {py:class}`~pytket.circuit.Circuit` contains [implicit qubit permutations](https://docs.quantinuum.com/tket/user-guide/manual/manual_circuit.html#implicit-qubit-permutations). -- Add new level 3 optimisation that uses `GreedyPauliSimp` ## 0.58.0 (October 2024) diff --git a/docs/index.md b/docs/index.md index 59d1dece..61058a88 100644 --- a/docs/index.md +++ b/docs/index.md @@ -190,7 +190,8 @@ For instance those familiar with qiskit may wish to convert their circuits to py ## Default Compilation -Every {py:class}`~pytket.backends.backend.Backend` in pytket has its own {py:meth}`~pytket.backends.Backend.default_compilation_pass` method. This method applies a sequence of optimisations to a circuit depending on the value of an `optimisation_level` parameter. This default compilation will ensure that the circuit meets all the constraints required to run on the {py:class}`~pytket.backends.backend.Backend`. The passes applied by different levels of optimisation are specified in the table below. +Every {py:class}`~pytket.backends.backend.Backend` in pytket has its own {py:meth}`~pytket.backends.Backend.default_compilation_pass` method. This method applies a sequence of optimisations to a circuit depending on the value of an `optimisation_level` parameter. This default compilation will ensure that the circuit meets all the constraints required to run on the {py:class}`~pytket.backends.backend.Backend`. The passes applied by different levels of optimisation are specified in the table below. Note that optimisation levels 0, 1 and +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 diff --git a/setup.py b/setup.py index b3e7a71d..32e713fb 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ packages=find_namespace_packages(include=["pytket.*"]), include_package_data=True, install_requires=[ - "pytket == 1.35.0rc2", + "pytket >= 1.35.0", "qiskit >= 1.2.4", "qiskit-ibm-runtime >= 0.30.0", "qiskit-aer >= 0.15.1", From b355f55be4578f0bcf840c49df595e324b945f1b Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:13:04 +0000 Subject: [PATCH 12/17] allow setting of timeout when using `get_compiled_circuit` --- pytket/extensions/qiskit/backends/aer.py | 69 ++++++++++++++++++++++++ pytket/extensions/qiskit/backends/ibm.py | 69 ++++++++++++++++++++++++ 2 files changed, 138 insertions(+) diff --git a/pytket/extensions/qiskit/backends/aer.py b/pytket/extensions/qiskit/backends/aer.py index 61337201..7e46a4de 100644 --- a/pytket/extensions/qiskit/backends/aer.py +++ b/pytket/extensions/qiskit/backends/aer.py @@ -250,6 +250,75 @@ def default_compilation_pass( optimisation_level, timeout ) + def get_compiled_circuit( + self, circuit: Circuit, optimisation_level: int = 2, timeout: int = 300 + ) -> Circuit: + """ + Return a single circuit compiled with :py:meth:`default_compilation_pass`. + + :param optimisation_level: Allows values of 0, 1, 2 or 3, with higher values + prompting more computationally heavy optimising compilation that + can lead to reduced gate count in circuits. + :type optimisation_level: int, optional + :param timeout: Only valid for optimisation level 3, gives a maximimum time + for running a single thread of the pass `GreedyPauliSimp`. Increase for + optimising larger circuits. + :type timeout: int, optional + + :return: An optimised quantum circuit + :rtype: Circuit + """ + return_circuit = circuit.copy() + if optimisation_level == 3 and circuit.n_gates_of_type(OpType.Barrier) > 0: + warnings.warn( + "Barrier operations in this circuit will be removed when using " + "optimisation level 3." + ) + self.default_compilation_pass(optimisation_level, timeout).apply(return_circuit) + return return_circuit + + def get_compiled_circuits( + self, + circuits: Sequence[Circuit], + optimisation_level: int = 2, + timeout: int = 300, + ) -> list[Circuit]: + """Compile a sequence of circuits with :py:meth:`default_compilation_pass` + and return the list of compiled circuits (does not act in place). + + As well as applying a degree of optimisation (controlled by the + `optimisation_level` parameter), this method tries to ensure that the circuits + can be run on the backend (i.e. successfully passed to + :py:meth:`process_circuits`), for example by rebasing to the supported gate set, + or routing to match the connectivity of the device. However, this is not always + possible, for example if the circuit contains classical operations that are not + supported by the backend. You may use :py:meth:`valid_circuit` to check whether + the circuit meets the backend's requirements after compilation. This validity + check is included in :py:meth:`process_circuits` by default, before any circuits + are submitted to the backend. + + If the validity check fails, you can obtain more information about the failure + by iterating through the predicates in the `required_predicates` property of the + backend, and running the :py:meth:`verify` method on each in turn with your + circuit. + + :param circuits: The circuits to compile. + :type circuit: Sequence[Circuit] + :param optimisation_level: The level of optimisation to perform during + compilation. See :py:meth:`default_compilation_pass` for a description of + the different levels (0, 1, 2 or 3). Defaults to 2. + :type optimisation_level: int, optional + :param timeout: Only valid for optimisation level 3, gives a maximimum time + for running a single thread of the pass `GreedyPauliSimp`. Increase for + optimising larger circuits. + :type timeout: int, optional + :return: Compiled circuits. + :rtype: List[Circuit] + """ + return [ + self.get_compiled_circuit(c, optimisation_level, timeout) for c in circuits + ] + def process_circuits( self, circuits: Sequence[Circuit], diff --git a/pytket/extensions/qiskit/backends/ibm.py b/pytket/extensions/qiskit/backends/ibm.py index f97313f1..fda58695 100644 --- a/pytket/extensions/qiskit/backends/ibm.py +++ b/pytket/extensions/qiskit/backends/ibm.py @@ -441,6 +441,75 @@ def default_compilation_pass_offline( ) return SequencePass(passlist) + def get_compiled_circuit( + self, circuit: Circuit, optimisation_level: int = 2, timeout: int = 300 + ) -> Circuit: + """ + Return a single circuit compiled with :py:meth:`default_compilation_pass`. + + :param optimisation_level: Allows values of 0, 1, 2 or 3, with higher values + prompting more computationally heavy optimising compilation that + can lead to reduced gate count in circuits. + :type optimisation_level: int, optional + :param timeout: Only valid for optimisation level 3, gives a maximimum time + for running a single thread of the pass `GreedyPauliSimp`. Increase for + optimising larger circuits. + :type timeout: int, optional + + :return: An optimised quantum circuit + :rtype: Circuit + """ + return_circuit = circuit.copy() + if optimisation_level == 3 and circuit.n_gates_of_type(OpType.Barrier) > 0: + warnings.warn( + "Barrier operations in this circuit will be removed when using " + "optimisation level 3." + ) + self.default_compilation_pass(optimisation_level, timeout).apply(return_circuit) + return return_circuit + + def get_compiled_circuits( + self, + circuits: Sequence[Circuit], + optimisation_level: int = 2, + timeout: int = 300, + ) -> list[Circuit]: + """Compile a sequence of circuits with :py:meth:`default_compilation_pass` + and return the list of compiled circuits (does not act in place). + + As well as applying a degree of optimisation (controlled by the + `optimisation_level` parameter), this method tries to ensure that the circuits + can be run on the backend (i.e. successfully passed to + :py:meth:`process_circuits`), for example by rebasing to the supported gate set, + or routing to match the connectivity of the device. However, this is not always + possible, for example if the circuit contains classical operations that are not + supported by the backend. You may use :py:meth:`valid_circuit` to check whether + the circuit meets the backend's requirements after compilation. This validity + check is included in :py:meth:`process_circuits` by default, before any circuits + are submitted to the backend. + + If the validity check fails, you can obtain more information about the failure + by iterating through the predicates in the `required_predicates` property of the + backend, and running the :py:meth:`verify` method on each in turn with your + circuit. + + :param circuits: The circuits to compile. + :type circuit: Sequence[Circuit] + :param optimisation_level: The level of optimisation to perform during + compilation. See :py:meth:`default_compilation_pass` for a description of + the different levels (0, 1, 2 or 3). Defaults to 2. + :type optimisation_level: int, optional + :param timeout: Only valid for optimisation level 3, gives a maximimum time + for running a single thread of the pass `GreedyPauliSimp`. Increase for + optimising larger circuits. + :type timeout: int, optional + :return: Compiled circuits. + :rtype: List[Circuit] + """ + return [ + self.get_compiled_circuit(c, optimisation_level, timeout) for c in circuits + ] + @property def _result_id_type(self) -> _ResultIdTuple: # IBMQ job ID, index, number of bits, post-processing circuit From 42d9329c7f07208bb5e6e8c9aa72a73e6e08278a Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Mon, 25 Nov 2024 09:10:45 +0000 Subject: [PATCH 13/17] Update backend_test.py --- tests/backend_test.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/backend_test.py b/tests/backend_test.py index d1d47e72..f73b825a 100644 --- a/tests/backend_test.py +++ b/tests/backend_test.py @@ -1516,6 +1516,7 @@ def test_optimisation_level_3_compilation() -> None: compiled_2 = b.get_compiled_circuit(c, 2) compiled_3 = b.get_compiled_circuit(c, 3) + compiled_3_timeout = b.get_compiled_circuit(c, 3, timeout=0) assert compiled_2.n_2qb_gates() == 78 assert compiled_2.n_gates == 205 @@ -1523,6 +1524,9 @@ def test_optimisation_level_3_compilation() -> None: assert compiled_3.n_2qb_gates() == 61 assert compiled_3.n_gates == 164 assert compiled_3.depth() == 114 + assert compiled_3_timeout.n_2qb_gates() == 69 + assert compiled_3_timeout.n_gates == 171 + assert compiled_3_timeout.depth() == 125 def test_optimisation_level_3_serialisation() -> None: From ea9770e3b52e3b538af613b8eb51947f6d21aaf6 Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Mon, 25 Nov 2024 09:13:18 +0000 Subject: [PATCH 14/17] Update changelog.md --- docs/changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.md b/docs/changelog.md index 2bd2ccb7..3ca085d1 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -10,6 +10,7 @@ - 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. +- Add `timeout` parameter to `get_compiled_circuit` and `get_compiled_circuits`. ## 0.59.0 (November 2024) From 48147a56828a25564f37855d6e43cb1e0daa55f1 Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Mon, 25 Nov 2024 09:13:38 +0000 Subject: [PATCH 15/17] Update ibm.py --- pytket/extensions/qiskit/backends/ibm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytket/extensions/qiskit/backends/ibm.py b/pytket/extensions/qiskit/backends/ibm.py index fda58695..04e9e9b0 100644 --- a/pytket/extensions/qiskit/backends/ibm.py +++ b/pytket/extensions/qiskit/backends/ibm.py @@ -461,7 +461,7 @@ def get_compiled_circuit( """ return_circuit = circuit.copy() if optimisation_level == 3 and circuit.n_gates_of_type(OpType.Barrier) > 0: - warnings.warn( + warn( "Barrier operations in this circuit will be removed when using " "optimisation level 3." ) From 205a777181f7c0418c10ee8514b2fc3e843fd42a Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:31:23 +0000 Subject: [PATCH 16/17] update gatesets --- pytket/extensions/qiskit/backends/aer.py | 57 +++++++++++++++++++++++- pytket/extensions/qiskit/backends/ibm.py | 29 +++++++++++- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/pytket/extensions/qiskit/backends/aer.py b/pytket/extensions/qiskit/backends/aer.py index f4f1f382..652f537b 100644 --- a/pytket/extensions/qiskit/backends/aer.py +++ b/pytket/extensions/qiskit/backends/aer.py @@ -209,9 +209,35 @@ def _arch_dependent_default_compilation_pass( [ DecomposeBoxes(), RemoveBarriers(), - AutoRebase({OpType.CX, OpType.H, OpType.Rz}), + AutoRebase( + { + OpType.Z, + OpType.X, + OpType.Y, + OpType.S, + OpType.Sdg, + OpType.V, + OpType.Vdg, + OpType.H, + OpType.CX, + OpType.CY, + OpType.CZ, + OpType.SWAP, + OpType.Rz, + OpType.Rx, + OpType.Ry, + OpType.T, + OpType.Tdg, + OpType.ZZMax, + OpType.ZZPhase, + OpType.XXPhase, + OpType.YYPhase, + OpType.PhasedX, + } + ), GreedyPauliSimp(thread_timeout=timeout, only_reduce=True, trials=10), *arch_specific_passes, + self.rebase_pass(), SynthesiseTket(), ], ) @@ -232,8 +258,35 @@ def _arch_independent_default_compilation_pass( [ DecomposeBoxes(), RemoveBarriers(), - AutoRebase({OpType.CX, OpType.H, OpType.Rz}), + AutoRebase( + { + OpType.Z, + OpType.X, + OpType.Y, + OpType.S, + OpType.Sdg, + OpType.V, + OpType.Vdg, + OpType.H, + OpType.CX, + OpType.CY, + OpType.CZ, + OpType.SWAP, + OpType.Rz, + OpType.Rx, + OpType.Ry, + OpType.T, + OpType.Tdg, + OpType.ZZMax, + OpType.ZZPhase, + OpType.XXPhase, + OpType.YYPhase, + OpType.PhasedX, + } + ), GreedyPauliSimp(thread_timeout=timeout, only_reduce=True, trials=10), + self.rebase_pass(), + SynthesiseTket(), ], ) diff --git a/pytket/extensions/qiskit/backends/ibm.py b/pytket/extensions/qiskit/backends/ibm.py index 04e9e9b0..00b44a41 100644 --- a/pytket/extensions/qiskit/backends/ibm.py +++ b/pytket/extensions/qiskit/backends/ibm.py @@ -410,7 +410,34 @@ def default_compilation_pass_offline( passlist.append(FullPeepholeOptimise()) elif optimisation_level == 3: passlist.append(RemoveBarriers()) - passlist.append(AutoRebase({OpType.CX, OpType.H, OpType.Rz})) + passlist.append( + AutoRebase( + { + OpType.Z, + OpType.X, + OpType.Y, + OpType.S, + OpType.Sdg, + OpType.V, + OpType.Vdg, + OpType.H, + OpType.CX, + OpType.CY, + OpType.CZ, + OpType.SWAP, + OpType.Rz, + OpType.Rx, + OpType.Ry, + OpType.T, + OpType.Tdg, + OpType.ZZMax, + OpType.ZZPhase, + OpType.XXPhase, + OpType.YYPhase, + OpType.PhasedX, + } + ), + ) passlist.append( GreedyPauliSimp(thread_timeout=timeout, only_reduce=True, trials=10) ) From b4f59281bb8be6958d68adab06a7af195b59558b Mon Sep 17 00:00:00 2001 From: Silas Dilkes <36165522+sjdilkes@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:32:33 +0000 Subject: [PATCH 17/17] Update ibm.py --- pytket/extensions/qiskit/backends/ibm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytket/extensions/qiskit/backends/ibm.py b/pytket/extensions/qiskit/backends/ibm.py index 00b44a41..e4069d06 100644 --- a/pytket/extensions/qiskit/backends/ibm.py +++ b/pytket/extensions/qiskit/backends/ibm.py @@ -447,8 +447,8 @@ def default_compilation_pass_offline( passlist.append(AutoRebase(primitive_gates)) passlist.append( CustomPass( - _gen_lightsabre_transformation(arch, optimisation_level), - "lightsabre", + _gen_lightsabre_transformation(arch), + "lightsabrepass", ) ) if optimisation_level == 1: