Skip to content

Commit

Permalink
added inverse function to Circuit (#78)
Browse files Browse the repository at this point in the history
* Inverse function for Gate and Circuit class
* Added phase gate support to allow T,S inverse
* added invertible_gates list
  • Loading branch information
JamesB-1qbit authored Dec 15, 2021
1 parent 2fc120f commit af2da55
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 6 deletions.
11 changes: 11 additions & 0 deletions tangelo/linq/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,5 +130,16 @@ def check_index_valid(index):
# Keep track of the total gate count
self._gate_counts[gate.name] = self._gate_counts.get(gate.name, 0) + 1

def inverse(self):
"""Return the inverse (adjoint) of a circuit
This is performed by reversing the Gate order and applying the inverse to each Gate.
Returns:
Circuit: the inverted circuit
"""
gate_list = [gate.inverse() for gate in reversed(self._gates)]
return Circuit(gate_list)

def serialize(self):
return {"type": "QuantumCircuit", "gates": [gate.serialize() for gate in self._gates]}
35 changes: 33 additions & 2 deletions tangelo/linq/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@
gate operation, without tying it to a particular backend or an underlying
mathematical operation.
"""

from math import pi
from typing import Union

from numpy import integer, ndarray
from numpy import integer, ndarray, floating

ONE_QUBIT_GATES = {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "PHASE"}
TWO_QUBIT_GATES = {"CNOT", "CX", "CY", "CZ", "CRX", "CRY", "CRZ", "CPHASE", "XX", "SWAP"}
THREE_QUBIT_GATES = {"CSWAP"}
PARAMETERIZED_GATES = {"RX", "RY", "RZ", "PHASE", "CRX", "CRY", "CRZ", "CPHASE", "XX"}
INVERTIBLE_GATES = {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "CH", "PHASE",
"CNOT", "CX", "CY", "CZ", "CRX", "CRY", "CRZ", "CPHASE", "XX", "SWAP"
"CSWAP"}


class Gate(dict):
Expand Down Expand Up @@ -100,6 +103,34 @@ def __str__(self):

return mystr

def inverse(self):
"""Returns the inverse (adjoint) of a gate.
Return:
Gate: the inverse of the gate.
"""
if self.name not in INVERTIBLE_GATES:
raise AttributeError(f"{self.gate} is not an invertible gate")
if self.parameter == "":
new_parameter = ""
elif isinstance(self.parameter, (float, floating, int, integer)):
new_parameter = -self.parameter
elif self.name in {"T", "S"}:
new_parameter = -pi / 2 if self.name == "T" else -pi / 4
return Gate(name="PHASE",
target=self.target,
control=self.control,
parameter=new_parameter,
is_variational=self.is_variational)

else:
raise AttributeError(f"{self.name} is not an invertible gate when parameter is {self.parameter}")
return Gate(name=self.name,
target=self.target,
control=self.control,
parameter=new_parameter,
is_variational=self.is_variational)

def serialize(self):
return {"type": "Gate",
"params": {"name": self.name, "target": self.target,
Expand Down
17 changes: 17 additions & 0 deletions tangelo/linq/tests/test_circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import unittest
import copy
from math import pi
from collections import Counter
from tangelo.linq import Gate, Circuit

Expand Down Expand Up @@ -98,6 +99,22 @@ def test_fixed_sized_circuit_below(self):
circuit_fixed = Circuit([Gate("H", 0)], n_qubits=n_qubits)
self.assertTrue(circuit_fixed.width == n_qubits)

def test_inverse(self):
""" Test if inverse function returns the proper set of gates by comparing print strings."""

mygates_inverse = list()
mygates_inverse.append(Gate("RX", 1, parameter=-2.))
mygates_inverse.append(Gate("Y", 0))
mygates_inverse.append(Gate("CNOT", 2, control=1))
mygates_inverse.append(Gate("CNOT", 1, control=0))
mygates_inverse.append(Gate("H", 2))
circuit1_inverse = Circuit(mygates_inverse)
self.assertTrue(circuit1.inverse().__str__(), circuit1_inverse.__str__())

ts_circuit = Circuit([Gate("T", 0), Gate("S", 1)])
ts_circuit_inverse = Circuit([Gate("PHASE", 0, parameter=-pi/4), Gate("PHASE", 0, parameter=-pi/2)])
self.assertTrue(ts_circuit.inverse().__str__(), ts_circuit_inverse.__str__())


if __name__ == "__main__":
unittest.main()
19 changes: 19 additions & 0 deletions tangelo/linq/tests/test_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,25 @@ def test_some_gates(self):
for gate in [H_gate, CNOT_gate, RX_gate, RZ_gate, CCCX_gate]:
print(gate)

def test_some_gates_inverse(self):
""" Test that some basic gates can be inverted with a few different parameters, and fails when non-invertible
parameters are passed"""

# Create a Hadamard gate acting on qubit 2
H_gate = Gate("H", 2)
H_gate_inverse = Gate("H", 2)
self.assertEqual(H_gate.inverse().__str__(), H_gate_inverse.__str__())

# Create a parameterized rotation on qubit 1 with angle 2 radians
RX_gate = Gate("RX", 1, parameter=2.)
RX_gate_inverse = Gate("RX", 1, parameter=-2.)
self.assertEqual(RX_gate.inverse().__str__(), RX_gate_inverse.__str__())

# Create a parameterized rotation on qubit 1 , with an undefined angle, that will be variational
RZ_gate = Gate("RZ", 1, parameter="an expression", is_variational=True)
with self.assertRaises(AttributeError):
RZ_gate.inverse()

def test_incorrect_gate(self):
""" Test to catch a gate with inputs that do not make sense """

Expand Down
18 changes: 18 additions & 0 deletions tangelo/linq/tests/test_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,24 @@ def test_unsupported_gate(self):
circ = Circuit([Gate("Potato", 0)])
self.assertRaises(ValueError, translator.translate_qiskit, circ)

def test_translate_ionq_inverse(self):
""" Test that inverse of T and S circuits for ionQ return Tdag and Sdag after translation """

# Generate [Gate("Tdag", 0), Gate("Sdag", 0)] equivalent
circ = Circuit([Gate("PHASE", 0, parameter=-np.pi/4), Gate("PHASE", 0, parameter=-np.pi/2)])
# Hard-coded inverse
inverse_circ = Circuit([Gate("S", 0), Gate("T", 0)])

ionq_circ_inverse = translator.translate_json_ionq(circ.inverse())
ionq_inverse_circ = translator.translate_json_ionq(inverse_circ)
ionq_circ = translator.translate_json_ionq(circ)
# Hard-coded circuit dictionary
ionq_circ_dict = {'qubits': 1, 'circuit': [{'gate': 'ti', 'target': 0}, {'gate': 'si', 'target': 0}]}

# ionq uses a dictionary to store circuits, convert to str and compare
self.assertEqual(str(ionq_inverse_circ), str(ionq_circ_inverse))
self.assertEqual(str(ionq_circ), str(ionq_circ_dict))


if __name__ == "__main__":
unittest.main()
14 changes: 13 additions & 1 deletion tangelo/linq/translator/translate_json_ionq.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- how the order and conventions for some of the inputs to the gate operations
may also differ.
"""
from math import pi, isclose


def get_ionq_gates():
Expand All @@ -31,7 +32,7 @@ def get_ionq_gates():
"""

GATE_JSON_IONQ = dict()
for name in {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "CNOT"}:
for name in {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ", "CNOT", "PHASE"}:
GATE_JSON_IONQ[name] = name.lower()
return GATE_JSON_IONQ

Expand All @@ -57,6 +58,17 @@ def translate_json_ionq(source_circuit):
json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target[0]})
elif gate.name in {"RX", "RY", "RZ"}:
json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target[0], 'rotation': gate.parameter})
elif gate.name in {"PHASE"}:
if isclose(gate.parameter, pi / 2, abs_tol=1.e-7):
json_gates.append({'gate': 's', 'target': gate.target[0]})
elif isclose(gate.parameter, - pi / 2, abs_tol=1.e-7):
json_gates.append({'gate': 'si', 'target': gate.target[0]})
elif isclose(gate.parameter, pi / 4, abs_tol=1.e-7):
json_gates.append({'gate': 't', 'target': gate.target[0]})
elif isclose(gate.parameter, - pi / 4, abs_tol=1.e-7):
json_gates.append({'gate': 'ti', 'target': gate.target[0]})
else:
raise ValueError(f"Only phases of pi/2, -pi/2, pi/4, -pi/4 are supported with JSON IonQ translation")
elif gate.name in {"CNOT"}:
json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target[0], 'control': gate.control[0]})
else:
Expand Down
4 changes: 3 additions & 1 deletion tangelo/linq/translator/translate_openqasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def get_openqasm_gates():
GATE_OPENQASM = dict()
for name in {"H", "X", "Y", "Z", "S", "T"}:
GATE_OPENQASM[name] = name.lower()
for name in {"RX", "RY", "RZ", "MEASURE"}:
for name in {"RX", "RY", "RZ", "MEASURE", "PHASE"}:
GATE_OPENQASM[name] = name.lower()
GATE_OPENQASM["CNOT"] = "cx"

Expand Down Expand Up @@ -110,6 +110,8 @@ def parse_param(s):
# TODO: Rethink the use of enums for gates to set the equality CX=CNOT and enable other refactoring
elif gate_name in {"cx"}:
gate = Gate("CNOT", qubit_indices[1], control=qubit_indices[0])
elif gate_name in {"p"}:
gate = Gate("PHASE", qubit_indices[0], parameter=eval(str(parameters[0])))
else:
raise ValueError(f"Gate '{gate_name}' not supported with openqasm translation")
abs_circ.add_gate(gate)
Expand Down
5 changes: 3 additions & 2 deletions tangelo/linq/translator/translate_projectq.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def get_projectq_gates():
for name in {"RX", "RY", "RZ", "MEASURE"}:
GATE_PROJECTQ[name] = name[0] + name[1:].lower()
GATE_PROJECTQ["CNOT"] = "CX"
GATE_PROJECTQ["PHASE"] = "R"

return GATE_PROJECTQ

Expand All @@ -63,7 +64,7 @@ def translate_projectq(source_circuit):
for gate in source_circuit._gates:
if gate.name in {"H", "X", "Y", "Z", "S", "T", "MEASURE"}:
projectq_circuit += f"{GATE_PROJECTQ[gate.name]} | Qureg[{gate.target[0]}]\n"
elif gate.name in {"RX", "RY", "RZ"}:
elif gate.name in {"RX", "RY", "RZ", "PHASE"}:
projectq_circuit += f"{GATE_PROJECTQ[gate.name]}({gate.parameter}) | Qureg[{gate.target[0]}]\n"
elif gate.name in {"CNOT"}:
projectq_circuit += f"{GATE_PROJECTQ[gate.name]} | ( Qureg[{gate.control[0]}], Qureg[{gate.target[0]}] )\n"
Expand Down Expand Up @@ -112,7 +113,7 @@ def _translate_projectq2abs(projectq_str):

if gate_name in {"H", "X", "Y", "Z", "S", "T"}:
gate = Gate(gate_mapping[gate_name], qubit_indices[0])
elif gate_name in {"Rx", "Ry", "Rz"}:
elif gate_name in {"Rx", "Ry", "Rz", "PHASE"}:
gate = Gate(gate_mapping[gate_name], qubit_indices[0], parameter=parameters[0])
# #TODO: Rethink the use of enums for gates to set the equality CX=CNOT and enable other refactoring
elif gate_name in {"CX"}:
Expand Down

0 comments on commit af2da55

Please sign in to comment.