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 c56d9683d628438ba288c6975d204c55 0 cb80184ba8f6482584f7ef64c2385407 X c56d9683d628438ba288c6975d204c55--cb80184ba8f6482584f7ef64c2385407 0eb28153fb4e4a9ba324bd1c61bd37b2 1 a91f53a3d8114da38d96147310b59d0f cb80184ba8f6482584f7ef64c2385407--a91f53a3d8114da38d96147310b59d0f 392e8f0c8eef4a4cb7df64618ab6f0c6 2de04065a53442089d6cdaeb2e1b703c Y 0eb28153fb4e4a9ba324bd1c61bd37b2--2de04065a53442089d6cdaeb2e1b703c 2de04065a53442089d6cdaeb2e1b703c--392e8f0c8eef4a4cb7df64618ab6f0c6

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 5144283454c44ba0a92aca80a9dd96d3 0 1e5354f7d4b24bf589f7a88998e7e05f RX(0.5) 5144283454c44ba0a92aca80a9dd96d3--1e5354f7d4b24bf589f7a88998e7e05f 69ff295747f94275bb2b7700337cad8b 1e5354f7d4b24bf589f7a88998e7e05f--69ff295747f94275bb2b7700337cad8b
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 ad02c4b7a7e24a44bdf04ea910685a93 0 adf01890b3de464fa2e2df0f4087acfb ad02c4b7a7e24a44bdf04ea910685a93--adf01890b3de464fa2e2df0f4087acfb 951e7056a8094ec8881ef0d2d0a29a86 1 1fe6a046ad084644b06f1b4b4a9b07db adf01890b3de464fa2e2df0f4087acfb--1fe6a046ad084644b06f1b4b4a9b07db ed6acdb78d9c453a8b4079a1a131a4c9 65a540679969425584211f8cdd0718c9 X 951e7056a8094ec8881ef0d2d0a29a86--65a540679969425584211f8cdd0718c9 65a540679969425584211f8cdd0718c9--adf01890b3de464fa2e2df0f4087acfb 65a540679969425584211f8cdd0718c9--ed6acdb78d9c453a8b4079a1a131a4c9

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 81182d417b4644e896b455ca791d9a2b 0 6ec84a6ca4ca42efb18fb3c84989a616 X 81182d417b4644e896b455ca791d9a2b--6ec84a6ca4ca42efb18fb3c84989a616 08eddfe2979b4bbbadcfcb522ddd5737 X 6ec84a6ca4ca42efb18fb3c84989a616--08eddfe2979b4bbbadcfcb522ddd5737 9b19f2e00f8f43c0b078b5d97a4fa0e3 08eddfe2979b4bbbadcfcb522ddd5737--9b19f2e00f8f43c0b078b5d97a4fa0e3
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 223c10bc8b8a46d8aee208a485eb0acd 0 045979b0fbc24095bd607afa80df962a X 223c10bc8b8a46d8aee208a485eb0acd--045979b0fbc24095bd607afa80df962a c01619582891435589b3f8fb5a4828bf 1 e8c0534af2034874b95b291b87eaa48a 045979b0fbc24095bd607afa80df962a--e8c0534af2034874b95b291b87eaa48a cb922f08fb6046fa9146890aaffd1fa3 e8c0534af2034874b95b291b87eaa48a--cb922f08fb6046fa9146890aaffd1fa3 5f403664609f467fb1c0c6fa865cbc67 950d13180d28471899f3c7fbe62404d9 c01619582891435589b3f8fb5a4828bf--950d13180d28471899f3c7fbe62404d9 7a7880ed28eb468eb41a1c3287b5b2a2 X 950d13180d28471899f3c7fbe62404d9--7a7880ed28eb468eb41a1c3287b5b2a2 7a7880ed28eb468eb41a1c3287b5b2a2--5f403664609f467fb1c0c6fa865cbc67

  • 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 6ed5f82d6d1547beb393d0a503bad09b 0 9d36581264b34dacb1301f7b30ce2b69 X 6ed5f82d6d1547beb393d0a503bad09b--9d36581264b34dacb1301f7b30ce2b69 24e6348568104b458f2f2bb38d54b90d 1 a0c47b0d1a0a4776a5d2173a02a4c953 9d36581264b34dacb1301f7b30ce2b69--a0c47b0d1a0a4776a5d2173a02a4c953 a4b43e2e29a640f78357b820b9ff8df7 b51388d314524a7896aa1ba3a19d36a9 X 24e6348568104b458f2f2bb38d54b90d--b51388d314524a7896aa1ba3a19d36a9 b51388d314524a7896aa1ba3a19d36a9--a4b43e2e29a640f78357b820b9ff8df7

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_edf4af67a9ab40a494c535a5cc01b0f8 subblock cluster_e91cc72d5fc242438210276a1ebb57a2 subblock 345e499570f74b6da2fb2ea01d60975c 0 6f5d53f0469c415db8ff85948977d862 X 345e499570f74b6da2fb2ea01d60975c--6f5d53f0469c415db8ff85948977d862 7282fa0560b94b5aaece0d056889a0cd 1 940050813d2e415e87433bfe6ad0ac94 X 6f5d53f0469c415db8ff85948977d862--940050813d2e415e87433bfe6ad0ac94 2fb35a1db5ed4b34bf5c38be94c9d04f 940050813d2e415e87433bfe6ad0ac94--2fb35a1db5ed4b34bf5c38be94c9d04f 04bf65e82a1c4304bba728b706f84d8c 4e5a70316368421ba8126d3e9e84c48e Y 7282fa0560b94b5aaece0d056889a0cd--4e5a70316368421ba8126d3e9e84c48e 2169804a8982412f98596e2c97271534 2 bcb8586460c549c98f8532674a33a90e Y 4e5a70316368421ba8126d3e9e84c48e--bcb8586460c549c98f8532674a33a90e bcb8586460c549c98f8532674a33a90e--04bf65e82a1c4304bba728b706f84d8c 1b7e225294a44ae2a0705819d9d55541 bb6ccaa98663472baebd13d54f3f4fb5 2169804a8982412f98596e2c97271534--bb6ccaa98663472baebd13d54f3f4fb5 c84d910b254a426d8ddb3294bce897c4 3 f1e4c7a3e0384acdb2bd42f7f342b1c8 bb6ccaa98663472baebd13d54f3f4fb5--f1e4c7a3e0384acdb2bd42f7f342b1c8 f1e4c7a3e0384acdb2bd42f7f342b1c8--1b7e225294a44ae2a0705819d9d55541 8e46d3c675534b2f8ed4fe3bc9f62a41 a01ca36b74bf491abcea3ee2fb37593b c84d910b254a426d8ddb3294bce897c4--a01ca36b74bf491abcea3ee2fb37593b 97169a2cfc464d3e9785817dea87202e 4 bf571afa245f486aa314577c703ac3a6 a01ca36b74bf491abcea3ee2fb37593b--bf571afa245f486aa314577c703ac3a6 bf571afa245f486aa314577c703ac3a6--8e46d3c675534b2f8ed4fe3bc9f62a41 0b714ed55c9e4769a07eab7c22d99bb3 56e2491149f04b9095c03aee18645fd9 X 97169a2cfc464d3e9785817dea87202e--56e2491149f04b9095c03aee18645fd9 56e2491149f04b9095c03aee18645fd9--a01ca36b74bf491abcea3ee2fb37593b c246f9f18dd34dd095b6a9affe1b44a6 X 56e2491149f04b9095c03aee18645fd9--c246f9f18dd34dd095b6a9affe1b44a6 c246f9f18dd34dd095b6a9affe1b44a6--bf571afa245f486aa314577c703ac3a6 c246f9f18dd34dd095b6a9affe1b44a6--0b714ed55c9e4769a07eab7c22d99bb3

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': 271, '10': 252, '01': 244, '11': 233})]
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, '110': 25, '000': 24, '010': 21})]

For more details on QuantumModel, see here.