Skip to content

Commit e15d756

Browse files
authored
Merge branch 'dev' into nlocal
2 parents 4172965 + b39ac31 commit e15d756

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+7856
-235
lines changed

examples/grover/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Grover's Search Algorithm
2+
3+
Grover's Search Algorithm [1] is an algorithm which can speed up an unstructured search problem quadratically using amplitude amplification. A detailed walkthrough can be found [here](https://quantum-computing.ibm.com/composer/docs/iqx/guide/grovers-algorithm). The file `grover_example_sudoku.py` provides an example of how to use the algorithm to solve a sudoku puzzle of size 2x2.
4+
5+
6+
## References
7+
8+
1. Grover, Lov K.. “A fast quantum mechanical algorithm for database search.” Symposium on the Theory of Computing (1996).
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""
2+
This example is based on Qiskt's Texbook: https://learn.qiskit.org/course/ch-algorithms/grovers-algorithm#sudoku
3+
4+
We will now tackle a 2x2 binary sudoku problem using Grover's algorithm, where we don't necessarily possess
5+
prior knowledge of the solution. The problem adheres to two simple rules:
6+
7+
1. No column can have the same value repeated.
8+
2. No row can have the same value repeated.
9+
10+
We will use the following 4 variables:
11+
12+
---------
13+
| a | b |
14+
---------
15+
| c | d |
16+
---------
17+
18+
Please keep in mind that while utilizing Grover's algorithm to solve this particular problem may not be practical
19+
(as the solution can likely be determined mentally), the intention of this example is to showcase the process of
20+
transforming classical decision problems into oracles suitable for Grover's algorithm.
21+
22+
We need to check for four conditions:
23+
24+
1. a != b
25+
2. c != d
26+
3. a != c
27+
4. b != d
28+
"""
29+
30+
import torchquantum as tq
31+
from torchquantum.algorithms import Grover
32+
33+
34+
# To simplify the process, we can compile this set of comparisons into a list of clauses for convenience.
35+
clauses = [ [0, 1], [0, 2], [1, 3], [2, 3] ]
36+
37+
# This circuit checks if input0 is equal to input1 and stores the output in output.
38+
# The output of each comparison is stored in a new bit.
39+
def XOR(input0, input1, output):
40+
op1 = {'name': 'cnot', 'wires': [input0, output]}
41+
op2 = {'name': 'cnot', 'wires': [input1, output]}
42+
return [op1, op2]
43+
44+
# To verify each clause, we repeat the above circuit for every pairing in the `clauses`.
45+
ops = []
46+
clause_qubits = [4, 5, 6, 7]
47+
for i, clause in enumerate(clauses):
48+
ops += XOR(clause[0], clause[1], clause_qubits[i])
49+
50+
# To determine if the assignments of a, b, c, d are a solution to the sudoku, we examine the final state
51+
# of the `clause_qubits`. Only when all of these qubits are 1, it indicates that the clauses are satisfied.
52+
# To achieve this, we incorporate a multi-controlled Toffoli gate in our checking circuit. This gate
53+
# ensures that a single output bit will be set to 1 if and only if all the clauses are satisfied,
54+
# allowing us to easily determine if our assignment is a solution.
55+
ops += [{'name': 'multicnot', 'n_wires': 5, 'wires': [4,5,6,7,8]}]
56+
57+
# In order to transform our checking circuit into a Grover oracle, it is crucial to ensure that the `clause_qubits`
58+
# are always returned to their initial state after the computation. This guarantees that `clause_qubits` are all
59+
# set to 0 once our circuit has finished running. To achieve this, we include a step called "uncomputation"
60+
# where we repeat the segment of the circuit that computes the clauses. This uncomputation step ensures the
61+
# desired state restoration, enabling us to effectively use the circuit as a Grover oracle.
62+
for qubit, clause in enumerate(clauses):
63+
ops += XOR(clause[0], clause[1], qubit + 4)
64+
65+
# Full Algorithm
66+
# We can combine all the components we have discussed so far
67+
68+
qmodule = tq.QuantumModule.from_op_history(ops)
69+
iterations = 2
70+
qdev = tq.QuantumDevice(n_wires=9, device="cpu")
71+
72+
# Initialize output qubit (last qubit) in state |->
73+
qdev.x(wires=8)
74+
qdev.h(wires=8)
75+
76+
# Perform Grover's Search
77+
grover = Grover(qmodule, iterations, 4)
78+
result = grover.execute(qdev)
79+
bitstring = result.bitstring[0]
80+
81+
# Extract the top two most likely solutions
82+
res = {k: v for k, v in sorted(bitstring.items(), key=lambda item: item[1], reverse=True)}
83+
84+
# Print the top two most likely solutions
85+
top_keys = list(res.keys())[:2]
86+
print("Top two most likely solutions:")
87+
for key in top_keys:
88+
print("Solution: ", key)
89+
print("a = ", key[0])
90+
print("b = ", key[1])
91+
print("c = ", key[2])
92+
print("d = ", key[3])
93+
print("")

examples/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ TorchQuantum Examples
88
param_shift_onchip_training/param_shift_onchip_training.ipynb
99
quantum_kernel_method/quantum_kernel_method.ipynb
1010
quanvolution/quanvolution.ipynb
11+
superdense_coding/superdense_coding_torchquantum.ipynb

examples/superdense_coding/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Superdense Coding
2+
3+
Superdense coding is a quantum communication protocol that allows the transmission of two classical bits of information using only one qubit`[1]`. It takes advantage of quantum entanglement and the ability to manipulate qubits in superposition. Charles H. Bennett and Stephen Wiesner proposed this technique in 1970(though it was not published until 1992`[2]`) and it was experimentally realised in 1996 by Klaus Mattle, Harald Weinfurter, Paul G. Kwiat, and Anton Zeilinger utilising entangled photon pairs.
4+
5+
## Author
6+
7+
[Soham Bopardikar](https://github.com/bopardikarsoham)
8+
9+
## References
10+
11+
[1] Bennett, C.H., Brassard, G., Crépeau, C., Jozsa, R., Peres, A. and Wootters, W.K., 1993. Teleporting an unknown quantum state via dual classical and Einstein-Podolsky-Rosen channels. Physical review letters, 70(13), p.1895.
12+
13+
[2] Bennett, C.H. and Wiesner, S.J., 1992. Communication via one-and two-particle operators on Einstein-Podolsky-Rosen states. Physical review letters, 69(20), p.2881.

examples/superdense_coding/superdense_coding_torchquantum.ipynb

Lines changed: 538 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import math
2+
3+
import torch
4+
import torch.nn as nn
5+
import torch.nn.functional as F
6+
from torch.nn.utils.rnn import pad_sequence
7+
import torchquantum as tq
8+
import torchquantum.functional as tqf
9+
import argparse
10+
import tqdm
11+
import time
12+
13+
# Preparing the entangled state/ 2 qubit bell pair.
14+
def bell_pair():
15+
qdev = tq.QuantumDevice(n_wires=2, bsz=1, device="cpu")
16+
qdev.h(wires=0)
17+
qdev.cnot(wires=[0, 1])
18+
return qdev
19+
20+
# Encoding the message
21+
def encode_message(qdev, qubit, msg):
22+
if len(msg) != 2 or not set(msg).issubset({"0","1"}):
23+
raise ValueError(f"message '{msg}' is invalid")
24+
if msg[1] == "1":
25+
qdev.x(wires=qubit)
26+
if msg[0] == "1":
27+
qdev.z(wires=qubit)
28+
return qdev
29+
30+
# Decoding the message
31+
def decode_message(qdev):
32+
qdev.cx(wires=[0, 1])
33+
qdev.h(wires=0)
34+
return qdev
35+
36+
# Putting all these functions together
37+
def main():
38+
# Creating the entangled pair between Alice and Bob
39+
qdev = bell_pair()
40+
# Encoding the message at Alice's end
41+
message = '10'
42+
qdev = encode_message(qdev, 1, message)
43+
# Decoding the original message at Bob's end
44+
qdev = decode_message(qdev)
45+
# Finally, Bob measures his qubits to read Alice's message
46+
print(tq.measure(qdev, n_shots=1024))
47+
48+
if __name__ == "__main__":
49+
main()

test/operator/test_op.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
from tqdm import tqdm
3434

3535
import qiskit.circuit.library.standard_gates as qiskit_gate
36+
import qiskit.circuit.library as qiskit_library
37+
3638

3739
RND_TIMES = 100
3840

@@ -73,6 +75,21 @@
7375
# {'qiskit': qiskit_gate.?, 'tq': tq.CU2},
7476
{"qiskit": qiskit_gate.CU3Gate, "tq": tq.CU3},
7577
{"qiskit": qiskit_gate.ECRGate, "tq": tq.ECR},
78+
{"qiskit": qiskit_library.QFT, "tq": tq.QFT},
79+
{"qiskit": qiskit_gate.SdgGate, "tq": tq.SDG},
80+
{"qiskit": qiskit_gate.TDgGate, "tq": tq.TDG},
81+
{"qiskit": qiskit_gate.SXdgGate, "tq": tq.SXDG},
82+
{"qiskit": qiskit_gate.CHGate, "tq": tq.CH},
83+
{"qiskit": qiskit_gate.CCZGate, "tq": tq.CCZ},
84+
{"qiskit": qiskit_gate.iSwapGate, "tq": tq.ISWAP},
85+
{"qiskit": qiskit_gate.CSGate, "tq": tq.CS},
86+
{"qiskit": qiskit_gate.CSdgGate, "tq": tq.CSDG},
87+
{"qiskit": qiskit_gate.CSXGate, "tq": tq.CSX},
88+
{"qiskit": qiskit_gate.DCXGate, "tq": tq.DCX},
89+
{'qiskit': qiskit_gate.XXMinusYYGate, 'tq': tq.XXMINYY},
90+
{'qiskit': qiskit_gate.XXPlusYYGate, 'tq': tq.XXPLUSYY},
91+
{"qiskit": qiskit_gate.C3XGate, "tq": tq.C3X},
92+
{"qiskit": qiskit_gate.RGate, "tq": tq.R},
7693
]
7794

7895
import os

torchquantum/algorithm/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,5 @@
2323
"""
2424

2525
from .vqe import *
26-
from .hamiltonian import *
26+
from .hamiltonian import *
27+
from .qft import *

torchquantum/algorithm/grover.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import torchquantum as tq
2+
3+
__all__ = ["Grover"]
4+
5+
class GroverResult(object):
6+
"""Result class for Grover algorithm"""
7+
def __init__(self) -> None:
8+
self.iterations: int
9+
10+
class Grover(object):
11+
"""Grover's search algorithm based the paper "A fast quantum mechanical algorithm for database search" by Lov K. Grover
12+
https://arxiv.org/abs/quant-ph/9605043
13+
"""
14+
15+
def __init__(self, oracle: tq.module.QuantumModule, iterations: int, n_wires:int) -> None:
16+
"""
17+
Args:
18+
oracle (tq.module.QuantumModule): The oracle is a quantum module that adds a negative phase to the
19+
solution states.
20+
iterations (int): The number of iterations to run the algorithm for.
21+
n_wires (int): The number of qubits used in the quantum circuit.
22+
"""
23+
super().__init__()
24+
self._oracle = oracle
25+
self._iterations = iterations
26+
self._n_wires = n_wires
27+
28+
def initial_state_prep(self):
29+
"""
30+
Prepares the initial state of a quantum circuit by applying a Hadamard gate to each qubit.
31+
32+
Returns:
33+
a `QuantumModule` object that represents the initial state preparation circuit.
34+
"""
35+
ops = []
36+
for i in range(self._n_wires):
37+
ops.append({'name': 'hadamard', 'wires': i})
38+
return tq.QuantumModule.from_op_history(ops)
39+
40+
def diffusion_operator(self):
41+
"""
42+
Prepares the diffusion operator for the grover's circuit.
43+
44+
Returns:
45+
a quantum module that represents the diffusion operator for a quantum circuit.
46+
"""
47+
ops = []
48+
hadamards = [{'name': 'hadamard', 'wires': i} for i in range(self._n_wires)]
49+
flips = [{'name': 'x', 'wires': i} for i in range(self._n_wires)]
50+
51+
ops += hadamards
52+
ops += flips
53+
54+
if self._n_wires == 1:
55+
ops += [{'name': 'z', 'wires': 0}]
56+
else:
57+
ops += [{'name': 'hadamard', 'wires': self._n_wires - 1}]
58+
ops += [{'name': 'multicnot', 'n_wires': self._n_wires, 'wires': range(self._n_wires)}]
59+
ops += [{'name': 'hadamard', 'wires': self._n_wires - 1}]
60+
61+
ops += flips
62+
ops += hadamards
63+
64+
return tq.QuantumModule.from_op_history(ops)
65+
66+
67+
def construct_grover_circuit(self, qdev: tq.QuantumDevice):
68+
"""
69+
Constructs a Grover's algorithm circuit with an initial state preparation, oracle,
70+
and diffusion operator, and iterates through them a specified number of times.
71+
72+
Args:
73+
qdev (tq.QuantumDevice): tq.QuantumDevice is an object representing a quantum device or
74+
simulator on which quantum circuits can be executed.
75+
76+
Returns:
77+
the modified quantum device `qdev` after applying the Grover's algorithm circuit with the
78+
specified number of iterations.
79+
"""
80+
81+
self.initial_state_prep()(qdev)
82+
for _ in range(self._iterations):
83+
self._oracle(qdev)
84+
self.diffusion_operator()(qdev)
85+
86+
return qdev
87+
88+
def execute(self, qdev: tq.QuantumDevice, n_shots: int =1024):
89+
"""
90+
Executes a Grover search algorithm on a given quantum device and returns the result.
91+
92+
Args:
93+
qdev (tq.QuantumDevice): tq.QuantumDevice is an object representing a quantum device or
94+
simulator on which quantum circuits can be executed.
95+
n_shots (int): The number of times the circuit is run to obtain measurement statistics.
96+
Defaults to 1024
97+
98+
Returns:
99+
an instance of the `GroverResult` class, which contains information about the results of
100+
running the Grover search algorithm on a quantum device. The `GroverResult` object includes the
101+
number of iterations performed, the measured bitstring, the top measurement (i.e. the most
102+
frequently measured bitstring), and the maximum probability of measuring the top measurement.
103+
"""
104+
105+
qdev = self.construct_grover_circuit(qdev)
106+
bitstring = tq.measure(qdev, n_shots=n_shots)
107+
top_measurement, max_probability = max(bitstring[0].items(), key=lambda x: x[1])
108+
max_probability /= n_shots
109+
110+
result = GroverResult()
111+
result.iterations = self._iterations
112+
result.bitstring = bitstring
113+
result.top_measurement = top_measurement
114+
result.max_probability = max_probability
115+
116+
return result

torchquantum/algorithm/qft.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import torchquantum as tq
2+
from typing import Iterable
3+
4+
__all__ = ["QFT"]
5+
6+
7+
class QFT(object):
8+
def __init__(
9+
self, n_wires: int = None, wires: Iterable = None, do_swaps=True
10+
) -> None:
11+
"""Init function for QFT class
12+
13+
Args:
14+
n_wires (int): Number of wires for the QFT as an integer
15+
wires (Iterable): Wires to perform the QFT as an Iterable
16+
add_swaps (bool): Whether or not to add the final swaps in a boolean format
17+
inverse (bool): Whether to create an inverse QFT layer in a boolean format
18+
"""
19+
super().__init__()
20+
21+
self.n_wires = n_wires
22+
self.wires = wires
23+
self.do_swaps = do_swaps
24+
25+
def construct_qft_circuit(self) -> tq.QuantumModule:
26+
"""Construct the QFT circuit."""
27+
return tq.layer.QFTLayer(
28+
n_wires=self.n_wires, wires=self.wires, do_swaps=self.do_swaps
29+
)
30+
31+
def construct_inverse_qft_circuit(self) -> tq.QuantumModule:
32+
"""Construct the inverse of a QFT circuit."""
33+
return tq.layer.QFTLayer(
34+
n_wires=self.n_wires, wires=self.wires, do_swaps=self.do_swaps, inverse=True
35+
)

0 commit comments

Comments
 (0)