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 7b9a0413d36248a7a26e3e353e428f3b 0 d37c6b55eaa34216ad477913f1a06cb8 X 7b9a0413d36248a7a26e3e353e428f3b--d37c6b55eaa34216ad477913f1a06cb8 3998a573da394c32913290d9e0791e4a 1 adc77daabdc74ddd95fcee50a54ed40d d37c6b55eaa34216ad477913f1a06cb8--adc77daabdc74ddd95fcee50a54ed40d aad086fca36b4f94b093ef9af2f7b2ce 07e2944f73c54332a7dd3c9a66e33555 Y 3998a573da394c32913290d9e0791e4a--07e2944f73c54332a7dd3c9a66e33555 07e2944f73c54332a7dd3c9a66e33555--aad086fca36b4f94b093ef9af2f7b2ce

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 9352ca2268ab4491908ae716ac1637e1 0 244c9c9d27194d69a1bff456e4183f30 RX(0.5) 9352ca2268ab4491908ae716ac1637e1--244c9c9d27194d69a1bff456e4183f30 63155083c097439fa74ac3b7a7fceea4 244c9c9d27194d69a1bff456e4183f30--63155083c097439fa74ac3b7a7fceea4
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 bf4733366153466f94a34e0917d82c53 0 a8d67fd0d8554558a719d30fabc3b4f5 bf4733366153466f94a34e0917d82c53--a8d67fd0d8554558a719d30fabc3b4f5 de18df1f056c4e96a68e4b47f9c7c6b7 1 cd7b78b9921b492c8707b32fc6409a22 a8d67fd0d8554558a719d30fabc3b4f5--cd7b78b9921b492c8707b32fc6409a22 ca9d4bc4e0a14990a7a0d3446e753c95 807fd9af177049ac90d1c2437d6b23cf X de18df1f056c4e96a68e4b47f9c7c6b7--807fd9af177049ac90d1c2437d6b23cf 807fd9af177049ac90d1c2437d6b23cf--a8d67fd0d8554558a719d30fabc3b4f5 807fd9af177049ac90d1c2437d6b23cf--ca9d4bc4e0a14990a7a0d3446e753c95

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 11f294e00e2043a5a0723b977d5eb91a 0 23749de27b69476a8cbd9153f65f3784 X 11f294e00e2043a5a0723b977d5eb91a--23749de27b69476a8cbd9153f65f3784 2ee37747fab7490887450c2283189d7d X 23749de27b69476a8cbd9153f65f3784--2ee37747fab7490887450c2283189d7d 53c84602120e4766a7784ee7314aa23b 2ee37747fab7490887450c2283189d7d--53c84602120e4766a7784ee7314aa23b
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 a757f88c56c54481839cc264d5261fa2 0 8779f24215ae42de91b6b0e997ee4c45 X a757f88c56c54481839cc264d5261fa2--8779f24215ae42de91b6b0e997ee4c45 a603dc89dee041499fc72ee70a9117e0 1 c1650f5228364a1dbce8a5dcf946fa89 8779f24215ae42de91b6b0e997ee4c45--c1650f5228364a1dbce8a5dcf946fa89 334a9d5c77c04e12b7b1ea93dbb1c18b c1650f5228364a1dbce8a5dcf946fa89--334a9d5c77c04e12b7b1ea93dbb1c18b ef1ec308b109411590f717fb7347f7ab f9dc3a74e08a4a479c531fb3d6fe66cd a603dc89dee041499fc72ee70a9117e0--f9dc3a74e08a4a479c531fb3d6fe66cd 6bc2372c04544bad9eecaf8179b3d060 X f9dc3a74e08a4a479c531fb3d6fe66cd--6bc2372c04544bad9eecaf8179b3d060 6bc2372c04544bad9eecaf8179b3d060--ef1ec308b109411590f717fb7347f7ab

  • 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 3f51f7e135954e6eb9e1a1f0d89ce00a 0 8bd52c1435d64847bbb18389fd78499e X 3f51f7e135954e6eb9e1a1f0d89ce00a--8bd52c1435d64847bbb18389fd78499e 73c1b97a1d7a4d07b8a90535742c73dd 1 39e52e5c5ab741da9c6840fb82858538 8bd52c1435d64847bbb18389fd78499e--39e52e5c5ab741da9c6840fb82858538 b720d810045b44928b02ec8e9b99cbb5 9cbc4ca9277a47d9bbe9f6a221508169 X 73c1b97a1d7a4d07b8a90535742c73dd--9cbc4ca9277a47d9bbe9f6a221508169 9cbc4ca9277a47d9bbe9f6a221508169--b720d810045b44928b02ec8e9b99cbb5

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_8e66f09e29354a0bb9a72d29e387f1bb subblock cluster_413a4c0800194376ae241c8ef109a98d subblock 48aad6d61ee441b4ad51b0981d4eb7aa 0 9d8f6a42ab8048379e6a3499943dbac4 X 48aad6d61ee441b4ad51b0981d4eb7aa--9d8f6a42ab8048379e6a3499943dbac4 9793b62d671940e781e3bd5a8853ed75 1 cefc04eeef7b4ad4bed2525ab57265b3 X 9d8f6a42ab8048379e6a3499943dbac4--cefc04eeef7b4ad4bed2525ab57265b3 f6939089e92445509865379fdf150a8f cefc04eeef7b4ad4bed2525ab57265b3--f6939089e92445509865379fdf150a8f e8a83ca8ec7941a7bbeaf988b14bf11d 70bb701348ae407bab1dfd73f8d35bda Y 9793b62d671940e781e3bd5a8853ed75--70bb701348ae407bab1dfd73f8d35bda f2ca276b07e74645b3a34eafa6a0ca4b 2 96b8ea15538d40e598a7fb1e4866c2b5 Y 70bb701348ae407bab1dfd73f8d35bda--96b8ea15538d40e598a7fb1e4866c2b5 96b8ea15538d40e598a7fb1e4866c2b5--e8a83ca8ec7941a7bbeaf988b14bf11d 1b55151ffd224b748cefd20e11bc11e2 a8e968c2f5374e128a72829759a8bb25 f2ca276b07e74645b3a34eafa6a0ca4b--a8e968c2f5374e128a72829759a8bb25 07a675a4d61f46a58909d5b175c5f703 3 08a2e2e958764ed68f7861f8c0234dea a8e968c2f5374e128a72829759a8bb25--08a2e2e958764ed68f7861f8c0234dea 08a2e2e958764ed68f7861f8c0234dea--1b55151ffd224b748cefd20e11bc11e2 e17ba372006f416d9011f3ace6d13a2d 1a8a9050f8b547fd97f4efcedc5aeee5 07a675a4d61f46a58909d5b175c5f703--1a8a9050f8b547fd97f4efcedc5aeee5 0f70eb5d378140fa87e0875ad0307b32 4 617542123078428cb9fda7c8dbf0b0f8 1a8a9050f8b547fd97f4efcedc5aeee5--617542123078428cb9fda7c8dbf0b0f8 617542123078428cb9fda7c8dbf0b0f8--e17ba372006f416d9011f3ace6d13a2d 43a2f3c2c9c1423f8894b20c903f9c4f 06173cd9de90424e94581c061318b91e X 0f70eb5d378140fa87e0875ad0307b32--06173cd9de90424e94581c061318b91e 06173cd9de90424e94581c061318b91e--1a8a9050f8b547fd97f4efcedc5aeee5 54a1d1685bb1499f88aa8a875420d2d1 X 06173cd9de90424e94581c061318b91e--54a1d1685bb1499f88aa8a875420d2d1 54a1d1685bb1499f88aa8a875420d2d1--617542123078428cb9fda7c8dbf0b0f8 54a1d1685bb1499f88aa8a875420d2d1--43a2f3c2c9c1423f8894b20c903f9c4f

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({'00': 271, '10': 252, '01': 247, '11': 230})]
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, '000': 27, '010': 15})]

For more details on QuantumModel, see here.