From 038c500ae6bd37c1d5e4cd3ec64d590bae9e877a Mon Sep 17 00:00:00 2001 From: AlexandreF-1qbit <76115575+AlexandreF-1qbit@users.noreply.github.com> Date: Thu, 18 Aug 2022 16:34:00 -0400 Subject: [PATCH] New support for other to other. --- tangelo/linq/tests/data/H2_JW_occfirst.data | 16 ++++ tangelo/linq/tests/test_translator_qubitop.py | 90 +++++++++++++++++++ tangelo/linq/translator/translate_qiskit.py | 62 ++++++++++++- tangelo/linq/translator/translate_qubitop.py | 58 ++++++++++++ 4 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 tangelo/linq/tests/data/H2_JW_occfirst.data create mode 100644 tangelo/linq/tests/test_translator_qubitop.py create mode 100644 tangelo/linq/translator/translate_qubitop.py diff --git a/tangelo/linq/tests/data/H2_JW_occfirst.data b/tangelo/linq/tests/data/H2_JW_occfirst.data new file mode 100644 index 000000000..0902dc2f8 --- /dev/null +++ b/tangelo/linq/tests/data/H2_JW_occfirst.data @@ -0,0 +1,16 @@ +QubitOperator: +(-0.10973055606700678+0j) [] + +(-0.0454428841443262+0j) [X0 X1 Y2 Y3] + +(0.0454428841443262+0j) [X0 Y1 Y2 X3] + +(0.0454428841443262+0j) [Y0 X1 X2 Y3] + +(-0.0454428841443262+0j) [Y0 Y1 X2 X3] + +(0.16988452027940382+0j) [Z0] + +(0.16821198673715723+0j) [Z0 Z1] + +(0.12005143072546026+0j) [Z0 Z2] + +(0.16549431486978647+0j) [Z0 Z3] + +(0.16988452027940382+0j) [Z1] + +(0.16549431486978647+0j) [Z1 Z2] + +(0.12005143072546026+0j) [Z1 Z3] + +(-0.21886306781219628+0j) [Z2] + +(0.17395378776494128+0j) [Z2 Z3] + +(-0.21886306781219633+0j) [Z3] \ No newline at end of file diff --git a/tangelo/linq/tests/test_translator_qubitop.py b/tangelo/linq/tests/test_translator_qubitop.py new file mode 100644 index 000000000..bdaac11cd --- /dev/null +++ b/tangelo/linq/tests/test_translator_qubitop.py @@ -0,0 +1,90 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import unittest + +import numpy as np +from openfermion.utils import load_operator +from openfermion.linalg import eigenspectrum + +from tangelo.helpers.utils import installed_backends +from tangelo.linq.translator.translate_qubitop import translate_operator +from tangelo.toolboxes.operators import QubitOperator + +# For openfermion.load_operator function. +pwd_this_test = os.path.dirname(os.path.abspath(__file__)) + +tangelo_op = QubitOperator("X0 Y1 Z2", 1.) + + +class TranslateOperatorTest(unittest.TestCase): + + def test_unsupported_source(self): + """Test error with an unsuported source.""" + + with self.assertRaises(NotImplementedError): + translate_operator(tangelo_op, source="sourcenotsupported", target="tangelo") + + def test_unsupported_target(self): + """Test error with an unsuported target.""" + + with self.assertRaises(NotImplementedError): + translate_operator(tangelo_op, source="tangelo", target="targetnotsupported") + + def test_tangelo_not_involved(self): + """Test error if tangelo is not the source nor the target.""" + + with self.assertRaises(NotImplementedError): + translate_operator(tangelo_op, source="nottangelo", target="nottangeloeither") + + @unittest.skipIf("qiskit" not in installed_backends, "Test Skipped: Qiskit not available \n") + def test_qiskit_to_tangelo(self): + """Test translation from a qiskit to a tangelo operator.""" + + from qiskit.opflow.primitive_ops import PauliSumOp + qiskit_op = PauliSumOp.from_list([("ZYX", 1.)]) + + test_op = translate_operator(qiskit_op, source="qiskit", target="tangelo") + self.assertEqual(test_op, tangelo_op) + + @unittest.skipIf("qiskit" not in installed_backends, "Test Skipped: Qiskit not available \n") + def test_tangelo_to_qiskit(self): + """Test translation from a tangelo to a qiskit operator.""" + + from qiskit.opflow.primitive_ops import PauliSumOp + qiskit_op = PauliSumOp.from_list([("ZYX", 1.)]) + + test_op = translate_operator(tangelo_op, source="tangelo", target="qiskit") + self.assertEqual(qiskit_op, test_op) + + @unittest.skipIf("qiskit" not in installed_backends, "Test Skipped: Qiskit not available \n") + def test_tangelo_to_qiskit_H2_eigenvalues(self): + """Test eigenvalues resulting from a tangelo to qiskit translation.""" + + from qiskit.algorithms import NumPyEigensolver + + qu_op = load_operator("H2_JW_occfirst.data", data_directory=pwd_this_test+"/data", plain_text=True) + test_op = translate_operator(qu_op, source="tangelo", target="qiskit") + + eigenvalues_tangelo = eigenspectrum(qu_op) + + qiskit_solver = NumPyEigensolver(2**4) + eigenvalues_qiskit = qiskit_solver.compute_eigenvalues(test_op) + + np.testing.assert_array_almost_equal(eigenvalues_tangelo, eigenvalues_qiskit.eigenvalues) + + +if __name__ == "__main__": + unittest.main() diff --git a/tangelo/linq/translator/translate_qiskit.py b/tangelo/linq/translator/translate_qiskit.py index eb972bd53..182426c80 100644 --- a/tangelo/linq/translator/translate_qiskit.py +++ b/tangelo/linq/translator/translate_qiskit.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Functions helping with quantum circuit format conversion between abstract -format and qiskit format. +"""Functions helping with quantum circuit and operator format conversion between +Tangelo format and qiskit format. In order to produce an equivalent circuit for the target backend, it is necessary to account for: @@ -22,6 +22,9 @@ may also differ. """ +from tangelo.toolboxes.operators import QubitOperator +from tangelo.linq.helpers import pauli_of_to_string, pauli_string_to_of + def get_qiskit_gates(): """Map gate name of the abstract format to the equivalent add_gate method of @@ -98,3 +101,58 @@ def translate_qiskit(source_circuit): else: raise ValueError(f"Gate '{gate.name}' not supported on backend qiskit") return target_circuit + + +def quop_tangelo_to_qiskit(qubit_operator, n_qubits): + """Helper function to translate a Tangelo QubitOperator to a qiskit + PauliSumOp. Qiskit must be installed for the function to work. + + Args: + qubit_operator (tangelo.toolboxes.operators.QubitOperator): Self-explanatory. + n_qubits (int): Number of qubits relevant to the operator. + + Returns: + (qiskit.opflow.primitive_ops.PauliSumOp): Qiskit qubit operator. + """ + + # Import qiskit qubit operator. + from qiskit.opflow.primitive_ops import PauliSumOp + + # Convert each term sequencially. + term_list = list() + for term_tuple, coeff in qubit_operator.terms.items(): + term_string = pauli_of_to_string(term_tuple, n_qubits) + + # Reverse the string because of qiskit convention. + term_list += [(term_string[::-1], coeff)] + + return PauliSumOp.from_list(term_list) + + +def quop_qiskit_to_tangelo(qubit_operator): + """Helper function to translate a a qiskit PauliSumOp to a Tangelo + QubitOperator. + + Args: + qubit_operator (qiskit.opflow.primitive_ops.PauliSumOp): Self-explanatory. + + Returns: + (tangelo.toolboxes.operators.QubitOperator): Tangelo qubit operator. + """ + + # Create a dictionary to append all terms at once. + terms_dict = dict() + for pauli_word in qubit_operator: + # Inversion of the string because of qiskit ordering. + term_string = pauli_word.to_pauli_op().primitive.to_label()[::-1] + term_tuple = pauli_string_to_of(term_string) + terms_dict[tuple(term_tuple)] = pauli_word.coeff + + # Create and copy the information into a new QubitOperator. + tangelo_op = QubitOperator() + tangelo_op.terms = terms_dict + + # Clean the QubitOperator. + tangelo_op.compress() + + return tangelo_op diff --git a/tangelo/linq/translator/translate_qubitop.py b/tangelo/linq/translator/translate_qubitop.py new file mode 100644 index 000000000..ce5b396d5 --- /dev/null +++ b/tangelo/linq/translator/translate_qubitop.py @@ -0,0 +1,58 @@ +# Copyright 2021 Good Chemistry Company. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module to convert qubit operators to different formats.""" + +from tangelo.toolboxes.operators import count_qubits +from tangelo.linq.translator.translate_qiskit import quop_qiskit_to_tangelo, quop_tangelo_to_qiskit + + +FROM_TANGELO = { + "qiskit": quop_tangelo_to_qiskit +} + +TO_TANGELO = { + "qiskit": quop_qiskit_to_tangelo +} + + +def translate_operator(qubit_operator, source, target, n_qubits=None): + """Function to convert a qubit operator defined within the "source" format + to another format. Only the translation from and to tangelo are currently + supported. + + Args: + qubit_operator (source format): Self-explanatory. + source (string): Identifier for the source format. + target (string): Identifier for the target format. + n_qubits (int): Number of qubits relevant to the operator. + + Returns: + (target format): Translated qubit operator. + """ + + source = source.lower() + target = target.lower() + + if source != "tangelo": + if source not in TO_TANGELO: + raise NotImplementedError(f"Source {source} is not supported.") + qubit_operator = TO_TANGELO[source](qubit_operator) + if target != "tangelo": + if target not in FROM_TANGELO: + raise NotImplementedError(f"Target {target} is not supported.") + n_qubits = count_qubits(qubit_operator) if n_qubits is None else n_qubits + qubit_operator = FROM_TANGELO[target](qubit_operator, n_qubits) + + return qubit_operator