Skip to content

Getting started

Quantum programs in Qadence are constructed via 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.

How to visualize blocks

There are two ways to display blocks in a Python interpreter: either as a tree in ASCII format using print:

from qadence import X, Y, kron

kron_block = kron(X(0), Y(1))
print(kron_block)
KronBlock(0,1)
├── X(0)
└── Y(1)

Or using the visualization package:

from qadence import X, Y, kron
from qadence.draw import display

kron_block = kron(X(0), Y(1))
# display(kron_block)  # un-comment this line
%3 b2c59af6d61446a5a59594cdb598e4a5 0 9714c79c82c94e4d95145e4cbe020269 X b2c59af6d61446a5a59594cdb598e4a5--9714c79c82c94e4d95145e4cbe020269 aa55b6457aa34d438b1c53fb28804972 1 57a34b50524542779dda97a9f58af5e2 9714c79c82c94e4d95145e4cbe020269--57a34b50524542779dda97a9f58af5e2 70d6f7678d7f4467811f8bf4218aaca0 93fc4463fe0f4fa0bd80febde6f465a8 Y aa55b6457aa34d438b1c53fb28804972--93fc4463fe0f4fa0bd80febde6f465a8 93fc4463fe0f4fa0bd80febde6f465a8--70d6f7678d7f4467811f8bf4218aaca0

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 RX

# A rotation gate on qubit 0 with a fixed numerical parameter.
rx_gate = RX(0, 0.5)
%3 c63ef315fdf6471b860cf68d7b2557bf 0 737cddb6828a4d3a8bf4a992bddcd36d RX(0.5) c63ef315fdf6471b860cf68d7b2557bf--737cddb6828a4d3a8bf4a992bddcd36d dd66532f297243f2bd69ade5aab79820 737cddb6828a4d3a8bf4a992bddcd36d--dd66532f297243f2bd69ade5aab79820
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 e1b60682e5384f64ae4b4cf9ac6289fd 0 98499d4dff7a43c2b44668e1db11bf6f e1b60682e5384f64ae4b4cf9ac6289fd--98499d4dff7a43c2b44668e1db11bf6f 71ceaf3e012846caa098e20387324192 1 728c9be4437742ef8b952450e89eebeb 98499d4dff7a43c2b44668e1db11bf6f--728c9be4437742ef8b952450e89eebeb 1a95e2d315c14fdda66071f022d22c70 723106a9beed45c18f4b34aaed85c912 X 71ceaf3e012846caa098e20387324192--723106a9beed45c18f4b34aaed85c912 723106a9beed45c18f4b34aaed85c912--98499d4dff7a43c2b44668e1db11bf6f 723106a9beed45c18f4b34aaed85c912--1a95e2d315c14fdda66071f022d22c70

A list of all instances of primitive blocks (also referred to as operations) can be found here.

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 on the same or overlapping qubit supports and results in a ChainBlock type. It is akin to applying a matrix product of the sub-blocks with the * operator.

from qadence import X, chain

# Chaining on the same qubit using a call to the function.
chain_x = chain(X(0), X(0))
%3 79b842bafbf748e4a69678ac013c374f 0 7a3c02a34f214c148af5d29ee043bd43 X 79b842bafbf748e4a69678ac013c374f--7a3c02a34f214c148af5d29ee043bd43 14c813a15f304286b26f9e89b0e430fa X 7a3c02a34f214c148af5d29ee043bd43--14c813a15f304286b26f9e89b0e430fa 78185856cede4689ae3b398246544577 14c813a15f304286b26f9e89b0e430fa--78185856cede4689ae3b398246544577
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 631e49abb7314a909ed378006605e2dc 0 22b058b2daa24e2b9f21eb3fd73a004e X 631e49abb7314a909ed378006605e2dc--22b058b2daa24e2b9f21eb3fd73a004e 9e238c2fe3f0493a9f6fd26df7d580da 1 9f890214bb564574a79ae1c4d6ad6ea4 22b058b2daa24e2b9f21eb3fd73a004e--9f890214bb564574a79ae1c4d6ad6ea4 d5f7585ab88343b19e505f3b1d004c84 9f890214bb564574a79ae1c4d6ad6ea4--d5f7585ab88343b19e505f3b1d004c84 d0f36e07be7a4cc490ca214668975f66 fad80dc65fdd4b9086f5079a9e768893 9e238c2fe3f0493a9f6fd26df7d580da--fad80dc65fdd4b9086f5079a9e768893 e0b7a5d7338a489b828a6bce1ccf14b6 X fad80dc65fdd4b9086f5079a9e768893--e0b7a5d7338a489b828a6bce1ccf14b6 e0b7a5d7338a489b828a6bce1ccf14b6--d0f36e07be7a4cc490ca214668975f66

  • kron applies a set of blocks in parallel (simultaneously) on disjoint qubit support and results in a KronBlock type. This is akin to applying a tensor product of the sub-blocks with the @ operator.
from qadence import X, kron

kron_xx = kron(X(0), X(1))  # Equivalent to X(0) @ X(1)
%3 67645dc80a624f90a0f82043729e071e 0 cc050147b63044e3841e3ba30297f447 X 67645dc80a624f90a0f82043729e071e--cc050147b63044e3841e3ba30297f447 52e344e4e2774841bf9205ee7a2ab954 1 036c7607a28f4472907e4a259d2756d2 cc050147b63044e3841e3ba30297f447--036c7607a28f4472907e4a259d2756d2 1609cddaff244298b7f374bbf453eaa2 95279b3250644c90a4866f3471c63554 X 52e344e4e2774841bf9205ee7a2ab954--95279b3250644c90a4866f3471c63554 95279b3250644c90a4866f3471c63554--1609cddaff244298b7f374bbf453eaa2

For the digital case, it should be noted that kron and chain are semantically equivalent up to the diagrammatic representation as chain implicitly fills blank wires with identities. However, Qadence also supports analog blocks, for which composing sequentially or in parallel becomes non-equivalent. More about analog blocks can be found in the digital-analog section.

  • add sums the corresponding matrix of each sub-block and results in a AddBlock type which can be used to construct Pauli operators. Please note that AddBlock can give rise to non-unitary computations that might not be supported by all backends.
Get the matrix of a block

It is always possible to retrieve the matrix representation of a block by calling the block.tensor() method. Please note that the returned tensor contains a batch dimension for the purposes of block parametrization.


X(0) * X(0) tensor = tensor([[[1.+0.j, 0.+0.j],
         [0.+0.j, 1.+0.j]]])
X(0) @ X(1) tensor = tensor([[[0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j],
         [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],
         [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
         [1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]]])

from qadence import X, Z

xz = X(0) + Z(0)
print(xz.tensor())
tensor([[[ 1.+0.j,  1.+0.j],
         [ 1.+0.j, -1.+0.j]]])

Finally, it is possible to tag blocks with human-readable names:

from qadence import X, Y, CNOT, kron, chain, tag

xy = kron(X(0), Y(1))
tag(xy, "subblock")

composite_block = kron(xy, CNOT(3,4))
final_block = chain(composite_block, composite_block)
%3 cluster_b39c43d141674ffebfd88fd7b49f34f9 subblock cluster_efe39f9ac72347dfa8a9d2d406eb7cc0 subblock 96c06ebe86e34029ad5a260022700275 0 bdb8a57009c44cdd8462bf42ea9e0cbc X 96c06ebe86e34029ad5a260022700275--bdb8a57009c44cdd8462bf42ea9e0cbc 2b0273c499c94528ab2e3b11ced6d41a 1 17aee307a0af4ab2b2a1b5ff92ba4e7e X bdb8a57009c44cdd8462bf42ea9e0cbc--17aee307a0af4ab2b2a1b5ff92ba4e7e dea79c185504469faa2f906c694bef3c 17aee307a0af4ab2b2a1b5ff92ba4e7e--dea79c185504469faa2f906c694bef3c 2271babdb9fc48b089f03220cab62fa1 dfc7ce2e3b4f45c1a963d926b87a2ac5 Y 2b0273c499c94528ab2e3b11ced6d41a--dfc7ce2e3b4f45c1a963d926b87a2ac5 5fff877121ae4867b9a2540fa8dffd22 2 2d8e40f0871b4d93bdd9a72ffb5c0de2 Y dfc7ce2e3b4f45c1a963d926b87a2ac5--2d8e40f0871b4d93bdd9a72ffb5c0de2 2d8e40f0871b4d93bdd9a72ffb5c0de2--2271babdb9fc48b089f03220cab62fa1 b38dcc4eca9b47c9b3c2008a1c628383 7f5f54428265491583e67719a414876c 5fff877121ae4867b9a2540fa8dffd22--7f5f54428265491583e67719a414876c a77f34e155714a84ae01aefa01025e14 3 c25af4e7fcab4f0686a6b1f90c17b633 7f5f54428265491583e67719a414876c--c25af4e7fcab4f0686a6b1f90c17b633 c25af4e7fcab4f0686a6b1f90c17b633--b38dcc4eca9b47c9b3c2008a1c628383 e8ecabf1032d4e76a0adce3e4ff1a087 fe2f33cfd7184dfc9a8fbcf5a2951f60 a77f34e155714a84ae01aefa01025e14--fe2f33cfd7184dfc9a8fbcf5a2951f60 59615f3561e14f81a200a9cd25014add 4 533e12744bdc4bd1bac4f69945a32be8 fe2f33cfd7184dfc9a8fbcf5a2951f60--533e12744bdc4bd1bac4f69945a32be8 533e12744bdc4bd1bac4f69945a32be8--e8ecabf1032d4e76a0adce3e4ff1a087 41a5dc806ace43ebb2370999e1bb63e1 b302cf6649a34d3490ccec13f38dfbd8 X 59615f3561e14f81a200a9cd25014add--b302cf6649a34d3490ccec13f38dfbd8 b302cf6649a34d3490ccec13f38dfbd8--fe2f33cfd7184dfc9a8fbcf5a2951f60 bf5f86781d1a4a6b995dae79029c1aab X b302cf6649a34d3490ccec13f38dfbd8--bf5f86781d1a4a6b995dae79029c1aab bf5f86781d1a4a6b995dae79029c1aab--533e12744bdc4bd1bac4f69945a32be8 bf5f86781d1a4a6b995dae79029c1aab--41a5dc806ace43ebb2370999e1bb63e1

Block execution

To quickly run quantum operations and access wavefunctions, samples or expectation values of observables, one can use the convenience functions run, sample and expectation. The following example shows an execution workflow with the natively available PyQTorch backend:

from qadence import chain, add, H, Z, run, sample, expectation

n_qubits = 2
block = chain(H(0), H(1))

# Compute the wavefunction.
# Please check the documentation for other available backends.
wf = run(block)

# Sample the resulting wavefunction with a given number of shots.
xs = sample(block, n_shots=1000)

# Compute an expectation based on an observable of Pauli-Z operators.
obs = add(Z(i) for i in range(n_qubits))
ex = expectation(block, obs)
wf = tensor([[0.5000+0.j, 0.5000+0.j, 0.5000+0.j, 0.5000+0.j]])
xs = [Counter({'11': 257, '10': 254, '00': 251, '01': 238})]
ex = tensor([[0.]])

More fine-grained control and better performance is provided via the high-level QuantumModel abstraction.

Execution via QuantumCircuit and QuantumModel

Quantum programs in Qadence are constructed in two steps:

  1. Build a QuantumCircuit which ties together a composite block and a register.
  2. Define a QuantumModel which differentiates, compiles and executes the circuit.

QuantumCircuit is a central class in Qadence and circuits are abstract objects from the actual hardware/simulator that they are expected to be executed on. They require to specify the Register of resources to execute your program on. Previous examples were already using QuantumCircuit with a Register that fits the qubit support for the given block.

from qadence import QuantumCircuit, Register, H, chain

# NOTE: Run a block which supports two qubits
# on a register of three qubits.
register = Register(3)
circuit = QuantumCircuit(register, chain(H(0), H(1)))
circuit = ChainBlock(0,1)
├── H(0)
└── H(1)

Registers and qubit supports

Registers can also be constructed from qubit coordinates to create arbitrary register topologies. See details in the digital-analog section. Qubit supports are subsets of the circuit register tied to blocks.

QuantumModel is another central class in Qadence. It specifies a Backend for the differentiation, compilation and execution of the abstract circuit.

from qadence import BackendName, DiffMode, QuantumCircuit, QuantumModel, Register, H, chain

reg = Register(3)
circ = QuantumCircuit(reg, chain(H(0), H(1)))
model = QuantumModel(circ, backend=BackendName.PYQTORCH, diff_mode=DiffMode.AD)

xs = model.sample(n_shots=100)
xs = [Counter({'110': 30, '100': 28, '010': 22, '000': 20})]

For more details on QuantumModel, see here.