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

Ibm backend v2 (solves #318 and #347) #349

Merged
merged 49 commits into from
Feb 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
a37c182
reduced Ry decomposition : modification of restrictedgateset setup to…
Ytterbot Oct 23, 2019
5aab56b
Addition of test file for the rz2rx decomposition
daisyrainsmith Nov 22, 2019
c005469
Revert "Addition of test file for the rz2rx decomposition"
daisyrainsmith Nov 22, 2019
161ceef
Create rz2rx_test file
daisyrainsmith Nov 22, 2019
1de8b0d
Update rz2rx.py
daisyrainsmith Nov 22, 2019
7aee572
Update rz2rx_test.py
daisyrainsmith Nov 22, 2019
1beffdb
Minor update: remove accidental file
daisyrainsmith Nov 25, 2019
3ad1128
Minor update rz2rx.py
daisyrainsmith Nov 26, 2019
6f71faa
Minor update rz2rx_test.py
daisyrainsmith Nov 26, 2019
c29f866
Create h2rx_test.py
daisyrainsmith Dec 5, 2019
ab231de
Improvement of the decomposition chooser; Note that basic restricted …
Ytterbot Dec 6, 2019
5e7e01b
Merge branch 'develop' of https://github.com/dbretaud/ProjectQ into d…
Ytterbot Dec 6, 2019
2b76e64
Updates to h2rx and cnot2rxx
daisyrainsmith Dec 6, 2019
a3d70a1
Merge branch 'develop' of https://github.com/dbretaud/ProjectQ into d…
daisyrainsmith Dec 6, 2019
60427da
Create cnot2rxx_test.py
daisyrainsmith Dec 6, 2019
1880aa8
basic rotation gates at an angle of 0 or 2pi are removed by the optim…
Ytterbot Dec 10, 2019
4748268
Update and create trapped_ion_decomposer_test.py
daisyrainsmith Dec 10, 2019
5ac7318
Minor update
daisyrainsmith Dec 10, 2019
cf6a793
Update on comments regarding Identity gates
Ytterbot Dec 10, 2019
0392ec0
Merge branch 'develop' into develop
dbretaud Dec 11, 2019
acdf41a
Changes 1/2 : command can be printed with unicode symbols only via new
Ytterbot Dec 19, 2019
448ba8e
Merge branch 'develop' of https://github.com/dbretaud/ProjectQ into d…
Ytterbot Dec 19, 2019
27f820c
Work in progress, is commutable
daisyrainsmith Dec 20, 2019
386cdf4
Merge branch 'develop' of https://github.com/dbretaud/ProjectQ into d…
daisyrainsmith Dec 20, 2019
419276c
Update to decomposition test files
daisyrainsmith Dec 20, 2019
ff74c8b
Revert "Work in progress, is commutable"
Ytterbot Dec 20, 2019
df74660
ibmq fix: projectq uses new ibmq API (no doc available, just look at …
Ytterbot Jan 2, 2020
88e0c63
minor fixes
Ytterbot Jan 2, 2020
34f280e
update on the ibm example
Ytterbot Jan 2, 2020
0413e10
minor fixes; revert rotation gates to [0;4pi)
Ytterbot Jan 7, 2020
8ea5a5c
fix comments
Ytterbot Jan 10, 2020
197e93c
fix mapper choice for simulator. added comments
Ytterbot Jan 14, 2020
03daf79
minor fixes with results, mapper and testing files. Improvement of
Ytterbot Jan 16, 2020
cd0452a
Merge branch 'develop' into ibm_V2
Ytterbot Jan 16, 2020
9a32b37
Revert "Merge branch 'develop' into ibm_V2"
Ytterbot Jan 16, 2020
17af2c8
minor fixes
Ytterbot Jan 16, 2020
483cb24
bug fix in client test file
Ytterbot Jan 16, 2020
43939fc
fixing bug and python2.7 compatibility for testing files
Ytterbot Jan 17, 2020
6173cac
fix errors
Ytterbot Jan 17, 2020
83be8e9
major fix on testing files
Ytterbot Jan 19, 2020
9b9a0d0
minor fix on comments and python 2.7 compatibility
Ytterbot Jan 19, 2020
7fb3777
fix 'super()' call for python 2.7
Ytterbot Jan 19, 2020
f0f2f69
additional tests
Ytterbot Jan 20, 2020
48c7721
python2.7 fix
Ytterbot Jan 21, 2020
776e8b5
increase coverage, fix a print statement
Ytterbot Jan 24, 2020
e7a6899
Some minor stylistic adjustments
Takishima Feb 4, 2020
67cab0f
Reindent files and fix some linting warnings/errors
Takishima Feb 4, 2020
2d8e925
Improve test coverage
Takishima Feb 4, 2020
eb27bc6
Improve code readability
Takishima Feb 4, 2020
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
19 changes: 15 additions & 4 deletions examples/ibm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
from projectq.backends import IBMBackend
from projectq.ops import Measure, Entangle, All
from projectq import MainEngine
import getpass


def run_entangle(eng, num_qubits=5):
def run_entangle(eng, num_qubits=3):
"""
Runs an entangling operation on the provided compiler engine.

Expand Down Expand Up @@ -37,9 +38,19 @@ def run_entangle(eng, num_qubits=5):


if __name__ == "__main__":
#devices commonly available :
#ibmq_16_melbourne (15 qubit)
#ibmq_essex (5 qubit)
#ibmq_qasm_simulator (32 qubits)
device = None #replace by the IBM device name you want to use
token = None #replace by the token given by IBMQ
if token is None:
token = getpass.getpass(prompt='IBM Q token > ')
if device is None:
token = getpass.getpass(prompt='IBM device > ')
# create main compiler engine for the IBM back-end
eng = MainEngine(IBMBackend(use_hardware=True, num_runs=1024,
verbose=False, device='ibmqx4'),
engine_list=projectq.setups.ibm.get_engine_list())
eng = MainEngine(IBMBackend(use_hardware=True, token=token num_runs=1024,
verbose=False, device=device),
engine_list=projectq.setups.ibm.get_engine_list(token=token, device=device))
# run the circuit and print the result
print(run_entangle(eng))
112 changes: 60 additions & 52 deletions projectq/backends/_ibm/_ibm.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# limitations under the License.

""" Back-end to run quantum program on IBM's Quantum Experience."""

import math
import random
import json

Expand Down Expand Up @@ -41,11 +41,11 @@

class IBMBackend(BasicEngine):
"""
The IBM Backend class, which stores the circuit, transforms it to JSON
QASM, and sends the circuit through the IBM API.
The IBM Backend class, which stores the circuit, transforms it to JSON,
and sends the circuit through the IBM API.
"""
def __init__(self, use_hardware=False, num_runs=1024, verbose=False,
user=None, password=None, device='ibmqx4',
token='', device='ibmq_essex',
num_retries=3000, interval=1,
retrieve_execution=None):
"""
Expand All @@ -59,10 +59,8 @@ def __init__(self, use_hardware=False, num_runs=1024, verbose=False,
verbose (bool): If True, statistics are printed, in addition to
the measurement result being registered (at the end of the
circuit).
user (string): IBM Quantum Experience user name
password (string): IBM Quantum Experience password
device (string): Device to use ('ibmqx4', or 'ibmqx5')
if use_hardware is set to True. Default is ibmqx4.
token (str): IBM quantum experience user password.
device (str): name of the IBM device to use. ibmq_essex By default
num_retries (int): Number of times to retry to obtain
results from the IBM API. (default is 3000)
interval (float, int): Number of seconds between successive
Expand All @@ -76,15 +74,15 @@ def __init__(self, use_hardware=False, num_runs=1024, verbose=False,
if use_hardware:
self.device = device
else:
self.device = 'simulator'
self.device = 'ibmq_qasm_simulator'
self._num_runs = num_runs
self._verbose = verbose
self._user = user
self._password = password
self._token=token
self._num_retries = num_retries
self._interval = interval
self._probabilities = dict()
self.qasm = ""
self._json=[]
self._measured_ids = []
self._allocated_qubits = set()
self._retrieve_execution = retrieve_execution
Expand All @@ -93,24 +91,29 @@ def is_available(self, cmd):
"""
Return true if the command can be executed.

The IBM quantum chip can do X, Y, Z, T, Tdag, S, Sdag,
rotation gates, barriers, and CX / CNOT.
The IBM quantum chip can only do U1,U2,U3,barriers, and CX / CNOT.
Conversion implemented for Rotation gates and H gates.

Args:
cmd (Command): Command for which to check availability
"""
g = cmd.gate
if g == NOT and get_control_count(cmd) <= 1:
if g == NOT and get_control_count(cmd) == 1:
return True
if get_control_count(cmd) == 0:
if g in (T, Tdag, S, Sdag, H, Y, Z):
if g == H:
return True
if isinstance(g, (Rx, Ry, Rz)):
return True
if g in (Measure, Allocate, Deallocate, Barrier):
return True
return False

def get_qasm(self):
""" Return the QASM representation of the circuit sent to the backend.
Should be called AFTER calling the ibm device """
return self.qasm

def _reset(self):
""" Reset all temporary variables (after flush gate). """
self._clear = True
Expand All @@ -129,10 +132,10 @@ def _store(self, cmd):
self._probabilities = dict()
self._clear = False
self.qasm = ""
self._json=[]
self._allocated_qubits = set()

gate = cmd.gate

if gate == Allocate:
self._allocated_qubits.add(cmd.qubits[0][0].id)
return
Expand All @@ -154,29 +157,36 @@ def _store(self, cmd):
ctrl_pos = cmd.control_qubits[0].id
qb_pos = cmd.qubits[0][0].id
self.qasm += "\ncx q[{}], q[{}];".format(ctrl_pos, qb_pos)
self._json.append({'qubits': [ctrl_pos, qb_pos], 'name': 'cx'})
elif gate == Barrier:
qb_pos = [qb.id for qr in cmd.qubits for qb in qr]
self.qasm += "\nbarrier "
qb_str = ""
for pos in qb_pos:
qb_str += "q[{}], ".format(pos)
self.qasm += qb_str[:-2] + ";"
self._json.append({'qubits': qb_pos, 'name': 'barrier'})
elif isinstance(gate, (Rx, Ry, Rz)):
assert get_control_count(cmd) == 0
qb_pos = cmd.qubits[0][0].id
u_strs = {'Rx': 'u3({}, -pi/2, pi/2)', 'Ry': 'u3({}, 0, 0)',
'Rz': 'u1({})'}
gate = u_strs[str(gate)[0:2]].format(gate.angle)
self.qasm += "\n{} q[{}];".format(gate, qb_pos)
else:
u_name = {'Rx': 'u3', 'Ry': 'u3',
'Rz': 'u1'}
u_angle = {'Rx': [gate.angle, -math.pi/2, math.pi/2], 'Ry': [gate.angle, 0, 0],
'Rz': [gate.angle]}
gate_qasm = u_strs[str(gate)[0:2]].format(gate.angle)
gate_name=u_name[str(gate)[0:2]]
params= u_angle[str(gate)[0:2]]
self.qasm += "\n{} q[{}];".format(gate_qasm, qb_pos)
self._json.append({'qubits': [qb_pos], 'name': gate_name,'params': params})
elif gate == H:
assert get_control_count(cmd) == 0
if str(gate) in self._gate_names:
gate_str = self._gate_names[str(gate)]
else:
gate_str = str(gate).lower()

qb_pos = cmd.qubits[0][0].id
self.qasm += "\n{} q[{}];".format(gate_str, qb_pos)
self.qasm += "\nu2(0,pi/2) q[{}];".format(qb_pos)
self._json.append({'qubits': [qb_pos], 'name': 'u2','params': [0, 3.141592653589793]})
else:
raise Exception('Command not authorized. You should run the circuit with the appropriate ibm setup.')

def _logical_to_physical(self, qb_id):
"""
Expand All @@ -198,6 +208,8 @@ def _logical_to_physical(self, qb_id):
def get_probabilities(self, qureg):
"""
Return the list of basis states with corresponding probabilities.
If input qureg is a subset of the register used for the experiment,
then returns the projected probabilities over the other states.

The measured bits are ordered according to the supplied quantum
register, i.e., the left-most bit in the state-string corresponds to
Expand All @@ -212,7 +224,7 @@ def get_probabilities(self, qureg):

Returns:
probability_dict (dict): Dictionary mapping n-bit strings to
probabilities.
probabilities.

Raises:
RuntimeError: If no data is available (i.e., if the circuit has
Expand All @@ -223,68 +235,70 @@ def get_probabilities(self, qureg):
raise RuntimeError("Please, run the circuit first!")

probability_dict = dict()

for state in self._probabilities:
mapped_state = ['0'] * len(qureg)
for i in range(len(qureg)):
mapped_state[i] = state[self._logical_to_physical(qureg[i].id)]
probability = self._probabilities[state]
probability_dict["".join(mapped_state)] = probability

mapped_state = "".join(mapped_state)
if mapped_state not in probability_dict:
probability_dict[mapped_state] = probability
else:
probability_dict[mapped_state] += probability
return probability_dict

def _run(self):
"""
Run the circuit.

Send the circuit via the IBM API (JSON QASM) using the provided user
data / ask for username & password.
Send the circuit via a non documented IBM API (using JSON written
circuits) using the provided user data / ask for the user token.
"""
# finally: add measurements (no intermediate measurements are allowed)
for measured_id in self._measured_ids:
qb_loc = self.main_engine.mapper.current_mapping[measured_id]
self.qasm += "\nmeasure q[{}] -> c[{}];".format(qb_loc,
qb_loc)

self._json.append({'qubits': [qb_loc], 'name': 'measure','memory':[qb_loc]})
# return if no operations / measurements have been performed.
if self.qasm == "":
return

max_qubit_id = max(self._allocated_qubits)
max_qubit_id = max(self._allocated_qubits) + 1
qasm = ("\ninclude \"qelib1.inc\";\nqreg q[{nq}];\ncreg c[{nq}];" +
self.qasm).format(nq=max_qubit_id + 1)
self.qasm).format(nq=max_qubit_id)
info = {}
info['qasms'] = [{'qasm': qasm}]
info['json']=self._json
info['nq']=max_qubit_id

info['shots'] = self._num_runs
info['maxCredits'] = 5
info['maxCredits'] = 10
info['backend'] = {'name': self.device}
info = json.dumps(info)

try:
if self._retrieve_execution is None:
res = send(info, device=self.device,
user=self._user, password=self._password,
shots=self._num_runs,
token=self._token,
num_retries=self._num_retries,
interval=self._interval,
verbose=self._verbose)
else:
res = retrieve(device=self.device, user=self._user,
password=self._password,
res = retrieve(device=self.device,
token=self._token,
jobid=self._retrieve_execution,
num_retries=self._num_retries,
interval=self._interval,
verbose=self._verbose)

counts = res['data']['counts']
# Determine random outcome
P = random.random()
p_sum = 0.
measured = ""
length=len(self._measured_ids)
for state in counts:
probability = counts[state] * 1. / self._num_runs
state = list(reversed(state))
state = "".join(state)
state="{0:b}".format(int(state,0))
state=state.zfill(max_qubit_id)
#states in ibmq are right-ordered, so need to reverse state string
state=state[::-1]
p_sum += probability
star = ""
if p_sum >= P and measured == "":
Expand Down Expand Up @@ -322,9 +336,3 @@ def receive(self, command_list):
else:
self._run()
self._reset()

"""
Mapping of gate names from our gate objects to the IBM QASM representation.
"""
_gate_names = {str(Tdag): "tdg",
str(Sdag): "sdg"}
Loading