diff --git a/qiskit_ibm_transpiler/ai/synthesis.py b/qiskit_ibm_transpiler/ai/synthesis.py index 691c6e2..2ca35bf 100644 --- a/qiskit_ibm_transpiler/ai/synthesis.py +++ b/qiskit_ibm_transpiler/ai/synthesis.py @@ -111,6 +111,9 @@ def __init__( if backend: self.backend = backend + # TODO: Removes once we deprecate backend_name + if not local_mode: + self.backend_name = backend.name elif backend_name and local_mode: try: runtime_service = QiskitRuntimeService() diff --git a/qiskit_ibm_transpiler/wrappers/ai_api_synthesis.py b/qiskit_ibm_transpiler/wrappers/ai_api_synthesis.py index ea4f909..a87ff0d 100644 --- a/qiskit_ibm_transpiler/wrappers/ai_api_synthesis.py +++ b/qiskit_ibm_transpiler/wrappers/ai_api_synthesis.py @@ -44,6 +44,7 @@ def transpile( backend: Union[Backend, None] = None, ): if coupling_map is not None: + logger.info("Running synthesis against the Qiskit Transpiler Service") transpile_resps = self.request_and_wait( endpoint="transpile", body={ @@ -56,6 +57,7 @@ def transpile( params=dict(), ) elif backend_name is not None: + logger.info("Running synthesis against the Qiskit Transpiler Service") transpile_resps = self.request_and_wait( endpoint="transpile", body={ @@ -169,6 +171,7 @@ def transpile( ): if coupling_map is not None: + logger.info("Running synthesis against the Qiskit Transpiler Service") transpile_resps = self.request_and_wait( endpoint="transpile", body={ @@ -179,6 +182,7 @@ def transpile( params=dict(), ) elif backend_name is not None: + logger.info("Running synthesis against the Qiskit Transpiler Service") transpile_resps = self.request_and_wait( endpoint="transpile", body={ diff --git a/tests/ai/test_ai_clifford_synthesis.py b/tests/ai/test_ai_clifford_synthesis.py new file mode 100644 index 0000000..e2cce25 --- /dev/null +++ b/tests/ai/test_ai_clifford_synthesis.py @@ -0,0 +1,324 @@ +# -*- coding: utf-8 -*- + +# (C) Copyright 2024 IBM. All Rights Reserved. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Unit-testing ai clifford syhtesis for both local and cloud modes""" +import pytest +from qiskit import QuantumCircuit +from qiskit.transpiler import PassManager + +from qiskit_ibm_transpiler.ai.collection import CollectCliffords +from qiskit_ibm_transpiler.ai.synthesis import AICliffordSynthesis +from qiskit_ibm_transpiler.utils import random_clifford_from_linear_function +from qiskit_ibm_runtime import QiskitRuntimeService + + +@pytest.fixture +def basic_cnot_circuit(): + circuit = QuantumCircuit(3) + circuit.cx(0, 1) + circuit.cx(1, 2) + + return circuit + + +# TODO: All the tests that use this circuit keeps the original circuit. Check if this is the better option +# for doing those tests +@pytest.fixture +def clifford_circuit(): + circuit = QuantumCircuit(8) + clifford = random_clifford_from_linear_function(8) + circuit.append(clifford, range(8)) + # Using decompose since we need a QuantumCircuit, not a Clifford. We created an empty + # circuit, so it contains only a Clifford + circuit = circuit.decompose(reps=1) + + return circuit + + +@pytest.fixture +def brisbane_backend_name(): + return "ibm_brisbane" + + +@pytest.fixture +def brisbane_backend(brisbane_backend_name): + backend = QiskitRuntimeService().backend(brisbane_backend_name) + + return backend + + +@pytest.fixture +def brisbane_coupling_map(brisbane_backend): + return brisbane_backend.coupling_map + + +@pytest.fixture +def brisbane_coupling_map_list_format(brisbane_backend): + return list(brisbane_backend.coupling_map.get_edges()) + + +# TODO: When testing the clifford synthesis with wrong backend, local and cloud behaves differently, +# so we should decide if this is correct or if we want to unify them +def test_ai_local_clifford_synthesis_wrong_backend(basic_cnot_circuit): + original_circuit = basic_cnot_circuit + + with pytest.raises( + PermissionError, + match=r"User doesn\'t have access to the specified backend: \w+", + ): + ai_clifford_synthesis_pass = PassManager( + [ + CollectCliffords(min_block_size=2), + AICliffordSynthesis(backend_name="wrong_backend"), + ] + ) + + ai_clifford_synthesis_pass.run(original_circuit) + + +def test_ai_cloud_clifford_synthesis_wrong_backend(random_circuit_transpiled, caplog): + ai_clifford_synthesis_pass = PassManager( + [ + CollectCliffords(), + AICliffordSynthesis(backend_name="wrong_backend", local_mode=False), + ] + ) + + ai_optimized_circuit = ai_clifford_synthesis_pass.run(random_circuit_transpiled) + + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert ( + "User doesn't have access to the specified backend: wrong_backend" + in caplog.text + ) + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +@pytest.mark.skip( + reason="Unreliable. It passes most of the times with the timeout of 1 second for the current circuits used" +) +def test_ai_cloud_clifford_synthesis_exceed_timeout( + random_circuit_transpiled, backend, caplog +): + ai_clifford_synthesis_pass = PassManager( + [ + CollectCliffords(), + AICliffordSynthesis(backend_name=backend, timeout=1, local_mode=False), + ] + ) + + ai_optimized_circuit = ai_clifford_synthesis_pass.run(random_circuit_transpiled) + + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +def test_ai_cloud_clifford_synthesis_wrong_token( + random_circuit_transpiled, brisbane_backend_name, caplog +): + ai_clifford_synthesis_pass = PassManager( + [ + CollectCliffords(), + AICliffordSynthesis( + backend_name=brisbane_backend_name, + token="invented_token_2", + local_mode=False, + ), + ] + ) + + ai_optimized_circuit = ai_clifford_synthesis_pass.run(random_circuit_transpiled) + + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert "Invalid authentication credentials" in caplog.text + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +@pytest.mark.disable_monkeypatch +def test_ai_cloud_clifford_synthesis_wrong_url( + random_circuit_transpiled, backend, caplog +): + ai_clifford_synthesis_pass = PassManager( + [ + CollectCliffords(), + AICliffordSynthesis( + backend_name=backend, base_url="https://ibm.com/", local_mode=False + ), + ] + ) + + ai_clifford_synthesis_pass.run(random_circuit_transpiled) + + assert "Internal error: 404 Client Error:" in caplog.text + assert "Keeping the original circuit" in caplog.text + + +@pytest.mark.disable_monkeypatch +def test_ai_cloud_clifford_synthesis_unexisting_url( + random_circuit_transpiled, backend, caplog +): + ai_clifford_synthesis_pass = PassManager( + [ + CollectCliffords(), + AICliffordSynthesis( + backend_name=backend, + base_url="https://invented-domain-qiskit-ibm-transpiler-123.com/", + local_mode=False, + ), + ] + ) + + ai_optimized_circuit = ai_clifford_synthesis_pass.run(random_circuit_transpiled) + + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert ( + "Error: HTTPSConnectionPool(host='invented-domain-qiskit-ibm-transpiler-123.com', port=443):" + in caplog.text + ) + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +# TODO: Tests pass if we add min_block_size=2 to CollectCliffords. If not, tests failed. Confirm why this is happening +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_clifford_synthesis_always_replace_original_circuit( + basic_cnot_circuit, brisbane_backend_name, caplog, local_mode +): + original_circuit = basic_cnot_circuit + + ai_clifford_synthesis_pass = PassManager( + [ + CollectCliffords(min_block_size=2), + AICliffordSynthesis( + backend_name=brisbane_backend_name, + replace_only_if_better=False, + local_mode=local_mode, + ), + ] + ) + + ai_optimized_circuit = ai_clifford_synthesis_pass.run(original_circuit) + + assert all(word in caplog.text for word in ["Running", "synthesis"]) + assert "Using the synthesized circuit" in caplog.text + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_clifford_synthesis_keep_original_if_better( + basic_cnot_circuit, brisbane_backend_name, caplog, local_mode +): + original_circuit = basic_cnot_circuit + + ai_clifford_synthesis_pass = PassManager( + [ + CollectCliffords(min_block_size=2), + AICliffordSynthesis( + backend_name=brisbane_backend_name, local_mode=local_mode + ), + ] + ) + + ai_optimized_circuit = ai_clifford_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert ai_optimized_circuit == original_circuit + assert all(word in caplog.text for word in ["Running", "synthesis"]) + assert "Keeping the original circuit" in caplog.text + + +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_clifford_synthesis_pass_with_backend_name( + clifford_circuit, brisbane_backend_name, caplog, local_mode +): + original_circuit = clifford_circuit + + ai_clifford_synthesis_pass = PassManager( + [ + CollectCliffords(), + AICliffordSynthesis( + backend_name=brisbane_backend_name, local_mode=local_mode + ), + ] + ) + + ai_optimized_circuit = ai_clifford_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert all(word in caplog.text for word in ["Running", "synthesis"]) + + +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_clifford_synthesis_pass_with_backend( + clifford_circuit, brisbane_backend, caplog, local_mode +): + original_circuit = clifford_circuit + + ai_clifford_synthesis_pass = PassManager( + [ + CollectCliffords(), + AICliffordSynthesis(backend=brisbane_backend, local_mode=local_mode), + ] + ) + + ai_optimized_circuit = ai_clifford_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert all(word in caplog.text for word in ["Running", "synthesis"]) + + +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +@pytest.mark.parametrize( + "coupling_map", + [brisbane_coupling_map, brisbane_coupling_map_list_format], + indirect=True, + ids=["coupling_map_object", "coupling_map_list"], +) +def test_ai_clifford_synthesis_pass_with_coupling_map( + clifford_circuit, caplog, local_mode, coupling_map +): + original_circuit = clifford_circuit + + ai_clifford_synthesis_pass = PassManager( + [ + CollectCliffords(min_block_size=2), + AICliffordSynthesis(coupling_map=coupling_map, local_mode=local_mode), + ] + ) + + ai_optimized_circuit = ai_clifford_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert all(word in caplog.text for word in ["Running", "synthesis"]) diff --git a/tests/ai/test_ai_linear_function_synthesis.py b/tests/ai/test_ai_linear_function_synthesis.py new file mode 100644 index 0000000..53d12e5 --- /dev/null +++ b/tests/ai/test_ai_linear_function_synthesis.py @@ -0,0 +1,317 @@ +# -*- coding: utf-8 -*- + +# (C) Copyright 2024 IBM. All Rights Reserved. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Unit-testing ai Linear Function syhtesis for both local and cloud modes""" +import pytest +from qiskit import QuantumCircuit +from qiskit.transpiler import PassManager + +from qiskit_ibm_transpiler.ai.collection import CollectLinearFunctions +from qiskit_ibm_transpiler.ai.synthesis import AILinearFunctionSynthesis +from qiskit_ibm_transpiler.utils import create_random_linear_function +from qiskit_ibm_runtime import QiskitRuntimeService + + +@pytest.fixture +def basic_cnot_circuit(): + circuit = QuantumCircuit(3) + circuit.cx(0, 1) + circuit.cx(1, 2) + + return circuit + + +# TODO: All the tests that use this circuit keeps the original circuit. Check if this is the better option +# for doing those tests +@pytest.fixture +def linear_function_circuit(): + circuit = QuantumCircuit(8) + linear_function = create_random_linear_function(8) + circuit.append(linear_function, range(8)) + # Using decompose since we need a QuantumCircuit, not a LinearFunction. We created an empty + # circuit, so it contains only a LinearFunction + circuit = circuit.decompose(reps=1) + + return circuit + + +@pytest.fixture +def brisbane_backend_name(): + return "ibm_brisbane" + + +@pytest.fixture +def brisbane_backend(): + backend = QiskitRuntimeService().backend("ibm_brisbane") + + return backend + + +# TODO: When testing the linear function synthesis with wrong backend, local and cloud behaves differently, +# so we should decide if this is correct or if we want to unify them +def test_ai_local_linear_function_synthesis_wrong_backend(basic_cnot_circuit): + original_circuit = basic_cnot_circuit + + with pytest.raises( + PermissionError, + match=r"User doesn\'t have access to the specified backend: \w+", + ): + ai_linear_functions_synthesis_pass = PassManager( + [ + CollectLinearFunctions(min_block_size=2), + AILinearFunctionSynthesis(backend_name="wrong_backend"), + ] + ) + + ai_linear_functions_synthesis_pass.run(original_circuit) + + +def test_ai_cloud_linear_function_synthesis_wrong_backend( + random_circuit_transpiled, caplog +): + ai_optimize_lf = PassManager( + [ + CollectLinearFunctions(), + AILinearFunctionSynthesis(backend_name="wrong_backend", local_mode=False), + ] + ) + ai_optimized_circuit = ai_optimize_lf.run(random_circuit_transpiled) + + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert ( + "User doesn't have access to the specified backend: wrong_backend" + in caplog.text + ) + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +@pytest.mark.skip( + reason="Unreliable. It passes most of the times with the timeout of 1 second for the current circuits used" +) +def test_ai_cloud_linear_function_synthesis_exceed_timeout( + random_circuit_transpiled, backend, caplog +): + ai_optimize_lf = PassManager( + [ + CollectLinearFunctions(), + AILinearFunctionSynthesis( + backend_name=backend, timeout=1, local_mode=False + ), + ] + ) + ai_optimized_circuit = ai_optimize_lf.run(random_circuit_transpiled) + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +@pytest.mark.skip( + reason="Unreliable many times. We'll research why it fails sporadically" +) +def test_ai_cloud_linear_function_synthesis_wrong_token( + random_circuit_transpiled, backend, caplog +): + ai_optimize_lf = PassManager( + [ + CollectLinearFunctions(), + AILinearFunctionSynthesis( + backend_name=backend, token="invented_token_2", local_mode=False + ), + ] + ) + ai_optimized_circuit = ai_optimize_lf.run(random_circuit_transpiled) + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert "Invalid authentication credentials" in caplog.text + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +@pytest.mark.skip( + reason="Unreliable many times. We'll research why it fails sporadically" +) +@pytest.mark.disable_monkeypatch +def test_ai_cloud_linear_function_synthesis_wrong_url( + random_circuit_transpiled, backend +): + ai_optimize_lf = PassManager( + [ + CollectLinearFunctions(), + AILinearFunctionSynthesis( + backend_name=backend, base_url="https://ibm.com/", local_mode=False + ), + ] + ) + try: + ai_optimized_circuit = ai_optimize_lf.run(random_circuit_transpiled) + pytest.fail("Error expected") + except Exception as e: + assert "Expecting value: line 1 column 1 (char 0)" in str(e) + assert type(e).__name__ == "JSONDecodeError" + + +@pytest.mark.skip( + reason="Unreliable many times. We'll research why it fails sporadically" +) +@pytest.mark.disable_monkeypatch +def test_ai_cloud_linear_function_synthesis_unexisting_url( + random_circuit_transpiled, backend, caplog +): + ai_optimize_lf = PassManager( + [ + CollectLinearFunctions(), + AILinearFunctionSynthesis( + backend_name=backend, + base_url="https://invented-domain-qiskit-ibm-transpiler-123.com/", + local_mode=False, + ), + ] + ) + ai_optimized_circuit = ai_optimize_lf.run(random_circuit_transpiled) + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert ( + "Error: HTTPSConnectionPool(host='invented-domain-qiskit-ibm-transpiler-123.com', port=443):" + in caplog.text + ) + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +# TODO: Tests pass if we add min_block_size=2 to CollectLinearFunctions. If not, tests failed. Confirm why this is happening +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_linear_function_synthesis_always_replace_original_circuit( + basic_cnot_circuit, brisbane_backend_name, caplog, local_mode +): + original_circuit = basic_cnot_circuit + + ai_linear_functions_synthesis_pass = PassManager( + [ + CollectLinearFunctions(min_block_size=2), + AILinearFunctionSynthesis( + backend_name=brisbane_backend_name, + replace_only_if_better=False, + local_mode=local_mode, + ), + ] + ) + + ai_optimized_circuit = ai_linear_functions_synthesis_pass.run(original_circuit) + + assert all(word in caplog.text for word in ["Running", "synthesis"]) + assert "Using the synthesized circuit" in caplog.text + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_linear_function_synthesis_keep_original_if_better( + basic_cnot_circuit, brisbane_backend_name, caplog, local_mode +): + original_circuit = basic_cnot_circuit + + ai_linear_functions_synthesis_pass = PassManager( + [ + CollectLinearFunctions(min_block_size=2), + AILinearFunctionSynthesis( + backend_name=brisbane_backend_name, local_mode=local_mode + ), + ] + ) + + ai_optimized_circuit = ai_linear_functions_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert ai_optimized_circuit == original_circuit + assert all(word in caplog.text for word in ["Running", "synthesis"]) + assert "Keeping the original circuit" in caplog.text + + +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_linear_function_synthesis_pass_with_backend_name( + linear_function_circuit, brisbane_backend_name, caplog, local_mode +): + original_circuit = linear_function_circuit + + ai_linear_functions_synthesis_pass = PassManager( + [ + CollectLinearFunctions(), + AILinearFunctionSynthesis( + backend_name=brisbane_backend_name, local_mode=local_mode + ), + ] + ) + + ai_optimized_circuit = ai_linear_functions_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert all(word in caplog.text for word in ["Running", "synthesis"]) + + +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_linear_function_synthesis_pass_with_backend( + linear_function_circuit, brisbane_backend, caplog, local_mode +): + original_circuit = linear_function_circuit + + ai_linear_functions_synthesis_pass = PassManager( + [ + CollectLinearFunctions(), + AILinearFunctionSynthesis(backend=brisbane_backend, local_mode=local_mode), + ] + ) + + ai_optimized_circuit = ai_linear_functions_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert all(word in caplog.text for word in ["Running", "synthesis"]) + + +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_linear_function_synthesis_pass_with_coupling_map( + linear_function_circuit, brisbane_backend, caplog, local_mode +): + original_circuit = linear_function_circuit + + backend_coupling_map = brisbane_backend.coupling_map + + ai_linear_functions_synthesis_pass = PassManager( + [ + CollectLinearFunctions(min_block_size=2), + AILinearFunctionSynthesis( + coupling_map=backend_coupling_map, local_mode=local_mode + ), + ] + ) + + ai_optimized_circuit = ai_linear_functions_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert all(word in caplog.text for word in ["Running", "synthesis"]) diff --git a/tests/ai/test_ai_local_clifford_synthesis.py b/tests/ai/test_ai_local_clifford_synthesis.py deleted file mode 100644 index 123c0e9..0000000 --- a/tests/ai/test_ai_local_clifford_synthesis.py +++ /dev/null @@ -1,160 +0,0 @@ -# -*- coding: utf-8 -*- - -# (C) Copyright 2024 IBM. All Rights Reserved. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Testing AI local synthesis for cliffords""" -import pytest -from qiskit import QuantumCircuit -from qiskit.transpiler import PassManager - -from qiskit_ibm_transpiler.ai.collection import CollectCliffords -from qiskit_ibm_transpiler.utils import random_clifford_from_linear_function -from qiskit_ibm_transpiler.ai.synthesis import AICliffordSynthesis -from qiskit_ibm_runtime import QiskitRuntimeService - - -@pytest.fixture -def basic_cnot_circuit(): - circuit = QuantumCircuit(3) - circuit.cx(0, 1) - circuit.cx(1, 2) - - return circuit - - -@pytest.fixture -def clifford_circuit(): - circuit = QuantumCircuit(8) - clifford = random_clifford_from_linear_function(8) - circuit.append(clifford, range(8)) - # Using decompose since we need a QuantumCircuit, not a Clifford. We created an empty - # circuit, so it contains only a Clifford - circuit = circuit.decompose(reps=1) - - return circuit - - -@pytest.fixture -def brisbane_backend(): - backend = QiskitRuntimeService().backend("ibm_brisbane") - - return backend - - -def test_ai_local_clifford_synthesis_wrong_backend(basic_cnot_circuit): - original_circuit = basic_cnot_circuit - - with pytest.raises( - PermissionError, - match=r"User doesn\'t have access to the specified backend: \w+", - ): - ai_cliffords_synthesis_pass = PassManager( - [ - CollectCliffords(min_block_size=2), - AICliffordSynthesis(backend_name="wrong_backend"), - ] - ) - - ai_cliffords_synthesis_pass.run(original_circuit) - - -def test_ai_local_clifford_synthesis_returns_original_circuit( - basic_cnot_circuit, caplog -): - # When the original circuit is better than the synthesized one, we keep the original - - original_circuit = basic_cnot_circuit - - ai_cliffords_synthesis_pass = PassManager( - [ - CollectCliffords(min_block_size=2), - AICliffordSynthesis(backend_name="ibm_brisbane"), - ] - ) - - synthesized_circuit = ai_cliffords_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - assert synthesized_circuit == original_circuit - assert "Keeping the original circuit" in caplog.text - - -def test_ai_local_clifford_synthesis_dont_returns_original_circuit( - basic_cnot_circuit, caplog -): - # When the original circuit is better than the synthesized one, - # but replace_only_if_better is False, we return the synthesized circuit - - original_circuit = basic_cnot_circuit - - ai_cliffords_synthesis_pass = PassManager( - [ - CollectCliffords(min_block_size=2), - AICliffordSynthesis( - backend_name="ibm_brisbane", replace_only_if_better=False - ), - ] - ) - - synthesized_circuit = ai_cliffords_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - assert synthesized_circuit == original_circuit - assert "Using the synthesized circuit" in caplog.text - - -def test_ai_local_clifford_synthesis_with_backend_name(clifford_circuit): - original_circuit = clifford_circuit - - ai_cliffords_synthesis_pass = PassManager( - [ - CollectCliffords(min_block_size=2), - AICliffordSynthesis(backend_name="ibm_brisbane"), - ] - ) - - synthesized_circuit = ai_cliffords_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - - -def test_ai_local_clifford_synthesis_with_backend(clifford_circuit, brisbane_backend): - original_circuit = clifford_circuit - - ai_cliffords_synthesis_pass = PassManager( - [ - CollectCliffords(min_block_size=2), - AICliffordSynthesis(backend=brisbane_backend), - ] - ) - - synthesized_circuit = ai_cliffords_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - - -def test_ai_local_clifford_synthesis_with_coupling_map( - clifford_circuit, brisbane_backend -): - original_circuit = clifford_circuit - - backend_coupling_map = brisbane_backend.coupling_map - - ai_cliffords_synthesis_pass = PassManager( - [ - CollectCliffords(min_block_size=2), - AICliffordSynthesis(coupling_map=backend_coupling_map), - ] - ) - - synthesized_circuit = ai_cliffords_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) diff --git a/tests/ai/test_ai_local_linear_function_synthesis.py b/tests/ai/test_ai_local_linear_function_synthesis.py deleted file mode 100644 index 2dbd620..0000000 --- a/tests/ai/test_ai_local_linear_function_synthesis.py +++ /dev/null @@ -1,162 +0,0 @@ -# -*- coding: utf-8 -*- - -# (C) Copyright 2024 IBM. All Rights Reserved. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Testing AI local synthesis for linear functions""" -import pytest -from qiskit import QuantumCircuit -from qiskit.transpiler import PassManager - -from qiskit_ibm_transpiler.ai.collection import CollectLinearFunctions -from qiskit_ibm_transpiler.utils import create_random_linear_function, get_metrics -from qiskit_ibm_transpiler.ai.synthesis import AILinearFunctionSynthesis -from qiskit_ibm_runtime import QiskitRuntimeService - - -@pytest.fixture -def basic_cnot_circuit(): - circuit = QuantumCircuit(3) - circuit.cx(0, 1) - circuit.cx(1, 2) - - return circuit - - -@pytest.fixture -def linear_function_circuit(): - circuit = QuantumCircuit(8) - linear_function = create_random_linear_function(8) - circuit.append(linear_function, range(8)) - # Using decompose since we need a QuantumCircuit, not a LinearFunction. We created an empty - # circuit, so it contains only a LinearFunction - circuit = circuit.decompose(reps=1) - - return circuit - - -@pytest.fixture -def brisbane_backend(): - backend = QiskitRuntimeService().backend("ibm_brisbane") - - return backend - - -def test_ai_local_linear_function_synthesis_wrong_backend(basic_cnot_circuit): - original_circuit = basic_cnot_circuit - - with pytest.raises( - PermissionError, - match=r"User doesn\'t have access to the specified backend: \w+", - ): - ai_linear_functions_synthesis_pass = PassManager( - [ - CollectLinearFunctions(min_block_size=2), - AILinearFunctionSynthesis(backend_name="wrong_backend"), - ] - ) - - ai_linear_functions_synthesis_pass.run(original_circuit) - - -def test_ai_local_linear_function_synthesis_returns_original_circuit( - basic_cnot_circuit, caplog -): - # When the original circuit is better than the synthesized one, we keep the original - - original_circuit = basic_cnot_circuit - - ai_linear_functions_synthesis_pass = PassManager( - [ - CollectLinearFunctions(min_block_size=2), - AILinearFunctionSynthesis(backend_name="ibm_brisbane"), - ] - ) - - synthesized_circuit = ai_linear_functions_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - assert synthesized_circuit == original_circuit - assert "Keeping the original circuit" in caplog.text - - -def test_ai_local_linear_function_synthesis_dont_returns_original_circuit( - basic_cnot_circuit, caplog -): - # When the original circuit is better than the synthesized one, - # but replace_only_if_better is False, we return the synthesized circuit - - original_circuit = basic_cnot_circuit - - ai_linear_functions_synthesis_pass = PassManager( - [ - CollectLinearFunctions(min_block_size=2), - AILinearFunctionSynthesis( - backend_name="ibm_brisbane", replace_only_if_better=False - ), - ] - ) - - synthesized_circuit = ai_linear_functions_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - assert synthesized_circuit == original_circuit - assert "Using the synthesized circuit" in caplog.text - - -def test_ai_local_linear_function_synthesis_with_backend_name(linear_function_circuit): - original_circuit = linear_function_circuit - - ai_linear_functions_synthesis_pass = PassManager( - [ - CollectLinearFunctions(min_block_size=2), - AILinearFunctionSynthesis(backend_name="ibm_brisbane"), - ] - ) - - synthesized_circuit = ai_linear_functions_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - - -def test_ai_local_linear_function_synthesis_with_backend( - linear_function_circuit, brisbane_backend -): - original_circuit = linear_function_circuit - - ai_linear_functions_synthesis_pass = PassManager( - [ - CollectLinearFunctions(min_block_size=2), - AILinearFunctionSynthesis(backend=brisbane_backend), - ] - ) - - synthesized_circuit = ai_linear_functions_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - - -def test_ai_local_linear_function_synthesis_with_coupling_map( - linear_function_circuit, brisbane_backend -): - original_circuit = linear_function_circuit - - backend_coupling_map = brisbane_backend.coupling_map - - ai_linear_functions_synthesis_pass = PassManager( - [ - CollectLinearFunctions(min_block_size=2), - AILinearFunctionSynthesis(coupling_map=backend_coupling_map), - ] - ) - - synthesized_circuit = ai_linear_functions_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) diff --git a/tests/ai/test_ai_local_permutation_synthesis.py b/tests/ai/test_ai_local_permutation_synthesis.py deleted file mode 100644 index ac2a552..0000000 --- a/tests/ai/test_ai_local_permutation_synthesis.py +++ /dev/null @@ -1,138 +0,0 @@ -# -*- coding: utf-8 -*- - -# (C) Copyright 2024 IBM. All Rights Reserved. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Testing AI local synthesis for linear functions""" -import pytest -from qiskit import QuantumCircuit -from qiskit.transpiler import PassManager - -from qiskit_ibm_transpiler.ai.collection import CollectPermutations -from qiskit_ibm_transpiler.ai.synthesis import AIPermutationSynthesis - - -def test_ai_local_permutation_synthesis_wrong_backend(basic_cnot_circuit): - original_circuit = basic_cnot_circuit - - with pytest.raises( - PermissionError, - match=r"User doesn\'t have access to the specified backend: \w+", - ): - ai_permutations_synthesis_pass = PassManager( - [ - CollectPermutations(min_block_size=2), - AIPermutationSynthesis(backend_name="wrong_backend"), - ] - ) - - ai_permutations_synthesis_pass.run(original_circuit) - - -@pytest.mark.skip( - reason="The original circuit doesn't return a DAGCircuit with nodes. We are deciding how the code should behave on this case" -) -def test_ai_local_permutation_synthesis_returns_original_circuit( - basic_cnot_circuit, caplog -): - # When the original circuit is better than the synthesized one, we keep the original - - original_circuit = basic_cnot_circuit - - ai_permutations_synthesis_pass = PassManager( - [ - CollectPermutations(min_block_size=2), - AIPermutationSynthesis(backend_name="ibm_brisbane"), - ] - ) - - synthesized_circuit = ai_permutations_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - assert synthesized_circuit == original_circuit - assert "Keeping the original circuit" in caplog.text - - -@pytest.mark.skip( - reason="The original circuit doesn't return a DAGCircuit with nodes. We are deciding how the code should behave on this case" -) -def test_ai_local_permutation_synthesis_dont_returns_original_circuit( - basic_cnot_circuit, caplog -): - # When the original circuit is better than the synthesized one, - # but replace_only_if_better is False, we return the synthesized circuit - - original_circuit = basic_cnot_circuit - - ai_permutations_synthesis_pass = PassManager( - [ - CollectPermutations(min_block_size=2), - AIPermutationSynthesis( - backend_name="ibm_brisbane", replace_only_if_better=False - ), - ] - ) - - synthesized_circuit = ai_permutations_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - assert synthesized_circuit == original_circuit - assert "Using the synthesized circuit" in caplog.text - - -def test_ai_local_permutation_synthesis_with_backend_name(permutation_circuit_brisbane): - original_circuit = permutation_circuit_brisbane - - ai_permutations_synthesis_pass = PassManager( - [ - CollectPermutations(min_block_size=6), - AIPermutationSynthesis(backend_name="ibm_brisbane"), - ] - ) - - synthesized_circuit = ai_permutations_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - - -def test_ai_local_permutation_synthesis_with_backend( - permutation_circuit_brisbane, brisbane_backend -): - original_circuit = permutation_circuit_brisbane - - ai_permutations_synthesis_pass = PassManager( - [ - CollectPermutations(min_block_size=6), - AIPermutationSynthesis(backend=brisbane_backend), - ] - ) - - synthesized_circuit = ai_permutations_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) - - -def test_ai_local_permutation_synthesis_with_coupling_map( - permutation_circuit_brisbane, brisbane_backend -): - original_circuit = permutation_circuit_brisbane - - backend_coupling_map = brisbane_backend.coupling_map - - ai_permutations_synthesis_pass = PassManager( - [ - CollectPermutations(min_block_size=6), - AIPermutationSynthesis(coupling_map=backend_coupling_map), - ] - ) - - synthesized_circuit = ai_permutations_synthesis_pass.run(original_circuit) - - assert isinstance(synthesized_circuit, QuantumCircuit) diff --git a/tests/ai/test_ai_permutation_synthesis.py b/tests/ai/test_ai_permutation_synthesis.py new file mode 100644 index 0000000..b508502 --- /dev/null +++ b/tests/ai/test_ai_permutation_synthesis.py @@ -0,0 +1,349 @@ +# -*- coding: utf-8 -*- + +# (C) Copyright 2024 IBM. All Rights Reserved. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Unit-testing ai clifford syhtesis for both local and cloud modes""" +import pytest +from qiskit import QuantumCircuit +from qiskit.transpiler import PassManager + +from qiskit_ibm_transpiler.ai.collection import CollectPermutations +from qiskit_ibm_transpiler.ai.synthesis import AIPermutationSynthesis +from qiskit_ibm_runtime import QiskitRuntimeService + + +@pytest.fixture +def basic_cnot_circuit(): + circuit = QuantumCircuit(3) + circuit.cx(0, 1) + circuit.cx(1, 2) + + return circuit + + +@pytest.fixture +def basic_swap_circuit(): + circuit = QuantumCircuit(3) + circuit.swap(0, 1) + circuit.swap(1, 2) + + return circuit + + +# TODO: All the tests that use this circuit keeps the original circuit. Check if this is the better option +# for doing those tests +@pytest.fixture +def permutation_circuit(peekskill_coupling_map_list_format): + circuit = QuantumCircuit(27) + coupling_map = peekskill_coupling_map_list_format + + for i, j in coupling_map: + circuit.h(i) + circuit.cx(i, j) + for i, j in coupling_map: + circuit.swap(i, j) + for i, j in coupling_map: + circuit.h(i) + circuit.cx(i, j) + for i, j in coupling_map[:4]: + circuit.swap(i, j) + for i, j in coupling_map: + circuit.cx(i, j) + + return circuit + + +@pytest.fixture +def brisbane_backend_name(): + return "ibm_brisbane" + + +@pytest.fixture +def brisbane_backend(brisbane_backend_name): + backend = QiskitRuntimeService().backend(brisbane_backend_name) + + return backend + + +@pytest.fixture +def brisbane_coupling_map(brisbane_backend): + return brisbane_backend.coupling_map + + +@pytest.fixture +def brisbane_coupling_map_list_format(brisbane_backend): + return list(brisbane_backend.coupling_map.get_edges()) + + +# TODO: When testing the permutation synthesis with wrong backend, local and cloud behaves differently, +# so we should decide if this is correct or if we want to unify them +def test_ai_local_permutation_synthesis_wrong_backend(basic_swap_circuit): + original_circuit = basic_swap_circuit + + with pytest.raises( + PermissionError, + match=r"User doesn\'t have access to the specified backend: \w+", + ): + ai_permutation_synthesis_pass = PassManager( + [ + CollectPermutations(min_block_size=2), + AIPermutationSynthesis(backend_name="wrong_backend"), + ] + ) + + ai_permutation_synthesis_pass.run(original_circuit) + + +# TODO: Tests pass if we add min_block_size=2, max_block_size=27 to CollectPermutations. If not, tests failed. Confirm why this is happening +def test_ai_cloud_permutation_synthesis_wrong_backend(basic_swap_circuit, caplog): + ai_permutation_synthesis_pass = PassManager( + [ + CollectPermutations(min_block_size=2, max_block_size=27), + AIPermutationSynthesis(backend_name="wrong_backend", local_mode=False), + ] + ) + + ai_optimized_circuit = ai_permutation_synthesis_pass.run(basic_swap_circuit) + + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert ( + "User doesn't have access to the specified backend: wrong_backend" + in caplog.text + ) + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +@pytest.mark.skip( + reason="Unreliable. It passes most of the times with the timeout of 1 second for the current circuits used" +) +def test_ai_cloud_permutation_synthesis_exceed_timeout( + basic_swap_circuit, backend, caplog +): + ai_permutation_synthesis_pass = PassManager( + [ + CollectPermutations(), + AIPermutationSynthesis(backend_name=backend, timeout=1, local_mode=False), + ] + ) + + ai_optimized_circuit = ai_permutation_synthesis_pass.run(basic_swap_circuit) + + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +# TODO: Tests pass if we add min_block_size=2, max_block_size=27 to CollectPermutations. If not, tests failed. Confirm why this is happening +def test_ai_cloud_permutation_synthesis_wrong_token( + basic_swap_circuit, brisbane_backend_name, caplog +): + ai_permutation_synthesis_pass = PassManager( + [ + CollectPermutations(min_block_size=2, max_block_size=27), + AIPermutationSynthesis( + backend_name=brisbane_backend_name, + token="invented_token_2", + local_mode=False, + ), + ] + ) + + ai_optimized_circuit = ai_permutation_synthesis_pass.run(basic_swap_circuit) + + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert "Invalid authentication credentials" in caplog.text + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +# TODO: Tests pass if we add min_block_size=2, max_block_size=27 to CollectPermutations. If not, tests failed. Confirm why this is happening +@pytest.mark.disable_monkeypatch +def test_ai_cloud_permutation_synthesis_wrong_url(basic_swap_circuit, backend, caplog): + ai_permutation_synthesis_pass = PassManager( + [ + CollectPermutations(min_block_size=2, max_block_size=27), + AIPermutationSynthesis( + backend_name=backend, base_url="https://ibm.com/", local_mode=False + ), + ] + ) + + ai_permutation_synthesis_pass.run(basic_swap_circuit) + + assert "Internal error: 404 Client Error:" in caplog.text + assert "Keeping the original circuit" in caplog.text + + +# TODO: When using basic_swap_circuit it works, when using random_circuit_transpiled doesn't. Check why +@pytest.mark.disable_monkeypatch +def test_ai_cloud_permutation_synthesis_unexisting_url( + basic_swap_circuit, backend, caplog +): + ai_permutation_synthesis_pass = PassManager( + [ + CollectPermutations(min_block_size=2, max_block_size=27), + AIPermutationSynthesis( + backend_name=backend, + base_url="https://invented-domain-qiskit-ibm-transpiler-123.com/", + local_mode=False, + ), + ] + ) + + ai_optimized_circuit = ai_permutation_synthesis_pass.run(basic_swap_circuit) + + assert "couldn't synthesize the circuit" in caplog.text + assert "Keeping the original circuit" in caplog.text + assert ( + "Error: HTTPSConnectionPool(host='invented-domain-qiskit-ibm-transpiler-123.com', port=443):" + in caplog.text + ) + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +@pytest.mark.skip( + reason="The original circuit doesn't return a DAGCircuit with nodes. We are deciding how the code should behave on this case" +) +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_permutation_synthesis_always_replace_original_circuit( + basic_cnot_circuit, brisbane_backend_name, caplog, local_mode +): + original_circuit = basic_cnot_circuit + + ai_permutation_synthesis_pass = PassManager( + [ + CollectPermutations(min_block_size=2), + AIPermutationSynthesis( + backend_name=brisbane_backend_name, + replace_only_if_better=False, + local_mode=local_mode, + ), + ] + ) + + ai_optimized_circuit = ai_permutation_synthesis_pass.run(original_circuit) + + assert all(word in caplog.text for word in ["Running", "synthesis"]) + assert "Using the synthesized circuit" in caplog.text + assert isinstance(ai_optimized_circuit, QuantumCircuit) + + +@pytest.mark.skip( + reason="The original circuit doesn't return a DAGCircuit with nodes. We are deciding how the code should behave on this case" +) +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_permutation_synthesis_keep_original_if_better( + basic_cnot_circuit, brisbane_backend_name, caplog, local_mode +): + original_circuit = basic_cnot_circuit + + ai_permutation_synthesis_pass = PassManager( + [ + CollectPermutations(min_block_size=2), + AIPermutationSynthesis( + backend_name=brisbane_backend_name, local_mode=local_mode + ), + ] + ) + + ai_optimized_circuit = ai_permutation_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert ai_optimized_circuit == original_circuit + assert all(word in caplog.text for word in ["Running", "synthesis"]) + assert "Keeping the original circuit" in caplog.text + + +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_permutation_synthesis_pass_with_backend_name( + permutation_circuit, brisbane_backend_name, caplog, local_mode +): + original_circuit = permutation_circuit + + ai_permutation_synthesis_pass = PassManager( + [ + CollectPermutations(), + AIPermutationSynthesis( + backend_name=brisbane_backend_name, local_mode=local_mode + ), + ] + ) + + ai_optimized_circuit = ai_permutation_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert all(word in caplog.text for word in ["Running", "synthesis"]) + + +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +def test_ai_permutation_synthesis_pass_with_backend( + permutation_circuit, brisbane_backend, caplog, local_mode +): + original_circuit = permutation_circuit + + ai_permutation_synthesis_pass = PassManager( + [ + CollectPermutations(), + AIPermutationSynthesis(backend=brisbane_backend, local_mode=local_mode), + ] + ) + + ai_optimized_circuit = ai_permutation_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert all(word in caplog.text for word in ["Running", "synthesis"]) + + +# TODO: The tests pass but some errors are logged. Check this +@pytest.mark.parametrize( + "local_mode", + [None, "true", "false"], + ids=["default_local_mode", "specify_local_mode", "specify_cloud_mode"], +) +@pytest.mark.parametrize( + "coupling_map", + [brisbane_coupling_map, brisbane_coupling_map_list_format], + indirect=True, + ids=["coupling_map_object", "coupling_map_list"], +) +def test_ai_permutation_synthesis_pass_with_coupling_map( + permutation_circuit, caplog, local_mode, coupling_map +): + original_circuit = permutation_circuit + + ai_permutation_synthesis_pass = PassManager( + [ + CollectPermutations(min_block_size=2), + AIPermutationSynthesis(coupling_map=coupling_map, local_mode=local_mode), + ] + ) + + ai_optimized_circuit = ai_permutation_synthesis_pass.run(original_circuit) + + assert isinstance(ai_optimized_circuit, QuantumCircuit) + assert all(word in caplog.text for word in ["Running", "synthesis"]) diff --git a/tests/ai/test_clifford_ai.py b/tests/ai/test_clifford_ai.py deleted file mode 100644 index 97c495e..0000000 --- a/tests/ai/test_clifford_ai.py +++ /dev/null @@ -1,137 +0,0 @@ -# -*- coding: utf-8 -*- - -# (C) Copyright 2024 IBM. All Rights Reserved. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Unit-testing clifford_ai""" -import pytest -from qiskit import QuantumCircuit -from qiskit.transpiler import PassManager - -from qiskit_ibm_transpiler.ai.collection import CollectCliffords -from qiskit_ibm_transpiler.ai.synthesis import AICliffordSynthesis - - -def test_clifford_wrong_backend(random_circuit_transpiled, caplog): - ai_optimize_cliff = PassManager( - [ - CollectCliffords(), - AICliffordSynthesis(backend_name="wrong_backend", local_mode=False), - ] - ) - ai_optimized_circuit = ai_optimize_cliff.run(random_circuit_transpiled) - - assert "couldn't synthesize the circuit" in caplog.text - assert "Keeping the original circuit" in caplog.text - assert ( - "User doesn't have access to the specified backend: wrong_backend" - in caplog.text - ) - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -@pytest.mark.skip( - reason="Unreliable. It passes most of the times with the timeout of 1 second for the current circuits used" -) -def test_clifford_exceed_timeout(random_circuit_transpiled, backend, caplog): - ai_optimize_cliff = PassManager( - [ - CollectCliffords(), - AICliffordSynthesis(backend_name=backend, timeout=1, local_mode=False), - ] - ) - ai_optimized_circuit = ai_optimize_cliff.run(random_circuit_transpiled) - assert "couldn't synthesize the circuit" in caplog.text - assert "Keeping the original circuit" in caplog.text - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -def test_clifford_wrong_token(random_circuit_transpiled, backend, caplog): - ai_optimize_cliff = PassManager( - [ - CollectCliffords(), - AICliffordSynthesis( - backend_name=backend, token="invented_token_2", local_mode=False - ), - ] - ) - ai_optimized_circuit = ai_optimize_cliff.run(random_circuit_transpiled) - assert "couldn't synthesize the circuit" in caplog.text - assert "Keeping the original circuit" in caplog.text - assert "Invalid authentication credentials" in caplog.text - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -@pytest.mark.disable_monkeypatch -def test_clifford_wrong_url(random_circuit_transpiled, backend, caplog): - ai_optimize_cliff = PassManager( - [ - CollectCliffords(), - AICliffordSynthesis( - backend_name=backend, base_url="https://ibm.com/", local_mode=False - ), - ] - ) - ai_optimized_circuit = ai_optimize_cliff.run(random_circuit_transpiled) - assert "Internal error: 404 Client Error:" in caplog.text - assert "Keeping the original circuit" in caplog.text - - -@pytest.mark.disable_monkeypatch -def test_clifford_unexisting_url(random_circuit_transpiled, backend, caplog): - ai_optimize_cliff = PassManager( - [ - CollectCliffords(), - AICliffordSynthesis( - backend_name=backend, - base_url="https://invented-domain-qiskit-ibm-transpiler-123.com/", - local_mode=False, - ), - ] - ) - ai_optimized_circuit = ai_optimize_cliff.run(random_circuit_transpiled) - assert "couldn't synthesize the circuit" in caplog.text - assert "Keeping the original circuit" in caplog.text - assert ( - "Error: HTTPSConnectionPool(host='invented-domain-qiskit-ibm-transpiler-123.com', port=443):" - in caplog.text - ) - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -def test_clifford_function(random_circuit_transpiled, backend): - ai_optimize_cliff = PassManager( - [ - CollectCliffords(), - AICliffordSynthesis(backend_name=backend, local_mode=False), - ] - ) - ai_optimized_circuit = ai_optimize_cliff.run(random_circuit_transpiled) - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -# TODO: Look for a better way to parametrize coupling maps -@pytest.mark.parametrize( - "use_coupling_map_as_list", [True, False], ids=["as_list", "as_object"] -) -def test_clifford_function_with_coupling_map( - random_circuit_transpiled, coupling_map, use_coupling_map_as_list -): - coupling_map_to_send = ( - list(coupling_map.get_edges()) if use_coupling_map_as_list else coupling_map - ) - ai_optimize_cliff = PassManager( - [ - CollectCliffords(), - AICliffordSynthesis(coupling_map=coupling_map_to_send, local_mode=False), - ] - ) - ai_optimized_circuit = ai_optimize_cliff.run(random_circuit_transpiled) - assert isinstance(ai_optimized_circuit, QuantumCircuit) diff --git a/tests/ai/test_linear_function_ai.py b/tests/ai/test_linear_function_ai.py deleted file mode 100644 index c9a6a22..0000000 --- a/tests/ai/test_linear_function_ai.py +++ /dev/null @@ -1,165 +0,0 @@ -# -*- coding: utf-8 -*- - -# (C) Copyright 2024 IBM. All Rights Reserved. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Unit-testing linear_function_ai""" -import pytest -from qiskit import QuantumCircuit -from qiskit.transpiler import PassManager - -from qiskit_ibm_transpiler.ai.collection import CollectLinearFunctions -from qiskit_ibm_transpiler.ai.synthesis import AILinearFunctionSynthesis - - -def test_linear_function_wrong_backend(random_circuit_transpiled, caplog): - ai_optimize_lf = PassManager( - [ - CollectLinearFunctions(), - AILinearFunctionSynthesis(backend_name="wrong_backend", local_mode=False), - ] - ) - ai_optimized_circuit = ai_optimize_lf.run(random_circuit_transpiled) - - assert "couldn't synthesize the circuit" in caplog.text - assert "Keeping the original circuit" in caplog.text - assert ( - "User doesn't have access to the specified backend: wrong_backend" - in caplog.text - ) - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -@pytest.mark.skip( - reason="Unreliable. It passes most of the times with the timeout of 1 second for the current circuits used" -) -def test_linear_function_exceed_timeout(random_circuit_transpiled, backend, caplog): - ai_optimize_lf = PassManager( - [ - CollectLinearFunctions(), - AILinearFunctionSynthesis( - backend_name=backend, timeout=1, local_mode=False - ), - ] - ) - ai_optimized_circuit = ai_optimize_lf.run(random_circuit_transpiled) - assert "couldn't synthesize the circuit" in caplog.text - assert "Keeping the original circuit" in caplog.text - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -@pytest.mark.skip( - reason="Unreliable many times. We'll research why it fails sporadically" -) -def test_linear_function_wrong_token(random_circuit_transpiled, backend, caplog): - ai_optimize_lf = PassManager( - [ - CollectLinearFunctions(), - AILinearFunctionSynthesis( - backend_name=backend, token="invented_token_2", local_mode=False - ), - ] - ) - ai_optimized_circuit = ai_optimize_lf.run(random_circuit_transpiled) - assert "couldn't synthesize the circuit" in caplog.text - assert "Keeping the original circuit" in caplog.text - assert "Invalid authentication credentials" in caplog.text - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -@pytest.mark.skip( - reason="Unreliable many times. We'll research why it fails sporadically" -) -@pytest.mark.disable_monkeypatch -def test_linear_function_wrong_url(random_circuit_transpiled, backend): - ai_optimize_lf = PassManager( - [ - CollectLinearFunctions(), - AILinearFunctionSynthesis( - backend_name=backend, base_url="https://ibm.com/", local_mode=False - ), - ] - ) - try: - ai_optimized_circuit = ai_optimize_lf.run(random_circuit_transpiled) - pytest.fail("Error expected") - except Exception as e: - assert "Expecting value: line 1 column 1 (char 0)" in str(e) - assert type(e).__name__ == "JSONDecodeError" - - -@pytest.mark.skip( - reason="Unreliable many times. We'll research why it fails sporadically" -) -@pytest.mark.disable_monkeypatch -def test_linear_function_unexisting_url(random_circuit_transpiled, backend, caplog): - ai_optimize_lf = PassManager( - [ - CollectLinearFunctions(), - AILinearFunctionSynthesis( - backend_name=backend, - base_url="https://invented-domain-qiskit-ibm-transpiler-123.com/", - local_mode=False, - ), - ] - ) - ai_optimized_circuit = ai_optimize_lf.run(random_circuit_transpiled) - assert "couldn't synthesize the circuit" in caplog.text - assert "Keeping the original circuit" in caplog.text - assert ( - "Error: HTTPSConnectionPool(host='invented-domain-qiskit-ibm-transpiler-123.com', port=443):" - in caplog.text - ) - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -def test_linear_always_replace(backend, caplog): - orig_qc = QuantumCircuit(3) - orig_qc.cx(0, 1) - orig_qc.cx(1, 2) - ai_optimize_lf = PassManager( - [ - CollectLinearFunctions(), - AILinearFunctionSynthesis( - backend_name=backend, replace_only_if_better=False, local_mode=False - ), - ] - ) - ai_optimized_circuit = ai_optimize_lf.run(orig_qc) - assert "Keeping the original circuit" not in caplog.text - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -def test_linear_function_only_replace_if_better(backend, caplog): - orig_qc = QuantumCircuit(3) - orig_qc.cx(0, 1) - orig_qc.cx(1, 2) - ai_optimize_lf = PassManager( - [ - CollectLinearFunctions(min_block_size=2), - AILinearFunctionSynthesis(backend_name=backend, local_mode=False), - ] - ) - ai_optimized_circuit = ai_optimize_lf.run(orig_qc) - assert ai_optimized_circuit == orig_qc - assert "Keeping the original circuit" in caplog.text - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -def test_linear_function_pass(random_circuit_transpiled, backend, caplog): - ai_optimize_lf = PassManager( - [ - CollectLinearFunctions(), - AILinearFunctionSynthesis(backend_name=backend, local_mode=False), - ] - ) - ai_optimized_circuit = ai_optimize_lf.run(random_circuit_transpiled) - - assert isinstance(ai_optimized_circuit, QuantumCircuit) diff --git a/tests/ai/test_permutation_ai.py b/tests/ai/test_permutation_ai.py deleted file mode 100644 index 5353dcd..0000000 --- a/tests/ai/test_permutation_ai.py +++ /dev/null @@ -1,178 +0,0 @@ -# -*- coding: utf-8 -*- - -# (C) Copyright 2024 IBM. All Rights Reserved. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Unit-testing permutation_ai""" -import pytest -from qiskit import QuantumCircuit -from qiskit.transpiler import PassManager -from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager - -from qiskit_ibm_transpiler.ai.collection import CollectPermutations -from qiskit_ibm_transpiler.ai.synthesis import AIPermutationSynthesis - - -@pytest.fixture -def permutations_circuit(backend_27q, cmap_backend): - coupling_map = cmap_backend[backend_27q] - cmap = list(coupling_map.get_edges()) - orig_qc = QuantumCircuit(27) - for i, j in cmap: - orig_qc.h(i) - orig_qc.cx(i, j) - for i, j in cmap: - orig_qc.swap(i, j) - for i, j in cmap: - orig_qc.h(i) - orig_qc.cx(i, j) - for i, j in cmap[:4]: - orig_qc.swap(i, j) - for i, j in cmap: - orig_qc.cx(i, j) - return orig_qc - - -def test_permutation_wrong_backend(caplog): - orig_qc = QuantumCircuit(3) - orig_qc.swap(0, 1) - orig_qc.swap(1, 2) - - ai_optimize_perm = PassManager( - [ - CollectPermutations(min_block_size=2, max_block_size=27), - AIPermutationSynthesis(backend_name="wrong_backend", local_mode=False), - ] - ) - ai_optimized_circuit = ai_optimize_perm.run(orig_qc) - assert "couldn't synthesize the circuit" in caplog.text - assert "Keeping the original circuit" in caplog.text - assert ( - "User doesn't have access to the specified backend: wrong_backend" - in caplog.text - ) - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -@pytest.mark.skip( - reason="Unreliable. It passes most of the times with the timeout of 1 second for the current circuits used" -) -def test_permutation_exceed_timeout(random_circuit_transpiled, backend_27q, caplog): - ai_optimize_perm = PassManager( - [ - CollectPermutations(min_block_size=2, max_block_size=27), - AIPermutationSynthesis( - backend_name=backend_27q, timeout=1, local_mode=False - ), - ] - ) - ai_optimized_circuit = ai_optimize_perm.run(random_circuit_transpiled) - assert "couldn't synthesize the circuit" in caplog.text - assert "Keeping the original circuit" in caplog.text - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -@pytest.mark.skip( - reason="Unreliable many times. We'll research why it fails sporadically" -) -def test_permutation_wrong_token(random_circuit_transpiled, backend_27q, caplog): - ai_optimize_perm = PassManager( - [ - CollectPermutations(min_block_size=2, max_block_size=27), - AIPermutationSynthesis( - backend_name=backend_27q, token="invented_token_2", local_mode=False - ), - ] - ) - ai_optimized_circuit = ai_optimize_perm.run(random_circuit_transpiled) - assert "Invalid authentication credentials" in caplog.text - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -@pytest.mark.skip( - reason="Unreliable many times. We'll research why it fails sporadically" -) -@pytest.mark.disable_monkeypatch -def test_permutation_wrong_url(random_circuit_transpiled, backend_27q): - ai_optimize_perm = PassManager( - [ - CollectPermutations(min_block_size=2, max_block_size=27), - AIPermutationSynthesis( - backend_name=backend_27q, base_url="https://ibm.com/", local_mode=False - ), - ] - ) - try: - ai_optimized_circuit = ai_optimize_perm.run(random_circuit_transpiled) - pytest.fail("Error expected") - except Exception as e: - assert "Expecting value: line 1 column 1 (char 0)" in str(e) - assert type(e).__name__ == "JSONDecodeError" - - -@pytest.mark.skip( - reason="Unreliable many times. We'll research why it fails sporadically" -) -@pytest.mark.disable_monkeypatch -def test_permutation_unexisting_url(random_circuit_transpiled, backend_27q, caplog): - ai_optimize_perm = PassManager( - [ - CollectPermutations(min_block_size=2, max_block_size=27), - AIPermutationSynthesis( - backend_name=backend_27q, - base_url="https://invented-domain-qiskit-ibm-transpiler-123.com/", - local_mode=False, - ), - ] - ) - ai_optimized_circuit = ai_optimize_perm.run(random_circuit_transpiled) - assert "couldn't synthesize the circuit" in caplog.text - assert "Keeping the original circuit" in caplog.text - assert ( - "Error: HTTPSConnectionPool(host='invented-domain-qiskit-ibm-transpiler-123.com', port=443):" - in caplog.text - ) - assert isinstance(ai_optimized_circuit, QuantumCircuit) - - -def test_permutation_collector(permutations_circuit, backend_27q, cmap_backend): - qiskit_lvl3_transpiler = generate_preset_pass_manager( - optimization_level=1, coupling_map=cmap_backend[backend_27q] - ) - permutations_circuit = qiskit_lvl3_transpiler.run(permutations_circuit) - - pm = PassManager( - [ - CollectPermutations(max_block_size=27), - ] - ) - perm_only_circ = pm.run(permutations_circuit) - from qiskit.converters import circuit_to_dag - - dag = circuit_to_dag(perm_only_circ) - perm_nodes = dag.named_nodes("permutation", "Permutation") - assert len(perm_nodes) == 2 - assert perm_nodes[0].op.num_qubits == 27 - assert perm_nodes[1].op.num_qubits == 4 - assert not dag.named_nodes("linear_function", "Linear_function") - assert not dag.named_nodes("clifford", "Clifford") - - -def test_permutation_pass(permutations_circuit, backend_27q, caplog): - - ai_optimize_perm = PassManager( - [ - CollectPermutations(max_block_size=27), - AIPermutationSynthesis(backend_name=backend_27q, local_mode=False), - ] - ) - ai_optimized_circuit = ai_optimize_perm.run(permutations_circuit) - assert "Requesting synthesis to the service" in caplog.text - assert isinstance(ai_optimized_circuit, QuantumCircuit) diff --git a/tests/ai/test_permutation_collection.py b/tests/ai/test_permutation_collection.py index cd82de5..fca9380 100644 --- a/tests/ai/test_permutation_collection.py +++ b/tests/ai/test_permutation_collection.py @@ -11,10 +11,32 @@ # that they have been altered from the originals. """Unit-testing linear_function_collection""" +import pytest from qiskit import QuantumCircuit from qiskit.transpiler import PassManager from qiskit_ibm_transpiler.ai.collection import CollectPermutations +from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager + + +@pytest.fixture +def permutations_circuit(backend_27q, cmap_backend): + coupling_map = cmap_backend[backend_27q] + cmap = list(coupling_map.get_edges()) + orig_qc = QuantumCircuit(27) + for i, j in cmap: + orig_qc.h(i) + orig_qc.cx(i, j) + for i, j in cmap: + orig_qc.swap(i, j) + for i, j in cmap: + orig_qc.h(i) + orig_qc.cx(i, j) + for i, j in cmap[:4]: + orig_qc.swap(i, j) + for i, j in cmap: + orig_qc.cx(i, j) + return orig_qc def test_permutation_collection_pass(random_circuit_transpiled): @@ -74,3 +96,26 @@ def test_permutation_collection_min_block_size(swap_circ): len(g.qubits) >= 7 or g.operation.name.lower() != "permutation" for g in collected_circuit ) + + +def test_permutation_collector(permutations_circuit, backend_27q, cmap_backend): + qiskit_lvl3_transpiler = generate_preset_pass_manager( + optimization_level=1, coupling_map=cmap_backend[backend_27q] + ) + permutations_circuit = qiskit_lvl3_transpiler.run(permutations_circuit) + + pm = PassManager( + [ + CollectPermutations(max_block_size=27), + ] + ) + perm_only_circ = pm.run(permutations_circuit) + from qiskit.converters import circuit_to_dag + + dag = circuit_to_dag(perm_only_circ) + perm_nodes = dag.named_nodes("permutation", "Permutation") + assert len(perm_nodes) == 2 + assert perm_nodes[0].op.num_qubits == 27 + assert perm_nodes[1].op.num_qubits == 4 + assert not dag.named_nodes("linear_function", "Linear_function") + assert not dag.named_nodes("clifford", "Clifford") diff --git a/tests/conftest.py b/tests/conftest.py index aac2f77..f4693ef 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -58,6 +58,13 @@ def backend_27q(): return "ibm_peekskill" +@pytest.fixture(scope="module") +def peekskill_coupling_map_list_format(backend_27q, cmap_backend): + coupling_map = cmap_backend[backend_27q] + + return list(coupling_map.get_edges()) + + @pytest.fixture(scope="module") def coupling_map(): return FakeQuebec().coupling_map