QPanda3  0.1.0
Supported by OriginQ
Loading...
Searching...
No Matches
Quantum Simulator

Prev Tutorial: Quantum Circuit and Quantum Program
Next Tutorial: Noise


Quantum Simulators in Quantum Computing

Quantum simulators play an important role in the field of quantum computing by providing researchers and developers with effective tools to simulate and analyze the behavior of quantum systems. These tools help not only in understanding quantum algorithms and the characteristics of quantum systems, but also in algorithm design and verification, especially when actual quantum computers are not yet widely available. When simulating quantum circuits, selecting the appropriate simulation backend is crucial. Different quantum circuit simulators are suited for various scenarios.

Full-Amplitude Quantum Simulator

The full-amplitude simulator can simulate and store all the amplitudes of a quantum state at once. However, it is limited by the machine's memory, and simulating more than 50 qubits becomes impractical. This type of simulator is suitable for low-qubit, high-depth quantum circuits, such as Google's random quantum circuits for low qubits, and scenarios that require obtaining the full simulation results.

Classical simulation of quantum circuits is crucial for better understanding the operations and behaviors of quantum computing. Such simulations allow researchers and developers to assess the complexity of new quantum algorithms and verify the effectiveness of quantum devices.

Full-amplitude simulation is one of the most classic simulation schemes in quantum computing, used to simulate small-scale quantum systems. In this simulation approach, the entire quantum system's state can be simulated accurately, providing insights into the system's evolution at different time points. The core idea behind this method is to represent and track the complete amplitude information of quantum states, using density matrices or pure state vectors to describe the quantum system's state.

For a single qubit, it can be considered as a two-level system. The quantum state \(|\psi\rangle\)can be represented as:

\[ |\psi\rangle = a_0|0\rangle + a_1|1\rangle \]

where \(a_0\), \(a_1\) are complex amplitudes, and:

\[ |a_0|^2 + |a_1|^2 = 1 \]

In this equation, \(∣0 \rangle\) and \(∣1\rangle \)are the two computational basis states. The quantum state can also be expressed as:

\[ |\psi\rangle = a_0 \begin{bmatrix} 1 \\ 0 \end{bmatrix} + a_1 \begin{bmatrix} 0 \\ 1 \end{bmatrix} = \begin{bmatrix} a_0 \\ a_1 \end{bmatrix} \]

For a n-qubit quantum system, the state can be represented by \(2^n\) amplitudes:

\[ |\psi\rangle = a_{0\ldots00}|0\ldots00\rangle + a_{0\ldots01}|0\ldots01\rangle + \ldots + a_{1\ldots11}|1\ldots11\rangle \]

All amplitudes must satisfy the normalization condition:

\[ \sum_i |a_i|^2 = 1 \]

During quantum computation, all quantum logic gates are applied to the system in matrix form. The application of a single gate U to the K-th qubit can be expressed as:

\[ A = I^{\otimes (n-k-1)} \otimes U \otimes I^{\otimes k} \]

where I is the 2×2 identity matrix, and U is a 2×2 unitary matrix. For two-qubit gates, the process is similar.

As the number of qubits increases, the number of quantum state amplitudes required grows exponentially. The following table illustrates this issue, which is known as Quantum Supremacy.

Number of Qubits Simulated Minimum Memory Required for Storing All Quantum States (GB)
26 1
27 2
28 4
29 8
30 16
... ...
48 \( 2^{22} \)
49 \( 2^{23} \)
50 \( 2^{24} \)

When the number of qubits reaches 50 or more, the required memory becomes astronomically large. This issue is known as Quantum Supremacy, which refers to the ability of quantum computers to outperform classical computers on certain specific tasks—tasks that classical computers would take years, hundreds of years, or even more to complete.

This phenomenon highlights the immense potential of quantum computers in certain fields, as they can solve problems that classical computers would be unable to solve within a feasible time frame.

We use CPUQVM to run the full-amplitude simulation algorithm. First, we construct the circuit, then run it using run(prog, shots, noise_model), and finally obtain the result via result().

from pyqpanda3.core import *
circuit = QCircuit(3)
circuit << H(0)
circuit << P(2, 0.2)
circuit << RX(1, 0.9)
circuit << RX(0, 0.6)
circuit << RX(1, 0.3)
circuit << RY(1, 0.3)
circuit << RY(2, 2.7)
circuit << RX(0, 1.5)
prog = QProg()
prog.append(circuit)
prog.append(measure(0, 0))
prog.append(measure(1, 1))
prog.append(measure(2, 2))
machine = CPUQVM()
machine.run(prog, 1000)
measure_result = machine.result().get_counts()
print(measure_result)
Definition core.py:1

The result is as follows.

{'000': 16, '101': 330, '110': 129, '111': 168, '010': 10, '100': 322, '001': 15, '011': 10}

Additionally, noise model parameters can be passed in.

from pyqpanda3.core import *
model = NoiseModel()
model.add_all_qubit_quantum_error(pauli_x_error(0.5),GateType.RX)
model.add_all_qubit_quantum_error(pauli_x_error(0.9),GateType.H)
machine = CPUQVM()
machine.run(prog, 1000, model)

The result is as follows.

{'000': 12, '101': 339, '001': 15, '100': 303, '110': 169, '011': 8, '111': 143, '010': 11}

Full-Amplitude Quantum Simulator With GPU

The full-amplitude simulation algorithm internally uses a large amount of parallel computation for acceleration, achieving greater performance improvements on multi-core machines. We can use the GPUQVM to accelerate quantum circuit simulation for the same circuit, and the usage is the same as with CPUQVM. However, CUDA must be installed and the GPU environment must be configured on the computer beforehand.

from pyqpanda3.core import *
circuit = QCircuit(3)
circuit << H(0)
circuit << P(2, 0.2)
circuit << RX(1, 0.9)
circuit << RX(0, 0.5)
circuit << RX(1, 0.2)
circuit << RY(1, 0.3)
circuit << RY(2, 2.5)
circuit << RX(0, 1.5)
prog = QProg()
prog.append(circuit)
prog.append(measure(0, 0))
prog.append(measure(1, 1))
prog.append(measure(2, 2))
machine = GPUQVM()
machine.run(prog, 1000)
measure_result = machine.result().get_counts()
print(measure_result)

The output is as follows:

{'000': 19, '101': 327, '110': 122, '111': 175, '010': 9, '100': 323, '001': 14, '011': 11}

Partial-Amplitude Quantum Simulator

Partial-amplitude simulators rely on results from other simulators that provide the amplitudes of low-qubit quantum circuits. They can simulate more qubits but with reduced circuit depth. These simulators are typically used to obtain partial amplitude simulation results of quantum states.

In recent years, significant progress has been made in semiconductor quantum chips. Quantum supremacy claims that if a device with 50 qubits is built, it will surpass the limits of classical computers (i.e., directly simulating 50 qubits would require approximately 16 PB of RAM to store the complete vector).

The Google and IBM teams have proposed effective methods to simulate low-depth circuits with more than 49 qubits (e.g., delayed entangling gates)

Here, we propose a method to optimize the classical simulation of low-depth quantum circuits with large sampling numbers, and we have simulated circuits with 64 qubits.

Specifically, the approach is to map the circuit onto multiple additional sub-circuits by transforming several controlled-Z (CZ) gates into measurement gates and single-qubit gates.

These sub-circuits consist of two blocks that are not entangled with each other, thereby transforming an N-qubit simulation problem into a set of N/2 circuit solving problems.

Our method is similar to the balanced cut of a two-dimensional grid: for a CZ gate, it is split into four sub-circuits, and then the results of all the sub-circuits are summed to reconstruct the final state.

Simulation Scheme Details The basic idea behind the Partial-Amplitude Quantum Virtual Machine is to split a large qubit quantum program into several smaller qubit quantum programs, with each small quantum circuit being computed using a full-amplitude algorithm. The rule for splitting the quantum program is as follows: when a two-qubit gate crossing nodes appears in the quantum circuit, the quantum program is split. Specifically, the circuit is divided near the halfway point of the total number of qubits. For example, if the total number of qubits is 10, then the boundary is between qubit 4 and qubit 5.

When a two-qubit gate's control qubit and target qubit are located on opposite sides of the boundary, it is called a "cross-node." For example, if the total number of qubits is 10, a CNOT(1,5) is a cross-node, but CNOT(1,4) is not.

A CZ gate can be converted into a combination of two measurement gates and single-qubit gates as follows:

\[ \mathrm{CZ} = P_0 \otimes I + P_1 \otimes Z \]

where I is the identity matrix, and Z is the Pauli-Z matrix.

\[ P_0 = \begin{bmatrix} 1 & 0 \\ 0 & 0 \end{bmatrix}, \quad P_1 = \begin{bmatrix} 0 & 0 \\ 0 & 1 \end{bmatrix} \]

For other two-qubit gates such as CR, iSWAP, and SQiSWAP, they can be converted into a combination of single-qubit gates and two-qubit gates that can be split, and the two-qubit gates are then split similarly.

We use PartialAmplitudeQVM to run the full-amplitude simulation algorithm. First, we construct the circuit, then run it using run(prog), and finally obtain the result via get_state_vector(list).

from pyqpanda3.core import *
circuit = QCircuit(3)
circuit << H(0)
circuit << RY(1, 0.3)
circuit << RY(2, 2.7)
circuit << RX(0, 1.5)
circuit << CNOT(0, 1)
circuit << CNOT(1, 2)
prog = QProg()
prog.append(circuit)
machine = PartialAmplitudeQVM()
machine.run(prog)
measure_result = machine.get_state_vector(["0","1","2"])
print(measure_result)

The result is as follows.

[(0.11203780214230272-0.1043740198556836j), (0.01693285765754904-0.015774590250509563j), (0.07543963588749045-0.0702792977322559j)]

Density Matrix Simulator

For mixed states, it is difficult to fully represent the quantum state of the system with state vectors. Instead, we use the density matrix to describe it:

\[ \rho = \sum_{i}^{n} p_i |\varphi_i\rangle \langle \varphi_i| \]

For pure states, this simplifies to:

\[ \rho = |\varphi\rangle \langle \varphi| \]

Returning to the mixed state described earlier, its density matrix is:

\[ \rho = \frac{1}{2} |0\rangle \langle 0| + \frac{1}{2} |1\rangle \langle 1| = \frac{1}{2} \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \]

For the pure state \( |\psi\rangle = \frac{1}{\sqrt{2}}|0\rangle + \frac{1}{\sqrt{2}}|1\rangle \), its density matrix is:

\[ \rho = |\psi\rangle \langle \psi| = \frac{1}{2} \begin{bmatrix} 1 & 1 \\ 1 & 1 \end{bmatrix} \]

From the density matrices, we can see that the quantum states are completely different.

For pure states, \( \text{tr}(\rho^2) = 1 \), while for mixed states \( \text{tr}(\rho^2) < 1 . \text{tr}(A) \)represents the trace of matrix \( A \), which is the sum of the diagonal elements in an \( n \)-dimensional matrix.

Unitary Matrix Logic Gates and Density Matrix Representation

For general unitary matrices, we can represent the evolution of the system's state using the density matrix:

\[ \rho' = U \rho U^{\dagger} \]

Using the density matrix, we can represent the measurement result, where the probability of measuring outcome \( m \)is:

\[ p(m) = \text{tr}(M_m^{\dagger} M_m \rho) \]

Here, \( M_m \)is the measurement operator, also known as the projection operator. For example, if our computational basis is \( |0\rangle \), the projection operator onto \( |0\rangle \)is \( ,|0\rangle \langle 0| \). Thus, the probability of measuring \( |0\rangle \)is:

\[ p(|0\rangle) = \text{tr}(|0\rangle \langle 0| |0\rangle \langle 0| \rho) \]

Example Calculation for Mixed and Pure States

Using the earlier examples for the mixed state and pure state, we calculate the measurement results as follows:

Mixed State:

\[ p(|0\rangle) = \text{tr}\left( \frac{1}{2} \begin{bmatrix} 1 & 0 \\ 0 & 0 \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & 0 \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & 1 \end{bmatrix} \right) = \frac{1}{2} \]

Pure State:

\[ p(|0\rangle) = \text{tr}\left( \frac{1}{2} \begin{bmatrix} 1 & 0 \\ 0 & 0 \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & 0 \end{bmatrix} \begin{bmatrix} 1 & 1 \\ 1 & 1 \end{bmatrix} \right) = \frac{1}{2} \]

The density matrix provides an alternative way to represent quantum states. The density matrix simulator is used to solve the density matrix corresponding to quantum circuits, compute quantum state probability distributions, simulate noisy quantum circuits, and calculate Hamiltonian expectation values, among other tasks.

We use DensityMatrixSimulator to simulate the density matrix of quantum states. First, we construct the circuit, then run it using run(prog) as before.

from pyqpanda3.core import *
circuit = QCircuit(2)
circuit << H(0)
circuit << H(1)
circuit << RX(0, 1.5)
circuit << CZ(0, 1)
circuit << CP(1, 0, 0.22)
prog = QProg()
prog.append(circuit)
machine = DensityMatrixSimulator()
machine.run(prog)
density_matrix = machine.density_matrix()
print(density_matrix)
reduced_density_matrix = machine.reduced_density_matrix([0])
print(reduced_density_matrix)

density_matrix is used to obtain the density matrix of the entire system after evolution, while reduced_density_matrix is used to obtain the density matrix of the subsystem. The output is as follows:

#density_matrix
[[ 0.25 +0.j 0.25 +0.j 0.25 +0.j
-0.24397436+0.05455741j]
[ 0.25 +0.j 0.25 +0.j 0.25 +0.j
-0.24397436+0.05455741j]
[ 0.25 +0.j 0.25 +0.j 0.25 +0.j
-0.24397436+0.05455741j]
[-0.24397436-0.05455741j -0.24397436-0.05455741j -0.24397436-0.05455741j
0.25 +0.j ]]
#reduced_density_matrix
[[0.5 +0.j 0.00602564+0.05455741j]
[0.00602564-0.05455741j 0.5 +0.j ]]

The density matrix also supports passing additional noise model parameters.

from pyqpanda3.core import *
circuit = QCircuit(2)
circuit << H(0)
circuit << CNOT(0, 1)
circuit << RX(0, 0.8)
circuit << RX(0, 1.2)
circuit << RX(0, 3.6)
circuit << CP(0, 1, 4.9)
prog = QProg()
prog.append(circuit)
machine = DensityMatrixSimulator()
machine.run(prog)
density_matrix = machine.density_matrix()
print(density_matrix)
model = NoiseModel()
model.add_all_qubit_quantum_error(pauli_x_error(0.6),GateType.RX)
model.add_all_qubit_quantum_error(pauli_z_error(0.9),GateType.H)
model.add_all_qubit_quantum_error(pauli_z_error(0.66),GateType.RX)
machine.run(prog, model)
density_matrix = machine.density_matrix()
print(density_matrix)

The output is as follows:

#density_matrix
[[ 0.44389147+0.00000000e+00j 0. -1.57816659e-01j
0. -1.57816659e-01j 0.08279125+4.36102334e-01j]
[-0. +1.57816659e-01j 0.05610853+0.00000000e+00j
0.05610853+0.00000000e+00j -0.15504739+2.94347591e-02j]
[-0. +1.57816659e-01j 0.05610853+0.00000000e+00j
0.05610853+0.00000000e+00j -0.15504739+2.94347591e-02j]
[ 0.08279125-4.36102334e-01j -0.15504739-2.94347591e-02j
-0.15504739-2.94347591e-02j 0.44389147+1.38777878e-17j]]
#density_matrix with noise
[[ 0.17717768+0.j 0. -0.23915875j 0. -0.23915875j
0.03304583+0.17406867j]
[ 0. +0.23915875j 0.32282232+0.j 0.32282232+0.j
-0.23496214+0.04460607j]
[ 0. +0.23915875j 0.32282232+0.j 0.32282232+0.j
-0.23496214+0.04460607j]
[ 0.03304583-0.17406867j -0.23496214-0.04460607j -0.23496214-0.04460607j
0.17717768+0.j ]]

Stabilizer

Superposition and entanglement are typical sources of quantum advantage. However, when the number of qubits N in the system increases, the number of quantum state coefficients grows exponentially with N . This makes it impossible to use classical computers for traditional full-amplitude simulation, a problem known as the exponential wall.

Based on the Gottesman-Knill theorem, we know that for stabilizer circuits formed from a specific gate set, simulation can be performed in polynomial time. This means that, in certain circuits constructed from specific logic gates, it is possible to break the quantum exponential advantage and apply classical simulation to quantum circuits, enabling verification of quantum computer results. Furthermore, in future fault-tolerant quantum computers, redundancy will be necessary for error correction, which is currently unachievable for large-scale quantum circuits in existing quantum simulation frameworks.

To address this, we can take a different approach. Using stabilizer and the corresponding Clifford gate set simulators, we can efficiently exploit the polynomial-time simulation property to solve fault-tolerant quantum computing based on Pauli noise. Moreover, to extend this to general quantum computing, we can incorporate the theoretical properties of stabilizers into Clifford+T simulation. With a Clifford+T simulator, we can simulate large quantum circuits under the condition of few non-Clifford logic gates (since Clifford+T can approximate any logic gate).

Principle Introduction

For a quantum state \( |\psi\rangle \) (typically referring to a pure state), if there exists a unitary matrix \( U \) such that \( U|\psi\rangle = |\psi\rangle \), then we say that \( |\psi\rangle \) is stabilized by \( U \), and \( U \)is a stabilizer of \( |\psi\rangle \), for example, \( Z|0\rangle = |0\rangle \).

It is clear that a quantum state can have multiple stabilizers. When there are multiple stabilizers, the product of these stabilizers is also a stabilizer:

\[ Z_1 Z_2 X_1 X_2 |\psi\rangle = Z_1 Z_2 |\psi\rangle = |\psi\rangle \]

This multiplicative closure tells us that stabilizers form a group.

For a quantum state \( |\psi\rangle \), if each element in the unitary transformation group \( S \)is a stabilizer of \( |\psi\rangle \), then the entire unitary transformation group \( S \)is called the stabilizer group of \( |\psi\rangle \).

In general, we focus on the Pauli matrices \( \{ X, Y, Z, I \} \) as stabilizers. The unitary transformation group is thus formed from the Pauli group, defined as:

\[ \text{Stab}(|\psi\rangle) = \{ P \in \mathcal{P}_n : P|\psi\rangle = |\psi\rangle \} \]

Here, the Pauli group \( \mathcal{P}_n \) is the set of Pauli operators acting on \( n \)qubits, where the phase coefficients are \( \pm 1 \) and \( \pm i \):

\[ \mathcal{P}_n = \{ i^{\gamma} X(a) Z(b) : \gamma \in \{ 0, 1, 2, 3 \}, a, b \in \{0, 1\}^n \} \]

If \( P^{(1)}, \dots, P^{(m)} \in \mathcal{P}_n \)are independent elements of the Pauli group, then we can use the special properties of the Pauli group to obtain:

\[ \begin{matrix} \text{Stab}(|00\rangle) = \{ I, Z_1, Z_2, Z_1 Z_2 \} = \langle Z_1, Z_2 \rangle \\ \text{Stab}(|++\rangle) = \{ I, X_1, X_2, X_1 X_2 \} = \langle X_1, X_2 \rangle \\ \text{Stab}\left( \frac{|00\rangle + |11\rangle}{\sqrt{2}} \right) = \{ I, X_1 X_2, Z_1 Z_2, -Y_1 Y_2 \} = \langle X_1 X_2, Z_1 Z_2 \rangle \\ \text{Stab}(|0^n\rangle) = \{ Z(a) : a \in \{0, 1\}^n \} = \langle Z_1, \dots, Z_n \rangle \end{matrix} \]

The problem is how to construct the Stabilizer Group. Here, we need to mention that when elements from the Clifford Group act on the Pauli group, they induce a transformation:

\[ \mathbf{P|\psi\rangle = |\psi\rangle} \Longleftrightarrow \left( \mathbf{UPU^{\dagger}} \right) \mathbf{U|\psi\rangle = U|\psi\rangle} \]

When we write the group as \( P = i^{\gamma} X(a) Z(b) \), the action of the Clifford Group is as follows:

\[ U_j P U_j^{\dagger} = i^{\gamma} X^{a_1} Z^{b_1} \otimes \ldots \otimes X^{a_{j-1}} Z^{b_{j-1}} \otimes U X^{a_j} Z^{b_j} U^{\dagger} \otimes X^{a_{j+1}} Z^{b_{j+1}} \otimes \ldots \otimes X^{a_n} Z^{b_n} \]

It is surprising to find that:

\[ U_j P U_j^{\dagger} = P_{\text{new}} \]

This is illustrated in the following diagram:

Operation Input Output Operation Input Output
\( CNOT \) \( X_1 \) \( X_1X_2 \) \( X \) \( X \) \( X \)
\( X_2 \) \( X_2 \) \( Z \) \( -Z \)
\( Z_1 \) \( Z_1 \) \( Y \) \( X \) \( -X \)
\( Z_2 \) \( Z_1Z_2 \) \( Z \) \( -Z \)
\( H \) \( X \) \( Z \) \( Z \) \( X \) \( -X \)
\( Z \) \( X \) \( Z \) \( Z \)
\( S \) \( X \) \( Y \)
\( Z \) \( Z \)

Here, we can see that the transformation of \( Y \)is removed because \( Y = IXZ \).

Thus, \( |\psi\rangle \rightarrow U|\psi\rangle \)is equivalent to tracking the evolution of the stabilizer. This allows us to obtain the complete dynamical information of the system.

\[ S \rightarrow U S U^{\dagger} \]

By converting the problem of quantum gate evolution into the problem of updating the stabilizer group of a quantum state, the core idea of using Stabilizer to simulate quantum circuits is to represent quantum states using the Stabilizer Group rather than the amplitudes used in traditional simulators.

In other words, for stabilizer circuits formed from a specific gate set, we can simulate large quantum circuits in polynomial time, as long as the circuits are composed of gates from the Clifford quantum logic gate set and its derived set: \( H, S, X, Y, Z, CNOT, CY, CZ, \text{SWAP} \).

We use Stabilizer to run large-bit Clifford circuits. The usage is the same as with other simulators; after running with run(prog, shots, noise_model), we obtain the result via result().

from pyqpanda3.core import *
prog = QProg()
prog.append(random_qcircuit([0,1,2,3,4,5 ], 20, ["H","X","Y","Z","S","CZ","CNOT","SWAP"]))
prog.append(measure(0,0))
prog.append(measure(1,1))
prog.append(measure(2,2))
machine = Stabilizer()
machine.run(prog,1000)
measure_result = machine.result().get_counts()
print(measure_result)

Stabilizer can support Clifford circuit simulation for up to 5000 qubits. The output is as follows:

{'011': 239, '001': 260, '010': 257, '000': 244}