Block system
Quantum programs in Qadence are constructed using a block-system, with an emphasis on composability of primitive blocks to obtain larger, composite blocks. This functional approach is different from other frameworks which follow a more object-oriented way to construct circuits and express programs.
Primitive blocks
Section titled “Primitive blocks”A PrimitiveBlock represents a digital or an analog time-evolution quantum operation applied to a qubit support. Programs can always be decomposed down into a sequence of PrimitiveBlock elements.
Two canonical examples of digital primitive blocks are the parametrized RX and the CNOT gates:
from qadence import chain, RX, CNOT
rx = RX(0, 0.5)cnot = CNOT(0, 1)
block = chain(rx, cnot)A list of all available primitive operations can be found here.
How to visualize blocks
from qadence import X, Y, kron
kron_block = kron(X(0), Y(1))print(kron_block)Composite Blocks
Section titled “Composite Blocks”Programs can be expressed by composing blocks to result in a larger CompositeBlock using three fundamental operations:
chain, kron, and add.
- chain applies a set of blocks in sequence, which can have overlapping qubit supports, and results in a
ChainBlocktype. It is akin to applying a matrix product of the sub-blocks, and can also be used with the*operator. - kron applies a set of blocks in parallel, requiring disjoint qubit support, and results in a
KronBlocktype. This is akin to applying a tensor product of the sub-blocks, and can also be used with the@operator. - add performs a direct sum of the operators, and results in an
AddBlocktype. Blocks constructed this way are typically non-unitary, as is the case for Hamiltonians which can be constructed through sums of Pauli strings. Addition can also be performed directly with the+operator.
from qadence import X, Y, chain, kron
chain_0 = chain(X(0), Y(0))chain_1 = chain(X(1), Y(1))
kron_block = kron(chain_0, chain_1)All composition functions support list comprehension syntax. Below we exemplify the creation of an XY Hamiltonian for qubits laid out on a line.
from qadence import X, Y, add
def xy_int(i: int, j: int): return (1/2) * (X(i)@X(j) + Y(i)@Y(j))
n_qubits = 3
xy_ham = add(xy_int(i, i+1) for i in range(n_qubits-1))AddBlock(0,1,2)├── [mul: 0.500]│ └── AddBlock(0,1)│ ├── KronBlock(0,1)│ │ ├── X(0)│ │ └── X(1)│ └── KronBlock(0,1)│ ├── Y(0)│ └── Y(1)└── [mul: 0.500] └── AddBlock(1,2) ├── KronBlock(1,2) │ ├── X(1) │ └── X(2) └── KronBlock(1,2) ├── Y(1) └── Y(2)Qadence blocks can be directly translated to matrix form by calling block.tensor(). Note that first dimension is the batch dimension, following PyTorch conventions. This becomes relevant if the block are parameterized and batched input values are passed, as we will see later.
from qadence import X, Y
xy = (1/2) * (X(0)@X(1) + Y(0)@Y(1))
print(xy.tensor().real)tensor([[[0., 0., 0., 0.], [0., 0., 1., 0.], [0., 1., 0., 0.], [0., 0., 0., 0.]]])For a final example of the flexibility of functional block composition, below is an implementation of the Quantum Fourier Transform on an arbitrary qubit support.
from qadence import H, CPHASE, PI, chain, kron
def qft_layer(qs: tuple, l: int): cphases = chain(CPHASE(qs[j], qs[l], PI/2**(j-l)) for j in range(l+1, len(qs))) return H(qs[l]) * cphases
def qft(qs: tuple): return chain(qft_layer(qs, l) for l in range(len(qs)))Other functionalities are directly built in the block system. For example, the inverse operation can be created with the dagger() method.
qft_inv = qft((0, 1, 2)).dagger()Digital-analog composition
Section titled “Digital-analog composition”In Qadence, analog operations are first-class citizens. 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. Qadence provides the HamEvo class to initialize analog operations. For a time-independent generator and some time variable , HamEvo(H, t) represents the evolution operator .
Analog operations constitute a generalization of digital operations, and all digital operations can also be represented as the evolution of some hermitian generator. For example, the RX gate is the evolution of X.
from qadence import X, RX, HamEvo, PIfrom torch import allclose
angle = PI/2
block_digital = RX(0, angle)
block_analog = HamEvo(0.5*X(0), angle)
print(allclose(block_digital.tensor(), block_analog.tensor()))TrueAs seen in the previous section, arbitrary Hamiltonians can be constructed using Pauli operators. Their evolution can be combined with other arbitrary digital operations and incorporated into any quantum program.
from qadence import X, Y, RX, HamEvofrom qadence import add, kron, PI
def xy_int(i: int, j: int): return (1/2) * (X(i)@X(j) + Y(i)@Y(j))
n_qubits = 3
xy_ham = add(xy_int(i, i+1) for i in range(n_qubits-1))
analog_evo = HamEvo(xy_ham, 1.0)
digital_block = kron(RX(i, i*PI/2) for i in range(n_qubits))
program = digital_block * analog_evo * digital_blockBlock execution
Section titled “Block execution”To quickly run block operations and access wavefunctions, samples or expectation values of observables, one can use the convenience functions run, sample and expectation.
from qadence import kron, add, H, Z, run, sample, expectation
n_qubits = 2
# Prepares a uniform stateh_block = kron(H(i) for i in range(n_qubits))
wf = run(h_block)
xs = sample(h_block, n_shots=1000)
obs = add(Z(i) for i in range(n_qubits))ex = expectation(h_block, obs)wf = tensor([[0.5000+0.j, 0.5000+0.j, 0.5000+0.j, 0.5000+0.j]])xs = [OrderedCounter({'01': 259, '11': 257, '00': 245, '10': 239})]ex = tensor([[0.]])Execution via QuantumCircuit and QuantumModel
Section titled “Execution via QuantumCircuit and QuantumModel”More fine-grained control and better performance is provided via the high-level QuantumModel abstraction. Quantum programs in Qadence are constructed in two steps:
- Build a
QuantumCircuitwhich ties together a composite block and a register. - Define a
QuantumModelwhich differentiates, compiles and executes the circuit.
Execution of more complex Qadence programs will be explored in the next tutorials.
Adding noise to gates
Section titled “Adding noise to gates”It is possible to add noise to gates. Please refer to the noise tutorial here.