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
539fad213a4f43bf8731b6aa1abd2a21 0 1eee343067f94fc59a990180f970814a X 539fad213a4f43bf8731b6aa1abd2a21--1eee343067f94fc59a990180f970814a 29c2ca8a4ed24c77b032f51acc8945ba 1 93283764ad62400cbc3a0293be2fe62f 1eee343067f94fc59a990180f970814a--93283764ad62400cbc3a0293be2fe62f 03b545e740804434a94f2ab08c4f3aad 4b2519917dc546bcab08b190166b602d Y 29c2ca8a4ed24c77b032f51acc8945ba--4b2519917dc546bcab08b190166b602d 4b2519917dc546bcab08b190166b602d--03b545e740804434a94f2ab08c4f3aad

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)
53d465e8a5cd4772a5c9bcb51506b71f 0 880bf770aa1f4477998d98c3d3a4b5b9 RX(0.5) 53d465e8a5cd4772a5c9bcb51506b71f--880bf770aa1f4477998d98c3d3a4b5b9 0a1438a2f6dc40388c39f0ff40baeae0 880bf770aa1f4477998d98c3d3a4b5b9--0a1438a2f6dc40388c39f0ff40baeae0
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
5510d41783494812a2f6b9311783e535 0 41da1b1789fd40019fb68b13b50f6bbd 5510d41783494812a2f6b9311783e535--41da1b1789fd40019fb68b13b50f6bbd 05512033918f443fb2f679d13c8adf49 1 96c0e3a11ed14762a041d5c87c49d561 41da1b1789fd40019fb68b13b50f6bbd--96c0e3a11ed14762a041d5c87c49d561 2c4bd40509b04a078fe0dc26ffe48ee5 3857b35515854158a925640878439341 X 05512033918f443fb2f679d13c8adf49--3857b35515854158a925640878439341 3857b35515854158a925640878439341--41da1b1789fd40019fb68b13b50f6bbd 3857b35515854158a925640878439341--2c4bd40509b04a078fe0dc26ffe48ee5

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))
6e0b890edbf948309cafea5bc4086b28 0 ecd0dfd0c8604237a9224517ca84183e X 6e0b890edbf948309cafea5bc4086b28--ecd0dfd0c8604237a9224517ca84183e 7e391968366b4353ba4674c2b9596e6a X ecd0dfd0c8604237a9224517ca84183e--7e391968366b4353ba4674c2b9596e6a a04328015ac84085ad9a96a0ec6e0c63 7e391968366b4353ba4674c2b9596e6a--a04328015ac84085ad9a96a0ec6e0c63
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
e2c82c3d8f074500991d8b3300c20b50 0 7e7dd5502b4d4cb0b9279bfb1afc715c X e2c82c3d8f074500991d8b3300c20b50--7e7dd5502b4d4cb0b9279bfb1afc715c 82c360b5f26c42d7a67a5757bde183b4 1 22e7bdb70f5f422b8598a8ab2a9efdd1 7e7dd5502b4d4cb0b9279bfb1afc715c--22e7bdb70f5f422b8598a8ab2a9efdd1 5c8eca6a970245eb9f8e95c367794ccc 22e7bdb70f5f422b8598a8ab2a9efdd1--5c8eca6a970245eb9f8e95c367794ccc 1217edd401c34695833a651a4a60c1cb a05d5fd8e0d74bdca4df58343be181cf 82c360b5f26c42d7a67a5757bde183b4--a05d5fd8e0d74bdca4df58343be181cf e4503248e05c486d99c48b0a95e03b03 X a05d5fd8e0d74bdca4df58343be181cf--e4503248e05c486d99c48b0a95e03b03 e4503248e05c486d99c48b0a95e03b03--1217edd401c34695833a651a4a60c1cb

  • 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)
c05dc3ac8f6c4b3d8140c33b9e06237a 0 157d1d0f0a924e1090bd3cd97df15335 X c05dc3ac8f6c4b3d8140c33b9e06237a--157d1d0f0a924e1090bd3cd97df15335 518ea1cead8242e2a918f5e44393df4d 1 66658844f2ff4d0ea26618aebbf6ec04 157d1d0f0a924e1090bd3cd97df15335--66658844f2ff4d0ea26618aebbf6ec04 a88906e3fd1f4729adae7b52318531b1 72da42ecc4e5417a8a724f1a5aae6478 X 518ea1cead8242e2a918f5e44393df4d--72da42ecc4e5417a8a724f1a5aae6478 72da42ecc4e5417a8a724f1a5aae6478--a88906e3fd1f4729adae7b52318531b1

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)
cluster_3eb7b4af3ad14d93bbbcb4b0d9af83ee subblock cluster_1a10cbca0ed74af08238e7061b1e529d subblock ad4dd81d9c864fb69f92d687cc419639 0 23ad9025b6154d0baa4d1802ba60fc08 X ad4dd81d9c864fb69f92d687cc419639--23ad9025b6154d0baa4d1802ba60fc08 f1342f593a1f46e787c19031436a6789 1 8f7ec60a5dc14d748ce81a1e7947b88e X 23ad9025b6154d0baa4d1802ba60fc08--8f7ec60a5dc14d748ce81a1e7947b88e 07b4f2a3e7e047558603a7d4d334ec4a 8f7ec60a5dc14d748ce81a1e7947b88e--07b4f2a3e7e047558603a7d4d334ec4a f66202a45fd6429594ed0b2bee4f8849 9be476c5cfb3461aa5042e14a62bd165 Y f1342f593a1f46e787c19031436a6789--9be476c5cfb3461aa5042e14a62bd165 1ce42d1b09c64dcd8094b02c0b928b9f 2 0f451b0dad0543fe931058416d723e1b Y 9be476c5cfb3461aa5042e14a62bd165--0f451b0dad0543fe931058416d723e1b 0f451b0dad0543fe931058416d723e1b--f66202a45fd6429594ed0b2bee4f8849 ede919219e7e419ab648f3ef8e027a24 bda67cc318864917859487c374bf7806 1ce42d1b09c64dcd8094b02c0b928b9f--bda67cc318864917859487c374bf7806 2d85e367df924ab48e8bd324afd52c18 3 c9cc096909c24ec3991c597c24fe8062 bda67cc318864917859487c374bf7806--c9cc096909c24ec3991c597c24fe8062 c9cc096909c24ec3991c597c24fe8062--ede919219e7e419ab648f3ef8e027a24 6896869881784559a5800680dd180511 0ff3b8e5cb374e4eaa4ead413770ccb9 2d85e367df924ab48e8bd324afd52c18--0ff3b8e5cb374e4eaa4ead413770ccb9 7db7381e42d0471b83914ff3e89f33a4 4 cba5039351ad4f069b18f1dcc8db9a64 0ff3b8e5cb374e4eaa4ead413770ccb9--cba5039351ad4f069b18f1dcc8db9a64 cba5039351ad4f069b18f1dcc8db9a64--6896869881784559a5800680dd180511 9cbbb3903d754341b629992322f7b732 47bdf0cc8bbf43c58f1cde1a3682f674 X 7db7381e42d0471b83914ff3e89f33a4--47bdf0cc8bbf43c58f1cde1a3682f674 47bdf0cc8bbf43c58f1cde1a3682f674--0ff3b8e5cb374e4eaa4ead413770ccb9 9073cd2ea04f4a46ba79cf5d6595abbd X 47bdf0cc8bbf43c58f1cde1a3682f674--9073cd2ea04f4a46ba79cf5d6595abbd 9073cd2ea04f4a46ba79cf5d6595abbd--cba5039351ad4f069b18f1dcc8db9a64 9073cd2ea04f4a46ba79cf5d6595abbd--9cbbb3903d754341b629992322f7b732

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.