Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translation function for qubit operators #196

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions tangelo/linq/tests/data/H2_JW_occfirst.data
Original file line number Diff line number Diff line change
@@ -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]
11 changes: 8 additions & 3 deletions tangelo/linq/tests/test_simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,31 @@
# Simple circuit for superposition, also tells us qubit ordering as well immediately from the statevector
# probabilities : |00> = 0.5 |01> = 0.5
circuit1 = Circuit([Gate("H", 0)], n_qubits=2)

# 2-qubit circuit checking all the basic gates that are not defined up to a convention (e.g unambiguous)
mygates = [Gate("H", 0), Gate("S", 0), Gate("X", 0), Gate("T", 1), Gate("Y", 1), Gate("Z", 1)]
mygates += [Gate("CNOT", 1, control=0)]
circuit2 = Circuit(mygates)

# Circuit for the parametrized rotation gates Rx and Ry. Some convention about the sign of theta or a phase may appear
circuit3 = Circuit([Gate("RX", 0, parameter=2.), Gate("RY", 1, parameter=-1.)])

# Circuit for the parametrized rotation gate Rz. Some convention about the sign of theta or a phase may appear
circuit4 = Circuit([Gate("RZ", 0, parameter=2.)], n_qubits=2)

# Circuit that tests all gates that are supported on all simulators
init_gates = [Gate('H', 0), Gate('X', 1), Gate('H', 2)]
one_qubit_gate_names = ["H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "PHASE"]
one_qubit_gates = [Gate(name, target=0) if name not in PARAMETERIZED_GATES else Gate(name, target=0, parameter=0.5)
for name in one_qubit_gate_names]
for name in one_qubit_gate_names]
one_qubit_gates += [Gate(name, target=1) if name not in PARAMETERIZED_GATES else Gate(name, target=1, parameter=0.2)
for name in one_qubit_gate_names]
for name in one_qubit_gate_names]
two_qubit_gate_names = ["CNOT", "CH", "CX", "CY", "CZ", "CRX", "CRY", "CRZ", "CPHASE"]
two_qubit_gates = [Gate(name, target=1, control=0) if name not in PARAMETERIZED_GATES
else Gate(name, target=1, control=0, parameter=0.5) for name in two_qubit_gate_names]
else Gate(name, target=1, control=0, parameter=0.5) for name in two_qubit_gate_names]
swap_gates = [Gate('SWAP', target=[1, 0]), Gate('CSWAP', target=[1, 2], control=0)]
circuit5 = Circuit(init_gates + one_qubit_gates + two_qubit_gates + swap_gates)

# Circuit preparing a mixed-state (e.g containing a MEASURE instruction in the middle of the circuit)
circuit_mixed = Circuit([Gate("RX", 0, parameter=2.), Gate("RY", 1, parameter=-1.), Gate("MEASURE", 0), Gate("X", 0)])

Expand Down
112 changes: 112 additions & 0 deletions tangelo/linq/tests/test_translator_qubitop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# 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 time
from itertools import product

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 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")

@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)

@unittest.skipIf("qiskit" not in installed_backends, "Test Skipped: Qiskit not available \n")
def test_tangelo_to_qiskit_big(self):
"""Test translation from a tangelo to a qiskit operator, for a large input"""

n_qubits = 10
n_terms = 3**n_qubits

# Build large operator made of all possible "full" Pauli words (no 'I') of length n_qubits
terms = {tuple(zip(range(n_qubits), pw)): 1.0 for pw in product(['X', 'Y', 'Z'], repeat=n_qubits)}
q_op = QubitOperator()
q_op.terms = terms

s, t = "tangelo", "qiskit"
tstart1 = time.time()
tmp_op = translate_operator(q_op, source=s, target=t)
tstop1 = time.time()
print(f"Qubit operator conversion {s} to {t}: {tstop1 - tstart1:.1f} (terms = {n_terms})")

t, s = s, t
tstart2 = time.time()
q_op2 = translate_operator(tmp_op, source=s, target=t)
tstop2 = time.time()
print(f"Qubit operator conversion {s} to {t}: {tstop2 - tstart2:.1f} (terms = {n_terms})")

assert(q_op == q_op2)


if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions tangelo/linq/translator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .translate_qdk import translate_qsharp, get_qdk_gates
from .translate_projectq import translate_projectq, _translate_projectq2abs, get_projectq_gates
from .translate_openqasm import translate_openqasm, _translate_openqasm2abs, get_openqasm_gates
from .translate_qubitop import translate_operator


# List all supported gates for all backends found
Expand Down
65 changes: 63 additions & 2 deletions tangelo/linq/translator/translate_qiskit.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@
# 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:
- how the gate names differ between the source backend to the target backend.
- how the order and conventions for some of the inputs to the gate operations
may also differ.

The module also enables bidirectional conversion between qiskit and Tangelo
qubit operators (linear combination of Pauli operators)
"""

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
Expand Down Expand Up @@ -98,3 +104,58 @@ def translate_qiskit(source_circuit):
else:
raise ValueError(f"Gate '{gate.name}' not supported on backend qiskit")
return target_circuit


def translate_op_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 translate_op_from_qiskit(qubit_operator):
"""Helper function to translate 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
59 changes: 59 additions & 0 deletions tangelo/linq/translator/translate_qubitop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# 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 translate_op_from_qiskit, translate_op_to_qiskit


FROM_TANGELO = {
"qiskit": translate_op_to_qiskit
}

TO_TANGELO = {
"qiskit": translate_op_from_qiskit
}


def translate_operator(qubit_operator, source, target, n_qubits=None):
"""Function to convert a qubit operator defined within the "source" format
to a "target" format.

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:
(operator in target format): Translated qubit operator.
"""

source = source.lower()
target = target.lower()

if source == target:
return qubit_operator
if source != "tangelo":
if source not in TO_TANGELO:
raise NotImplementedError(f"Qubit operator conversion from {source} to {target} is not supported.")
qubit_operator = TO_TANGELO[source](qubit_operator)
if target != "tangelo":
if target not in FROM_TANGELO:
raise NotImplementedError(f"Qubit operator conversion from {source} to {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