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 5d0aa1675a3745a8958d7ac60d97c5ed 0 aa5b2a53422440cfa46214a457680dd9 X 5d0aa1675a3745a8958d7ac60d97c5ed--aa5b2a53422440cfa46214a457680dd9 b36ede26dd4b4eedb03cd48fb0b4153f 1 cf64ac361c39457db819630e3f97972d aa5b2a53422440cfa46214a457680dd9--cf64ac361c39457db819630e3f97972d af986badd56645c4ac530d49b9d846d3 2eaaec1ef1314396988e73b80708491c Y b36ede26dd4b4eedb03cd48fb0b4153f--2eaaec1ef1314396988e73b80708491c 2eaaec1ef1314396988e73b80708491c--af986badd56645c4ac530d49b9d846d3

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 fe6904489dd2435e966bc034720d1cb0 0 824326cb79454d06973259fbb6c9d8ea RX(0.5) fe6904489dd2435e966bc034720d1cb0--824326cb79454d06973259fbb6c9d8ea c1941b1ad77b44cc9feab0bd765b2814 824326cb79454d06973259fbb6c9d8ea--c1941b1ad77b44cc9feab0bd765b2814
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 c67f5647901047e8937439d45d8dc2a9 0 e6b34dd476ff4f6883257597595faca4 c67f5647901047e8937439d45d8dc2a9--e6b34dd476ff4f6883257597595faca4 36276a2de9114029af6218f1d546d559 1 7fee931e8c754cefbbd18f98fa433aa0 e6b34dd476ff4f6883257597595faca4--7fee931e8c754cefbbd18f98fa433aa0 176d550e121a4ad0b8619785a6869975 6174e1a2c0eb4335b85d8f1b7d812d01 X 36276a2de9114029af6218f1d546d559--6174e1a2c0eb4335b85d8f1b7d812d01 6174e1a2c0eb4335b85d8f1b7d812d01--e6b34dd476ff4f6883257597595faca4 6174e1a2c0eb4335b85d8f1b7d812d01--176d550e121a4ad0b8619785a6869975

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 9854b569a37849929217873a95ef7218 0 dfb43223bf6b4133af443cb970d46b38 X 9854b569a37849929217873a95ef7218--dfb43223bf6b4133af443cb970d46b38 d4e582558d3d4ee4902da1cda8e70fd4 X dfb43223bf6b4133af443cb970d46b38--d4e582558d3d4ee4902da1cda8e70fd4 198bb08ee8bb43f8926602ffea18db40 d4e582558d3d4ee4902da1cda8e70fd4--198bb08ee8bb43f8926602ffea18db40
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 6e728cb5ce174cbfb5e6b34022918332 0 1ea4f9dcc4b74e928d4ceab84e91e841 X 6e728cb5ce174cbfb5e6b34022918332--1ea4f9dcc4b74e928d4ceab84e91e841 706e624ce88440639ff0cff699cbb0cc 1 e2bb9a60f58a42948abd78699a39c7bc 1ea4f9dcc4b74e928d4ceab84e91e841--e2bb9a60f58a42948abd78699a39c7bc e1c3e2dde07c4a2fa697a87687fbf83c e2bb9a60f58a42948abd78699a39c7bc--e1c3e2dde07c4a2fa697a87687fbf83c 8585bdff16e141819b27046aaa2d6844 9b9ace5d59c245fbb1b4ce2203454fd9 706e624ce88440639ff0cff699cbb0cc--9b9ace5d59c245fbb1b4ce2203454fd9 0b907e296cf6454da19ec289d1d265f1 X 9b9ace5d59c245fbb1b4ce2203454fd9--0b907e296cf6454da19ec289d1d265f1 0b907e296cf6454da19ec289d1d265f1--8585bdff16e141819b27046aaa2d6844

  • 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 2176be6a38724f039c5a72fe8697a949 0 4f416f9454de461ebb6ebcb5761d8506 X 2176be6a38724f039c5a72fe8697a949--4f416f9454de461ebb6ebcb5761d8506 fafd22063692493a8624b3238b0fae61 1 39f66708779d498fb4df4c7aac644c51 4f416f9454de461ebb6ebcb5761d8506--39f66708779d498fb4df4c7aac644c51 34d31280b73d4ed080dac5c42485e089 3f45d22b295a455fa6feab44e4b1e8b8 X fafd22063692493a8624b3238b0fae61--3f45d22b295a455fa6feab44e4b1e8b8 3f45d22b295a455fa6feab44e4b1e8b8--34d31280b73d4ed080dac5c42485e089

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_df49a8b0f4b848c8b9084c190e3f16ad subblock cluster_84267126be224804ba44ca30fcb5981e subblock 608c1ed66a56407abe93ae78ab587d34 0 df69956b87c744e7be48e915eb976879 X 608c1ed66a56407abe93ae78ab587d34--df69956b87c744e7be48e915eb976879 79ad3b85990f47dbb4530411b0b31da5 1 80f9e61a6b244a80912dd39482015bd3 X df69956b87c744e7be48e915eb976879--80f9e61a6b244a80912dd39482015bd3 53d28a56fb4b40fead534878a0860201 80f9e61a6b244a80912dd39482015bd3--53d28a56fb4b40fead534878a0860201 24e40ffbba454380a408f868936e3c6f 2628edd55d8d4ea481deee21dd648e4f Y 79ad3b85990f47dbb4530411b0b31da5--2628edd55d8d4ea481deee21dd648e4f bbb9a528efa64660963d0eea094a7a28 2 b1e5bc6f266b4b2784338785fd69c7df Y 2628edd55d8d4ea481deee21dd648e4f--b1e5bc6f266b4b2784338785fd69c7df b1e5bc6f266b4b2784338785fd69c7df--24e40ffbba454380a408f868936e3c6f ba523cd8a9a74d1491b39f6fdc0abde7 2208577595264e9aae7c950261902256 bbb9a528efa64660963d0eea094a7a28--2208577595264e9aae7c950261902256 bc33b7da0db44eb38e3f8cb57a84e97c 3 3a5235e574f341de8816632051fc4539 2208577595264e9aae7c950261902256--3a5235e574f341de8816632051fc4539 3a5235e574f341de8816632051fc4539--ba523cd8a9a74d1491b39f6fdc0abde7 e0a1bcfd59164359bbb5b90b7bc786a0 364cd2c3e34b4b4c89f40f92f7ef4fc0 bc33b7da0db44eb38e3f8cb57a84e97c--364cd2c3e34b4b4c89f40f92f7ef4fc0 86c2c06d4e074915b31fd087336cedfc 4 cfa31ff102df4c82a44f925c042223f4 364cd2c3e34b4b4c89f40f92f7ef4fc0--cfa31ff102df4c82a44f925c042223f4 cfa31ff102df4c82a44f925c042223f4--e0a1bcfd59164359bbb5b90b7bc786a0 ee26465371f34effa143a9b49704e03b e192932d11d84f92b95633a4b44697ba X 86c2c06d4e074915b31fd087336cedfc--e192932d11d84f92b95633a4b44697ba e192932d11d84f92b95633a4b44697ba--364cd2c3e34b4b4c89f40f92f7ef4fc0 05b48ed02c114f18ab9dcfb25ae8c758 X e192932d11d84f92b95633a4b44697ba--05b48ed02c114f18ab9dcfb25ae8c758 05b48ed02c114f18ab9dcfb25ae8c758--cfa31ff102df4c82a44f925c042223f4 05b48ed02c114f18ab9dcfb25ae8c758--ee26465371f34effa143a9b49704e03b

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': 262, '00': 249, '10': 248, '01': 241})]
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({'100': 30, '010': 27, '000': 24, '110': 19})]

For more details on QuantumModel, see here.