Welcome to horqrux
horqrux is a state vector and density matrix simulator designed for quantum machine learning written in JAX.
Setup
To install horqrux
, you can go into any virtual environment of your
choice and install it normally with pip
:
Digital operations
horqrux
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.
from horqrux import X, random_state, apply_gates
state = random_state(2)
new_state = apply_gates(state, X(0))
We can also make any gate controlled, in the case of X, we have to pass the target qubit first!
import jax.numpy as jnp
from horqrux import X, product_state, apply_gates
n_qubits = 2
state = product_state('11')
control = 0
target = 1
# This is equivalent to performing CNOT(0,1)
new_state= apply_gates(state, X(target,control))
assert jnp.allclose(new_state, product_state('10'))
When applying parametric gates, we can either pass a numeric value or a parameter name for the parameter as the first argument.
import jax.numpy as jnp
from horqrux import RX, random_state, apply_gates
target_qubit = 1
state = random_state(target_qubit+1)
param_value = 1 / 4 * jnp.pi
new_state = apply_gates(state, RX(param_value, target_qubit))
# Parametric horqrux gates also accept parameter names in the form of strings.
# Simply pass a dictionary of parameter names and values to the 'apply_gates' function
new_state = apply_gates(state, RX('theta', target_qubit), {'theta': jnp.pi})
We can also make any parametric gate controlled simply by passing a control qubit.
import jax.numpy as jnp
from horqrux import RX, product_state, apply_gates
n_qubits = 2
target_qubit = 1
control_qubit = 0
state = product_state('11')
param_value = 1 / 4 * jnp.pi
new_state = apply_gates(state, RX(param_value, target_qubit, control_qubit))
Using sparse matrices
horqrux
also provide the possibility to use sparse matrices when performing operations using Batched-coordinate (BCOO) sparse matrices. Note though that Jax's sparse matrices are still considered an experimental feature. For this, the input state and all operations should be initialized with sparse=True
. One can also perform the sparse conversion of operators using the to_sparse
method. And one can perform the reverse conversion using the to_dense
method.
import jax.numpy as jnp
from horqrux import RX, product_state, apply_gates
from horqrux.utils.conversion import to_sparse, to_dense
n_qubits = 2
target_qubit = 1
control_qubit = 0
state = product_state('11', sparse=True)
param_value = 1 / 4 * jnp.pi
gate = to_sparse(RX(param_value, target_qubit, control_qubit))
new_state = apply_gates(state, gate)
gate = to_dense(RX(param_value, target_qubit, control_qubit))
state = to_dense(state)
new_state = apply_gates(state, gate)
Experimental Sparse matrices scope
Note this is an experimental feature (raise an issue if needed).
We only support noiseless state-vector simulation with digital operations when using sparse matrices.
Note that jax.grad
or jax.experimental.sparse.grad
does not work with sparse expectation operations.
Analog Operations
horqrux
also allows for global state evolution via the HamiltonianEvolution
operation.
Note that it expects a hamiltonian and a time evolution parameter passed as numpy
or jax.numpy
arrays. To build arbitrary Pauli hamiltonians, we recommend using Qadence.
from jax.numpy import pi, array, diag, kron, cdouble
from horqrux.analog import HamiltonianEvolution
from horqrux.apply import apply_gates
from horqrux.utils.operator_utils import uniform_state
sigmaz = diag(array([1.0, -1.0], dtype=cdouble))
Hbase = kron(sigmaz, sigmaz)
Hamiltonian = kron(Hbase, Hbase)
n_qubits = 4
t_evo = pi / 4
hamevo = HamiltonianEvolution(tuple([i for i in range(n_qubits)]))
psi = uniform_state(n_qubits)
psi_star = apply_gates(psi, hamevo, {"hamiltonian": Hamiltonian, "time_evolution": t_evo})
Circuits
Using digital and analog operations, you can can build fully differentiable quantum circuits using the QuantumCircuit
class.
import jax.numpy as jnp
from horqrux import Z, RX, RY, NOT, expectation
from horqrux.circuit import QuantumCircuit
from horqrux.composite import Observable
from horqrux.utils.operator_utils import zero_state
ops = [RX("theta", 0), RY("epsilon", 0), RX("phi", 0), NOT(1, 0), RX("omega", 0, 1)]
circuit = QuantumCircuit(2, ops)
observable = [Observable([Z(0)])]
values = {
"theta": 0.2,
"epsilon": 0.3,
"phi": 0.4,
"omega": 0.5,
}
state = zero_state(2)
exp_qc = expectation(state, circuit, observable, values)