Welcome to pyqtorch
pyqtorch is a state vector simulator designed for quantum machine learning written in PyTorch. It allows for building fully differentiable quantum circuits comprised of both digital and analog operations using a intuitive torch.nn.Module-based API. It can be used standalone as shown in these docs, or through our framework for quantum programming: Qadence.
Setup
To install pyqtorch
, you can go into any virtual environment of your
choice and install it normally with pip
:
Digital Operations
pyqtorch
implements a large selection of both primitive and parametric single to n-qubit, digital quantum gates.
Let's have a look at primitive gates first.
import torch
from pyqtorch import X, CNOT, random_state
x = X(0)
state = random_state(n_qubits=2)
new_state = x(state)
cnot = CNOT(0,1)
new_state= cnot(state)
Parametric gates can be initialized with or without a param_name
. In the former case, a dictionary containing the param_name
and a torch.Tensor
for the parameter is expected when calling the forward method of the gate.
import torch
from pyqtorch import X, RX, CNOT, CRX, random_state
state = random_state(n_qubits=2)
rx_with_param = RX(0, 'theta')
theta = torch.rand(1)
values = {'theta': theta}
new_state = rx_with_param(state, values)
crx = CRX(0, 1, 'theta')
new_state = crx(state, values)
However, if you want to run a quick state vector simulation, you can initialize parametric gates without passing a param_name
, in which case the forward method of the gate will simply expect a torch.Tensor
.
import torch
from pyqtorch import RX, random_state
state = random_state(n_qubits=2)
rx = RX(0)
new_state = rx(state, torch.rand(1))
Analog Operations
An analog operation is one whose unitary is best described by the evolution of some hermitian generator, or Hamiltonian, acting on an arbitrary number of qubits. For a time-independent generator \(\mathcal{H}\) and some time variable \(t\), the evolution operator is \(\exp(-i\mathcal{H}t)\).
pyqtorch
also contains a analog
module which allows for global state evolution through the HamiltonianEvolution
class. There exists several ways to pass a generator, and we present them in Analog Operations. Below, we show an example where the generator \(\mathcal{H}\) is an arbitrary tensor. To build arbitrary Pauli hamiltonians, we recommend using Qadence.
import torch
from pyqtorch import uniform_state, HamiltonianEvolution, is_normalized
from pyqtorch.matrices import DEFAULT_MATRIX_DTYPE
n_qubits = 4
# Random hermitian hamiltonian
matrix = torch.rand(2**n_qubits, 2**n_qubits, dtype=DEFAULT_MATRIX_DTYPE)
hermitian_matrix = matrix + matrix.T.conj()
# To be evolved for a batch of times
t_list = torch.tensor([0.0, 0.5, 1.0, 2.0])
hamiltonian_evolution = HamiltonianEvolution(hermitian_matrix, t_list, [i for i in range(n_qubits)])
# Starting from a uniform state
psi_start = uniform_state(n_qubits)
# Returns an evolved state at each time value
psi_end = hamiltonian_evolution(
state = psi_start)
assert is_normalized(psi_end, atol=1e-05)
Dimensionless units
The quantity \(\mathcal{H}t\) has to be considered dimensionless for exponentiation in pyqtorch
.
Circuits
Using digital and analog operations, you can can build fully differentiable quantum circuits using the QuantumCircuit
class; note that the default differentiation mode in pyqtorch is using torch.autograd.
import torch
import pyqtorch as pyq
rx = pyq.RX(0, param_name="theta")
y = pyq.Y(0)
cnot = pyq.CNOT(0, 1)
ops = [rx, y, cnot]
n_qubits = 2
circ = pyq.QuantumCircuit(n_qubits, ops)
state = pyq.random_state(n_qubits)
theta = torch.rand(1, requires_grad=True)
obs = pyq.Observable(pyq.Z(0))
expval = pyq.expectation(circ, state, {"theta": theta}, obs)
dfdtheta = torch.autograd.grad(expval, theta, torch.ones_like(expval))