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 b11fe6de0c0a46398bd4459561458e3e 0 c32f72a3fbe6447cadcd9bb8ee303c35 X b11fe6de0c0a46398bd4459561458e3e--c32f72a3fbe6447cadcd9bb8ee303c35 2fc54a5cc6074c2f8af35b0b2c297667 1 1e30ed5280794aee939ed40d07652945 c32f72a3fbe6447cadcd9bb8ee303c35--1e30ed5280794aee939ed40d07652945 3f01e8a2831f40df8ca14ab555970152 42a149e659db4a0b860b8299451e7076 Y 2fc54a5cc6074c2f8af35b0b2c297667--42a149e659db4a0b860b8299451e7076 42a149e659db4a0b860b8299451e7076--3f01e8a2831f40df8ca14ab555970152

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 8595e658a01a4825a0ad6c5c0172d333 0 26e0954a8b1047678afbe6ebf0399b97 RX(0.5) 8595e658a01a4825a0ad6c5c0172d333--26e0954a8b1047678afbe6ebf0399b97 ef4d508266dd4c69b747e89fb5396ad9 26e0954a8b1047678afbe6ebf0399b97--ef4d508266dd4c69b747e89fb5396ad9
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 d50ea3d023d04c5d9d4b5bfb17b78fd8 0 288b9755a2cf46ea9e4620df18a96933 d50ea3d023d04c5d9d4b5bfb17b78fd8--288b9755a2cf46ea9e4620df18a96933 06e3a2fd950845c68043e4a9544819b8 1 61d7704464854b638a7a21fd9cfc82df 288b9755a2cf46ea9e4620df18a96933--61d7704464854b638a7a21fd9cfc82df e3d53b8fadd34c8b956489525aba4fe8 6f93a89332a34ba8b49d2b5458d910ef X 06e3a2fd950845c68043e4a9544819b8--6f93a89332a34ba8b49d2b5458d910ef 6f93a89332a34ba8b49d2b5458d910ef--288b9755a2cf46ea9e4620df18a96933 6f93a89332a34ba8b49d2b5458d910ef--e3d53b8fadd34c8b956489525aba4fe8

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 c2c62df38c3248c2a38ddaad57512b1d 0 567bc66074d84625b21566fa86619b03 X c2c62df38c3248c2a38ddaad57512b1d--567bc66074d84625b21566fa86619b03 06290216aa06409689eec4a4fdffa582 X 567bc66074d84625b21566fa86619b03--06290216aa06409689eec4a4fdffa582 3e53dca490224bd5ac9a80a468ee2798 06290216aa06409689eec4a4fdffa582--3e53dca490224bd5ac9a80a468ee2798
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 55370c91c87b4b14ab24f015bcee945b 0 63e3ad98fcec49d68daf754497ce62b6 X 55370c91c87b4b14ab24f015bcee945b--63e3ad98fcec49d68daf754497ce62b6 7401eea32e674e7099c592ab06aeb403 1 7929782684964a7c83e8fc7f6f5335a4 63e3ad98fcec49d68daf754497ce62b6--7929782684964a7c83e8fc7f6f5335a4 322b41e48f2046b3824efd493634b8d7 7929782684964a7c83e8fc7f6f5335a4--322b41e48f2046b3824efd493634b8d7 8d9dda173ce14d6fa639dc28b08211df 806e027416524c718532bd45998448a5 7401eea32e674e7099c592ab06aeb403--806e027416524c718532bd45998448a5 c375dbe8daca4aa9926198c9fa315f06 X 806e027416524c718532bd45998448a5--c375dbe8daca4aa9926198c9fa315f06 c375dbe8daca4aa9926198c9fa315f06--8d9dda173ce14d6fa639dc28b08211df

  • 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 4a570e3d898249b1b105a7584be7d41d 0 a7ecc4b01fb6456dab1f21b5b5e1bd60 X 4a570e3d898249b1b105a7584be7d41d--a7ecc4b01fb6456dab1f21b5b5e1bd60 d1d9772ce847486f96ea766b99280c3a 1 91c11eb5ab564d32929ef578670c6a8b a7ecc4b01fb6456dab1f21b5b5e1bd60--91c11eb5ab564d32929ef578670c6a8b 609809bcebcb4e08a5e75f4a6721aa21 bc16a4231b0a49c6980470f68bd1176a X d1d9772ce847486f96ea766b99280c3a--bc16a4231b0a49c6980470f68bd1176a bc16a4231b0a49c6980470f68bd1176a--609809bcebcb4e08a5e75f4a6721aa21

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_008f4b2eecc14bc8ad87e0da8ee968f9 subblock cluster_f70766164e054a67a2c42bef326909ff subblock cdf18caa23ec4206ab22232eec3c7819 0 0898119770a34458894678f37afa4b34 X cdf18caa23ec4206ab22232eec3c7819--0898119770a34458894678f37afa4b34 a3b76bfca25c46d79becf99d1e60dd54 1 5d2874d826244534aafe74a4697fa35a X 0898119770a34458894678f37afa4b34--5d2874d826244534aafe74a4697fa35a 47ed70a81eb942eaa66d5694faf2bff9 5d2874d826244534aafe74a4697fa35a--47ed70a81eb942eaa66d5694faf2bff9 0cfcf0e6a8854b4f9bda1bf1963d8dcb 3fc4a18ecdd847c195f9e107f88017ab Y a3b76bfca25c46d79becf99d1e60dd54--3fc4a18ecdd847c195f9e107f88017ab 40ebcd44b21e427f9d4bff3ca9f34e19 2 718c792868354dc59a205f23aabb7523 Y 3fc4a18ecdd847c195f9e107f88017ab--718c792868354dc59a205f23aabb7523 718c792868354dc59a205f23aabb7523--0cfcf0e6a8854b4f9bda1bf1963d8dcb d83dff7fea374cd4ab21be8d6e18154b 84ac4de26c87404abd06b2fd980dea85 40ebcd44b21e427f9d4bff3ca9f34e19--84ac4de26c87404abd06b2fd980dea85 71c80af80dd147229404e9ff5ba67a9e 3 5111d0f4d9d147ae835a8f6194db9a11 84ac4de26c87404abd06b2fd980dea85--5111d0f4d9d147ae835a8f6194db9a11 5111d0f4d9d147ae835a8f6194db9a11--d83dff7fea374cd4ab21be8d6e18154b 240b5e32511a47daa04cc82e774fbb0f 1631c550e996467da09e1047a43d9f35 71c80af80dd147229404e9ff5ba67a9e--1631c550e996467da09e1047a43d9f35 cb467342f55b480d85fbebcf8c9cd40f 4 969cfab403d74bb78221c591d0beaab7 1631c550e996467da09e1047a43d9f35--969cfab403d74bb78221c591d0beaab7 969cfab403d74bb78221c591d0beaab7--240b5e32511a47daa04cc82e774fbb0f 9d739eca12bb4b269eb137bd73a2b32a ccbce1313a1f4ae79c37df6ff8afb58f X cb467342f55b480d85fbebcf8c9cd40f--ccbce1313a1f4ae79c37df6ff8afb58f ccbce1313a1f4ae79c37df6ff8afb58f--1631c550e996467da09e1047a43d9f35 386e326254314a5e922aaf0638fffeed X ccbce1313a1f4ae79c37df6ff8afb58f--386e326254314a5e922aaf0638fffeed 386e326254314a5e922aaf0638fffeed--969cfab403d74bb78221c591d0beaab7 386e326254314a5e922aaf0638fffeed--9d739eca12bb4b269eb137bd73a2b32a

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.