Skip to content

Commit

Permalink
Merge pull request #389 from Quantum-TII/examples_grover
Browse files Browse the repository at this point in the history
Examples grover
  • Loading branch information
scarrazza authored Apr 26, 2021
2 parents 6681af2 + 2f9f151 commit a7da4d1
Show file tree
Hide file tree
Showing 15 changed files with 574 additions and 19 deletions.
1 change: 1 addition & 0 deletions doc/source/applications.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ problems.
tutorials/shor/README.md
tutorials/qPDF/qPDF.ipynb
tutorials/bell-variational/README.md
tutorials/grover/README.md
2 changes: 1 addition & 1 deletion doc/source/hep.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ x. Further details and references about this model are presented in the
qPDF circuit model
^^^^^^^^^^^^^^^^^^

.. autoclass:: qibo.hep.qPDF
.. autoclass:: qibo.models.hep.qPDF
:members:
:member-order: bysource
1 change: 1 addition & 0 deletions doc/source/tutorials/grover/README.md
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ physics problems.
- [Shor's factorization algorithm](shor/README.md)
- [Determining the proton content with proton with a quantum computer](qPDF/qPDF.ipynb)
- [Maximal violation of Bell inequalities variationally](bell-variational/README.md)
- [A general Grover model](grover/README.md)

In the `benchmarks` folder we have included examples concerning:
- A generic benchmark script for multiple circuits (`benchmarks/main.py`)
Expand Down
125 changes: 125 additions & 0 deletions examples/grover/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# A General Grover Model

Code at: [https://github.com/Quantum-TII/qibo/tree/master/examples/grover](https://github.com/Quantum-TII/qibo/tree/master/examples/grover).

The examples presented here provide information to run a general Grover model accessible as
`from qibo.models import Grover`. This model allows to construct a general
circuit to make use of generalized Grover models to search for states on an
unstructured database. References can be checked at

- For Grover's original search algorithm: [arXiv:quant-ph/9605043](https://arxiv.org/abs/quant-ph/9605043)
- For the iterative version with unknown solutions:[arXiv:quant-ph/9605034](https://arxiv.org/abs/quant-ph/9605034)
- For the Grover algorithm with any superposition:[arXiv:quant-ph/9712011](https://arxiv.org/abs/quant-ph/9712011)

The arguments of the model are

- oracle (`qibo.core.circuit.Circuit`): quantum circuit that flips
the sign using a Grover ancilla initialized with -X-H-. Grover ancilla
expected to be the last qubit of oracle circuit.
- superposition_circuit (`qibo.core.circuit.Circuit`): quantum circuit that
takes an initial state to a superposition. Expected to use the first
set of qubits to store the relevant superposition.
- initial_state_circuit (`qibo.core.circuit.Circuit`): quantum circuit
that initializes the state. If empty defaults to |000..00>
- superposition_qubits (int): number of qubits that store the relevant superposition.
Leave empty if superposition does not use ancillas.
- superposition_size (int): how many states are in a superposition.
Leave empty if its an equal superposition of quantum states.
- number_solutions (int): number of expected solutions. Needed for normal Grover.
Leave empty for iterative version.
- target_amplitude (float): absolute value of the amplitude of the target state. Only for
advanced use and known systems.
- check (function): function that returns True if the solution has been
found. Required of iterative approach.
First argument should be the bitstring to check.
- check_args (tuple): arguments needed for the check function.
The found bitstring not included.
- iterative (bool): force the use of the iterative Grover

We provide three different examples to run Grover with:

### Example 1: standard Hadamard superposition and simple oracle.

In this first example we show how to use a standard Grover search where the
search space is an equally weighted superposition of quantum states and the oracle
is simply defined through a layer of Hadamard gates. This example makes no use of
ancilla qubits, so the command lines simplify greatly.
1. First we create a superposition circuit. It is done by just initializing a 5-qubit circuit,
and adding Hadamard gates
```python
superposition = Circuit(5)
superposition.add([gates.H(i) for i in range(5)])
```

2. The next step is creating the oracle. In this case, we look for the states where all the
qubits are in the `1` state
```python
oracle = Circuit(5 + 1)
oracle.add(gates.X(5).controlled_by(*range(5)))
```

3. Now we create the Grover model.
```python
grover = Grover(oracle, superposition_circuit=superposition, number_solutions=1)
solution, iterations = grover()
```
In this case there are no ancilla qubits, and it is straightforward to see that there is only
one possible solution, so we do not have to use any further information as the input for the circuit.

### Example 2: standard Hadamard superposition and oracle with ancillas

This second example is more complicated since we create an oracle function that makes use of ancilla qubits.
We want to create a Grover model such that it searches all the basis states with `num_1` qubits in the
`1` state. The search space is all the possibilities with `qubits` qubits. Those parameters
are to be defined in the script. Functions `one_sum, sum_circuit, oracle` create the corresponding circuits for the oracle.
The superposition circuit is the standard Hadamard one and is therefore not specified. However, note that since
there are ancilla qubits in the oracle, we need to give the information of the size of the search space through the
argument `superposition_qubits`. We also provide a `check` function counting the number of `1` in a bit string to be used
in the iterative approach.

In the non-iterative standard case we must write
```python
grover = Grover(oracle, superposition_qubits=qubits, number_solutions=int(binom(qubits, num_1)))
solution, iterations = grover()
```

For the iterative case, the corresponding code is
```python
grover = Grover(oracle, superposition_qubits=qubits, check=check, check_args=(num_1,))
solution, iterations = grover()
```


### Example 3: Ancillas for superposition and oracle, setting size of search space

In this third example we create a Grover model with two components:
- A superposition circuit creating all the elements in the computational basis with `num_1` qubits in the `1` state.
- An oracle checking that the first `num_1` qubits are in the `1` state and does not care about any other qubit. This
oracle is written in such a way that it needs ancilla qubits. This is not necessary in Qibo, it is done in this way for
illustrating the model.

The joint action of both elements looks for the |1...10...0> element. The size of the search space is not 2^n in this case,
but smaller.

1. First, the superposition circuit is created
```python
superposition = superposition_circuit(qubits, num_1)
```
This circuit has `qubits` superposition qubits and some ancillas.

2. Then the oracle is created
```python
oracle = oracle(qubits, num_1)
or_circuit = Circuit(oracle.nqubits)
or_circuit.add(oracle.on_qubits(*(list(range(qubits)) + [oracle.nqubits - 1] + list(range(qubits, oracle.nqubits - 1)))))
```
The `oracle` object has `qubits` qubits for the superposition, an ancilla qubit detecting whether the conditions are
fulfilled or not, and some other auxiliary ancillas. In order to relabel the qubits so that the important ancilla is
at the bottom of the circuit, we must add two more lines and use a feature provided by Qibo.

Again, calling the Grover model is enough to execute the circuit. The binomial function allows to obtain the exact size
of the search space.
```python
grover = Grover(or_circuit, superposition_circuit=superposition, superposition_qubits=qubits, number_solutions=1,
superposition_size=int(binomial(qubits, num_1)))
```
41 changes: 41 additions & 0 deletions examples/grover/example1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from qibo import gates
from qibo.models.grover import Grover
from qibo.models import Circuit
import argparse


def main(nqubits):
"""Create an oracle, find state |11...11> for a number of qubits.
Args:
nqubits (int): number of qubits
Returns:
solution (str): found string
iterations (int): number of iterations needed
"""
superposition = Circuit(nqubits)
superposition.add([gates.H(i) for i in range(nqubits)])

oracle = Circuit(nqubits + 1)
oracle.add(gates.X(nqubits).controlled_by(*range(nqubits)))
# Create superoposition circuit: Full superposition over the selected number qubits.

# Generate and execute Grover class
grover = Grover(oracle, superposition_circuit=superposition,
number_solutions=1)

solution, iterations = grover()

print('The solution is', solution)
print('Number of iterations needed:', iterations)

return solution, iterations


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--nqubits", default=10, type=int,
help="Number of qubits.")
args = vars(parser.parse_args())
main(**args)
117 changes: 117 additions & 0 deletions examples/grover/example2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
from qibo import gates
from qibo.models.grover import Grover
from qibo.models import Circuit
import numpy as np
from scipy.special import binom
import argparse


def one_sum(sum_qubits):
c = Circuit(sum_qubits + 1)

for q in range(sum_qubits, 0, -1):
c.add(gates.X(q).controlled_by(*range(0, q)))

return c


def sum_circuit(qubits):
sum_qubits = int(np.ceil(np.log2(qubits))) + 1
sum_circuit = Circuit(qubits + sum_qubits)
sum_circuit.add(gates.X(qubits).controlled_by(0))
sum_circuit.add(gates.X(qubits).controlled_by(1))
sum_circuit.add(gates.X(qubits + 1).controlled_by(*[0, 1]))

for qub in range(2, qubits):
sum_circuit.add(one_sum(sum_qubits).on_qubits(
*([qub] + list(range(qubits, qubits + sum_qubits)))))

return sum_circuit


def oracle(qubits, num_1):
sum = sum_circuit(qubits)
oracle = Circuit(sum.nqubits + 1)
oracle.add(sum.on_qubits(*range(sum.nqubits)))

booleans = np.binary_repr(num_1, int(np.ceil(np.log2(qubits)) + 1))

for i, b in enumerate(booleans[::-1]):
if b == '0':
oracle.add(gates.X(qubits + i))

oracle.add(gates.X(sum.nqubits).controlled_by(*range(qubits, sum.nqubits)))

for i, b in enumerate(booleans[::-1]):
if b == '0':
oracle.add(gates.X(qubits + i))

oracle.add(sum.invert().on_qubits(*range(sum.nqubits)))

return oracle


def check(instance, num_1):
res = instance.count('1') == num_1
return res


def main(nqubits, num_1, iterative=False):
"""Create an oracle, find the states with some 1's among all the states with a fixed number of qubits
Args:
nqubits (int): number of qubits
num_1 (int): number of 1's to find
iterative (bool): use iterative model
Returns:
solution (str): found string
iterations (int): number of iterations needed
"""
oracle_circuit = oracle(nqubits, num_1)

#################################################################
###################### NON ITERATIVE MODEL ######################
#################################################################

if not iterative:
grover = Grover(oracle_circuit, superposition_qubits=nqubits,
number_solutions=int(binom(nqubits, num_1)))

solution, iterations = grover()

print('\nNON ITERATIVE MODEL: \n')

print('The solution is', solution)
print('Number of iterations needed:', iterations)
print('\nFound number of solutions: ', len(solution),
'\nTheoretical number of solutions:', int(binom(nqubits, num_1)))

return solution, iterations

#################################################################
######################## ITERATIVE MODEL ########################
#################################################################

print('\nITERATIVE MODEL: \n')

if iterative:
grover = Grover(oracle_circuit, superposition_qubits=nqubits,
check=check, check_args=(num_1,))
solution, iterations = grover()

print('Found solution:', solution)
print('Number of iterations needed:', iterations)

return solution, iterations


if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--nqubits", default=10, type=int,
help="Number of qubits.")
parser.add_argument("--num_1", default=2, type=int,
help="Number of 1's to find.")
parser.add_argument('--iterative', action='store_true',
help="Use iterative model")
args = vars(parser.parse_args())
main(**args)
Loading

0 comments on commit a7da4d1

Please sign in to comment.