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 ef3d70bd1c224dcf99a61b84af1c95ae 0 bc6dcf2d0be848e3854ea649f9b9af0e X ef3d70bd1c224dcf99a61b84af1c95ae--bc6dcf2d0be848e3854ea649f9b9af0e fa1bf6b83a0d486e80ab11dedf08619a 1 5087cf666b7f4657be2adff338a125f6 bc6dcf2d0be848e3854ea649f9b9af0e--5087cf666b7f4657be2adff338a125f6 eaab9334db994de2a147dfbdc492e1ac fc0ed4265b3d4c079680ed31118aa9c8 Y fa1bf6b83a0d486e80ab11dedf08619a--fc0ed4265b3d4c079680ed31118aa9c8 fc0ed4265b3d4c079680ed31118aa9c8--eaab9334db994de2a147dfbdc492e1ac

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 87924f8078db4666b886f0f066c62ef1 0 a5eeefb4dca7475998a033f4c990d399 RX(0.5) 87924f8078db4666b886f0f066c62ef1--a5eeefb4dca7475998a033f4c990d399 d8f448556b67422d8650cbeb045391ca a5eeefb4dca7475998a033f4c990d399--d8f448556b67422d8650cbeb045391ca
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 68c4bdd666e3453a873191be2dbe1d05 0 40cc1b764abc49ac9dd27da4a22dac42 68c4bdd666e3453a873191be2dbe1d05--40cc1b764abc49ac9dd27da4a22dac42 0a0959aa2540407d81f1b077a992a1b2 1 b2a179e16e204816b9dcce245b2fcee1 40cc1b764abc49ac9dd27da4a22dac42--b2a179e16e204816b9dcce245b2fcee1 24d37f196406490e9daaa8e0834b77ce 617423743b0944a4b3d157e2cd58251d X 0a0959aa2540407d81f1b077a992a1b2--617423743b0944a4b3d157e2cd58251d 617423743b0944a4b3d157e2cd58251d--40cc1b764abc49ac9dd27da4a22dac42 617423743b0944a4b3d157e2cd58251d--24d37f196406490e9daaa8e0834b77ce

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 260f23898d384a0882527d37809bfd28 0 5cb4572ee35748ba8ea2f942fc6ec9af X 260f23898d384a0882527d37809bfd28--5cb4572ee35748ba8ea2f942fc6ec9af 664a19b6b00a4e15a7c81d1ab79a3448 X 5cb4572ee35748ba8ea2f942fc6ec9af--664a19b6b00a4e15a7c81d1ab79a3448 6760318b4a29485ab3e3ab2656ff74fd 664a19b6b00a4e15a7c81d1ab79a3448--6760318b4a29485ab3e3ab2656ff74fd
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 3efa19ae57a04a0caabbba926baa648f 0 45694c9288484b1286205236f4cbbca7 X 3efa19ae57a04a0caabbba926baa648f--45694c9288484b1286205236f4cbbca7 7a487ad5401c4d7c9fd8466bedd402f1 1 72598a25c6c943e48941e3f730aa8427 45694c9288484b1286205236f4cbbca7--72598a25c6c943e48941e3f730aa8427 d259a5f6020c4934b520c245edc4f873 72598a25c6c943e48941e3f730aa8427--d259a5f6020c4934b520c245edc4f873 a0048ad0f3f141cc87fc4b81fb73d9ac ef55e55b64de40e39ba59d58e3a27bc7 7a487ad5401c4d7c9fd8466bedd402f1--ef55e55b64de40e39ba59d58e3a27bc7 cd08b25af6394d95bea8e71bd832007d X ef55e55b64de40e39ba59d58e3a27bc7--cd08b25af6394d95bea8e71bd832007d cd08b25af6394d95bea8e71bd832007d--a0048ad0f3f141cc87fc4b81fb73d9ac

  • 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 a92b0c2e28be469bb1b7fb5d5899ff37 0 5759e1de0b0c428ea662530f4128cbca X a92b0c2e28be469bb1b7fb5d5899ff37--5759e1de0b0c428ea662530f4128cbca 267722fefb344267bc58176df0b077ab 1 88f97de2e5904e348c950acf206e5e14 5759e1de0b0c428ea662530f4128cbca--88f97de2e5904e348c950acf206e5e14 25bb0875bec44a55943e0346d0dd194a 502501c34b4842569ac1499c9143084e X 267722fefb344267bc58176df0b077ab--502501c34b4842569ac1499c9143084e 502501c34b4842569ac1499c9143084e--25bb0875bec44a55943e0346d0dd194a

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_b039f49df1b44dcd844abd441ddab50d subblock cluster_768535408ef74be495a363f75bf49755 subblock b136ed40d20448c2aeb0e2899c9e6488 0 4c318c72928b4d02bd7984a2f7518034 X b136ed40d20448c2aeb0e2899c9e6488--4c318c72928b4d02bd7984a2f7518034 b1f0da8df983468d844403e166e3a4a2 1 ec31ed50bc664bdd84da05cebd723866 X 4c318c72928b4d02bd7984a2f7518034--ec31ed50bc664bdd84da05cebd723866 734444a46fc14f8fbe5c668b33703917 ec31ed50bc664bdd84da05cebd723866--734444a46fc14f8fbe5c668b33703917 58355d885cd94695bd0a3b8f72759309 1d950c60d72e4a4c8ba87fde8dc3b46c Y b1f0da8df983468d844403e166e3a4a2--1d950c60d72e4a4c8ba87fde8dc3b46c 1aa334d7271f46b292cf78046d82c66f 2 04eb93bd4e844551805fc9938ea9b330 Y 1d950c60d72e4a4c8ba87fde8dc3b46c--04eb93bd4e844551805fc9938ea9b330 04eb93bd4e844551805fc9938ea9b330--58355d885cd94695bd0a3b8f72759309 342e78b1200445c1841aab33588a6440 fd268a45b5044a99a50a6bd4b8c49da0 1aa334d7271f46b292cf78046d82c66f--fd268a45b5044a99a50a6bd4b8c49da0 92a59ad358ee4046b4f7314299e1a7d6 3 40c19c7f9bed42ee97fc700ff197ccff fd268a45b5044a99a50a6bd4b8c49da0--40c19c7f9bed42ee97fc700ff197ccff 40c19c7f9bed42ee97fc700ff197ccff--342e78b1200445c1841aab33588a6440 61a6e141150c48c89ecf45baac977f2c 2bf2f303945e4b6399c4995c71b4f95b 92a59ad358ee4046b4f7314299e1a7d6--2bf2f303945e4b6399c4995c71b4f95b b71cf59b6f014d518379219ade45d394 4 10de3aabffeb4b19b0e462e956476eb8 2bf2f303945e4b6399c4995c71b4f95b--10de3aabffeb4b19b0e462e956476eb8 10de3aabffeb4b19b0e462e956476eb8--61a6e141150c48c89ecf45baac977f2c 3739059808c74153bb3b6ab378b8caa7 c8390fb55ee946c293a28986daf93590 X b71cf59b6f014d518379219ade45d394--c8390fb55ee946c293a28986daf93590 c8390fb55ee946c293a28986daf93590--2bf2f303945e4b6399c4995c71b4f95b 0f469847953b4135b3e1a19fce871936 X c8390fb55ee946c293a28986daf93590--0f469847953b4135b3e1a19fce871936 0f469847953b4135b3e1a19fce871936--10de3aabffeb4b19b0e462e956476eb8 0f469847953b4135b3e1a19fce871936--3739059808c74153bb3b6ab378b8caa7

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({'01': 267, '00': 253, '10': 242, '11': 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({'000': 28, '010': 25, '110': 25, '100': 22})]

For more details on QuantumModel, see here.