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
0cb287fd00024e8a940d1249d18e738a 0 6e4831e0ca704e978398dd139b9a28c2 X 0cb287fd00024e8a940d1249d18e738a--6e4831e0ca704e978398dd139b9a28c2 6a283fe15ef94c2f86f038c4cc94e7e3 1 8f52a07bc7b74324bb0764fddfccbe4d 6e4831e0ca704e978398dd139b9a28c2--8f52a07bc7b74324bb0764fddfccbe4d b72920d10379475bb4ea750614954178 d35d852719e245f2af044ca79051280f Y 6a283fe15ef94c2f86f038c4cc94e7e3--d35d852719e245f2af044ca79051280f d35d852719e245f2af044ca79051280f--b72920d10379475bb4ea750614954178

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)
6675fe15b1504068afc3ca8f09b2418c 0 f8fbe09d67ae4b4084411870e07cb892 RX(0.5) 6675fe15b1504068afc3ca8f09b2418c--f8fbe09d67ae4b4084411870e07cb892 a11ec75654db46fca78403280a76664a f8fbe09d67ae4b4084411870e07cb892--a11ec75654db46fca78403280a76664a
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
51b85be6164a4fa9b4c686d7e4da311c 0 1530c23ed0034be1bb306275ea287ca1 51b85be6164a4fa9b4c686d7e4da311c--1530c23ed0034be1bb306275ea287ca1 bfcf7b92746f408a92a929bf7afcdaac 1 45b10c880f974847813fe837b76c8cce 1530c23ed0034be1bb306275ea287ca1--45b10c880f974847813fe837b76c8cce 12cfec2f4fd1459d83abf3a19bc7261b b96a537759894494bc00e512e69b78b1 X bfcf7b92746f408a92a929bf7afcdaac--b96a537759894494bc00e512e69b78b1 b96a537759894494bc00e512e69b78b1--1530c23ed0034be1bb306275ea287ca1 b96a537759894494bc00e512e69b78b1--12cfec2f4fd1459d83abf3a19bc7261b

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))
059c5f7fb45c485a9fbb68ffeb6e63d5 0 c2e1c8af64ca413d81a38f97204c6d07 X 059c5f7fb45c485a9fbb68ffeb6e63d5--c2e1c8af64ca413d81a38f97204c6d07 09c7f3619e4347c4ada3a843f5cd82a2 X c2e1c8af64ca413d81a38f97204c6d07--09c7f3619e4347c4ada3a843f5cd82a2 4fc3ddd58e444a09ad582c40aa1a300a 09c7f3619e4347c4ada3a843f5cd82a2--4fc3ddd58e444a09ad582c40aa1a300a
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
10ef95dde55f4f0e935b84881b07d665 0 55eb733eae0f453ab83b65bbdbf8599d X 10ef95dde55f4f0e935b84881b07d665--55eb733eae0f453ab83b65bbdbf8599d a8058bd1f8044fd584f45c87ae600952 1 8fd68bf4e32d4fcc893d1838cc35c0b8 55eb733eae0f453ab83b65bbdbf8599d--8fd68bf4e32d4fcc893d1838cc35c0b8 e083fb0e825c48d08b73920340f68f63 8fd68bf4e32d4fcc893d1838cc35c0b8--e083fb0e825c48d08b73920340f68f63 c3570abb1c2348ec9e91db1f6e54640d 4fe301c0462d40d0b0775d364eb39ed9 a8058bd1f8044fd584f45c87ae600952--4fe301c0462d40d0b0775d364eb39ed9 d2e67654747d4b5e8b66a29d952a8d92 X 4fe301c0462d40d0b0775d364eb39ed9--d2e67654747d4b5e8b66a29d952a8d92 d2e67654747d4b5e8b66a29d952a8d92--c3570abb1c2348ec9e91db1f6e54640d

  • 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)
48c12049d9174ecb9fe8e9a2b3c6ed96 0 d0115fbfb2e64cf7b3e76479f5663682 X 48c12049d9174ecb9fe8e9a2b3c6ed96--d0115fbfb2e64cf7b3e76479f5663682 a44574eea195427cba888f2726f32b83 1 f6b4573f42594ba9b287198ea6389be9 d0115fbfb2e64cf7b3e76479f5663682--f6b4573f42594ba9b287198ea6389be9 978b32b0cb984ef79712ad001f573123 bf050afd57034211b5cc8c9d1423009f X a44574eea195427cba888f2726f32b83--bf050afd57034211b5cc8c9d1423009f bf050afd57034211b5cc8c9d1423009f--978b32b0cb984ef79712ad001f573123

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_cfde6b17e85e493ab644862afaefa1dd subblock cluster_4a4372684de846bea87ae9ab03fda058 subblock 4e182463bada4b9bb7a514399219e3ab 0 d6e66910ed98460084fe09fc6e290f3f X 4e182463bada4b9bb7a514399219e3ab--d6e66910ed98460084fe09fc6e290f3f 8255fd2b5b884c7b8f490ac7511e279c 1 8a23b26f10454d5fa1bfabd1c28a491e X d6e66910ed98460084fe09fc6e290f3f--8a23b26f10454d5fa1bfabd1c28a491e 38cbbce35ef9470c9be879a0c27fc6cb 8a23b26f10454d5fa1bfabd1c28a491e--38cbbce35ef9470c9be879a0c27fc6cb 5eedce7aacdd4d6ca77c2ebb1769d74a 9a88e3c972a24436b3d9bc935d026eb3 Y 8255fd2b5b884c7b8f490ac7511e279c--9a88e3c972a24436b3d9bc935d026eb3 70c726d42cc54676808765871231c83f 2 3cba2b320cde43bf95c01f68e8094d8c Y 9a88e3c972a24436b3d9bc935d026eb3--3cba2b320cde43bf95c01f68e8094d8c 3cba2b320cde43bf95c01f68e8094d8c--5eedce7aacdd4d6ca77c2ebb1769d74a a255e9e14d6d445daab910ad77add63f 62145c9a81864ae198d44b5ac006a8ae 70c726d42cc54676808765871231c83f--62145c9a81864ae198d44b5ac006a8ae 8df817b6ad324a209441b7c6fe4e02dc 3 db759ce34dc242859615fb83d78a7486 62145c9a81864ae198d44b5ac006a8ae--db759ce34dc242859615fb83d78a7486 db759ce34dc242859615fb83d78a7486--a255e9e14d6d445daab910ad77add63f 41c64c47ebb84660a2e30d8c7df02008 5caeafaab2364bf099f0f74c45c444e4 8df817b6ad324a209441b7c6fe4e02dc--5caeafaab2364bf099f0f74c45c444e4 2c47eb0ac82343e1888e40d713adae32 4 86301e9367df46baa3abe31757c8b905 5caeafaab2364bf099f0f74c45c444e4--86301e9367df46baa3abe31757c8b905 86301e9367df46baa3abe31757c8b905--41c64c47ebb84660a2e30d8c7df02008 02b6febcf5df42528ff84b3c7f9d202c 42b59f63af454843a39e4cb6302f7133 X 2c47eb0ac82343e1888e40d713adae32--42b59f63af454843a39e4cb6302f7133 42b59f63af454843a39e4cb6302f7133--5caeafaab2364bf099f0f74c45c444e4 1f90268e0d5d4830b95ec6bd3148d91d X 42b59f63af454843a39e4cb6302f7133--1f90268e0d5d4830b95ec6bd3148d91d 1f90268e0d5d4830b95ec6bd3148d91d--86301e9367df46baa3abe31757c8b905 1f90268e0d5d4830b95ec6bd3148d91d--02b6febcf5df42528ff84b3c7f9d202c

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': 263, '11': 254, '01': 246, '10': 237})]
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': 38, '100': 27, '010': 20, '000': 15})]

For more details on QuantumModel, see here.