Skip to content

Commit

Permalink
added multi-controls multi-targets, extra gates
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesB-1qbit committed Nov 16, 2021
1 parent c4da558 commit bd77169
Show file tree
Hide file tree
Showing 12 changed files with 305 additions and 65 deletions.
12 changes: 7 additions & 5 deletions qsdk/backendbuddy/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,11 +122,13 @@ def check_index_valid(index):
f"Gate = {gate}")

# Track qubit indices
self._qubit_indices.add(gate.target)
check_index_valid(gate.target)
if isinstance(gate.control, int):
self._qubit_indices.add(gate.control)
check_index_valid(gate.control)
for t in gate.target:
self._qubit_indices.add(t)
check_index_valid(t)
if isinstance(gate.control, list):
for c in gate.control:
self._qubit_indices.add(c)
check_index_valid(c)

# Keep track of the total gate count
self._gate_counts[gate.name] = self._gate_counts.get(gate.name, 0) + 1
Expand Down
33 changes: 24 additions & 9 deletions qsdk/backendbuddy/gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
mathematical operation.
"""

ONE_QUBIT_GATES = {"H", "X", "Y", "Z", "S", "T", "RX", "RY", "RZ"}
TWO_QUBIT_GATES = {"CNOT"}
from typing import Union

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


class Gate(dict):
Expand All @@ -37,15 +40,27 @@ class Gate(dict):
variational or not.
"""

# TODO: extend control to a list to support gates such as the Toffoli gate etc in the future
# TODO: extend target to a list to support gates such as U2, U3 etc in the future
def __init__(self, name: str, target: int, control: int=None, parameter="", is_variational: bool=False):
def __init__(self, name: str, target: Union[int, list], control: Union[int, list] = None, parameter="", is_variational: bool = False):
""" This gate class is basically a dictionary with extra methods. """

if not (isinstance(target, int) and target >= 0):
raise ValueError("Qubit index must be a positive integer.")
if control and (not (isinstance(control, int) and control >= 0)):
raise ValueError("Qubit index must be a positive integer.")
if not isinstance(target, (int, list)):
raise ValueError("Qubit index must be int or list of ints.")
else:
if isinstance(target, int):
target = [target]
for t in target:
if not isinstance(t, int) or t < 0:
raise ValueError(f"Target {t} of input {target} is not a positive integer")

if control is not None:
if not isinstance(control, (int, list)):
raise ValueError("Qubit index must be int or list of ints.")
else:
if isinstance(control, int):
control = [control]
for c in control:
if not isinstance(c, int) or c < 0:
raise ValueError(f"Target {c} of input {control} is not a positive integer")

self.__dict__ = {"name": name, "target": target, "control": control,
"parameter": parameter, "is_variational": is_variational}
Expand Down
5 changes: 2 additions & 3 deletions qsdk/backendbuddy/simulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,8 @@ def simulate(self, source_circuit, return_statevector=False, initial_statevector
# Noiseless simulation using the statevector simulator otherwise
else:
backend = qiskit.Aer.get_backend("aer_simulator", method='statevector')
save_state_circuit = qiskit.QuantumCircuit(source_circuit.width, source_circuit.width)
save_state_circuit.save_statevector()
translated_circuit = translated_circuit.compose(save_state_circuit)
translated_circuit = qiskit.transpile(translated_circuit, backend)
translated_circuit.save_statevector()
sim_results = backend.run(translated_circuit).result()
self._current_state = sim_results.get_statevector(translated_circuit)
frequencies = self._statevector_to_frequencies(self._current_state)
Expand Down
69 changes: 69 additions & 0 deletions qsdk/backendbuddy/tests/test_translator.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@
path_data = os.path.dirname(os.path.realpath(__file__)) + '/data'

gates = [Gate("H", 2), Gate("CNOT", 1, control=0), Gate("CNOT", 2, control=1), Gate("Y", 0), Gate("S", 0)]
multi_controlled_gates = [Gate("X", 0), Gate("X", 1), Gate("CX", target=2, control=[0,1])]
abs_circ = Circuit(gates) + Circuit([Gate("RX", 1, parameter=2.)])
abs_multi_circ = Circuit(multi_controlled_gates)
references = [0., 0.38205142 ** 2, 0., 0.59500984 ** 2, 0., 0.38205142 ** 2, 0., 0.59500984 ** 2]
references_multi = [0., 0., 0., 0., 0., 0., 0., 1.]

abs_circ_mixed = Circuit(gates) + Circuit([Gate("RX", 1, parameter=1.5), Gate("MEASURE", 0)])

Expand Down Expand Up @@ -68,6 +71,30 @@ def test_qulacs(self):
# Assert that both simulations returned the same state vector
np.testing.assert_array_equal(state1.get_vector(), state2.get_vector())

# Generates the qulacs circuit by translating from the abstract one
translated_circuit = translator.translate_qulacs(abs_multi_circ)

# Run the simulation
state1 = qulacs.QuantumState(abs_multi_circ.width)
translated_circuit.update_quantum_state(state1)

# Directly define the same circuit through qulacs
# NB: this includes convention fixes for some parametrized rotation gates (-theta instead of theta)
qulacs_circuit = qulacs.QuantumCircuit(3)
qulacs_circuit.add_X_gate(0)
qulacs_circuit.add_X_gate(1)
mat_gate = qulacs.gate.to_matrix_gate(qulacs.gate.X(2))
mat_gate.add_control_qubit(0, 1)
mat_gate.add_control_qubit(1, 1)
qulacs_circuit.add_gate(mat_gate)

# Run the simulation
state2 = qulacs.QuantumState(abs_multi_circ.width)
qulacs_circuit.update_quantum_state(state2)

# Assert that both simulations returned the same state vector
np.testing.assert_array_equal(state1.get_vector(), state2.get_vector())

@unittest.skipIf("qiskit" not in installed_backends, "Test Skipped: Backend not available \n")
def test_qiskit(self):
"""
Expand Down Expand Up @@ -104,6 +131,9 @@ def test_qiskit(self):

np.testing.assert_array_equal(v1, v2)

#Return error when attempting to use qiskit with multiple controls
self.assertRaises(ValueError, translator.translate_qiskit, abs_multi_circ)

@unittest.skipIf("cirq" not in installed_backends, "Test Skipped: Backend not available \n")
def test_cirq(self):
"""
Expand Down Expand Up @@ -138,6 +168,21 @@ def test_cirq(self):

np.testing.assert_array_equal(v1, v2)

translated_circuit = translator.translate_cirq(abs_multi_circ)
circ = cirq.Circuit()
circ.append(cirq.X(qubit_labels[0]))
circ.append(cirq.X(qubit_labels[1]))
next_gate = cirq.X.controlled(num_controls=2)
circ.append(next_gate(qubit_labels[0], qubit_labels[1], qubit_labels[2]))

job_sim = cirq_simulator.simulate(circ)
v1 = job_sim.final_state_vector

job_sim = cirq_simulator.simulate(translated_circuit)
v2 = job_sim.final_state_vector

np.testing.assert_array_equal(v1, v2)

@unittest.skipIf("qdk" not in installed_backends, "Test Skipped: Backend not available \n")
def test_qdk(self):
""" Compares the frequencies computed by the QDK/Q# shot-based simulator to the theoretical ones """
Expand All @@ -163,6 +208,27 @@ def test_qdk(self):
# Compares with theoretical probabilities obtained through a statevector simulator
np.testing.assert_almost_equal(np.array(probabilities), np.array(references), 2)

# Generate the qdk circuit by translating from the abstract one and print it
translated_circuit = translator.translate_qsharp(abs_multi_circ)
print(translated_circuit)

# Write to file
with open('tmp_circuit.qs', 'w+') as f_out:
f_out.write(translated_circuit)

# Compile all qsharp files found in directory and import the qsharp operation
import qsharp
qsharp.reload()
from MyNamespace import EstimateFrequencies

# Simulate, return frequencies
n_shots = 10**4
probabilities = EstimateFrequencies.simulate(nQubits=abs_multi_circ.width, nShots=n_shots)
print("Q# frequency estimation with {0} samples: \n {1}".format(n_shots, probabilities))

# Compares with theoretical probabilities obtained through a statevector simulator
np.testing.assert_almost_equal(np.array(probabilities), np.array(references_multi), 2)

@unittest.skipIf("projectq" not in installed_backends, "Test Skipped: Backend not available \n")
def test_projectq(self):
""" Compares state vector of native ProjectQ circuit against translated one """
Expand Down Expand Up @@ -300,6 +366,9 @@ def test_braket(self):

np.testing.assert_array_equal(circ_result.values[0], translated_result.values[0])

#Return error when attempting to use braket with multiple controls
self.assertRaises(ValueError, translator.translate_braket, abs_multi_circ)

@unittest.skipIf("qiskit" not in installed_backends, "Test Skipped: Backend not available \n")
def test_unsupported_gate(self):
""" Must return an error if a gate is not supported for the target backend """
Expand Down
33 changes: 28 additions & 5 deletions qsdk/backendbuddy/translator/translate_braket.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,21 @@ def get_braket_gates():
GATE_BRAKET["X"] = BraketCircuit.x
GATE_BRAKET["Y"] = BraketCircuit.y
GATE_BRAKET["Z"] = BraketCircuit.z
GATE_BRAKET["CX"] = BraketCircuit.cnot
GATE_BRAKET["CY"] = BraketCircuit.cy
GATE_BRAKET["CZ"] = BraketCircuit.cz
GATE_BRAKET["S"] = BraketCircuit.s
GATE_BRAKET["T"] = BraketCircuit.t
GATE_BRAKET["RX"] = BraketCircuit.rx
GATE_BRAKET["RY"] = BraketCircuit.ry
GATE_BRAKET["RZ"] = BraketCircuit.rz
GATE_BRAKET["XX"] = BraketCircuit.xx
GATE_BRAKET["CRZ"] = [BraketCircuit.cphaseshift, BraketCircuit.cphaseshift10]
GATE_BRAKET["PHASE"] = BraketCircuit.phaseshift
GATE_BRAKET["CPHASE"] = BraketCircuit.cphaseshift
GATE_BRAKET["CNOT"] = BraketCircuit.cnot
GATE_BRAKET["SWAP"] = BraketCircuit.swap
GATE_BRAKET["CSWAP"] = BraketCircuit.cswap
# GATE_BRAKET["MEASURE"] = ? (mid-circuit measurement currently unsupported?)

return GATE_BRAKET
Expand All @@ -65,12 +74,26 @@ def translate_braket(source_circuit):

# Map the gate information properly. Different for each backend (order, values)
for gate in source_circuit._gates:
if gate.control is not None:
if len(gate.control) > 1:
raise ValueError('braket does not support multi controlled gates: Gate {gate.name} with controls {gate.control} is invalid')
if gate.name in {"H", "X", "Y", "Z", "S", "T"}:
(GATE_BRAKET[gate.name])(target_circuit, gate.target)
elif gate.name in {"RX", "RY", "RZ"}:
(GATE_BRAKET[gate.name])(target_circuit, gate.target, gate.parameter)
elif gate.name in {"CNOT"}:
(GATE_BRAKET[gate.name])(target_circuit, control=gate.control, target=gate.target)
(GATE_BRAKET[gate.name])(target_circuit, gate.target[0])
elif gate.name in {"RX", "RY", "RZ", "PHASE"}:
(GATE_BRAKET[gate.name])(target_circuit, gate.target[0], gate.parameter)
elif gate.name in {"CNOT", "CX", "CY", "CZ"}:
(GATE_BRAKET[gate.name])(target_circuit, control=gate.control, target=gate.target[0])
elif gate.name in {"XX"}:
(GATE_BRAKET[gate.name])(target_circuit, gate.target[0], gate.target[1], gate.parameter)
elif gate.name in {"CRZ"}:
(GATE_BRAKET[gate.name][0])(target_circuit, gate.control[0], gate.target[0], gate.parameter/2.)
(GATE_BRAKET[gate.name][1])(target_circuit, gate.control[0], gate.target[0], -gate.parameter/2.)
elif gate.name in {"SWAP"}:
(GATE_BRAKET[gate.name])(target_circuit, gate.target[0], gate.target[1])
elif gate.name in {"CSWAP"}:
(GATE_BRAKET[gate.name])(target_circuit, gate.control[0], gate.target[0], gate.target[1])
elif gate.name in {"CPHASE"}:
(GATE_BRAKET[gate.name])(target_circuit, gate.control[0], gate.target[0], gate.parameter)
# elif gate.name in {"MEASURE"}:
# implement if mid-circuit measurement available through Braket later on
else:
Expand Down
72 changes: 57 additions & 15 deletions qsdk/backendbuddy/translator/translate_cirq.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


def get_cirq_gates():
Expand All @@ -29,17 +30,29 @@ def get_cirq_gates():
"""
import cirq

GATE_CIRQ = dict()
GATE_CIRQ = dict()
GATE_CIRQ["H"] = cirq.H
GATE_CIRQ["X"] = cirq.X
GATE_CIRQ["Y"] = cirq.Y
GATE_CIRQ["Z"] = cirq.Z
GATE_CIRQ["CX"] = cirq.X
GATE_CIRQ["CY"] = cirq.Y
GATE_CIRQ["CZ"] = cirq.Z
GATE_CIRQ["S"] = cirq.S
GATE_CIRQ["T"] = cirq.T
GATE_CIRQ["RX"] = cirq.rx
GATE_CIRQ["RY"] = cirq.ry
GATE_CIRQ["RZ"] = cirq.rz
GATE_CIRQ["CNOT"] = cirq.CNOT
GATE_CIRQ["CRZ"] = cirq.rz
GATE_CIRQ["CRX"] = cirq.rx
GATE_CIRQ["CRY"] = cirq.ry
GATE_CIRQ["PHASE"] = cirq.ZPowGate
GATE_CIRQ["CPHASE"] = cirq.ZPowGate
GATE_CIRQ["XX"] = cirq.XXPowGate
GATE_CIRQ["SWAP"] = cirq.SWAP
GATE_CIRQ["CSWAP"] = cirq.SWAP
GATE_CIRQ["MEASURE"] = cirq.measure
return GATE_CIRQ

Expand Down Expand Up @@ -69,15 +82,40 @@ def translate_cirq(source_circuit, noise_model=None):

# Maps the gate information properly. Different for each backend (order, values)
for gate in source_circuit._gates:
if gate.control is not None and gate.name is not 'CNOT':
control_list = []
num_controls = len(gate.control)
for c in gate.control:
control_list.append(qubit_list[c])
if gate.name in {"H", "X", "Y", "Z", "S", "T"}:
target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target]))
target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target[0]]))
elif gate.name in {"CX", "CY", "CZ"}:
next_gate = GATE_CIRQ[gate.name].controlled(num_controls)
target_circuit.append(next_gate(*control_list, qubit_list[gate.target[0]]))
elif gate.name in {"RX", "RY", "RZ"}:
next_gate = GATE_CIRQ[gate.name](gate.parameter)
target_circuit.append(next_gate(qubit_list[gate.target]))
target_circuit.append(next_gate(qubit_list[gate.target[0]]))
elif gate.name in {"CNOT"}:
target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.control], qubit_list[gate.target]))
target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.control[0]], qubit_list[gate.target[0]]))
elif gate.name in {"MEASURE"}:
target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target]))
target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target[0]]))
elif gate.name in {"CRZ", "CRX", "CRY"}:
next_gate = GATE_CIRQ[gate.name](gate.parameter).controlled(num_controls)
target_circuit.append(next_gate(*control_list, qubit_list[gate.target[0]]))
elif gate.name in {"XX"}:
next_gate = GATE_CIRQ[gate.name](exponent=gate.parameter/pi)
target_circuit.append(next_gate(qubit_list[gate.target[0]], qubit_list[gate.target1[0]]))
elif gate.name in {"PHASE"}:
next_gate = GATE_CIRQ[gate.name](exponent=gate.parameter/pi)
target_circuit.append(next_gate(qubit_list[gate.target[0]]))
elif gate.name in {"CPHASE"}:
next_gate = GATE_CIRQ[gate.name](exponent=gate.parameter/pi).controlled(num_controls)
target_circuit.append(next_gate(*control_list, qubit_list[gate.target[0]]))
elif gate.name in {"SWAP"}:
target_circuit.append(GATE_CIRQ[gate.name](qubit_list[gate.target[0]], qubit_list[gate.target[1]]))
elif gate.name in {"CSWAP"}:
next_gate = GATE_CIRQ[gate.name].controlled(num_controls)
target_circuit.append(next_gate(*control_list, qubit_list[gate.target[0]], qubit_list[gate.target[1]]))
else:
raise ValueError(f"Gate '{gate.name}' not supported on backend cirq")

Expand All @@ -87,17 +125,21 @@ def translate_cirq(source_circuit, noise_model=None):
if nt == 'pauli':
# Define pauli gate in cirq language
depo = cirq.asymmetric_depolarize(np[0], np[1], np[2])
target_circuit.append(depo(qubit_list[gate.target]))
if gate.control or gate.control == 0:
target_circuit.append(depo(qubit_list[gate.control]))
for t in gate.target:
target_circuit.append(depo(qubit_list[t]))
if gate.control is not None:
for c in gate.control:
target_circuit.append(depo(qubit_list[c]))
elif nt == 'depol':
if gate.control or gate.control == 0:
# define 2-qubit depolarization gate
depo = cirq.depolarize(np*15/16, 2) # sparam, num_qubits
target_circuit.append(depo(qubit_list[gate.control], qubit_list[gate.target])) # gates targetted
else:
# sdefine 1-qubit depolarization gate
depo = cirq.depolarize(np*3/4, 1)
target_circuit.append(depo(qubit_list[gate.target]))
depo_list = []
if gate.control is not None:
for c in gate.control:
depo_list.append(qubit_list[c])
for t in gate.target:
depo_list.append(qubit_list[t])
depo_size = len(depo_list)
# define depo_size-qubit depolarization gate
depo = cirq.depolarize(np*(4**depo_size-1)/4**depo_size, depo_size) # sparam, num_qubits
target_circuit.append(depo(*depo_list)) # gates targetted

return target_circuit
6 changes: 3 additions & 3 deletions qsdk/backendbuddy/translator/translate_json_ionq.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,11 @@ def translate_json_ionq(source_circuit):
json_gates = []
for gate in source_circuit._gates:
if gate.name in {"H", "X", "Y", "Z", "S", "T"}:
json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target})
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, 'rotation': gate.parameter})
json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target[0], 'rotation': gate.parameter})
elif gate.name in {"CNOT"}:
json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target, 'control': gate.control})
json_gates.append({'gate': GATE_JSON_IONQ[gate.name], 'target': gate.target[0], 'control': gate.control[0]})
else:
raise ValueError(f"Gate '{gate.name}' not supported with JSON IonQ translation")

Expand Down
6 changes: 3 additions & 3 deletions qsdk/backendbuddy/translator/translate_projectq.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ 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}]\n"
projectq_circuit += f"{GATE_PROJECTQ[gate.name]} | Qureg[{gate.target[0]}]\n"
elif gate.name in {"RX", "RY", "RZ"}:
projectq_circuit += f"{GATE_PROJECTQ[gate.name]}({gate.parameter}) | Qureg[{gate.target}]\n"
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}], Qureg[{gate.target}] )\n"
projectq_circuit += f"{GATE_PROJECTQ[gate.name]} | ( Qureg[{gate.control[0]}], Qureg[{gate.target[0]}] )\n"
else:
raise ValueError(f"Gate '{gate.name}' not supported on backend projectQ")

Expand Down
Loading

0 comments on commit bd77169

Please sign in to comment.