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 1986b960686b4cfd9910dcb1d874f007 0 783f8d2def934a3fbd09d31ae59df11a X 1986b960686b4cfd9910dcb1d874f007--783f8d2def934a3fbd09d31ae59df11a 0121d34d589d420cbaf2976e7c6187f6 1 d18bbdea9a3a4508a1889a22de761406 783f8d2def934a3fbd09d31ae59df11a--d18bbdea9a3a4508a1889a22de761406 7bd1b0c275e3490aa08a5a85a59279a0 6a4038ada38d4ad48df5be109d143592 Y 0121d34d589d420cbaf2976e7c6187f6--6a4038ada38d4ad48df5be109d143592 6a4038ada38d4ad48df5be109d143592--7bd1b0c275e3490aa08a5a85a59279a0

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 310bd1b9b1504efba56de552ef6d17ba 0 4a5d3f49aec0423dba6b82e1f4bd836a RX(0.5) 310bd1b9b1504efba56de552ef6d17ba--4a5d3f49aec0423dba6b82e1f4bd836a 7e6928609f7941bd9f3bf107f733fea7 4a5d3f49aec0423dba6b82e1f4bd836a--7e6928609f7941bd9f3bf107f733fea7
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 d667f247a7f1426ea62c4d6e25621d63 0 b14941fade5e484fb73d790e31ed2485 d667f247a7f1426ea62c4d6e25621d63--b14941fade5e484fb73d790e31ed2485 d8b40347fd7b492a89997c9fafe7b5a2 1 c73b4e9fb4fd4d45bebd227fdf316b4a b14941fade5e484fb73d790e31ed2485--c73b4e9fb4fd4d45bebd227fdf316b4a ee093483b3d042b2b949da91ae9122cb 3b7a0210c60149bc87d9070cf2021a56 X d8b40347fd7b492a89997c9fafe7b5a2--3b7a0210c60149bc87d9070cf2021a56 3b7a0210c60149bc87d9070cf2021a56--b14941fade5e484fb73d790e31ed2485 3b7a0210c60149bc87d9070cf2021a56--ee093483b3d042b2b949da91ae9122cb

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 e985a65067c34009b4d758fc942c51fe 0 cd9438612f5f4d2ebb7db5cd30b8ecb6 X e985a65067c34009b4d758fc942c51fe--cd9438612f5f4d2ebb7db5cd30b8ecb6 e765095a6ebc4eb6ab777a4c7c595121 X cd9438612f5f4d2ebb7db5cd30b8ecb6--e765095a6ebc4eb6ab777a4c7c595121 e671b46f86694648a67893de38d3e655 e765095a6ebc4eb6ab777a4c7c595121--e671b46f86694648a67893de38d3e655
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 7927cdf84d2b419a9c3b66b60502e730 0 e13ec9654771467fb4f9f1191d14827b X 7927cdf84d2b419a9c3b66b60502e730--e13ec9654771467fb4f9f1191d14827b 42833b90416a41719ed5e75652bb9e88 1 725a085746e342d2ae3c3df7f1401d80 e13ec9654771467fb4f9f1191d14827b--725a085746e342d2ae3c3df7f1401d80 940b98f494ce4cc5ac01027776bb236f 725a085746e342d2ae3c3df7f1401d80--940b98f494ce4cc5ac01027776bb236f 0902be9f916c4f79830e7ce5582338e8 2488190755224537863636e1eca8f23a 42833b90416a41719ed5e75652bb9e88--2488190755224537863636e1eca8f23a 1aeab724e84e4caf939b032857b62e84 X 2488190755224537863636e1eca8f23a--1aeab724e84e4caf939b032857b62e84 1aeab724e84e4caf939b032857b62e84--0902be9f916c4f79830e7ce5582338e8

  • 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 9e0c5387f4e54fd481164b51b1d49b42 0 ba146d79ff26466e8c868a1ca0abeb71 X 9e0c5387f4e54fd481164b51b1d49b42--ba146d79ff26466e8c868a1ca0abeb71 6f77d4896b8c4ba9aaacb396e0562bb9 1 d3dc0207ddd1444d834c0a17a582076b ba146d79ff26466e8c868a1ca0abeb71--d3dc0207ddd1444d834c0a17a582076b 2fa76e78ce8c45c99b5330df867c6d84 284c887405ca4496a7d5e85c9f9dfe3b X 6f77d4896b8c4ba9aaacb396e0562bb9--284c887405ca4496a7d5e85c9f9dfe3b 284c887405ca4496a7d5e85c9f9dfe3b--2fa76e78ce8c45c99b5330df867c6d84

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_471de753b2d3487f984749ba8e9d120b subblock cluster_82a407046e044fd98d4f7b542f877007 subblock 0bac61884c3f4f56b32a6ac1a8c54ceb 0 80fcbb6ec1594891b7e2ac690e9e1a94 X 0bac61884c3f4f56b32a6ac1a8c54ceb--80fcbb6ec1594891b7e2ac690e9e1a94 63f22412a1354b7e96902db94cd46af7 1 aab746403c844187b4587e0995ac03a1 X 80fcbb6ec1594891b7e2ac690e9e1a94--aab746403c844187b4587e0995ac03a1 f8d8791d248749cf9925f81493e92e70 aab746403c844187b4587e0995ac03a1--f8d8791d248749cf9925f81493e92e70 f35a2cbda1ab40f697173e9b935477b6 91963cf642bd49828ae3765fc516620f Y 63f22412a1354b7e96902db94cd46af7--91963cf642bd49828ae3765fc516620f 967d38af614e4cc9ba88d491543237a3 2 1087ab61ffe2433b822500eac1e52ebc Y 91963cf642bd49828ae3765fc516620f--1087ab61ffe2433b822500eac1e52ebc 1087ab61ffe2433b822500eac1e52ebc--f35a2cbda1ab40f697173e9b935477b6 fc0587d99e8544d5b7234a8bd83a5638 9fd1f5ac44e54005b8099caf75f44004 967d38af614e4cc9ba88d491543237a3--9fd1f5ac44e54005b8099caf75f44004 0ab7a3e448484743be28891eb087e6c1 3 d608a221900548908fa43ad375bdf3fd 9fd1f5ac44e54005b8099caf75f44004--d608a221900548908fa43ad375bdf3fd d608a221900548908fa43ad375bdf3fd--fc0587d99e8544d5b7234a8bd83a5638 f6ca2f2719ec4998b256417601d8fcbc 501112ba41e14f0c988e100b38046a30 0ab7a3e448484743be28891eb087e6c1--501112ba41e14f0c988e100b38046a30 30b69f5da7a74922b5c95b9d845bd93d 4 79745d86f2974aa98d359a64fc4218d9 501112ba41e14f0c988e100b38046a30--79745d86f2974aa98d359a64fc4218d9 79745d86f2974aa98d359a64fc4218d9--f6ca2f2719ec4998b256417601d8fcbc d3c09e174fd24fcfabbc64c978153f6c 18765e7db24e4a748526977abc6f1292 X 30b69f5da7a74922b5c95b9d845bd93d--18765e7db24e4a748526977abc6f1292 18765e7db24e4a748526977abc6f1292--501112ba41e14f0c988e100b38046a30 94d362d5a380475eb581762a2e206d33 X 18765e7db24e4a748526977abc6f1292--94d362d5a380475eb581762a2e206d33 94d362d5a380475eb581762a2e206d33--79745d86f2974aa98d359a64fc4218d9 94d362d5a380475eb581762a2e206d33--d3c09e174fd24fcfabbc64c978153f6c

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': 267, '00': 253, '10': 242, '11': 238})]
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': 28, '010': 25, '110': 25, '100': 22})]

For more details on QuantumModel, see here.