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 117159dde83c415392a4bd4e6e8c6fb0 0 561e550aa22e4c24b91f1f60545674b0 X 117159dde83c415392a4bd4e6e8c6fb0--561e550aa22e4c24b91f1f60545674b0 da1a2c95626b4e43a8711cda4f0117d4 1 6deaafc4e06a41e58e39c332df006567 561e550aa22e4c24b91f1f60545674b0--6deaafc4e06a41e58e39c332df006567 c2275550cc45455ab6589c9ba9737844 377547ec5ca641b593c397035c8274a5 Y da1a2c95626b4e43a8711cda4f0117d4--377547ec5ca641b593c397035c8274a5 377547ec5ca641b593c397035c8274a5--c2275550cc45455ab6589c9ba9737844

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 73bf5c2821974e4aaedfcddc40cea47b 0 4b246d1b84aa4921b748e6b9b0d2052d RX(0.5) 73bf5c2821974e4aaedfcddc40cea47b--4b246d1b84aa4921b748e6b9b0d2052d 6a4d52b249d44c3e9cc8665c230021eb 4b246d1b84aa4921b748e6b9b0d2052d--6a4d52b249d44c3e9cc8665c230021eb
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 3e141753b85545eb8e40383370de7090 0 6840bef67c324965a0c5fd2363828048 3e141753b85545eb8e40383370de7090--6840bef67c324965a0c5fd2363828048 5c56250817904968af508c96102b26f3 1 ce695c1da8924ebcb06ff85dd842f70d 6840bef67c324965a0c5fd2363828048--ce695c1da8924ebcb06ff85dd842f70d fd7f5b40603b416e9c4897b2c67624aa 3d7239c0367b4e9a80c7856b3ead8428 X 5c56250817904968af508c96102b26f3--3d7239c0367b4e9a80c7856b3ead8428 3d7239c0367b4e9a80c7856b3ead8428--6840bef67c324965a0c5fd2363828048 3d7239c0367b4e9a80c7856b3ead8428--fd7f5b40603b416e9c4897b2c67624aa

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 6798bcde565c4275b50573470ef01922 0 e57b52ed3d3940439249ffd44b206a1d X 6798bcde565c4275b50573470ef01922--e57b52ed3d3940439249ffd44b206a1d 95c69825fda747c2bdcebb6851481f45 X e57b52ed3d3940439249ffd44b206a1d--95c69825fda747c2bdcebb6851481f45 5cfb3ae6c2f14ccf90484173b5e99a22 95c69825fda747c2bdcebb6851481f45--5cfb3ae6c2f14ccf90484173b5e99a22
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 20ab084fc1ed41dba6f5efe39a0345da 0 0a181324272b410b82ce0f91f58420b8 X 20ab084fc1ed41dba6f5efe39a0345da--0a181324272b410b82ce0f91f58420b8 c6730b8c91124834880a9e41a51fe4ad 1 84486706cac04bbab6680dc92b0799d5 0a181324272b410b82ce0f91f58420b8--84486706cac04bbab6680dc92b0799d5 f21f0e4f93a84c1c95d7f9523428b251 84486706cac04bbab6680dc92b0799d5--f21f0e4f93a84c1c95d7f9523428b251 6e4b848dc12247728bc9d34014f21181 4a145e17ba8c4a0cb5dc7f8dbdeeb2f9 c6730b8c91124834880a9e41a51fe4ad--4a145e17ba8c4a0cb5dc7f8dbdeeb2f9 750a965c62fc4ed4a0892338fce6ac20 X 4a145e17ba8c4a0cb5dc7f8dbdeeb2f9--750a965c62fc4ed4a0892338fce6ac20 750a965c62fc4ed4a0892338fce6ac20--6e4b848dc12247728bc9d34014f21181

  • 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 b26d51fde4ed4c5584aea0a3b2e8531b 0 4f0ba6d36cc04ac6a0260c3a19882825 X b26d51fde4ed4c5584aea0a3b2e8531b--4f0ba6d36cc04ac6a0260c3a19882825 58b60af36b0e462c914be704de54e760 1 0b4c6c4cdeb74a93a481b12e268d25ae 4f0ba6d36cc04ac6a0260c3a19882825--0b4c6c4cdeb74a93a481b12e268d25ae 55dec08f8b6f4545ac5f7d24ad867c5e aab5b58f6de948e7be218188a0c1cf04 X 58b60af36b0e462c914be704de54e760--aab5b58f6de948e7be218188a0c1cf04 aab5b58f6de948e7be218188a0c1cf04--55dec08f8b6f4545ac5f7d24ad867c5e

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_2c6d40fce7764a21abbc5329ed338cf7 subblock cluster_a287b1ec9e0c40129b1b09899e058764 subblock b19d903f812b4c87a66569ba4ee169f4 0 28a3f460359a4add8469a6f3fa2cd605 X b19d903f812b4c87a66569ba4ee169f4--28a3f460359a4add8469a6f3fa2cd605 f213574df08e438f8051afd04e641948 1 7bb9e3ae081547f8976461672edb2a81 X 28a3f460359a4add8469a6f3fa2cd605--7bb9e3ae081547f8976461672edb2a81 af682277d49843b3a2b58adf3c17d9a4 7bb9e3ae081547f8976461672edb2a81--af682277d49843b3a2b58adf3c17d9a4 8021b6d978a7481b97a344adfef6208a 535d21384a1949288224a45299a5f668 Y f213574df08e438f8051afd04e641948--535d21384a1949288224a45299a5f668 06bde84afc2f44869aa13707f1113010 2 b385783fcf4640b881902acaab664815 Y 535d21384a1949288224a45299a5f668--b385783fcf4640b881902acaab664815 b385783fcf4640b881902acaab664815--8021b6d978a7481b97a344adfef6208a 3d16d221cc5e4614b6ece69422ff2aaf 76176980f82c489b8ce51c9a7b894990 06bde84afc2f44869aa13707f1113010--76176980f82c489b8ce51c9a7b894990 938b9b9711d84e019508c128b2a5cfd8 3 03cbc5ea099c441193bee87905d2e6c4 76176980f82c489b8ce51c9a7b894990--03cbc5ea099c441193bee87905d2e6c4 03cbc5ea099c441193bee87905d2e6c4--3d16d221cc5e4614b6ece69422ff2aaf 04d64f9db108470292af6708228f1644 3dd8a0e4aa6641d699931a8391e27831 938b9b9711d84e019508c128b2a5cfd8--3dd8a0e4aa6641d699931a8391e27831 fcb9d0800b394e81aa5d7d1adba8f303 4 09948d8e2301485fac4b956204087dce 3dd8a0e4aa6641d699931a8391e27831--09948d8e2301485fac4b956204087dce 09948d8e2301485fac4b956204087dce--04d64f9db108470292af6708228f1644 e58737aca3f547d3aec80ab8a04de369 62d1de21474b407e9ac31cb4f573c74d X fcb9d0800b394e81aa5d7d1adba8f303--62d1de21474b407e9ac31cb4f573c74d 62d1de21474b407e9ac31cb4f573c74d--3dd8a0e4aa6641d699931a8391e27831 205841ee58f54472b948690bb030332b X 62d1de21474b407e9ac31cb4f573c74d--205841ee58f54472b948690bb030332b 205841ee58f54472b948690bb030332b--09948d8e2301485fac4b956204087dce 205841ee58f54472b948690bb030332b--e58737aca3f547d3aec80ab8a04de369

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': 283, '11': 247, '10': 241, '00': 229})]
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': 29, '010': 29, '110': 27, '100': 15})]

For more details on QuantumModel, see here.