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 926afc1f15924066a4eb3577276845a4 0 a2c480c972ec4c44bdfb1addf0024ebd X 926afc1f15924066a4eb3577276845a4--a2c480c972ec4c44bdfb1addf0024ebd 9c245414f25e4596871d363e33a16614 1 26ec69f11b56481294db22b48b468737 a2c480c972ec4c44bdfb1addf0024ebd--26ec69f11b56481294db22b48b468737 7011b372f085491f8db4221a149632c2 0dc0fe55d91b424d8d4519728f29436d Y 9c245414f25e4596871d363e33a16614--0dc0fe55d91b424d8d4519728f29436d 0dc0fe55d91b424d8d4519728f29436d--7011b372f085491f8db4221a149632c2

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 e7217fcd128c4b85a77c3fb4c7f673f8 0 f8e65a2d17dd4dbdbd624f70da1d1e1e RX(0.5) e7217fcd128c4b85a77c3fb4c7f673f8--f8e65a2d17dd4dbdbd624f70da1d1e1e aa217b040c8443dbb9baa1d13c2c2a3c f8e65a2d17dd4dbdbd624f70da1d1e1e--aa217b040c8443dbb9baa1d13c2c2a3c
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 0bd16f728b164487a399146b8faa27c0 0 40d76bd0f3e949509ef917d5fa0c3358 0bd16f728b164487a399146b8faa27c0--40d76bd0f3e949509ef917d5fa0c3358 50760741be114b2bbd3bafc9f17f54b0 1 9e376effcbbf458ebb2a4376fe2972b4 40d76bd0f3e949509ef917d5fa0c3358--9e376effcbbf458ebb2a4376fe2972b4 439362cbd22147e9af5e240889395207 fe37a546acd844719d82b7e41eb449c1 X 50760741be114b2bbd3bafc9f17f54b0--fe37a546acd844719d82b7e41eb449c1 fe37a546acd844719d82b7e41eb449c1--40d76bd0f3e949509ef917d5fa0c3358 fe37a546acd844719d82b7e41eb449c1--439362cbd22147e9af5e240889395207

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 3f1c89e3d9834776afecf4c842d9816e 0 c9a4c79439e945e4b7f6c21839efd46e X 3f1c89e3d9834776afecf4c842d9816e--c9a4c79439e945e4b7f6c21839efd46e 66bf5f9a4380440b9c8acf83d24e87ac X c9a4c79439e945e4b7f6c21839efd46e--66bf5f9a4380440b9c8acf83d24e87ac f72dd11c8a5142968dbddd2f1e9cb0e5 66bf5f9a4380440b9c8acf83d24e87ac--f72dd11c8a5142968dbddd2f1e9cb0e5
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 02ff494324ec4c91862b462d6f1a314c 0 999fea471cc34c5ea1dda227ab5f87be X 02ff494324ec4c91862b462d6f1a314c--999fea471cc34c5ea1dda227ab5f87be 1efdfaf9e7f1497888503215f4f1c839 1 45cd67f949354fb28255dbeaf6e42706 999fea471cc34c5ea1dda227ab5f87be--45cd67f949354fb28255dbeaf6e42706 f0436b1b1cf744a4914cd956f0e2c11f 45cd67f949354fb28255dbeaf6e42706--f0436b1b1cf744a4914cd956f0e2c11f d383be802abb4088bb15c4bd0b65359c 228874fcb855405a9bfb036ab910eed9 1efdfaf9e7f1497888503215f4f1c839--228874fcb855405a9bfb036ab910eed9 4944d8e9a612440bb295eb7d329307b6 X 228874fcb855405a9bfb036ab910eed9--4944d8e9a612440bb295eb7d329307b6 4944d8e9a612440bb295eb7d329307b6--d383be802abb4088bb15c4bd0b65359c

  • 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 e23f354aaffd4b79889221c110768714 0 47d47282a3134e12911488420041f2ee X e23f354aaffd4b79889221c110768714--47d47282a3134e12911488420041f2ee dec76b0d5e42490a887c99c8cc15f5b0 1 b50c682d19644cddb3e2424344c35774 47d47282a3134e12911488420041f2ee--b50c682d19644cddb3e2424344c35774 a8f736b8fe674bba91a941bbf0925dfe bb860a4500944376b7cdc990894423bc X dec76b0d5e42490a887c99c8cc15f5b0--bb860a4500944376b7cdc990894423bc bb860a4500944376b7cdc990894423bc--a8f736b8fe674bba91a941bbf0925dfe

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_d69173c244a14958917e758a8da44564 subblock cluster_30d644e7e1de4fceb0d43e6bd7a8354a subblock dc47f64b461a42f28c457da8ce0d862c 0 e6988a3f0f01493ea407321fe1650fb7 X dc47f64b461a42f28c457da8ce0d862c--e6988a3f0f01493ea407321fe1650fb7 4e8a80075e3b4efa8bc9e5196206943f 1 7579152657af4639bb461d0b35c46bb4 X e6988a3f0f01493ea407321fe1650fb7--7579152657af4639bb461d0b35c46bb4 72f22d0882fd44b8935da921ce25ad7d 7579152657af4639bb461d0b35c46bb4--72f22d0882fd44b8935da921ce25ad7d 46f2abc33bf24fc2a829404e9c3ca049 c7acb673c16242b0a5d9568887d686aa Y 4e8a80075e3b4efa8bc9e5196206943f--c7acb673c16242b0a5d9568887d686aa 95b469a3b0874a799ad493708cd4d976 2 c4b22dc7e7a84c36bf6c19b422d8e1a4 Y c7acb673c16242b0a5d9568887d686aa--c4b22dc7e7a84c36bf6c19b422d8e1a4 c4b22dc7e7a84c36bf6c19b422d8e1a4--46f2abc33bf24fc2a829404e9c3ca049 dc490ba365454094bd673634d13f7fac 5877832240d048379f739c5577740f83 95b469a3b0874a799ad493708cd4d976--5877832240d048379f739c5577740f83 9c93f55180674581add82b5b134a0c91 3 0d8e77bd1ceb4fc880101a6757257e21 5877832240d048379f739c5577740f83--0d8e77bd1ceb4fc880101a6757257e21 0d8e77bd1ceb4fc880101a6757257e21--dc490ba365454094bd673634d13f7fac 06a0ce00268443a182f7c95a6616e4e2 e403700281824a61bbced8fd8ee643e0 9c93f55180674581add82b5b134a0c91--e403700281824a61bbced8fd8ee643e0 8d4a2efab4da47e180ea807fb40d215a 4 7d3cb472a8814a85bb17ce88d0f3dfa8 e403700281824a61bbced8fd8ee643e0--7d3cb472a8814a85bb17ce88d0f3dfa8 7d3cb472a8814a85bb17ce88d0f3dfa8--06a0ce00268443a182f7c95a6616e4e2 1c5c98c618ee4cca9cd9f090f97f7a70 84b4a3d5fce341ea947f287b61492016 X 8d4a2efab4da47e180ea807fb40d215a--84b4a3d5fce341ea947f287b61492016 84b4a3d5fce341ea947f287b61492016--e403700281824a61bbced8fd8ee643e0 bc69e7f9b0d14a12851d4a383bb45b79 X 84b4a3d5fce341ea947f287b61492016--bc69e7f9b0d14a12851d4a383bb45b79 bc69e7f9b0d14a12851d4a383bb45b79--7d3cb472a8814a85bb17ce88d0f3dfa8 bc69e7f9b0d14a12851d4a383bb45b79--1c5c98c618ee4cca9cd9f090f97f7a70

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': 260, '10': 253, '00': 250, '01': 237})]
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({'110': 31, '100': 27, '000': 24, '010': 18})]

For more details on QuantumModel, see here.