From 7ce9b1206ab7a3ee797944d6d035be37d8d13ba2 Mon Sep 17 00:00:00 2001 From: Alec Edgington <54802828+cqc-alec@users.noreply.github.com> Date: Tue, 29 Aug 2023 11:18:51 +0100 Subject: [PATCH] [feature] Use a backend configuration for compilation options (#210) --- docs/api.rst | 2 +- docs/changelog.rst | 20 +- docs/intro.txt | 22 ++ .../quantinuum/backends/quantinuum.py | 210 +++++++----------- tests/integration/backend_test.py | 39 +++- tests/unit/convert_test.py | 63 +++--- 6 files changed, 177 insertions(+), 179 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 49d0dd3f..3e4a6f34 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -3,7 +3,7 @@ API documentation .. automodule:: pytket.extensions.quantinuum :special-members: - :members: QuantinuumBackend, QuantinuumAPI, QuantinuumAPIOffline, Language + :members: QuantinuumBackend, QuantinuumBackendCompilationConfig, QuantinuumAPI, QuantinuumAPIOffline, Language .. automodule:: pytket.extensions.quantinuum.backends.config :members: diff --git a/docs/changelog.rst b/docs/changelog.rst index e70ce35b..f0f37547 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,11 +6,21 @@ Unreleased * Ensure results retrieval works even with an old-format ``ResultHandle`` (generated by a pre-0.17.0 version of pytket-quantinuum). -* Add new methods ``QuantinuumBackend.default_compilation_pass_with_options``, - ``QuantinuumBackend.get_compiled_circuit_with_options`` and - ``QuantinuumBackend.get_compiled_circuits_with_options`` which take kwarg - options that affect resulting compilation, currently supporting the - use of implicit wire swaps and of targeting a specific two-qubit gate. +* Add properties ``QuantinuumBackend.default_two_qubit_gate`` and + ``QuantinuumBackend.two_qubit_gate_set`` providing the default and supported + two-qubit gates for a device. +* Make ``ZZPhase`` the default two-qubit gate target on all devices. +* Add ``QuantinuumBackendCompilationConfig`` dataclass, which can be passed as + an optional argument when constructing a ``QuantinuumBackend``. Configuration + can be inspected using ``QuantinuumBackend.get_compilation_config()`` and + modified using the methods + ``QuantinuumBackend.set_compilation_config_allow_implicit_swaps()`` and + ``QuantinuumBackend.set_compilation_config_target_2qb_gate()``. +* Add optional argument ``allow_2q_gate_rebase`` argument to + ``process_circuit()``, ``process_circuits()`` and ``submit_program()`` to + allow the backend to rebase to rebase the circuit to a different two-qubit + gate judged to have better fidelity before being run. The default is to not + allow this. * Fix handling of multiple classical registers when submitting QIR. * Change ``ResultHandle`` format. (Old ``ResultHandle`` objects will continue to work after upgrading.) diff --git a/docs/intro.txt b/docs/intro.txt index d5c5051a..672883cc 100644 --- a/docs/intro.txt +++ b/docs/intro.txt @@ -112,6 +112,28 @@ The passes applied by different levels of optimisation are specified in the tabl .. note:: If ``optimisation_level = 0`` the device constraints are solved but no additional optimisation is applied. Setting ``optimisation_level = 1`` applies some light optimisations to the circuit. More intensive optimisation is applied by level 2 at the expense of increased runtime. .. note:: The pass ``ZZPhaseToRz`` is left out of ``optimisation_level=2`` as the passes applied by ``FullPeepholeOptimise`` will already cover these optimisations. +Target Two-Qubit Gate +===================== + +Backends may offer several alternatives as the native two-qubit gate: the +current possibilities are ``ZZMax``, ``ZZPhase`` and ``TK2``. The set of +supported gates may be queried using the +``QuantinuumBackend.two_qubit_gate_set`` property. Each device also has a +default two-qubit gate, which may be queried using the +``QuantinuumBackend.default_two_qubit_gate`` property. Currently, the default +two-qubit gate for all devices is ``ZZPhase``. + +The default compilation pass and rebase pass will target the default gate by +default. This may be overridden using the method +``QuantinuumBackend.set_compilation_config_target_2qb_gate()`` or by passing a +``QuantinuumBackendCompilationConfig`` when constructing the backend. + +If desired, backends may be allowed to rebase to a different two-qubit gate +judged to have better fidelity before being run. The default is to run with the +gate provided in the submitted circuit, but this behaviour may be overridden +using the optional ``allow_2q_gate_rebase`` argument to ``process_circuit()``, +``process_circuits()`` or ``submit_program()``. + Device Predicates ================= diff --git a/pytket/extensions/quantinuum/backends/quantinuum.py b/pytket/extensions/quantinuum/backends/quantinuum.py index 689874fa..df1b7674 100644 --- a/pytket/extensions/quantinuum/backends/quantinuum.py +++ b/pytket/extensions/quantinuum/backends/quantinuum.py @@ -93,6 +93,7 @@ OpType.Rz, OpType.PhasedX, OpType.ZZMax, + OpType.ZZPhase, OpType.Reset, OpType.Measure, OpType.Barrier, @@ -107,10 +108,14 @@ } +def _default_2q_gate(device_name: str) -> OpType: + # If we change this, we should update the main documentation page and highlight it + # in the changelog. + return OpType.ZZPhase + + def _get_gateset(gates: List[str]) -> Set[OpType]: gs = _GATE_SET.copy() - if "RZZ" in gates: - gs.add(OpType.ZZPhase) if "Rxxyyzz" in gates: gs.add(OpType.TK2) return gs @@ -187,6 +192,18 @@ class Language(Enum): QuumKwargTypes = Union[KwargTypes, WasmFileHandler, Dict[str, Any], OpType, bool] +@dataclass +class QuantinuumBackendCompilationConfig: + """ + Options to configure default compilation and rebase passes. + """ + + allow_implicit_swaps: bool = True # Allow use of implicit swaps when rebasing. + target_2qb_gate: Optional[ + OpType + ] = None # Choice of two-qubit gate. The default is to use the device's default. + + class QuantinuumBackend(Backend): """ Interface to a Quantinuum device. @@ -208,6 +225,7 @@ def __init__( provider: Optional[str] = None, machine_debug: bool = False, api_handler: QuantinuumAPI = DEFAULT_API_HANDLER, + compilation_config: Optional[QuantinuumBackendCompilationConfig] = None, **kwargs: QuumKwargTypes, ): """Construct a new Quantinuum backend. @@ -227,6 +245,8 @@ def __init__( :type simulator: str, optional :param api_handler: Instance of API handler, defaults to DEFAULT_API_HANDLER :type api_handler: QuantinuumAPI + :param compilation_config: Optional compilation configuration + :type compilation_config: QuantinuumBackendCompilationConfig Supported kwargs: @@ -250,6 +270,33 @@ def __init__( self._process_circuits_options = cast(Dict[str, Any], kwargs.get("options", {})) + self._default_2q_gate = _default_2q_gate(device_name) + if compilation_config is None: + self.compilation_config = QuantinuumBackendCompilationConfig( + allow_implicit_swaps=True, target_2qb_gate=self._default_2q_gate + ) + else: + self.compilation_config = compilation_config + + def get_compilation_config(self) -> QuantinuumBackendCompilationConfig: + """Get the current compilation configuration.""" + return self.compilation_config + + def set_compilation_config_allow_implicit_swaps( + self, allow_implicit_swaps: bool + ) -> None: + """Set the option to allow or disallow implicit swaps during compilation.""" + self.compilation_config.allow_implicit_swaps = allow_implicit_swaps + + def set_compilation_config_target_2qb_gate(self, target_2qb_gate: OpType) -> None: + """Set the target two-qubit gate for compilation.""" + if target_2qb_gate not in self.two_qubit_gate_set: + raise QuantinuumAPIError( + "Requested target_2qb_gate is not supported by the given Device. " + "The supported gateset is: " + str(self.two_qubit_gate_set) + ) + self.compilation_config.target_2qb_gate = target_2qb_gate + @classmethod def _available_devices( cls, @@ -374,17 +421,19 @@ def required_predicates(self) -> List[Predicate]: return preds @property - def _two_qubit_gate_set(self) -> Set[OpType]: - """ - Assumes that only possibly supported two-qubit gates are - ZZPhase, ZZMax and TK2. + def default_two_qubit_gate(self) -> OpType: + """Returns the default two-qubit gate for the device.""" + return self._default_2q_gate - :return: Set of two-qubit OpType in gateset. - :rtype: Set[OpType] + @property + def two_qubit_gate_set(self) -> Set[OpType]: + """Returns the set of supported two-qubit gates. + + Submitted circuits must contain only one of these. """ return self._gate_set & set([OpType.ZZPhase, OpType.ZZMax, OpType.TK2]) - def rebase_pass(self, **kwargs: QuumKwargTypes) -> BasePass: + def rebase_pass(self) -> BasePass: """ Supported kwargs: * `implicit_swaps`: Boolean flag, which if true, returns @@ -392,24 +441,19 @@ def rebase_pass(self, **kwargs: QuumKwargTypes) -> BasePass: Default False. * `target_2qb_gate`: pytket OpType, if provided, will return a rebasing pass that only allows given - two-qubit gate type. + two-qubit gate type. By default, the rebase will target the default + two-qubit gate for the device. :return: Compilation pass for rebasing circuits :rtype: BasePass """ - target_2qb_optype: OpType = kwargs.get("target_2qb_gate", OpType.ZZMax) - if target_2qb_optype not in self._two_qubit_gate_set: - raise QuantinuumAPIError( - "Requested target_2qb_gate is not supported by the given Device. " - "The supported gateset is: " + str(self._two_qubit_gate_set) - ) + assert self.compilation_config.target_2qb_gate in self.two_qubit_gate_set return auto_rebase_pass( - (self._gate_set - self._two_qubit_gate_set) | {target_2qb_optype}, - allow_swaps=bool(kwargs.get("implicit_swap", True)), + (self._gate_set - self.two_qubit_gate_set) + | {self.compilation_config.target_2qb_gate}, + allow_swaps=self.compilation_config.allow_implicit_swaps, ) - def default_compilation_pass_with_options( - self, optimisation_level: int = 2, **kwargs: QuumKwargTypes - ) -> BasePass: + def default_compilation_pass(self, optimisation_level: int = 2) -> BasePass: """ :param optimisation_level: Allows values of 0,1 or 2, with higher values prompting more computationally heavy optimising compilation that @@ -417,14 +461,6 @@ def default_compilation_pass_with_options( :type optimisation_level: int :return: Compilation pass for compiling circuits to Quantinuum devices :rtype: BasePass - - Supported kwargs: - * `implicit_swaps`: Boolean flag, which if true, allows rebasing of - Circuit to use implicit wire swaps in circuit - construction if it reduces the total 2qb qate count. - * `target_2qb_gate`: :py:class:`OpType`, if provided, will rebase - circuits such that the only two-qubit gates will be of the - provided type. """ assert optimisation_level in range(3) passlist = [ @@ -437,25 +473,21 @@ def default_compilation_pass_with_options( # If ZZPhase is available we should prefer it to ZZMax. if OpType.ZZPhase in self._gate_set: fidelities["ZZPhase_fidelity"] = lambda x: 1.0 - elif OpType.ZZMax in self._gate_set: - fidelities["ZZMax_fidelity"] = 1.0 else: - raise QuantinuumAPIError( - "Either ZZMax or ZZPhase gate must be supported by device" - ) + fidelities["ZZMax_fidelity"] = 1.0 # If you make changes to the default_compilation_pass, # then please update this page accordingly # https://cqcl.github.io/pytket-quantinuum/api/index.html#default-compilation # Edit this docs source file -> pytket-quantinuum/docs/intro.txt if optimisation_level == 0: - passlist.append(self.rebase_pass(**kwargs)) + passlist.append(self.rebase_pass()) elif optimisation_level == 1: passlist.extend( [ SynthesiseTK(), NormaliseTK2(), DecomposeTK2(**fidelities), - self.rebase_pass(**kwargs), + self.rebase_pass(), ZZPhaseToRz(), RemoveRedundancies(), squash, @@ -470,7 +502,7 @@ def default_compilation_pass_with_options( FullPeepholeOptimise(target_2qb_gate=OpType.TK2), NormaliseTK2(), DecomposeTK2(**fidelities), - self.rebase_pass(**kwargs), + self.rebase_pass(), RemoveRedundancies(), squash, SimplifyInitial( @@ -496,100 +528,6 @@ def default_compilation_pass_with_options( passlist.append(FlattenRelabelRegistersPass("q")) return SequencePass(passlist) - def default_compilation_pass( - self, - optimisation_level: int = 2, - ) -> BasePass: - """ - :param optimisation_level: Allows values of 0,1 or 2, with higher values - prompting more computationally heavy optimising compilation that - can lead to reduced gate count in circuits. - :type optimisation_level: int - :return: Compilation pass for compiling circuits to Quantinuum devices - :rtype: BasePass - """ - target_2qb_gate: OpType = OpType.TK2 - if OpType.TK2 not in self._two_qubit_gate_set: - if OpType.ZZPhase in self._two_qubit_gate_set: - target_2qb_gate = OpType.ZZPhase - elif OpType.ZZMax in self._two_qubit_gate_set: - target_2qb_gate = OpType.ZZMax - else: - raise QuantinuumAPIError( - "Device does not support either TK2, ZZPhase or ZZMax gates." - ) - return self.default_compilation_pass_with_options( - optimisation_level, implicit_swap=True, target_2qb_gate=target_2qb_gate - ) - - def get_compiled_circuit_with_options( - self, circuit: Circuit, optimisation_level: int = 2, **kwargs: KwargTypes - ) -> Circuit: - """ - Return a single circuit compiled with :py:meth:`default_compilation_pass` See - :py:meth:`Backend.get_compiled_circuits`. - - Supported kwargs: - * `implicit_swaps`: Boolean flag, which if true, allows rebasing of - Circuit via TK2 gates to use implicit wire swaps in circuit - construction if it reduces the total 2qb qate count. - * `target_2qb_gate`: :py:class:`OpType`, if provided, will ensure that - the rebased circuit contains only two-qubit gates of this type. - """ - return_circuit = circuit.copy() - self.default_compilation_pass_with_options(optimisation_level, **kwargs).apply( - return_circuit - ) - return return_circuit - - def get_compiled_circuits_with_options( - self, - circuits: Sequence[Circuit], - optimisation_level: int = 2, - **kwargs: KwargTypes, - ) -> 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 or 2). Defaults to 2. - :type optimisation_level: int, optional - :return: Compiled circuits. - :rtype: List[Circuit] - - - Supported kwargs: - * `implicit_swaps`: Boolean flag, which if true, allows rebasing of - Circuit via TK2 gates to use implicit wire swaps in circuit - construction if it reduces the total 2qb qate count. - * `target_2qb_gate`: :py:class:`OpType`, if provided, will ensure that - the rebased circuits contain only two-qubit gates of this type. - """ - return [ - self.get_compiled_circuit_with_options(c, optimisation_level, **kwargs) - for c in circuits - ] - @property def _result_id_type(self) -> _ResultIdTuple: return tuple((str, str, int, str)) @@ -663,6 +601,7 @@ def submit_program( wasm_file_handler: Optional[WasmFileHandler] = None, pytket_pass: Optional[BasePass] = None, no_opt: bool = False, + allow_2q_gate_rebase: bool = False, options: Optional[Dict[str, Any]] = None, request_options: Optional[Dict[str, Any]] = None, results_selection: Optional[List[Tuple[str, int]]] = None, @@ -688,6 +627,9 @@ def submit_program( :type wasm_file_handler: Optional[WasmFileHandler], optional :param no_opt: if true, requests that the backend perform no optimizations :type no_opt: bool, defaults to False + :param allow_2q_gate_rebase: if true, allow rebasing of the two-qubit gates to + a higher-fidelity alternative gate at the discretion of the backend + :type allow_2q_gate_rebase: bool, defaults to False :param pytket_pass: ``pytket.passes.BasePass`` intended to be applied by the backend (beta feature, may be ignored), defaults to None :type pytket_pass: Optional[BasePass], optional @@ -717,6 +659,7 @@ def submit_program( "options": { "simulator": self.simulator_type, "no-opt": no_opt, + "noreduce": not allow_2q_gate_rebase, "error-model": noisy_simulation, "tket": dict(), }, @@ -790,6 +733,8 @@ def process_circuits( * `pytketpass`: a ``pytket.passes.BasePass`` intended to be applied by the backend (beta feature, may be ignored). * `no_opt`: if true, requests that the backend perform no optimizations + * `allow_2q_gate_rebase`: if true, allow rebasing of the two-qubit gates to a + higher-fidelity alternative gate at the discretion of the backend * `options`: items to add to the "options" dictionary of the request body, as a json-style dictionary (in addition to any that were set in the backend constructor) @@ -832,6 +777,8 @@ def process_circuits( no_opt = cast(bool, kwargs.get("no_opt", False)) + allow_2q_gate_rebase = cast(bool, kwargs.get("allow_2q_gate_rebase", False)) + language = cast(Language, kwargs.get("language", Language.QASM)) handle_list = [] @@ -906,6 +853,7 @@ def process_circuits( wasm_file_handler=wasm_fh, pytket_pass=pytket_pass, no_opt=no_opt, + allow_2q_gate_rebase=allow_2q_gate_rebase, options=cast(Dict[str, Any], kwargs.get("options", {})), request_options=cast( Dict[str, Any], kwargs.get("request_options", {}) diff --git a/tests/integration/backend_test.py b/tests/integration/backend_test.py index b12ffe59..084dd969 100644 --- a/tests/integration/backend_test.py +++ b/tests/integration/backend_test.py @@ -511,6 +511,9 @@ def test_retrieve_available_devices( api_handler=authenticated_quum_handler ) assert len(backend_infos) > 0 + assert all( + OpType.ZZPhase in backend_info.gate_set for backend_info in backend_infos + ) @pytest.mark.flaky(reruns=3, reruns_delay=10) @@ -570,10 +573,7 @@ def test_zzphase( c.measure_all() c0 = backend.get_compiled_circuit(c, 0) - if OpType.ZZPhase in backend._gate_set: - assert c0.n_gates_of_type(OpType.ZZPhase) > 0 - else: - assert c0.n_gates_of_type(OpType.ZZMax) > 0 + assert c0.n_gates_of_type(backend.default_two_qubit_gate) > 0 n_shots = 4 handle = backend.process_circuits([c0], n_shots)[0] @@ -603,11 +603,7 @@ def test_zzphase_support_opti2( c.measure_all() c0 = backend.get_compiled_circuit(c, 2) - # backend._gate_set requires API access. - if OpType.ZZPhase in backend._gate_set: - assert c0.n_gates_of_type(OpType.ZZPhase) == 1 - else: - assert c0.n_gates_of_type(OpType.ZZMax) == 1 + assert c0.n_gates_of_type(backend.default_two_qubit_gate) == 1 @pytest.mark.skipif(skip_remote_tests, reason=REASON) @@ -629,10 +625,13 @@ def test_prefer_zzphase( .measure_all() ) c0 = backend.get_compiled_circuit(c) - if OpType.ZZPhase in backend._gate_set: + if backend.default_two_qubit_gate == OpType.ZZPhase: assert c0.n_gates_of_type(OpType.ZZPhase) == 2 - else: + elif backend.default_two_qubit_gate == OpType.ZZMax: assert c0.n_gates_of_type(OpType.ZZMax) == 2 + else: + assert backend.default_two_qubit_gate == OpType.TK2 + assert c0.n_gates_of_type(OpType.TK2) == 1 @pytest.mark.skipif(skip_remote_tests, reason=REASON) @@ -756,6 +755,24 @@ def test_no_opt(authenticated_quum_backend: QuantinuumBackend) -> None: assert len(shots[0]) == 1 +@pytest.mark.skipif(skip_remote_tests, reason=REASON) +@pytest.mark.parametrize( + "authenticated_quum_backend", + [{"device_name": name} for name in pytest.ALL_SYNTAX_CHECKER_NAMES], # type: ignore + indirect=True, +) +def test_allow_2q_gate_rebase(authenticated_quum_backend: QuantinuumBackend) -> None: + c0 = Circuit(2).H(0).CX(0, 1).measure_all() + b = authenticated_quum_backend + b.set_compilation_config_target_2qb_gate(OpType.ZZMax) + c = b.get_compiled_circuit(c0, 0) + h = b.process_circuits([c], n_shots=1, allow_2q_gate_rebase=True) + r = b.get_results(h)[0] + shots = r.get_shots() + assert len(shots) == 1 + assert len(shots[0]) == 1 + + @pytest.mark.skipif(skip_remote_tests, reason=REASON) @pytest.mark.parametrize( "authenticated_quum_backend", [{"device_name": "H1-1SC"}], indirect=True diff --git a/tests/unit/convert_test.py b/tests/unit/convert_test.py index 29a5c0e4..b3b0cb47 100644 --- a/tests/unit/convert_test.py +++ b/tests/unit/convert_test.py @@ -31,9 +31,10 @@ def test_convert() -> None: circ.add_barrier([2]) circ.measure_all() - QuantinuumBackend("", machine_debug=True).rebase_pass(implicit_swap=False).apply( - circ - ) + b = QuantinuumBackend("", machine_debug=True) + b.set_compilation_config_target_2qb_gate(OpType.ZZMax) + b.set_compilation_config_allow_implicit_swaps(False) + b.rebase_pass().apply(circ) circ_quum = circuit_to_qasm_str(circ, header="hqslib1") qasm_str = circ_quum.split("\n")[6:-1] assert all( @@ -50,9 +51,9 @@ def test_convert_rzz() -> None: circ.add_gate(OpType.ZZMax, [2, 3]) circ.measure_all() - QuantinuumBackend("", machine_debug=True).rebase_pass(implicit_swap=False).apply( - circ - ) + b = QuantinuumBackend("", machine_debug=True) + b.set_compilation_config_allow_implicit_swaps(False) + b.rebase_pass().apply(circ) circ_quum = circuit_to_qasm_str(circ, header="hqslib1") qasm_str = circ_quum.split("\n")[6:-1] assert all( @@ -132,28 +133,28 @@ def test_resize_scratch_registers() -> None: def test_implicit_swap_removal() -> None: b = QuantinuumBackend("", machine_debug=True) c = Circuit(2).ISWAPMax(0, 1) + b.set_compilation_config_target_2qb_gate(OpType.ZZMax) compiled = b.get_compiled_circuit(c, 0) assert compiled.n_gates_of_type(OpType.ZZMax) == 1 assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 iqp = compiled.implicit_qubit_permutation() assert iqp[Qubit(0)] == Qubit(1) assert iqp[Qubit(1)] == Qubit(0) - c = b.get_compiled_circuit_with_options( - Circuit(2).ISWAPMax(0, 1), 0, implicit_swap=False - ) + b.set_compilation_config_allow_implicit_swaps(False) + c = b.get_compiled_circuit(Circuit(2).ISWAPMax(0, 1), 0) assert c.n_gates_of_type(OpType.ZZMax) == 2 assert c.n_gates_of_type(OpType.ZZPhase) == 0 c = Circuit(2).Sycamore(0, 1) + b.set_compilation_config_allow_implicit_swaps(True) compiled = b.get_compiled_circuit(c, 0) assert compiled.n_gates_of_type(OpType.ZZMax) == 2 assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 iqp = compiled.implicit_qubit_permutation() assert iqp[Qubit(0)] == Qubit(1) assert iqp[Qubit(1)] == Qubit(0) - c = b.get_compiled_circuit_with_options( - Circuit(2).Sycamore(0, 1), 0, implicit_swap=False - ) + b.set_compilation_config_allow_implicit_swaps(False) + c = b.get_compiled_circuit(Circuit(2).Sycamore(0, 1), 0) assert c.n_gates_of_type(OpType.ZZMax) == 3 assert c.n_gates_of_type(OpType.ZZPhase) == 0 @@ -164,13 +165,12 @@ def test_implicit_swap_removal() -> None: iqp = compiled.implicit_qubit_permutation() assert iqp[Qubit(0)] == Qubit(0) assert iqp[Qubit(1)] == Qubit(1) - c = b.get_compiled_circuit_with_options( - Circuit(2).ISWAP(0.3, 0, 1), 0, implicit_swap=False - ) + c = b.get_compiled_circuit(Circuit(2).ISWAP(0.3, 0, 1), 0) assert c.n_gates_of_type(OpType.ZZMax) == 2 assert c.n_gates_of_type(OpType.ZZPhase) == 0 c = Circuit(2).ISWAPMax(0, 1).ISWAPMax(1, 0) + b.set_compilation_config_allow_implicit_swaps(True) compiled = b.get_compiled_circuit(c, 0) assert compiled.n_gates_of_type(OpType.ZZMax) == 2 assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 @@ -183,22 +183,23 @@ def test_implicit_swap_removal() -> None: assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 iqp = compiled.implicit_qubit_permutation() assert iqp[Qubit(0)] == Qubit(0) - c = b.get_compiled_circuit_with_options( - Circuit(2).ISWAPMax(0, 1).ISWAPMax(1, 0), 0, implicit_swap=False - ) + b.set_compilation_config_allow_implicit_swaps(False) + c = b.get_compiled_circuit(Circuit(2).ISWAPMax(0, 1).ISWAPMax(1, 0), 0) assert c.n_gates_of_type(OpType.ZZMax) == 4 assert c.n_gates_of_type(OpType.ZZPhase) == 0 c = Circuit(2).SWAP(0, 1) + b.set_compilation_config_allow_implicit_swaps(True) + b.set_compilation_config_target_2qb_gate(OpType.ZZPhase) compiled = b.get_compiled_circuit(c, 0) assert compiled.n_gates_of_type(OpType.ZZMax) == 0 assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 iqp = compiled.implicit_qubit_permutation() assert iqp[Qubit(0)] == Qubit(1) assert iqp[Qubit(1)] == Qubit(0) - c = b.get_compiled_circuit_with_options( - Circuit(2).SWAP(0, 1), 0, implicit_swap=False - ) + b.set_compilation_config_allow_implicit_swaps(False) + b.set_compilation_config_target_2qb_gate(OpType.ZZMax) + c = b.get_compiled_circuit(Circuit(2).SWAP(0, 1), 0) assert c.n_gates_of_type(OpType.ZZMax) == 3 assert c.n_gates_of_type(OpType.ZZPhase) == 0 @@ -213,26 +214,24 @@ def test_switch_target_2qb_gate() -> None: c = Circuit(2).ISWAPMax(0, 1) # Default behaviour compiled = b.get_compiled_circuit(c, 0) - assert compiled.n_gates_of_type(OpType.ZZMax) == 1 - assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 + assert compiled.n_gates_of_type(OpType.ZZMax) == 0 + assert compiled.n_gates_of_type(OpType.ZZPhase) == 1 assert compiled.n_gates_of_type(OpType.TK2) == 0 # Targeting allowed gate - compiled = b.get_compiled_circuit_with_options(c, 0, target_2qb_gate=OpType.ZZMax) + b.set_compilation_config_target_2qb_gate(OpType.ZZMax) + compiled = b.get_compiled_circuit(c, 0) assert compiled.n_gates_of_type(OpType.ZZMax) == 1 assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 assert compiled.n_gates_of_type(OpType.TK2) == 0 # Targeting allowed gate but no wire swap - compiled = b.get_compiled_circuit_with_options( - c, 0, target_2qb_gate=OpType.ZZMax, implicit_swap=False - ) + b.set_compilation_config_allow_implicit_swaps(False) + compiled = b.get_compiled_circuit(c, 0) assert compiled.n_gates_of_type(OpType.ZZMax) == 2 assert compiled.n_gates_of_type(OpType.ZZPhase) == 0 assert compiled.n_gates_of_type(OpType.TK2) == 0 # Targeting unsupported gate with pytest.raises(QuantinuumAPIError): - compiled = b.get_compiled_circuit_with_options( - c, 0, target_2qb_gate=OpType.ZZPhase - ) + b.set_compilation_config_target_2qb_gate(OpType.ISWAPMax) # Confirming that if ZZPhase is added to gate set that it functions b._MACHINE_DEBUG = False @@ -245,7 +244,9 @@ def test_switch_target_2qb_gate() -> None: ) assert OpType.ZZMax in b._gate_set assert OpType.ZZPhase in b._gate_set - compiled = b.get_compiled_circuit_with_options(c, 0, target_2qb_gate=OpType.ZZPhase) + b.set_compilation_config_allow_implicit_swaps(True) + b.set_compilation_config_target_2qb_gate(OpType.ZZPhase) + compiled = b.get_compiled_circuit(c, 0) assert compiled.n_gates_of_type(OpType.ZZMax) == 0 assert compiled.n_gates_of_type(OpType.ZZPhase) == 1 assert compiled.n_gates_of_type(OpType.TK2) == 0