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

Add support for IonQ native gates #144

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
32 changes: 29 additions & 3 deletions qiskit_braket_provider/providers/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import qiskit.circuit.library as qiskit_gates

from qiskit.transpiler import InstructionProperties, Target
from qiskit_ionq import ionq_gates
from qiskit_braket_provider.exception import QiskitBraketException

BRAKET_TO_QISKIT_NAMES = {
Expand Down Expand Up @@ -65,6 +66,9 @@
"cswap": "cswap",
"cphaseshift": "cp",
"ecr": "ecr",
"gpi": "gpi",
"gpi2": "gpi2",
"ms": "ms",
}

_CONTROLLED_GATES_BY_QUBIT_COUNT = {
Expand Down Expand Up @@ -119,6 +123,13 @@
"ryy": lambda angle: [braket_gates.YY(angle)],
"ecr": lambda: [braket_gates.ECR()],
"iswap": lambda: [braket_gates.ISwap()],
# IonQ gates
"gpi": lambda angle: [braket_gates.GPi(2 * pi * angle)],
"gpi2": lambda angle: [braket_gates.GPi2(2 * pi * angle)],
"ms": lambda angle_1, angle_2, angle_3: [
braket_gates.MS(2 * pi * angle_1, 2 * pi * angle_2, 2 * pi * angle_3)
],
"zz": lambda angle: [braket_gates.ZZ(2 * pi * angle)],
}

_QISKIT_CONTROLLED_GATE_NAMES_TO_BRAKET_GATES: dict[str, Callable] = {
Expand Down Expand Up @@ -172,6 +183,13 @@
"zz": qiskit_gates.RZZGate(Parameter("theta")),
"ecr": qiskit_gates.ECRGate(),
"iswap": qiskit_gates.iSwapGate(),
"gpi": ionq_gates.GPIGate(Parameter("phi") / (2 * pi)),
"gpi2": ionq_gates.GPI2Gate(Parameter("phi") / (2 * pi)),
"ms": ionq_gates.MSGate(
Parameter("phi0") / (2 * pi),
Parameter("phi1") / (2 * pi),
Parameter("theta") / (2 * pi),
),
}


Expand Down Expand Up @@ -568,7 +586,7 @@ def to_qiskit(circuit: Circuit) -> QuantumCircuit:
else:
gate_params.append(value)

gate = _create_gate(gate_name, gate_params)
gate = _create_qiskit_gate(gate_name, gate_params)

if instruction.power != 1:
gate = gate**instruction.power
Expand All @@ -584,12 +602,20 @@ def to_qiskit(circuit: Circuit) -> QuantumCircuit:
return qiskit_circuit


def _create_gate(
def _create_qiskit_gate(
gate_name: str, gate_params: list[Union[float, Parameter]]
) -> Instruction:
gate_instance = GATE_NAME_TO_QISKIT_GATE.get(gate_name, None)
new_gate_params = []
for param_expression, value in zip(gate_instance.params, gate_params):
param = list(param_expression.parameters)[0]
if isinstance(value, Parameter):
new_gate_params.append(value)
else:
bound_param_expression = param_expression.bind({param: value})
new_gate_params.append(bound_param_expression)
if gate_instance is not None:
gate_cls = gate_instance.__class__
else:
raise TypeError(f'Braket gate "{gate_name}" not supported in Qiskit')
return gate_cls(*gate_params)
return gate_cls(*new_gate_params)
6 changes: 5 additions & 1 deletion qiskit_braket_provider/providers/braket_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,11 @@ def run(
[run_input] if isinstance(run_input, QuantumCircuit) else list(run_input)
)
gateset = self._get_gateset()
circuits: list[Circuit] = [to_braket(circ, gateset) for circ in convert_input]
verbatim = options.pop("verbatim", False)
circuits: list[Circuit] = [
to_braket(circ, gateset, verbatim) for circ in convert_input
]

shots = options["shots"] if "shots" in options else 1024
if shots == 0:
circuits = list(map(lambda x: x.state_vector(), circuits))
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
certifi>=2021.5.30
qiskit>=0.34.2, <1.0
qiskit-ionq>=0.4.7
amazon-braket-sdk>=1.56.0

setuptools>=40.1.0
Expand Down
114 changes: 106 additions & 8 deletions tests/providers/test_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

from qiskit.circuit.library import standard_gates as qiskit_gates

from qiskit_ionq import ionq_gates

from qiskit_braket_provider.providers.adapter import (
to_qiskit,
to_braket,
Expand Down Expand Up @@ -91,6 +93,14 @@
]


qiskit_ionq_gates = [
ionq_gates.GPIGate(Parameter("φ")),
ionq_gates.GPI2Gate(Parameter("φ")),
ionq_gates.MSGate(Parameter("φ0"), Parameter("φ1"), Parameter("ϴ")),
ionq_gates.ZZGate(Parameter("ϴ")),
]


class TestAdapter(TestCase):
"""Tests adapter."""

Expand Down Expand Up @@ -195,6 +205,49 @@ def test_standard_gate_decomp(self):
f"and absolute difference {abs_diff}. Original values {values}",
)

def test_ionq_gates(self):
"""Tests adapter decomposition of all standard gates to forms that can be translated"""
aer_backend = BasicAer.get_backend("statevector_simulator")
backend = BraketLocalBackend()

for gate in qiskit_ionq_gates:
qiskit_circuit = QuantumCircuit(gate.num_qubits)
qiskit_circuit.append(gate, range(gate.num_qubits))

parameters = gate.params
parameter_values = [
(137 / 61) * np.pi / i for i in range(1, len(parameters) + 1)
]
parameter_bindings = dict(zip(parameters, parameter_values))
qiskit_circuit = qiskit_circuit.assign_parameters(parameter_bindings)

circuit_from_gate_unitary = QuantumCircuit(gate.num_qubits)
gate_unitary = gate.__class__(*parameter_values)
circuit_from_gate_unitary.unitary(gate_unitary, range(gate.num_qubits))

with self.subTest(f"Circuit with {gate.name} gate."):
braket_job = backend.run(qiskit_circuit, shots=1000, verbatim=True)
braket_result = braket_job.result().get_counts()

qiskit_job = aer_backend.run(circuit_from_gate_unitary, shots=1000)
qiskit_result = qiskit_job.result().get_counts()

combined_results = combine_dicts(
{k: float(v) / 1000.0 for k, v in braket_result.items()},
qiskit_result,
)

for key, values in combined_results.items():
percent_diff = abs(
((float(values[0]) - values[1]) / values[0]) * 100
)
abs_diff = abs(values[0] - values[1])
self.assertTrue(
percent_diff < 10 or abs_diff < 0.05,
f"Key {key} with percent difference {percent_diff} "
f"and absolute difference {abs_diff}. Original values {values}",
)

def test_global_phase(self):
"""Tests conversion when transpiler generates a global phase"""
qiskit_circuit = QuantumCircuit(1, global_phase=np.pi / 2)
Expand Down Expand Up @@ -259,6 +312,7 @@ def test_mappers(self):
"cp": "cphaseshift",
"rxx": "xx",
"ryy": "yy",
"zz": "zz",
}

qiskit_to_braket_gate_names |= {
Expand All @@ -283,17 +337,20 @@ def test_mappers(self):
"cz",
"cswap",
"ecr",
"gpi",
"gpi2",
"ms",
]
}

self.assertEqual(
list(sorted(qiskit_to_braket_gate_names.keys())),
list(sorted(GATE_NAME_TO_BRAKET_GATE.keys())),
set(qiskit_to_braket_gate_names.keys()),
set(GATE_NAME_TO_BRAKET_GATE.keys()),
)

self.assertEqual(
list(sorted(qiskit_to_braket_gate_names.values())),
list(sorted(GATE_NAME_TO_QISKIT_GATE.keys())),
set(qiskit_to_braket_gate_names.values()),
set(GATE_NAME_TO_QISKIT_GATE.keys()),
)

def test_type_error_on_bad_input(self):
Expand Down Expand Up @@ -465,14 +522,17 @@ def test_all_standard_gates(self):
gate_set = [attr for attr in dir(Gate) if attr[0].isupper()]

for gate_name in gate_set:
if gate_name.lower() not in GATE_NAME_TO_QISKIT_GATE:
if (
gate_name.lower() not in GATE_NAME_TO_QISKIT_GATE
or gate_name.lower() in ["gpi", "gpi2", "ms"]
):
continue

gate = getattr(Gate, gate_name)
if issubclass(gate, AngledGate):
op = gate(0)
op = gate(0.1)
elif issubclass(gate, TripleAngledGate):
op = gate(0, 0, 0)
op = gate(0.1, 0.1, 0.1)
else:
op = gate()
target = range(op.qubit_count)
Expand All @@ -487,11 +547,49 @@ def test_all_standard_gates(self):
)
expected_qiskit_circuit.measure_all()
expected_qiskit_circuit = expected_qiskit_circuit.assign_parameters(
{p: 0 for p in expected_qiskit_circuit.parameters}
{p: 0.1 for p in expected_qiskit_circuit.parameters}
)

self.assertEqual(qiskit_circuit, expected_qiskit_circuit)

def test_all_ionq_gates(self):
"""
Tests braket to qiskit conversion with ionq gates.

Note: Braket gate angles are in radians while Qiskit IonQ gates expect turns.
"""

gate_set = ["GPi", "GPi2", "MS"]

for gate_name in gate_set:
gate = getattr(Gate, gate_name)
value = 0.1
qiskit_gate_cls = GATE_NAME_TO_QISKIT_GATE.get(gate_name.lower()).__class__
qiskit_value = 0.1 / (2 * np.pi)
if issubclass(gate, AngledGate):
op = gate(value)
qiskit_gate = qiskit_gate_cls(qiskit_value)
elif issubclass(gate, TripleAngledGate):
args = [value] * 3
qiskit_args = [qiskit_value] * 3
op = gate(*args)
qiskit_gate = qiskit_gate_cls(*qiskit_args)
else:
op = gate()
qiskit_gate = qiskit_gate_cls()

target = range(op.qubit_count)
instr = Instruction(op, target)

braket_circuit = Circuit().add_instruction(instr)
qiskit_circuit = to_qiskit(braket_circuit)

expected_qiskit_circuit = QuantumCircuit(op.qubit_count)
expected_qiskit_circuit.append(qiskit_gate, target)
expected_qiskit_circuit.measure_all()

self.assertEqual(qiskit_circuit, expected_qiskit_circuit)

def test_parametric_gates(self):
"""
Tests braket to qiskit conversion with standard gates.
Expand Down
Loading