Challenge 1: Break the Bank - Las Vegas

Your objective in this project, should you choose to accept it, you and your team will play dice at a Las Vegas casino and win every time! How? By creating a pair of quantum dice!

The Plan

In order for this to work, you will need to create at least two entangled dice. However, be sure that you entangle them in a way that security teams won't figure it out and catch you before you make your big move! Ideally you want the dice to provide enough information to you without anyone figuring out what happened. You can do this by ensuring that the entangled dice not roll out the same entangled result each time, but rather different results.

For example: If all of your dice roll the same values, it would be too obvious: Dice 1 = 101, Dice 2 = 101 (Where each entangled die is at a different table)

If all your dice roll different yet expected values, it would not be too obvious: Dice 1 = 101, Dice 2 = 010 (This is perfect, not easily detected yet as you can see by the results, perfectly entangled)

Let's get to work!

You're team will occupy two tables. The first table we will call the 'source', and the other tables we will call the 'target'. The plan is to have your team at the source table, and you at the target table.

You will then bet according to the results of the roll of the dice of the team at the Source table. So the source table rolls a '010' (2), then because your dice are entangled with the Source dice, you will roll the opposite (binary digits), '101' (5). This is why we want to ensure that the values are opposite, so that casino security won't think there is anything wrong, wheras if both dice are entangled resulting in the same value each.

The next steps here are to "program" your entangled dice, good luck!


In [1]:
# begin by importing the essential libraries from qiskit
from qiskit import IBMQ, Aer
from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit

# The following options are recomended for Jupter notebook visualization using matplotlib
%matplotlib inline

Now let's create the circuit to implement and visualize the quantum circuit needed to create the entangled dice. In this example, to keep things simple, we will create an 8-sided die with binary values 0-7.


In [2]:
# set up Quantum Register and Classical Register for 3 qubits (binary values 000 -> 111)
q = QuantumRegister(3)
c = ClassicalRegister(3)
# Create a Quantum Circuit
qc = QuantumCircuit(q, c)

In [3]:
# now let's set our source die into superposition and measure
# so to simulate random die values 0-7, or 1-8 since we dice do not have a 0 value. 
# Note: the 'barrier' is there just so the circuit displays well, it has no effect on the circuit itself.
qc.h(q)
qc.barrier(q)
qc.measure(q, c)


Out[3]:
<qiskit.circuit.instructionset.InstructionSet at 0x1a1bda0e10>

In [4]:
# For completeness, let's see what the openqasm code looks like. 
QASM_source = qc.qasm()
print(QASM_source)


OPENQASM 2.0;
include "qelib1.inc";
qreg q0[3];
creg c0[3];
h q0[0];
h q0[1];
h q0[2];
barrier q0[0],q0[1],q0[2];
measure q0[0] -> c0[0];
measure q0[1] -> c0[1];
measure q0[2] -> c0[2];


In [5]:
# While we're at it, let's also look at our circuit visually. 
# This is similar to what we would see in the IBM Q Composer. 
qc.draw()


Out[5]:
         ┌───┐ ░ ┌─┐      
q0_0: |0>┤ H ├─░─┤M├──────
         ├───┤ ░ └╥┘┌─┐   
q0_1: |0>┤ H ├─░──╫─┤M├───
         ├───┤ ░  ║ └╥┘┌─┐
q0_2: |0>┤ H ├─░──╫──╫─┤M├
         └───┘ ░  ║  ║ └╥┘
 c0_0: 0 ═════════╩══╬══╬═
                     ║  ║ 
 c0_1: 0 ════════════╩══╬═
                        ║ 
 c0_2: 0 ═══════════════╩═
                          

Now let's run and test out our die. We will run a single shot (to simulate rolling the die on a casino table). Rerun the cell below a few times so to see the values of the die change for each roll.


In [6]:
from qiskit import execute
job = execute(qc, backend=Aer.get_backend('qasm_simulator'), shots=1)
result = job.result().get_counts(qc)
print(result)


{'101': 1}

Create the Target die

In this section we will now create the target die, which we will entangle to our source die. To simplify this let's create the second die by adding 3 more qubits. Then the first three qubits we can refer to as the source die, and the second set of three qubits are for the target die.

For example, the classical bit result {010111} means that the source die (first three bits from left, LSB) = 111, and the target die (remaing three bits) are the bits to the right of the first three = 010.


In [7]:
# load IBM Q account, only if you want to run this on a real device
IBMQ.load_accounts()


/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/utils/deprecation.py:53: DeprecationWarning: IBMQ.load_accounts() is being deprecated. Please use IBM Q Experience v2 credentials and IBMQ.load_account() (note the singular form) instead. You can find the instructions to make the updates here: 
https://github.com/Qiskit/qiskit-ibmq-provider#updating-to-the-new-ibm-q-experience
  DeprecationWarning)
---------------------------------------------------------------------------
JSONDecodeError                           Traceback (most recent call last)
/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/api/utils.py in obtain_token(self, config)
    135             response.raise_for_status()
--> 136             self.data_credentials = response.json()
    137         except (requests.HTTPError, ValueError) as ex:

/anaconda3/lib/python3.7/site-packages/requests/models.py in json(self, **kwargs)
    896                     pass
--> 897         return complexjson.loads(self.text, **kwargs)
    898 

/anaconda3/lib/python3.7/json/__init__.py in loads(s, encoding, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)
    347             parse_constant is None and object_pairs_hook is None and not kw):
--> 348         return _default_decoder.decode(s)
    349     if cls is None:

/anaconda3/lib/python3.7/json/decoder.py in decode(self, s, _w)
    336         """
--> 337         obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    338         end = _w(s, end).end()

/anaconda3/lib/python3.7/json/decoder.py in raw_decode(self, s, idx)
    354         except StopIteration as err:
--> 355             raise JSONDecodeError("Expecting value", s, err.value) from None
    356         return obj, end

JSONDecodeError: Expecting value: line 1 column 1 (char 0)

During handling of the above exception, another exception occurred:

ApiError                                  Traceback (most recent call last)
/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/ibmqsingleprovider.py in _authenticate(cls, credentials)
     84             return IBMQConnector(credentials.token, config_dict,
---> 85                                  credentials.verify)
     86         except Exception as ex:

/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/api/ibmqconnector.py in __init__(self, token, config, verify)
    102 
--> 103         self.req = Request(token, config=config, verify=verify)
    104 

/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/api/utils.py in __init__(self, token, config, verify, retries, timeout_interval)
    216                                       proxy_urls=self.proxy_urls,
--> 217                                       ntlm_credentials=self.ntlm_credentials)
    218 

/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/api/utils.py in __init__(self, token, config, verify, proxy_urls, ntlm_credentials)
     67         if token:
---> 68             self.obtain_token(config=self.config)
     69         else:

/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/api/utils.py in obtain_token(self, config)
    137         except (requests.HTTPError, ValueError) as ex:
--> 138             raise ApiError('error during login: %s' % str(ex))
    139 

ApiError: error during login: Expecting value: line 1 column 1 (char 0)

The above exception was the direct cause of the following exception:

ConnectionError                           Traceback (most recent call last)
<ipython-input-7-b72266d43695> in <module>
      1 # load IBM Q account, only if you want to run this on a real device
----> 2 IBMQ.load_accounts()

/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/utils/deprecation.py in _wrapper(self, *args, **kwargs)
     52             .format(func.__name__, func.__name__[:-1]),
     53             DeprecationWarning)
---> 54         return func(self, *args, **kwargs)
     55 
     56     return _wrapper

/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/ibmqfactory.py in load_accounts(self, **kwargs)
    485         else:
    486             # Old API v1 call
--> 487             self._v1_provider.load_accounts(**kwargs)
    488 
    489     @deprecated

/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/ibmqprovider.py in load_accounts(self, **kwargs)
    191         for credentials in discover_credentials().values():
    192             if self._credentials_match_filter(credentials, kwargs):
--> 193                 self._append_account(credentials)
    194 
    195         if not self._accounts:

/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/ibmqprovider.py in _append_account(self, credentials)
    256             warnings.warn('Credentials are already in use.')
    257 
--> 258         single_provider = IBMQSingleProvider(credentials, self)
    259 
    260         self._accounts[credentials.unique_id()] = single_provider

/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/ibmqsingleprovider.py in __init__(self, credentials, ibmq_provider)
     49         # Get a connection to IBMQ.
     50         self.credentials = credentials
---> 51         self._api = self._authenticate(self.credentials)
     52         self._ibm_provider = ibmq_provider
     53 

/anaconda3/lib/python3.7/site-packages/qiskit/providers/ibmq/ibmqsingleprovider.py in _authenticate(cls, credentials)
     91                 root_exception = None
     92             raise ConnectionError("Couldn't connect to IBMQ server: {0}"
---> 93                                   .format(ex)) from root_exception
     94 
     95     def _discover_remote_backends(self):

ConnectionError: Couldn't connect to IBMQ server: error during login: Expecting value: line 1 column 1 (char 0)

In [8]:
# First let's double the size of our bit set.
bitSize = 3

# since we have just two dice, source and target, we will multiply by two.
q = QuantumRegister(2*bitSize)
c = ClassicalRegister(2*bitSize)
# Create a Quantum Circuit, same as before.
qc = QuantumCircuit(q, c)

# now, just as before, let's set our source die into superposition and measure
qc.h(q)
qc.barrier(q)
qc.measure(q, c)


Out[8]:
<qiskit.circuit.instructionset.InstructionSet at 0x1a1be59d68>

In [9]:
# Review the openqasm code. 
QASM_source = qc.qasm()
print(QASM_source)


OPENQASM 2.0;
include "qelib1.inc";
qreg q1[6];
creg c1[6];
h q1[0];
h q1[1];
h q1[2];
h q1[3];
h q1[4];
h q1[5];
barrier q1[0],q1[1],q1[2],q1[3],q1[4],q1[5];
measure q1[0] -> c1[0];
measure q1[1] -> c1[1];
measure q1[2] -> c1[2];
measure q1[3] -> c1[3];
measure q1[4] -> c1[4];
measure q1[5] -> c1[5];

Note the measure mapping above maps to 6 classic bits, we will view bits 0-2 (first three) as the source, and bits 3-5 (remaining three) as the target.


In [10]:
# Now draw the circuit with a bundled classical register
qc.draw()


Out[10]:
         ┌───┐ ░ ┌─┐               
q1_0: |0>┤ H ├─░─┤M├───────────────
         ├───┤ ░ └╥┘┌─┐            
q1_1: |0>┤ H ├─░──╫─┤M├────────────
         ├───┤ ░  ║ └╥┘┌─┐         
q1_2: |0>┤ H ├─░──╫──╫─┤M├─────────
         ├───┤ ░  ║  ║ └╥┘┌─┐      
q1_3: |0>┤ H ├─░──╫──╫──╫─┤M├──────
         ├───┤ ░  ║  ║  ║ └╥┘┌─┐   
q1_4: |0>┤ H ├─░──╫──╫──╫──╫─┤M├───
         ├───┤ ░  ║  ║  ║  ║ └╥┘┌─┐
q1_5: |0>┤ H ├─░──╫──╫──╫──╫──╫─┤M├
         └───┘ ░  ║  ║  ║  ║  ║ └╥┘
 c1_0: 0 ═════════╩══╬══╬══╬══╬══╬═
                     ║  ║  ║  ║  ║ 
 c1_1: 0 ════════════╩══╬══╬══╬══╬═
                        ║  ║  ║  ║ 
 c1_2: 0 ═══════════════╩══╬══╬══╬═
                           ║  ║  ║ 
 c1_3: 0 ══════════════════╩══╬══╬═
                              ║  ║ 
 c1_4: 0 ═════════════════════╩══╬═
                                 ║ 
 c1_5: 0 ════════════════════════╩═
                                   

Ok, now that you have built the circuit we are ready to test this out. Run this with one shot and note the results.


In [11]:
# We will use the qasm simulator to run this first. 
# You can then try running it on the real machine and compare the results.
job = execute(qc, backend=Aer.get_backend('qasm_simulator'), shots=1)
result = job.result().get_counts(qc)
print(result)


{'000001': 1}

After executing the above, you will note that each time you run it, the source and target dice change randomly. This is fine, except this does not yet help with what we need to accomplish our objective. We want to now entangle them so that they are not so random between source and target. In order to do this we need to do two things.

  1. Only the source die should be random, since this will simulate the random roll of a die.
  2. Entangle the target die to the source die.

In [12]:
# First, reset the circuit
q = QuantumRegister(2*bitSize)
c = ClassicalRegister(2*bitSize)
# Create a Quantum Circuit, same as before.
qc = QuantumCircuit(q, c)

# Set only the source die to random by adding a Hadamard gate to the first three, then
# we want to entangle the the first three qubits to the last three qubits. We'll do this using a simple for loop.

# Adding h to first three, and cx between source and target
for i in range(bitSize):
    qc.h(q[i])
    qc.cx(q[i], q[i+bitSize])

qc.barrier(q)
# Measure the full circuit    
qc.measure(q, c)


Out[12]:
<qiskit.circuit.instructionset.InstructionSet at 0x1a1bf95e80>

In [13]:
# Visualize the circuit
qc.draw()


Out[13]:
         ┌───┐                ░ ┌─┐               
q2_0: |0>┤ H ├──■─────────────░─┤M├───────────────
         ├───┤  │             ░ └╥┘┌─┐            
q2_1: |0>┤ H ├──┼────■────────░──╫─┤M├────────────
         ├───┤  │    │        ░  ║ └╥┘┌─┐         
q2_2: |0>┤ H ├──┼────┼────■───░──╫──╫─┤M├─────────
         └───┘┌─┴─┐  │    │   ░  ║  ║ └╥┘┌─┐      
q2_3: |0>─────┤ X ├──┼────┼───░──╫──╫──╫─┤M├──────
              └───┘┌─┴─┐  │   ░  ║  ║  ║ └╥┘┌─┐   
q2_4: |0>──────────┤ X ├──┼───░──╫──╫──╫──╫─┤M├───
                   └───┘┌─┴─┐ ░  ║  ║  ║  ║ └╥┘┌─┐
q2_5: |0>───────────────┤ X ├─░──╫──╫──╫──╫──╫─┤M├
                        └───┘ ░  ║  ║  ║  ║  ║ └╥┘
 c2_0: 0 ════════════════════════╩══╬══╬══╬══╬══╬═
                                    ║  ║  ║  ║  ║ 
 c2_1: 0 ═══════════════════════════╩══╬══╬══╬══╬═
                                       ║  ║  ║  ║ 
 c2_2: 0 ══════════════════════════════╩══╬══╬══╬═
                                          ║  ║  ║ 
 c2_3: 0 ═════════════════════════════════╩══╬══╬═
                                             ║  ║ 
 c2_4: 0 ════════════════════════════════════╩══╬═
                                                ║ 
 c2_5: 0 ═══════════════════════════════════════╩═
                                                  

In [14]:
# Execute the circuit on the simulator and note the results, 
# set to just a single shot and run a few times to compare results.
job = execute(qc, backend=Aer.get_backend('qasm_simulator'), shots=1)
result = job.result().get_counts(qc)
print(result)


{'101101': 1}

Since we have these dice entangled we should see the results with the source bits (bit 0-2, LSB) and target bits (3-5), and are exactly the same each and every time we execute the experiment.

Challenge

Since we don't want the casino to get too suspicious by noticing that two tables always have the same results, we will now update this to that the two dice are entangled, but not the same value. In order to do this, we will want the source and target to have "opposite" yet predictable values.

For example: {101010} Source = 101 Target = 010

Create a circuit which would represent this.

Solution: for workshop lead (Hide cell and code from attendees)

Hint: You only need X (NOT) gates.

Answer: Just add an X-gate before the CNOT on the target qubits.


In [15]:
bitSize = 3
q = QuantumRegister(2*bitSize)
c = ClassicalRegister(2*bitSize)
qc = QuantumCircuit(q, c)
for i in range(bitSize):
    qc.h(q[i])
    qc.x(q[i+bitSize])
    qc.cx(q[i], q[i+bitSize])

qc.barrier(q)  
qc.measure(q, c)

job = execute(qc, backend=Aer.get_backend('qasm_simulator'), shots=1)
result = job.result().get_counts(qc)
print(result)


{'101010': 1}

In [16]:
# Visualize the circuit
qc.draw()


Out[16]:
         ┌───┐                ░ ┌─┐               
q3_0: |0>┤ H ├──■─────────────░─┤M├───────────────
         ├───┤  │             ░ └╥┘┌─┐            
q3_1: |0>┤ H ├──┼────■────────░──╫─┤M├────────────
         ├───┤  │    │        ░  ║ └╥┘┌─┐         
q3_2: |0>┤ H ├──┼────┼────■───░──╫──╫─┤M├─────────
         ├───┤┌─┴─┐  │    │   ░  ║  ║ └╥┘┌─┐      
q3_3: |0>┤ X ├┤ X ├──┼────┼───░──╫──╫──╫─┤M├──────
         ├───┤└───┘┌─┴─┐  │   ░  ║  ║  ║ └╥┘┌─┐   
q3_4: |0>┤ X ├─────┤ X ├──┼───░──╫──╫──╫──╫─┤M├───
         ├───┤     └───┘┌─┴─┐ ░  ║  ║  ║  ║ └╥┘┌─┐
q3_5: |0>┤ X ├──────────┤ X ├─░──╫──╫──╫──╫──╫─┤M├
         └───┘          └───┘ ░  ║  ║  ║  ║  ║ └╥┘
 c3_0: 0 ════════════════════════╩══╬══╬══╬══╬══╬═
                                    ║  ║  ║  ║  ║ 
 c3_1: 0 ═══════════════════════════╩══╬══╬══╬══╬═
                                       ║  ║  ║  ║ 
 c3_2: 0 ══════════════════════════════╩══╬══╬══╬═
                                          ║  ║  ║ 
 c3_3: 0 ═════════════════════════════════╩══╬══╬═
                                             ║  ║ 
 c3_4: 0 ════════════════════════════════════╩══╬═
                                                ║ 
 c3_5: 0 ═══════════════════════════════════════╩═
                                                  

Congratulations! You have just completed the first challenge!


In [ ]: