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 bbbaf10295a542f6bc4cb7d8120fb6b8 0 2017aae9049c47f0bb50fc538e87f6ff X bbbaf10295a542f6bc4cb7d8120fb6b8--2017aae9049c47f0bb50fc538e87f6ff 42f05504a4e94c30a2d5b0ef23e364ab 1 61014342fa6e4c788ca18f553d1c3527 2017aae9049c47f0bb50fc538e87f6ff--61014342fa6e4c788ca18f553d1c3527 f801b0e285fe48e284896759dcbfca6a 218557e1682d4ab0ad279a0bdf6eb7bc Y 42f05504a4e94c30a2d5b0ef23e364ab--218557e1682d4ab0ad279a0bdf6eb7bc 218557e1682d4ab0ad279a0bdf6eb7bc--f801b0e285fe48e284896759dcbfca6a

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 af6d4544ced94d709558ca64165def11 0 79dc509820564454af4b7b25a161ccdb RX(0.5) af6d4544ced94d709558ca64165def11--79dc509820564454af4b7b25a161ccdb 6109b0d6aaab412aa8d243b0d900a002 79dc509820564454af4b7b25a161ccdb--6109b0d6aaab412aa8d243b0d900a002
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 ac0d2ffd19e54b6fbce39b7f040c910b 0 073e90eaa16e45d0aa6f5fa3d5d1e05e ac0d2ffd19e54b6fbce39b7f040c910b--073e90eaa16e45d0aa6f5fa3d5d1e05e 05a8f662671b4c7982492f24b2741b08 1 a058a12510944d6f8b55ea141cd8a976 073e90eaa16e45d0aa6f5fa3d5d1e05e--a058a12510944d6f8b55ea141cd8a976 1c028c27293b41588c5d01d8c09d2d68 671bb9e5385e469c8da4a67026c74a6f X 05a8f662671b4c7982492f24b2741b08--671bb9e5385e469c8da4a67026c74a6f 671bb9e5385e469c8da4a67026c74a6f--073e90eaa16e45d0aa6f5fa3d5d1e05e 671bb9e5385e469c8da4a67026c74a6f--1c028c27293b41588c5d01d8c09d2d68

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 b20c18f4b1834702922e19f7a9f06002 0 47e5af7c463f47d9b28b70fabde98199 X b20c18f4b1834702922e19f7a9f06002--47e5af7c463f47d9b28b70fabde98199 4d44be3b87a9450ca672f9687ce72480 X 47e5af7c463f47d9b28b70fabde98199--4d44be3b87a9450ca672f9687ce72480 db1c1c20020f47a7b38972fe2104474b 4d44be3b87a9450ca672f9687ce72480--db1c1c20020f47a7b38972fe2104474b
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 676fc009a721497c8309721006293b0a 0 5ab1524f78564f6dbd6ea349bfe1ee6c X 676fc009a721497c8309721006293b0a--5ab1524f78564f6dbd6ea349bfe1ee6c e4d3339346784c158c1b554685c3195b 1 73de1be255834f54958c9be9948d6d9a 5ab1524f78564f6dbd6ea349bfe1ee6c--73de1be255834f54958c9be9948d6d9a b6a78ebe8ead43a1b05647c2d7a703be 73de1be255834f54958c9be9948d6d9a--b6a78ebe8ead43a1b05647c2d7a703be c0bd9926378f4b58ba5aeac1b4bd17e3 bc7bfc61fa8a41c597667314c1af4844 e4d3339346784c158c1b554685c3195b--bc7bfc61fa8a41c597667314c1af4844 f7641a41f5ab4fd5a4855f0ef52c8944 X bc7bfc61fa8a41c597667314c1af4844--f7641a41f5ab4fd5a4855f0ef52c8944 f7641a41f5ab4fd5a4855f0ef52c8944--c0bd9926378f4b58ba5aeac1b4bd17e3

  • 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 255822f46b3345eb8388b9c063d1561f 0 8d555a8bbf674da493f3ab321b775312 X 255822f46b3345eb8388b9c063d1561f--8d555a8bbf674da493f3ab321b775312 25cd7767dfd243af9a86d40551b466e3 1 a6baa04146094ede87763845cd799bbd 8d555a8bbf674da493f3ab321b775312--a6baa04146094ede87763845cd799bbd a0b9954446be45c7a5a0c0115dc4fd78 c1762deb402b4db58ecfb049e45a89f6 X 25cd7767dfd243af9a86d40551b466e3--c1762deb402b4db58ecfb049e45a89f6 c1762deb402b4db58ecfb049e45a89f6--a0b9954446be45c7a5a0c0115dc4fd78

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_d0ca4f4dacb24ba2b01043e216a13f82 subblock cluster_812d2fc29ee64fd4b72c47ef15cc0475 subblock d21c33bdc1df4c3f930a1986fabc60cf 0 6c220884b79d4a2c93baf93ca0a5a634 X d21c33bdc1df4c3f930a1986fabc60cf--6c220884b79d4a2c93baf93ca0a5a634 f1990727edfe4207a063b4e3795e1b1c 1 30076970d47449819f2717156f26708e X 6c220884b79d4a2c93baf93ca0a5a634--30076970d47449819f2717156f26708e 69da517133fd41e1bf41206c9bca58d3 30076970d47449819f2717156f26708e--69da517133fd41e1bf41206c9bca58d3 ed262f98b4ae4479abfdaa4005971da3 11714677c79f4d438a067bd152657f69 Y f1990727edfe4207a063b4e3795e1b1c--11714677c79f4d438a067bd152657f69 c9dd3c6bb7394a82926c57356f8b53a1 2 c606da8e91d74426ac1ae9824fae2d6d Y 11714677c79f4d438a067bd152657f69--c606da8e91d74426ac1ae9824fae2d6d c606da8e91d74426ac1ae9824fae2d6d--ed262f98b4ae4479abfdaa4005971da3 96c5336b705e4e54b57287f6e5778e2c be19ccde8e9b477d91f634f64dda1e6a c9dd3c6bb7394a82926c57356f8b53a1--be19ccde8e9b477d91f634f64dda1e6a f946763842234ce5bd49c33e35b7db06 3 3ed00239452847bf936def72bac8ab74 be19ccde8e9b477d91f634f64dda1e6a--3ed00239452847bf936def72bac8ab74 3ed00239452847bf936def72bac8ab74--96c5336b705e4e54b57287f6e5778e2c 86f740061ccb42b2bcb00154ebd9ef54 fb8046be05cf488c9af610f7bc1a656f f946763842234ce5bd49c33e35b7db06--fb8046be05cf488c9af610f7bc1a656f a66002772f2c40efaf0450659c1b884f 4 40e25bc884644059b49eeed1d22d2193 fb8046be05cf488c9af610f7bc1a656f--40e25bc884644059b49eeed1d22d2193 40e25bc884644059b49eeed1d22d2193--86f740061ccb42b2bcb00154ebd9ef54 6ad54d9561224a90bd50d2b6035966fa 2e937b5aeb40426daaca171135d8e0e5 X a66002772f2c40efaf0450659c1b884f--2e937b5aeb40426daaca171135d8e0e5 2e937b5aeb40426daaca171135d8e0e5--fb8046be05cf488c9af610f7bc1a656f d36e8382e75544f3b07e123109e9d3fb X 2e937b5aeb40426daaca171135d8e0e5--d36e8382e75544f3b07e123109e9d3fb d36e8382e75544f3b07e123109e9d3fb--40e25bc884644059b49eeed1d22d2193 d36e8382e75544f3b07e123109e9d3fb--6ad54d9561224a90bd50d2b6035966fa

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.