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 20d346c05bd248768e2af226cb3f9ec2 0 c7dbea98353c459990fe7f0d890198fe X 20d346c05bd248768e2af226cb3f9ec2--c7dbea98353c459990fe7f0d890198fe bc17223155a541d0ab068e83d9be6cd1 1 26b67d0810704a80b11ace544cb53016 c7dbea98353c459990fe7f0d890198fe--26b67d0810704a80b11ace544cb53016 e4c24e2976fd431da61e0bd03f49d1e9 b8f60607d022467da697ac7a7388e9dd Y bc17223155a541d0ab068e83d9be6cd1--b8f60607d022467da697ac7a7388e9dd b8f60607d022467da697ac7a7388e9dd--e4c24e2976fd431da61e0bd03f49d1e9

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 98397221d1a642a191e7b8c3db58549f 0 89c0a18f1070478a9138b80a86595533 RX(0.5) 98397221d1a642a191e7b8c3db58549f--89c0a18f1070478a9138b80a86595533 00402c1460d64da99e44ee8193a2d8d5 89c0a18f1070478a9138b80a86595533--00402c1460d64da99e44ee8193a2d8d5
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 c82c6e0db3ec482baaff88e8cb60131a 0 0f396be59c574fd88b6fb27debf0e047 c82c6e0db3ec482baaff88e8cb60131a--0f396be59c574fd88b6fb27debf0e047 b1510963512042738c511d9e01146e5e 1 69d60084687143b5a934d23aa9874241 0f396be59c574fd88b6fb27debf0e047--69d60084687143b5a934d23aa9874241 e4ba67bb26d446f08cea7ced75cf5e9b f1b43b6189fb44b89433deeeccb944bc X b1510963512042738c511d9e01146e5e--f1b43b6189fb44b89433deeeccb944bc f1b43b6189fb44b89433deeeccb944bc--0f396be59c574fd88b6fb27debf0e047 f1b43b6189fb44b89433deeeccb944bc--e4ba67bb26d446f08cea7ced75cf5e9b

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 1ca9699ecfd046ecbb5a0df28d8c462a 0 797162402be541b6a74e007a97e44a75 X 1ca9699ecfd046ecbb5a0df28d8c462a--797162402be541b6a74e007a97e44a75 c009058b647642249dad13f60bbae513 X 797162402be541b6a74e007a97e44a75--c009058b647642249dad13f60bbae513 0505943044a4444a8d0fedce938bc5d5 c009058b647642249dad13f60bbae513--0505943044a4444a8d0fedce938bc5d5
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 d77e6aacc08c4b67b33569af143e3426 0 7e0fcadab82a4776b5711b5cc41ff406 X d77e6aacc08c4b67b33569af143e3426--7e0fcadab82a4776b5711b5cc41ff406 87f45ff6bb254f31abe484aaabeb0076 1 c7f825a798e9482ba5f17f01544c257b 7e0fcadab82a4776b5711b5cc41ff406--c7f825a798e9482ba5f17f01544c257b fe6c3e2eddde4383b4dc46f7e27e0bff c7f825a798e9482ba5f17f01544c257b--fe6c3e2eddde4383b4dc46f7e27e0bff e0b03052b71349ca8782596fd0b555ad 0476c65690c145be9a20aee92559bba7 87f45ff6bb254f31abe484aaabeb0076--0476c65690c145be9a20aee92559bba7 3ac50fcc2dbb45848e0c7ff851325a87 X 0476c65690c145be9a20aee92559bba7--3ac50fcc2dbb45848e0c7ff851325a87 3ac50fcc2dbb45848e0c7ff851325a87--e0b03052b71349ca8782596fd0b555ad

  • 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 5226e1f061cd485d931356305e5a8455 0 59e1fc3f958d414295ff709473bb4f84 X 5226e1f061cd485d931356305e5a8455--59e1fc3f958d414295ff709473bb4f84 23d77099811340f08d768a7a9111c8cf 1 e0e53c7e67664c0b87203c83de548262 59e1fc3f958d414295ff709473bb4f84--e0e53c7e67664c0b87203c83de548262 e8f67bbfbc0943bcb86bc2495ba6dbb8 d460676fdbc743508203ed85b3678e98 X 23d77099811340f08d768a7a9111c8cf--d460676fdbc743508203ed85b3678e98 d460676fdbc743508203ed85b3678e98--e8f67bbfbc0943bcb86bc2495ba6dbb8

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_d859d98305414dda8bb3f4f72b7f6d6f subblock cluster_178c317730f840f8a7fc443ea640ba95 subblock e322b07e201e43cca7aefca59ad1f488 0 c170630cd9b948c5bc9d46b4a40bc202 X e322b07e201e43cca7aefca59ad1f488--c170630cd9b948c5bc9d46b4a40bc202 eede8b4ab4a2425383efc8715bd80dcd 1 9bae8c0857d44d6ab8f4b73aed459567 X c170630cd9b948c5bc9d46b4a40bc202--9bae8c0857d44d6ab8f4b73aed459567 30268aa3dcf14e5980253696b58c717f 9bae8c0857d44d6ab8f4b73aed459567--30268aa3dcf14e5980253696b58c717f d825bc54ca704a3ab15a8d91266c7577 ad741086e37644048f6e38a0a4aaf406 Y eede8b4ab4a2425383efc8715bd80dcd--ad741086e37644048f6e38a0a4aaf406 57e702753ae140989ae54b14619a5d88 2 9831928599ce47599d8d00f2ee399eb7 Y ad741086e37644048f6e38a0a4aaf406--9831928599ce47599d8d00f2ee399eb7 9831928599ce47599d8d00f2ee399eb7--d825bc54ca704a3ab15a8d91266c7577 1df3a2d1ac274bc58d246de0db15d1e0 c39454c5c0d44228b31c6e0853ccd5ea 57e702753ae140989ae54b14619a5d88--c39454c5c0d44228b31c6e0853ccd5ea 45fec5c245a444c4bff6b76c03913ebf 3 84f8a4748d1141a9aaeb0c8f8a30f6a7 c39454c5c0d44228b31c6e0853ccd5ea--84f8a4748d1141a9aaeb0c8f8a30f6a7 84f8a4748d1141a9aaeb0c8f8a30f6a7--1df3a2d1ac274bc58d246de0db15d1e0 ab02a0b776f54c4c9fd8512e8340cb92 05e8f6d7dd8f472db33c48a997d2568a 45fec5c245a444c4bff6b76c03913ebf--05e8f6d7dd8f472db33c48a997d2568a 3adcb148a4ed4ba69ed285b109b6e5be 4 0a7cab8b51714f448ca6323acce9388c 05e8f6d7dd8f472db33c48a997d2568a--0a7cab8b51714f448ca6323acce9388c 0a7cab8b51714f448ca6323acce9388c--ab02a0b776f54c4c9fd8512e8340cb92 49579329556e4357887b2f035435b127 a2985afa6ee146acb221af0eef266a3a X 3adcb148a4ed4ba69ed285b109b6e5be--a2985afa6ee146acb221af0eef266a3a a2985afa6ee146acb221af0eef266a3a--05e8f6d7dd8f472db33c48a997d2568a 2bd0b24fac0b49ae9c949d98e8fe59cf X a2985afa6ee146acb221af0eef266a3a--2bd0b24fac0b49ae9c949d98e8fe59cf 2bd0b24fac0b49ae9c949d98e8fe59cf--0a7cab8b51714f448ca6323acce9388c 2bd0b24fac0b49ae9c949d98e8fe59cf--49579329556e4357887b2f035435b127

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.