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 6d851e71c001477c81cc37d59d32318f 0 d1bb605de8e94048931c39c3b8b197b8 X 6d851e71c001477c81cc37d59d32318f--d1bb605de8e94048931c39c3b8b197b8 faa669053ab04391bc9dac3fc7984fd8 1 7b66c76355c44292a5145d6fa8b29a16 d1bb605de8e94048931c39c3b8b197b8--7b66c76355c44292a5145d6fa8b29a16 113a03e6a6e34c56b7cd4bb3c25a2adc 5ce1779236884dd1979bafefcba050fd Y faa669053ab04391bc9dac3fc7984fd8--5ce1779236884dd1979bafefcba050fd 5ce1779236884dd1979bafefcba050fd--113a03e6a6e34c56b7cd4bb3c25a2adc

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 b08b6a4084134f44a0d252c260fcb981 0 2af3863341e64ba8963276dab8585634 RX(0.5) b08b6a4084134f44a0d252c260fcb981--2af3863341e64ba8963276dab8585634 99b2a8fee7454486b1c30e076e40eb7d 2af3863341e64ba8963276dab8585634--99b2a8fee7454486b1c30e076e40eb7d
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 f803d46bbc6444409840407eabdc7d46 0 aaae40e012a74ad5807f4f5dd8018e5a f803d46bbc6444409840407eabdc7d46--aaae40e012a74ad5807f4f5dd8018e5a 6a7f4981c3c1431d9942c8ba85832d06 1 49356af98bab454b88a542831d13d528 aaae40e012a74ad5807f4f5dd8018e5a--49356af98bab454b88a542831d13d528 68d470e3abd848ed81e3647ebb47752d 1bc5d07e6eef4d28bee9ac1aa9938719 X 6a7f4981c3c1431d9942c8ba85832d06--1bc5d07e6eef4d28bee9ac1aa9938719 1bc5d07e6eef4d28bee9ac1aa9938719--aaae40e012a74ad5807f4f5dd8018e5a 1bc5d07e6eef4d28bee9ac1aa9938719--68d470e3abd848ed81e3647ebb47752d

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 4a4d4e0021134d6886c51d9c4d8e8296 0 9773d28fdf734ef2b2af92d4ba5358a8 X 4a4d4e0021134d6886c51d9c4d8e8296--9773d28fdf734ef2b2af92d4ba5358a8 85d68c9576504e49bd0924ef54799feb X 9773d28fdf734ef2b2af92d4ba5358a8--85d68c9576504e49bd0924ef54799feb 1faaa4c6c29145e1954c8e21da67bdc7 85d68c9576504e49bd0924ef54799feb--1faaa4c6c29145e1954c8e21da67bdc7
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 5e83caae88a04487aab3711286a313e9 0 ee2bbc66b6b645ecaef2bf6d483b7722 X 5e83caae88a04487aab3711286a313e9--ee2bbc66b6b645ecaef2bf6d483b7722 453c430f474349578c972d10ef00c305 1 310fbddb386342b29518214d89568e42 ee2bbc66b6b645ecaef2bf6d483b7722--310fbddb386342b29518214d89568e42 0fe9429472df4b688500c5886d9d0528 310fbddb386342b29518214d89568e42--0fe9429472df4b688500c5886d9d0528 13219ea41a1043d2a9cc711f21bf0397 4a56b212a80d4f428fa768f04bb8a09a 453c430f474349578c972d10ef00c305--4a56b212a80d4f428fa768f04bb8a09a a8ff556e321146ceb80a47c9ee4360e4 X 4a56b212a80d4f428fa768f04bb8a09a--a8ff556e321146ceb80a47c9ee4360e4 a8ff556e321146ceb80a47c9ee4360e4--13219ea41a1043d2a9cc711f21bf0397

  • 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 13b324d68be046ac8a7396f13775d26d 0 f0ce015efdd245efbac6b69b694fa896 X 13b324d68be046ac8a7396f13775d26d--f0ce015efdd245efbac6b69b694fa896 59eaacb434ce4dd5ab46a72bf11a8cd9 1 9db5c0702379451181e91807c20f3c7b f0ce015efdd245efbac6b69b694fa896--9db5c0702379451181e91807c20f3c7b 51d9c8ffa0284432b377ae111af05dfc fb7f5c094f174180b5cf859c820608c2 X 59eaacb434ce4dd5ab46a72bf11a8cd9--fb7f5c094f174180b5cf859c820608c2 fb7f5c094f174180b5cf859c820608c2--51d9c8ffa0284432b377ae111af05dfc

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_85222885363348138e99083958b54b98 subblock cluster_3b9112ae6d0542bda367f496e5e1b821 subblock 2322cf5641924e45bd8076524036e8fd 0 9adf988d87ee4c78bbba1af27fd20847 X 2322cf5641924e45bd8076524036e8fd--9adf988d87ee4c78bbba1af27fd20847 7e4f17e71add41e3a6b95c1ec688a545 1 83b87a85e079413a9bc7ba12091aaa98 X 9adf988d87ee4c78bbba1af27fd20847--83b87a85e079413a9bc7ba12091aaa98 4b47ff86814b4a7a8c75a8f1d9704ed0 83b87a85e079413a9bc7ba12091aaa98--4b47ff86814b4a7a8c75a8f1d9704ed0 346e2e5c53c240b09e887c90df1a69cd 1ca5a05bcce4420c9b1561155348cf83 Y 7e4f17e71add41e3a6b95c1ec688a545--1ca5a05bcce4420c9b1561155348cf83 8f75cbdf49114a1496909a8c48b744f6 2 debc9af345bc451989a0d51196681482 Y 1ca5a05bcce4420c9b1561155348cf83--debc9af345bc451989a0d51196681482 debc9af345bc451989a0d51196681482--346e2e5c53c240b09e887c90df1a69cd e270bc27b1934122936015ac3883ba69 4aacf6dd039e4abf952a6b5b38e00fd7 8f75cbdf49114a1496909a8c48b744f6--4aacf6dd039e4abf952a6b5b38e00fd7 76aab02740bf46edb2158c5ae0080027 3 791292686ebc4890b2648971dbc3d5f4 4aacf6dd039e4abf952a6b5b38e00fd7--791292686ebc4890b2648971dbc3d5f4 791292686ebc4890b2648971dbc3d5f4--e270bc27b1934122936015ac3883ba69 2db93451c4214542bb96ce68e69c7760 480438b55c624c1da646c7e62f4962aa 76aab02740bf46edb2158c5ae0080027--480438b55c624c1da646c7e62f4962aa 57ae620dc0124491bdd9345960ef6cc6 4 427efa005b28438fb0060a649064a98f 480438b55c624c1da646c7e62f4962aa--427efa005b28438fb0060a649064a98f 427efa005b28438fb0060a649064a98f--2db93451c4214542bb96ce68e69c7760 4ee4fd50bbb24f6db010d34de2cdedd8 4f4d1dc0a5554c48a7e9679ca6413a3c X 57ae620dc0124491bdd9345960ef6cc6--4f4d1dc0a5554c48a7e9679ca6413a3c 4f4d1dc0a5554c48a7e9679ca6413a3c--480438b55c624c1da646c7e62f4962aa 20340659dd97468ab230c7cc87b2a8cd X 4f4d1dc0a5554c48a7e9679ca6413a3c--20340659dd97468ab230c7cc87b2a8cd 20340659dd97468ab230c7cc87b2a8cd--427efa005b28438fb0060a649064a98f 20340659dd97468ab230c7cc87b2a8cd--4ee4fd50bbb24f6db010d34de2cdedd8

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.