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 which opens an interactive window:

from qadence import X, Y, kron
#from visualization import display

kron_block = kron(X(0), Y(1))
#display(kron_block)
%3 b023312f8b3e4083816878584391f103 0 8c3288307bd741cd802a0cf98f26b12c X b023312f8b3e4083816878584391f103--8c3288307bd741cd802a0cf98f26b12c c6c5bd7dca7044568988687830c98cd0 1 b3a90d1f290c4f54b6413be42edc950c 8c3288307bd741cd802a0cf98f26b12c--b3a90d1f290c4f54b6413be42edc950c c81ebf4df3554ee0ae0fc24224daea8f 4ad8af668efe44749586cd8bc15b8c90 Y c6c5bd7dca7044568988687830c98cd0--4ad8af668efe44749586cd8bc15b8c90 4ad8af668efe44749586cd8bc15b8c90--c81ebf4df3554ee0ae0fc24224daea8f

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 18ad0689c36e40abb696ab83d798dcb7 0 db9549c6536c4fc48c35fe4fc8b0a152 RX(0.5) 18ad0689c36e40abb696ab83d798dcb7--db9549c6536c4fc48c35fe4fc8b0a152 fca6aff035794e439441088a0f21fc15 db9549c6536c4fc48c35fe4fc8b0a152--fca6aff035794e439441088a0f21fc15
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 d3124ac02281425996038cb69866e2d3 0 7f59411c9d7f48f3976c8b6959127427 d3124ac02281425996038cb69866e2d3--7f59411c9d7f48f3976c8b6959127427 85cd423b6f0d49c18c1bd59f09aa6879 1 1b3558142d244405b3442e5ce7db53ab 7f59411c9d7f48f3976c8b6959127427--1b3558142d244405b3442e5ce7db53ab cc6111bc090a44c5a46dd5bd42d72dc6 57c0c1a233b5448daa075bffaa938183 X 85cd423b6f0d49c18c1bd59f09aa6879--57c0c1a233b5448daa075bffaa938183 57c0c1a233b5448daa075bffaa938183--7f59411c9d7f48f3976c8b6959127427 57c0c1a233b5448daa075bffaa938183--cc6111bc090a44c5a46dd5bd42d72dc6

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 95e10d4fb23b49ad858a026517ec8ee8 0 e8b42000f55a42b9b106495b302028d3 X 95e10d4fb23b49ad858a026517ec8ee8--e8b42000f55a42b9b106495b302028d3 d438121994e846f1a98dc4e9aca4364a X e8b42000f55a42b9b106495b302028d3--d438121994e846f1a98dc4e9aca4364a 1f71ff61e7714361a114034856463b58 d438121994e846f1a98dc4e9aca4364a--1f71ff61e7714361a114034856463b58
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 862ee90b6eec40a18bacbccd50e2c970 0 e3f887257eca4240a66111b876981eb5 X 862ee90b6eec40a18bacbccd50e2c970--e3f887257eca4240a66111b876981eb5 924d9f0059174e0a828c18d9ed40655e 1 b0c1613418634ea7a43685bea6ac06d8 e3f887257eca4240a66111b876981eb5--b0c1613418634ea7a43685bea6ac06d8 6a5cb316bb134c80b71c51f60fcd08d4 b0c1613418634ea7a43685bea6ac06d8--6a5cb316bb134c80b71c51f60fcd08d4 450b1e7bb6734e93b2ccbdf23ca38218 ed2e6d0ddaf343ad91a6203fb01cc1ab 924d9f0059174e0a828c18d9ed40655e--ed2e6d0ddaf343ad91a6203fb01cc1ab dee155304b2743c88aeeb072b7c6b8ec X ed2e6d0ddaf343ad91a6203fb01cc1ab--dee155304b2743c88aeeb072b7c6b8ec dee155304b2743c88aeeb072b7c6b8ec--450b1e7bb6734e93b2ccbdf23ca38218

  • 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 0dac9f66c78243e8b81b3ab7f79039d2 0 e712b7da5f3d44ac897bc25248c0d682 X 0dac9f66c78243e8b81b3ab7f79039d2--e712b7da5f3d44ac897bc25248c0d682 7be2764603d34f5a826dd119fe655729 1 4c9177d1084945a396ca42f5ee02ce5e e712b7da5f3d44ac897bc25248c0d682--4c9177d1084945a396ca42f5ee02ce5e d3f4ad8c887d4e1c8f7cb9efec512b4f f81c9d4ab1814317b0b7b03b46e17841 X 7be2764603d34f5a826dd119fe655729--f81c9d4ab1814317b0b7b03b46e17841 f81c9d4ab1814317b0b7b03b46e17841--d3f4ad8c887d4e1c8f7cb9efec512b4f

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_c317f650d1014e6a80e1578a5b652f43 subblock cluster_b6f5caadba8945479146acd38edb1221 subblock 4124d515da744a39a13041c85ef10ddd 0 74afa942d1ad4f7daf8aebb7ef3d968f X 4124d515da744a39a13041c85ef10ddd--74afa942d1ad4f7daf8aebb7ef3d968f 6955a6c32de14f1ba65053023d7a97b0 1 8d1f4043ac5b49fb8c8c5f47a0cca67a X 74afa942d1ad4f7daf8aebb7ef3d968f--8d1f4043ac5b49fb8c8c5f47a0cca67a 79a7eb3497b54d6d8e6a342a23d95dbd 8d1f4043ac5b49fb8c8c5f47a0cca67a--79a7eb3497b54d6d8e6a342a23d95dbd e01277fe42184e639eae55576861de91 94ef13f2cbc24b85831ce637f362e878 Y 6955a6c32de14f1ba65053023d7a97b0--94ef13f2cbc24b85831ce637f362e878 da4dda0dfcd342d0bce0d81a92e33ea3 2 88d036b28ee248c7aa6661562d432b1d Y 94ef13f2cbc24b85831ce637f362e878--88d036b28ee248c7aa6661562d432b1d 88d036b28ee248c7aa6661562d432b1d--e01277fe42184e639eae55576861de91 c8854eb3bf5d4fb7a85f43fe75bd9cfc c4fb40b468ff4d6a89dfb46041317fae da4dda0dfcd342d0bce0d81a92e33ea3--c4fb40b468ff4d6a89dfb46041317fae 801a341f415841a78c1447825449ced7 3 938a08c6236c4896ad3836a2e1d4dc78 c4fb40b468ff4d6a89dfb46041317fae--938a08c6236c4896ad3836a2e1d4dc78 938a08c6236c4896ad3836a2e1d4dc78--c8854eb3bf5d4fb7a85f43fe75bd9cfc 8595580477434c468e3e3e3fbe53c0e8 580798793de04e86812e5decf4acda94 801a341f415841a78c1447825449ced7--580798793de04e86812e5decf4acda94 91504d7f604f488eada08718ae0e877d 4 65e0aec9318c4e43967dc775d4677bd1 580798793de04e86812e5decf4acda94--65e0aec9318c4e43967dc775d4677bd1 65e0aec9318c4e43967dc775d4677bd1--8595580477434c468e3e3e3fbe53c0e8 7097b0388e8844d48bee3e8e70c28587 b9ffacb127d94d45956a25ec62e2279b X 91504d7f604f488eada08718ae0e877d--b9ffacb127d94d45956a25ec62e2279b b9ffacb127d94d45956a25ec62e2279b--580798793de04e86812e5decf4acda94 a43c70723f1e4ad3b9d3bdfa5b96461a X b9ffacb127d94d45956a25ec62e2279b--a43c70723f1e4ad3b9d3bdfa5b96461a a43c70723f1e4ad3b9d3bdfa5b96461a--65e0aec9318c4e43967dc775d4677bd1 a43c70723f1e4ad3b9d3bdfa5b96461a--7097b0388e8844d48bee3e8e70c28587

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, '01': 253, '00': 248, '10': 242})]
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': 32, '100': 25, '010': 22, '110': 21})]

For more details on QuantumModel, see here.