Shor’s Algorithm Based on MindSpore Quantum
Introduction to Shor’s Algorithm
The time complexity of Shor’s algorithm to decompose an integer
Basic Idea of Shor’s Algorithm
Shor’s algorithm aims to solve the problem: given an integer
Factorization involves some knowledge in number theory, and it is possible to reduce the factorization problem to the function
where
Set
It indicates that the factors of
Therefore, the main idea of Shor’s algorithm is to transform the problem of factoring large numbers into the problem of finding the function’s period. Since we can use the superposition principle to perform parallel computing in quantum computing, we can quickly find the period period finding algorithm
in this document ). In general, we need to implement the function:
Taking
Randomly choose a number, such as
Find the greatest common divisor,
Find the period of the function
, so thatRunning the quantum circuit we can get
Find the greatest common divisor,
Find the greatest common divisor,
Hence, the prime factor of
are 3 and 5, and the decomposition operation is complete.
The quantum circuit of Shor’s algorithm is shown as follows:
Implementing Shor’s Algorithm Using MindSpore Quantum
First, we need to import some required modules.
[1]:
#pylint: disable=W0611
import numpy as np
from fractions import Fraction
from mindquantum.core.gates import X, H, UnivMathGate, Measure
from mindquantum.core.circuit import Circuit, UN
from mindquantum.algorithm.library import qft
from mindquantum.simulator import Simulator
From the basic idea of Shor’s algorithm, we can see that the main part of Shor’s algorithm is period finding subroutine processed by quantum computers, and the most difficult part of the period search algorithm is the operator
Constructing the Oracle
Shor’s algorithm’s core quantum part is the period finding, and its key lies in efficiently implementing a unitary operator
Specifically, the unitary operator
Where:
is a register of qubits, used to store the exponent from to ( ). is also a register of qubits, used to store intermediate results. is the result of the classical modular exponentiation. represents the bitwise XOR operation. XOR is chosen to facilitate the construction of the corresponding unitary matrix (a permutation matrix) and ensure the operation’s reversibility.
Although the complete
Implementation Steps
Determine the number of qubits:
Target register (register 2): Needs
qubits to store (range from to ).Control register (register 1): Stores the exponent
. To ensure the Quantum Fourier Transform yields the period with high probability, the number of qubits should satisfy , i.e., . Hence, in theory we choose , where is the size of the control register’s state space.Simplified approach in this tutorial: For demonstration and resource considerations, we use
qubits for the control register (thus ), giving a total of qubits. While this works for and , for larger it significantly reduces the probability of finding the correct period , possibly requiring more trials or only yielding a factor of , or failing.Note on notation: In the remainder of this tutorial, whenever we refer to the control register’s qubit count or its corresponding state space size
, we are referring to the simplified and .
Calculate modular exponentiation values: For all
(where ), compute .Construct the Unitary Matrix :math:`U`: Create a
zero matrix. For each basis state ( ), compute: and .Set
. This permutation matrix is unitary.
Create ``UnivMathGate``: Instantiate a
UnivMathGate
with the constructed matrix and apply it toregister2 + register1
(ensuring corresponds to the lower bits).
Example: N=15, a=2
We need
We can obtain
[2]:
q = 4 # number of qubits
N = 15
a = 2
x = []
f = []
for i in range(2**q):
x.append(i)
f.append(a**i % N)
print('x: ', x)
print('f(x): ', f)
x: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
f(x): [1, 2, 4, 8, 1, 2, 4, 8, 1, 2, 4, 8, 1, 2, 4, 8]
We can observe that
Next, we construct the unitary gate corresponding to the modular exponentiation operation:
[3]:
def create_mod_exp_oracle(N, a, register1, register2):
"""
Construct the gate for modular exponentiation U|x>|y> = |x>|y XOR (a^x mod N)>.
Args:
N (int): The number to be factored.
a (int): The random base chosen in Shor's algorithm.
register1 (list): Qubit indices for register 1 (requires q qubits where 2^q >= N).
register2 (list): Qubit indices for register 2 (requires q qubits where 2^q >= N).
Returns:
UnivMathGate: The gate corresponding to the modular exponentiation U|x>|y> = |x>|y XOR (a^x mod N)>.
"""
q = len(register1)
n_qubits = 2 * q
dim = 2**n_qubits
Q = 2**q
U_matrix = np.zeros((dim, dim), dtype=complex)
# Precompute f(x) = a^x mod N
fx_map = {}
for x in range(Q):
fx_map[x] = pow(a, x, N)
# Construct the permutation matrix
for x in range(Q): # Iterate through states |x> of register 1
fx = fx_map[x]
for y in range(Q): # Iterate through states |y> of register 2
idx_in = (x << q) + y # Index of |x>|y>
idx_out = (x << q) + (y ^ fx) # Index of |x>|y XOR f(x)>
U_matrix[idx_out, idx_in] = 1
# Verify unitarity
assert np.allclose(U_matrix @ U_matrix.conj().T, np.eye(dim))
# Create the gate
# Note: The order in .on() is register2 + register1 to match the matrix construction where y is the lower bits
oracle_gate = UnivMathGate(f'ModExp({a},{N})', U_matrix).on(register2 + register1)
return oracle_gate
Now, the gate constructed by the create_mod_exp_oracle()
function can perform modular exponentiation on the quantum state
Little-Endian Convention and Qubit Allocation
MindQuantum uses the little-endian convention to represent quantum states. In this convention, the index of a qubit corresponds to its significance in representing a numerical value: the qubit with the lowest index (index 0) represents the Least Significant Bit (LSB). Therefore, an N-qubit state is typically written as
To naturally align MindQuantum’s little-endian convention with the quantum state notation
Register 1 (logical value :math:`x`): Qubits
to (higher-indexed qubits).Register 2 (logical value :math:`y`): Qubits
to (lower-indexed qubits).
This means that although register 1 might be depicted above register 2 in schematic diagrams of Shor’s algorithm, the circuit diagram drawn by MindQuantum will show register 2 above register 1. Quantum gates and measurements will be adjusted accordingly.
This allocation ensures that in MindQuantum’s state vector representation, the qubits associated with the logical value UnivMathGate
. It’s important to emphasize that this index-based allocation does not change the core logical function of the Oracle, which is to modify the value of register 2 (
Verify the Oracle
We can verify if the Oracle works as expected by applying it to a specific initial state. For example, let’s compute
Since
1000
. 0001
. So the final state 1000 0001
(with register 1 as the higher bits).
[4]:
#pylint: disable=W0104
register1 = range(4, 8)
register2 = range(4)
circuit = Circuit(X.on(7)) # Create circuit, initialize state to |1000>|0000>, i.e., x=8, |8>|0>
circuit += create_mod_exp_oracle(15, 2, list(register1), list(register2)) # Apply the oracle operator
circuit.svg() # Print the circuit diagram
[4]:
[5]:
print(circuit.get_qs('mqvector', ket=True)) # Print the final state
1¦10000001⟩
The result in register 1 is 1000
, and the result in register 2 is 0001
. We previously calculated
Next, we need to implement the period finding algorithm.
Period Finding Subroutine
In register 1, we need
qubits to record the binary number of the variable , and we also need qubits in register 2 to record binary form. At this time, register 1 and register 2 can respectively record the integers of , where .The Hadamard gate is applied to all bits in register 1, and the bits in register 1 are in a uniform superposition state of all integers in
Perform function operation
on the state stored in register 1, and store the result in register 2. This step is completed by the previously constructed U_operator . Due to the direct operation on the superposition state , this step can completed in one step, which shows the Quantum Advantage - parallel computing. At this time, the state stored in the circuit is an entangled state, which can be expressed asPerform an inverse Quantum Fourier Transform (iQFT) on register 1. This transform uses a
-order unit root , which evenly distributes the amplitude of any given state on states of . As shown in step 3, the equivalent states of , , etc. in register 1 are all entangled with the same state in register 2. Due to quantum interference, the final measurement probability for a state is larger when the phase factor is closer to 1 (i.e., points towards the positive real axis). In other words, the measured state has a high probability that is close to an integer . For a more detailed mathematical description, please refer to the link: https://en.wikipedia.org/wiki/Shor%27s_algorithm.Measure register 1 to get the binary string. Convert the binary string to the decimal number
. At this point, , where is an unknown integer. Use the continued fraction algorithm to find the irreducible fraction that approximates (with denominator no larger than ). The denominator of this fraction is the period candidate . However, there might be another fraction closer to , or and might share a common factor, resulting in being a factor of the true period. In such cases, the calculation fails, and we need to repeat the process.
Taking the example of Constructing the Oracle
, we calculated each
[6]:
#pylint: disable=W0104
circuit = Circuit() # Create a quantum circuit
register1 = range(4, 8) # Set qubits 4-7 to register 1
register2 = range(4) # Set qubits 0-3 to register 2
circuit += UN(H, register1) # Apply H gate to all bits in register 1
# Perform the modular exponentiation operation using the Oracle
# U|x>|y> -> |x>|y XOR a^x mod N>
circuit += create_mod_exp_oracle(15, 2, list(register1), list(register2))
# Perform the inverse Quantum Fourier Transform on register 1.
# Note the qubit order for QFT: [::-1] reverses the register order for correct transformation.
circuit += qft(register1[::-1]).hermitian()
circuit += UN(Measure(), register1) # Measure register 1
circuit.svg() # Draw a circuit diagram
[6]:
From the circuit diagram, we can intuitively see that the entire period-finding circuit consists of four parts: Superposition Generation
Next, run the circuit 100 times and observe the measurement results.
[7]:
# pylint: disable=W0104
sim = Simulator('mqvector', circuit.n_qubits) # Create a quantum circuit simulator
# Simulate the circuit 100 times, print the measurement results, set the random seed to a random integer within 100
result = sim.sampling(circuit, shots=100, seed=np.random.randint(100))
result.svg()
[7]:
From the statistical results, we can see that only 4 states can be measured in the last register 1, which are
Next we are going to construct a general period-finding algorithm.
[8]:
def period_finder(N, a, q):
circuit = Circuit() # Create a quantum circuit
register1 = range(q, 2 * q) # Set qubits q to 2q-1 for register 1
register2 = range(q) # Set qubits 0 to q-1 for register 2
circuit += UN(H, register1) # Apply H gate to all qubits in register 1
# Apply the modular exponentiation Oracle as one big U gate
circuit += create_mod_exp_oracle(N, a, list(register1), list(register2))
circuit += qft(register1[::-1]).hermitian() # Perform inverse QFT on register 1 (note reversed order)
circuit += UN(Measure(), register1) # Measure register 1
sim = Simulator('mqvector', circuit.n_qubits) # Create a quantum circuit simulator
# Simulate the circuit once, collect the measurement result, random seed in [0,100)
result = sim.sampling(circuit, seed=np.random.randint(100), shots=1)
# result.data is a dict where key is measured binary string, value is count (1)
result = list(result.data.keys())[0] # Get the measured binary string
result = int(result, 2) # Convert the result from binary to decimal
# Use continued fraction to approximate result/2**q with denominator <= N
eigenphase = float(result / 2**q)
f = Fraction.from_float(eigenphase).limit_denominator(N)
r = f.denominator # The denominator is the period candidate
# Verify if r is the actual period
if pow(a, r, N) == 1:
return r
return None
Classic Computer Part
The classical computer part is responsible for transforming the factorization problem into the problem of finding function period. The specific steps are as follows:
Randomly pick an integer
less than , use the gcd algorithm to verify whether and are mutually prime, if there is a common factor between and , then we directly get one of ’s factor, output the result.Determine the number of qubits
required to store the binary representation of (such that ).Use the period finding algorithm (quantum part) to get the period
of the function .Determine whether
is an even number. If not, go back to step 1.Calculate
and . Compute the greatest common divisor (gcd) of each of these with . One of these gcds might be a non-trivial factor of . However, it’s possible that is divisible by , or the gcd is 1 or N. If a non-trivial factor is found, output the factors. Otherwise (e.g., if is odd or the gcds are trivial), go back to step 1.
[9]:
#pylint: disable=C0121,R1705
def shor(N):
while True:
a = np.random.randint(N - 2) + 2 # Generate a random integer a in [2, N-1]
b = np.gcd(a, N) # Compute gcd(a, N)
if b != 1:
return b, int(N / b) # If b is not equal to 1, then b is a prime factor of N. Return the decomposition result
# Determine the number of bits q such that 2**q >= N
q = 0
while True:
Q = 2**q
if Q >= N:
break
q += 1
r = period_finder(N, a, q) # Get the period r
# If r is not even or not found, retry
if r != None and r % 2 == 0:
break
# Compute a**(r/2)+1 and a**(r/2)-1 and verify that they have a convention with N. If so, output the result
c = np.gcd(a**(int(r / 2)) + 1, N)
d = np.gcd(a**(int(r / 2)) - 1, N)
if c != 1 and N % c == 0:
return c, int(N / c)
else:
return d, int(N / d)
It should be noted that since we directly constructed the oracle as a huge unitary matrix gate, the simulation time has increased significantly. Therefore, for cases where
Finally, let’s try to factor
[10]:
N = 35
print("Factoring N = p * q =", N)
p, q = shor(N)
print("p =", p)
print("q =", q)
Factoring N = p * q = 35
p = 5
q = 7
As we can see from the results, we successfully decomposed 35 into two prime factors 5 and 7.
So far, we have successfully implemented the Shor’s algorithm using MindSpore Quantum.
[11]:
from mindquantum.utils.show_info import InfoTable
InfoTable('mindquantum', 'scipy', 'numpy')
[11]:
Software | Version |
---|---|
mindquantum | 0.10.0 |
scipy | 1.15.2 |
numpy | 1.26.4 |
System | Info |
Python | 3.10.16 |
OS | Darwin arm64 |
Memory | 17.18 GB |
CPU Max Thread | 10 |
Date | Fri May 16 19:22:48 2025 |