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 fbdd8ac31fec480a9e6f024d1b1b043a 0 bcbcffe29d484bc0a3fbab2e8ce07521 X fbdd8ac31fec480a9e6f024d1b1b043a--bcbcffe29d484bc0a3fbab2e8ce07521 a9f0a76efafd496685575d1e25acad05 1 9058ac3fd7c74442b35d65bfa7968fca bcbcffe29d484bc0a3fbab2e8ce07521--9058ac3fd7c74442b35d65bfa7968fca 213f0be8bd6241fa90ab28e7bddc20d3 ffb337fb55734f01b52512a6d95c0317 Y a9f0a76efafd496685575d1e25acad05--ffb337fb55734f01b52512a6d95c0317 ffb337fb55734f01b52512a6d95c0317--213f0be8bd6241fa90ab28e7bddc20d3

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 3bf6cb1f44f74eee8a6244d224a7bc8b 0 9639381565bb4d0fb687d5144b32f881 RX(0.5) 3bf6cb1f44f74eee8a6244d224a7bc8b--9639381565bb4d0fb687d5144b32f881 c38c13975fa045868bfb40c462b77013 9639381565bb4d0fb687d5144b32f881--c38c13975fa045868bfb40c462b77013
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 24477c006ba8482c984ecd67f377c63b 0 590988cbeb0b4bf58e5737215a749a6e 24477c006ba8482c984ecd67f377c63b--590988cbeb0b4bf58e5737215a749a6e f3060f6fe5654f76b3c059a2533131b8 1 b55acad5291444e68fec9811b557a8eb 590988cbeb0b4bf58e5737215a749a6e--b55acad5291444e68fec9811b557a8eb d0b0b8dfe40844569cd26b853fded392 7eeadf29f54f4cd48fa300b80fcd74b5 X f3060f6fe5654f76b3c059a2533131b8--7eeadf29f54f4cd48fa300b80fcd74b5 7eeadf29f54f4cd48fa300b80fcd74b5--590988cbeb0b4bf58e5737215a749a6e 7eeadf29f54f4cd48fa300b80fcd74b5--d0b0b8dfe40844569cd26b853fded392

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 9bbd4161bb6b4276998a6938aa3e7b08 0 5614ed8eb342450dab65bc57623e466e X 9bbd4161bb6b4276998a6938aa3e7b08--5614ed8eb342450dab65bc57623e466e 80722e8c52314bc4b412c96b0fc41fba X 5614ed8eb342450dab65bc57623e466e--80722e8c52314bc4b412c96b0fc41fba 6831db49e8cb4f68862b06ca12c3c704 80722e8c52314bc4b412c96b0fc41fba--6831db49e8cb4f68862b06ca12c3c704
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 8d6381499049469884150ff25f5297c7 0 f6cac03efb4b41e3b16d54c359d15867 X 8d6381499049469884150ff25f5297c7--f6cac03efb4b41e3b16d54c359d15867 0f37f06b523a4b3fa441f5fd823ae26b 1 23863978837b42d7b765c8f5f4e85380 f6cac03efb4b41e3b16d54c359d15867--23863978837b42d7b765c8f5f4e85380 e5d3eb7cb2204967ac9e1b593aa0546b 23863978837b42d7b765c8f5f4e85380--e5d3eb7cb2204967ac9e1b593aa0546b 5960ff312d0a403fabe2554f7a5d6b77 791acae75cc44c4490348c0e1f10b1cf 0f37f06b523a4b3fa441f5fd823ae26b--791acae75cc44c4490348c0e1f10b1cf 9264b62252df4f118a8d3af59e0d76f6 X 791acae75cc44c4490348c0e1f10b1cf--9264b62252df4f118a8d3af59e0d76f6 9264b62252df4f118a8d3af59e0d76f6--5960ff312d0a403fabe2554f7a5d6b77

  • 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 69f14cf2f94848ce85d9c0cb08e78131 0 b2ada67e6a794c1488471e9ade9b7598 X 69f14cf2f94848ce85d9c0cb08e78131--b2ada67e6a794c1488471e9ade9b7598 adf859bd4f12483f984e27c739aae091 1 8259c8f155384fdfbc12de4bd763d5ee b2ada67e6a794c1488471e9ade9b7598--8259c8f155384fdfbc12de4bd763d5ee 92d654ec222341848c05b4bf31b2007b 280d8935c19643129060feaca431293d X adf859bd4f12483f984e27c739aae091--280d8935c19643129060feaca431293d 280d8935c19643129060feaca431293d--92d654ec222341848c05b4bf31b2007b

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_65cc6199c54d4bfb92c6b4c65dc7189b subblock cluster_12239fa6fa3c49eab8ad4059545de9d4 subblock d5bf650eb10c4248a6186c84e03b703d 0 79d0e0c02201494fa848e63ff885677f X d5bf650eb10c4248a6186c84e03b703d--79d0e0c02201494fa848e63ff885677f 09247c2558fb4356b7369c6d57bb38e5 1 957b7fd980a34dd289b07a9a89170e79 X 79d0e0c02201494fa848e63ff885677f--957b7fd980a34dd289b07a9a89170e79 9e9635c86b274207a22c559b25c84901 957b7fd980a34dd289b07a9a89170e79--9e9635c86b274207a22c559b25c84901 fbe2e61178834e1d8de891a0046f5ff9 0c3b71f5fadf49b094b709ba23d24758 Y 09247c2558fb4356b7369c6d57bb38e5--0c3b71f5fadf49b094b709ba23d24758 5584c97ad89f4492b98e9e7c7f8064bd 2 b97ea31d26704e7caf816eaaf5cb4569 Y 0c3b71f5fadf49b094b709ba23d24758--b97ea31d26704e7caf816eaaf5cb4569 b97ea31d26704e7caf816eaaf5cb4569--fbe2e61178834e1d8de891a0046f5ff9 d46965ef06c44cb5b9e1b6a2c0e6b87d 0d49d572f59e4098ae05ed493f52613c 5584c97ad89f4492b98e9e7c7f8064bd--0d49d572f59e4098ae05ed493f52613c 16ea4221e9ab46559a68058b24d4ccc9 3 6e8d75744e6f42ee9f000f0b51c76075 0d49d572f59e4098ae05ed493f52613c--6e8d75744e6f42ee9f000f0b51c76075 6e8d75744e6f42ee9f000f0b51c76075--d46965ef06c44cb5b9e1b6a2c0e6b87d 6fb3cf1ac3f54efb95145cb09304aec1 a96bb5fb142e46d29bac4a190b6a21fa 16ea4221e9ab46559a68058b24d4ccc9--a96bb5fb142e46d29bac4a190b6a21fa 83c863a9475249988e7b139df75017cd 4 67c24fc9429344ed997d80651addad67 a96bb5fb142e46d29bac4a190b6a21fa--67c24fc9429344ed997d80651addad67 67c24fc9429344ed997d80651addad67--6fb3cf1ac3f54efb95145cb09304aec1 ec0bbc0c91904a6da02e6d60ca106475 c6b67ecc94b0403191d4dfb5a52f9287 X 83c863a9475249988e7b139df75017cd--c6b67ecc94b0403191d4dfb5a52f9287 c6b67ecc94b0403191d4dfb5a52f9287--a96bb5fb142e46d29bac4a190b6a21fa e663aeae3f48470499b1d60f5ab40ba6 X c6b67ecc94b0403191d4dfb5a52f9287--e663aeae3f48470499b1d60f5ab40ba6 e663aeae3f48470499b1d60f5ab40ba6--67c24fc9429344ed997d80651addad67 e663aeae3f48470499b1d60f5ab40ba6--ec0bbc0c91904a6da02e6d60ca106475

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.