3-1. How to use QURI Parts

The up-to-date documentation of QURI Parts is available here

QURI Parts is an open source library suite for creating and executing quantum algorithms on various quantum computers and simulators. In this section, you will learn how to install and use basic functions provided by QURI Parts.

Covered areas and components

  1. Core components: quri-parts-circuit: Quantum circuit (Gate, circuit, noise etc.) quri-parts-core: General components (Operator, state, estimator, sampler etc.)

  2. Platform (device/simulator) support: Quantum circuit simulators: quri-parts-qulacs: Qulacs quri-parts-stim: Stim quri-parts-itensor: ITensor Quantum platforms/SDKs: quri-parts-braket: Amazon Braket SDK quri-parts-cirq: Cirq (Only circuit conversion is supported yet) quri-parts-qiskit: Qiskit (Circuit conversion and execution are not supported yet)

  3. Intermediate representation support: quri-parts-openqasm: OpenQASM 3.0 quri-parts-algo: Algorithm (Ansatz, optimizer, error mitigation etc.) Chemistry quri-parts-chem: General concepts, Fermion-qubit mapping etc. Library support: quri-parts-openfermion Installation of QURI Parts QURI Parts requires Python 3.9.8 or later. Default installation only contains components not depending specific platforms (devices-simulators) or external libraries. You need to specify extras with square brackets to use those platforms and external libraries with QURI Parts: Use pip to install QURI Parts:

[ ]:
## Default installation, no extras
!pip install quri-parts

## Use Qulacs, a quantum circuit simulator
!pip install "quri-parts[qulacs]"

## Use Amazon Braket SDK
!pip install "quri-parts[braket]"

## Use Qulacs and OpenFermion, a quantum chemistry library for quantum computers
!pip install "quri-parts[qulacs,openfermion]"

Currently available extras are as follows: qulacs,braket,qiskit,cirq,openfermion,stim,openqasm,itensor

Basic utilities of QURI Parts

Quantum gates and circuits are essential when working on quantum computing. Here we describe basic treatment of them in QURI Parts. QURI Parts modules used in this tutorial: quri-parts-circuit, quri-parts-core and quri-parts-qulacs. You can install them as follows:

[ ]:
!pip install "quri-parts[qulacs]"

QuantumGate object

In QURI Parts, a quantum gate is represented by a QuantumGate object (more precisely NamedTuple). A QuantumGate contains not only the kind of the gate but also some additional information such as gate parameters and qubits on which the gate acts. You can create gate objects using QuantumGate:

[ ]:
from math import pi
from quri_parts.circuit import QuantumGate
gates = [
    # X gate acting on qubit 0 <
    QuantumGate("X", target_indices=(0,)),
    # Rotation gate acting on qubit 1 with angle pi/3
    QuantumGate("RX", target_indices=(1,), params=(pi/3,)),
    # CNOT gate on control qubit 2 and target qubit 1
    QuantumGate("CNOT", target_indices=(1,), control_indices=(2,))
]
for gate in gates:
print(gate)
QuantumGate(name= X, target_indices=(0,), control_indices=(), params=(), pauli_ids=())
QuantumGate(name= RX, target_indices=(1,), control_indices=(), params=(1.0471975511965976,), pauli_ids=())
QuantumGate(name= CNOT, target_indices=(1,), control_indices=(2,), params=(), pauli_ids=())

However it is more convenient to use factory functions:

[2]:
##Import necessary modules
from quri_parts.circuit import X, RX, CNOT
gates = [
    #X gate acting on qubit 0 <
    X(0),
    # Rotation gate acting on qubit 1 with angle pi/3
    RX(1, pi/3),
    # CNOT gate on control qubit 2 and target qubit 1
    CNOT(2, 1)
]

for gate in gates:
print(gate)
QuantumGate(name= X, target_indices=(0,), control_indices=(), params=(), pauli_ids=())
QuantumGate(name= RX, target_indices=(1,), control_indices=(), params=(1.0471975511965976,), pauli_ids=())
QuantumGate(name= CNOT, target_indices=(1,), control_indices=(2,), params=(), pauli_ids=())

QuantumCircuit object

You can construct a quantum circuit by specifying the number of qubits used in the circuit as follows:

[3]:
from quri_parts.circuit import QuantumCircuit

 # Create a circuit for 3 qubits
 circuit = QuantumCircuit(3)
 # Add an already created QuantumGate object
 circuit.add_gate(X(0))
 # Or use methods to add gates
 circuit.add_X_gate(0),
 circuit.add_RX_gate(1, pi/3)
 circuit.add_CNOT_gate(2, 1)
 circuit.add_PauliRotation_gate(target_qubits=(0, 1, 2), pauli_id_list=(1, 2, 3), angle=pi/3)

A QuantumCircuit object has several properties:

[4]:
print("Qubit count:", circuit.qubit_count)
print("Circuit depth:", circuit.depth)

gates = circuit.gates # .gates returns the gates in the circuit as a sequence
print("# of gates in the circuit:", len(gates))
for gate in gates:
    print(gate)
Qubit count: 3
Circuit depth: 3
# of gates in the circuit: 5
QuantumGate(name= X, target_indices=(0,), control_indices=(), params=(), pauli_ids=())
QuantumGate(name= X, target_indices=(0,), control_indices=(), params=(), pauli_ids=())
QuantumGate(name= RX, target_indices=(1,), control_indices=(), params=(1.0471975511965976,), pauli_ids=())
QuantumGate(name= CNOT, target_indices=(1,), control_indices=(2,), params=(), pauli_ids=())
QuantumGate(name= PauliRotation, target_indices=(0, 1, 2), control_indices=(), params=(1.0471975511965976,), pauli_ids=(1, 2, 3))

QuantumCircuit objects with the same number of qubits can be combined and extended:

[5]:
circuit2 = QuantumCircuit(3)
circuit2.add_Y_gate(1)
circuit2.add_H_gate(2)

combined = circuit + circuit2 # equivalent: combined = circuit.combine(circuit2)
print("Combined circuit:", combined.gates)

circuit2 += circuit # equivalent: circuit2.extend(circuit)
print("Extended circuit:", circuit2.gates)

Combined circuit: (QuantumGate(name= X , target_indices=(0,), control_indices=(), params=(), pauli_ids=()), QuantumGate(name= X , target_indices=(0,), control_indices=(), params=(), pauli_ids=()), QuantumGate(name= RX, target_indices=(1,), control_indices=(), params=(1.0471975511965976,), pauli_ids=()), QuantumGate(name= CNOT, target_indices=(1,), control_indices=(2,), params=(), pauli_ids=()), QuantumGate(name= PauliRotation, target_indices=(0, 1, 2), control_indices=(), params=(1.0471975511965976,), pauli_ids=(1, 2, 3)), QuantumGate(name='Y', target_indices=(1,), control_indices=(), params=(), pauli_ids=()), QuantumGate(name= H, target_indices=(2,), control_indices=(), params=(), pauli_ids=()))
Extended circuit: (QuantumGate(name= Y, target_indices=(1,), control_indices=(), params=(), pauli_ids=()), QuantumGate(name= H, target_indices=(2,), control_indices=(), params=(), pauli_ids=()), QuantumGate(name= X, target_indices=(0,), control_indices=(), params=(), pauli_ids=()), QuantumGate(name= X, target_indices=(0,), control_indices=(), params=(), pauli_ids=()), QuantumGate(name= RX, target_indices=(1,), control_indices=(), params=(1.0471975511965976,), pauli_ids=()), QuantumGate(name= CNOT, target_indices=(1,), control_indices=(2,), params=(), pauli_ids=()), QuantumGate(name= PauliRotation , target_indices=(0, 1, 2), control_indices=(), params=(1.0471975511965976,), pauli_ids=(1, 2, 3)))

Operators:

An operator is defined as a set of pairs of a Pauli label and its complex coefficient. For example, an operator: \((0.5+0.5j)*X0 Y1 + 0.2j*Z0 Z2 + (0.3+0.4j)*I\) can be defined as follows:

[6]:
from quri_parts.core.operator import Operator
op = Operator({
   pauli_label("X0 Y1"): 0.5 + 0.5j,
   pauli_label("Z0 Z2"): 0.2j,
   PAULI_IDENTITY: 0.3 + 0.4j,
})
print(op)
(0.5+0.5j)*X0 Y1 + 0.2j*Z0 Z2 + (0.3+0.4j)*I

To get a hermitian conjugated operator:

[7]:
conj = op.hermitian_conjugated()
print(conj)
(0.5-0.5j)*X0 Y1 - 0.2j*Z0 Z2 + (0.3-0.4j)*I

Quantum state

The 3 types of states in QURI Parts: ComputationalBasisState, GeneralCircuitQuantumState and QuantumStateVector

Computational Basis State

Several types of quantum states are available. Here we introduce the most basic states: computational basis states. Other types of states will be described later.

A computational basis state is a quantum state where each qubit is in 0 or 1 eigenstate. To construct a computational basis state for 5 qubits:

[ ]:
from quri_parts.core.state import ComputationalBasisState
state1 = ComputationalBasisState(5, bits=0b10100)
print(state1)

Here bits=0b10100 means that qubit \(0\) is in \(|0\rangle\), qubit \(1\) is in \(|0\rangle\), qubit \(2\) is in \(|1\rangle\), qubit \(3\) is in \(|0\rangle\) and qubit \(4\) is in \(|1\rangle\). We use \(0\)-based indices for qubits, and bits for qubits are ordered from the least significant bit to the most significant bit.

You can also create a superposition of two computational basis states. Note that the resulting state is not a computational basis state anymore. comp_basis_superposition() takes four arguments. First two are computational basis states to be superposed. The third argument determines the weight for the superposition and the fourth argument determines the phase factor for the superposition: coefficients for two states are given as \(\cos \theta\) and \(e^{i\phi} \sin \theta\).

[ ]:
import math
from quri_parts.core.state import comp_basis_superposition
state2 = ComputationalBasisState(5, bits=0b01011)
sp_state = comp_basis_superposition(state1, state2, math.pi/2, math.pi/4)
print(sp_state)

State preparation with a quantum circuit

A quantum state prepared by applying a quantum circuit to \(|00 \cdots 0\rangle\) is represented by a CircuitQuantumState interface. You can create a CircuitQuantumState with a quantum circuit object by GeneralCircuitQuantumState:

[ ]:
from quri_parts.core.state import GeneralCircuitQuantumState

circuit = QuantumCircuit(2)
circuit.add_Z_gate(0)
circuit.add_H_gate(1)

# A quantum state of 2 qubits with an empty circuit (i.e. |00>)
circuit_state = GeneralCircuitQuantumState(2)
# A quantum state of 2 qubits with a given circuit (i.e. C|00> where C is the ciruict)
circuit_state = GeneralCircuitQuantumState(2, circuit)

Note that the ComputationalBasisState we introduced above is also a CircuitQuantumState, since such a state can always be constructed by applying a circuit to a zero state.

[ ]:
from quri_parts.core.state import ComputationalBasisState

cb_state = ComputationalBasisState(2, bits=0b01)

CircuitQuantumState has some properties and methods:

[ ]:
print("(circuit_state)")
# Get how many qubits this state is for.
print("qubit_count:", circuit_state.qubit_count)
# Get the circuit of the state. This returns an immutable circuit.
print("circuit:", circuit_state.circuit)
# Create a new state with some new gates added.
gate_added_state = circuit_state.with_gates_applied([X(1), CNOT(1, 0)])
print("original circuit len:", len(circuit_state.circuit.gates))
print("new circuit len:", len(gate_added_state.circuit.gates))
[ ]:
print("(cb_state)")
# Get how many qubits this state is for.
print("qubit_count:", cb_state.qubit_count)
# Get the circuit of the state. This returns an immutable circuit.
cb_circuit = cb_state.circuit
for gate in cb_circuit.gates:
    print(gate)
# Create a new state with some new gates added.
# Note that the new state is no longer a ComputationalBasisState.
gate_added_cb_state = cb_state.with_gates_applied([X(1), CNOT(1, 0)])
print("original circuit len:", len(cb_state.circuit.gates))
print("new circuit len:", len(gate_added_cb_state.circuit.gates))

For more information on functionality of QURI Parts visit:

[1] https://quri-parts.qunasys.com/ [online] ( 2023-10-22)