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 01be931260634f0fb244d058701a7ddf 0 d9dbf8ea134c461dbb7554876268f540 X 01be931260634f0fb244d058701a7ddf--d9dbf8ea134c461dbb7554876268f540 df8776bde9c041d684a9859beeabfd87 1 18b53b35f6c049ff9dc71f616d70a116 d9dbf8ea134c461dbb7554876268f540--18b53b35f6c049ff9dc71f616d70a116 c37d14b96864460d8e7bc76d3ed8af50 6e9016b8085d4f9b9f0802e29e8b33ce Y df8776bde9c041d684a9859beeabfd87--6e9016b8085d4f9b9f0802e29e8b33ce 6e9016b8085d4f9b9f0802e29e8b33ce--c37d14b96864460d8e7bc76d3ed8af50

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 9122aa2bd7b44e91a8a394e2f6ad7c33 0 75e44d38da7a4f8e946bd948bcb0e319 RX(0.5) 9122aa2bd7b44e91a8a394e2f6ad7c33--75e44d38da7a4f8e946bd948bcb0e319 f3c32eb342224e5e83ae2f2e68b713ca 75e44d38da7a4f8e946bd948bcb0e319--f3c32eb342224e5e83ae2f2e68b713ca
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 cc6e442889c04f348fa2f56a0c9623bb 0 1d19e97845d24d20ad3ba742e35b4a18 cc6e442889c04f348fa2f56a0c9623bb--1d19e97845d24d20ad3ba742e35b4a18 12d4e4f4f36f455b9c2433a082838776 1 041cfa71c2094b2c9d8a1675f37254d5 1d19e97845d24d20ad3ba742e35b4a18--041cfa71c2094b2c9d8a1675f37254d5 0b8d696559c6477b99432e2809e9705a f92ed35be2604479a2dbb1acc8f1b110 X 12d4e4f4f36f455b9c2433a082838776--f92ed35be2604479a2dbb1acc8f1b110 f92ed35be2604479a2dbb1acc8f1b110--1d19e97845d24d20ad3ba742e35b4a18 f92ed35be2604479a2dbb1acc8f1b110--0b8d696559c6477b99432e2809e9705a

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 48ce18a2158f47d190968ad0aebc5bb0 0 94ffd6741acb4febbbb50ab4fdc2b5ca X 48ce18a2158f47d190968ad0aebc5bb0--94ffd6741acb4febbbb50ab4fdc2b5ca de50931162c04431aebe2b5fcdbed80e X 94ffd6741acb4febbbb50ab4fdc2b5ca--de50931162c04431aebe2b5fcdbed80e 600d3f5c0b94430789c0fdcacac00c63 de50931162c04431aebe2b5fcdbed80e--600d3f5c0b94430789c0fdcacac00c63
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 2a25359034a64b8f82d38a25d93671af 0 b0fd816b88474f218b0887fed0a15321 X 2a25359034a64b8f82d38a25d93671af--b0fd816b88474f218b0887fed0a15321 d7c72a203c9246c583467a1c5f52c1e5 1 7cff2d734938452bbdae6de3cd5fa811 b0fd816b88474f218b0887fed0a15321--7cff2d734938452bbdae6de3cd5fa811 902b1d2cef6c418db05a67d57a6648f1 7cff2d734938452bbdae6de3cd5fa811--902b1d2cef6c418db05a67d57a6648f1 5b723f8fddb9469db441e3fd8c435935 11b276ab8d5246c49fc54a066e1042c9 d7c72a203c9246c583467a1c5f52c1e5--11b276ab8d5246c49fc54a066e1042c9 de8a30c023144ed5af50b15509e8659a X 11b276ab8d5246c49fc54a066e1042c9--de8a30c023144ed5af50b15509e8659a de8a30c023144ed5af50b15509e8659a--5b723f8fddb9469db441e3fd8c435935

  • 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 1aa0b04ebf154c2caa9774f953c7b53e 0 13ed3f85080442839e1c0ba878c3ace3 X 1aa0b04ebf154c2caa9774f953c7b53e--13ed3f85080442839e1c0ba878c3ace3 9f09bce1afa94576898fae994c16b6e8 1 99e71385e7cf4416b46fbd43678ae5d6 13ed3f85080442839e1c0ba878c3ace3--99e71385e7cf4416b46fbd43678ae5d6 9c4494579b754ad5aa411e168a6a8afb 965e8fd6395d47a1bb1eb2b0f34aa1bc X 9f09bce1afa94576898fae994c16b6e8--965e8fd6395d47a1bb1eb2b0f34aa1bc 965e8fd6395d47a1bb1eb2b0f34aa1bc--9c4494579b754ad5aa411e168a6a8afb

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_0bd8791b17594e86b618550f2358ad12 subblock cluster_60bfcd6f4c874ac19834b42c6e1356f8 subblock e34b6f4da4fb437c8fb76403cd11dc80 0 75de5610306349cab70af2e0fc8762de X e34b6f4da4fb437c8fb76403cd11dc80--75de5610306349cab70af2e0fc8762de 5dcdff1ae1604c319abfd090630a3448 1 e1c7b73ab48a4c9c86b68cc3ddbe341a X 75de5610306349cab70af2e0fc8762de--e1c7b73ab48a4c9c86b68cc3ddbe341a dd6bddb9d5fa4bf08f1dd08317fbe135 e1c7b73ab48a4c9c86b68cc3ddbe341a--dd6bddb9d5fa4bf08f1dd08317fbe135 fe183bab34b547c1a66bc53d490ebfa4 2ae0d8edf7e94bffab4815ea59c2335a Y 5dcdff1ae1604c319abfd090630a3448--2ae0d8edf7e94bffab4815ea59c2335a 35cc78e67a444e71a4e1475776dde79f 2 08da807797964e118e7dc77c68f078c4 Y 2ae0d8edf7e94bffab4815ea59c2335a--08da807797964e118e7dc77c68f078c4 08da807797964e118e7dc77c68f078c4--fe183bab34b547c1a66bc53d490ebfa4 705dd8643c4a4a048807fe939a4da3ca 865184cfb70e446687f5a42495e907ee 35cc78e67a444e71a4e1475776dde79f--865184cfb70e446687f5a42495e907ee 5840c41833594c1eb65e31bf9d6f4112 3 32bfae3c69ea4caba25bfcb9c0008fed 865184cfb70e446687f5a42495e907ee--32bfae3c69ea4caba25bfcb9c0008fed 32bfae3c69ea4caba25bfcb9c0008fed--705dd8643c4a4a048807fe939a4da3ca f4ba600569e649edb384c20e78b91de4 601382034d7e4689b94af25abc165eb7 5840c41833594c1eb65e31bf9d6f4112--601382034d7e4689b94af25abc165eb7 9ca3c91d6382434f8a63389293a8ea4a 4 4d7846c402274836a5bfa268314b9e8d 601382034d7e4689b94af25abc165eb7--4d7846c402274836a5bfa268314b9e8d 4d7846c402274836a5bfa268314b9e8d--f4ba600569e649edb384c20e78b91de4 98b8f1193b4040c6a75a554d34e3dd6f e6732f0a4e264d57bae3e74dae2bbf61 X 9ca3c91d6382434f8a63389293a8ea4a--e6732f0a4e264d57bae3e74dae2bbf61 e6732f0a4e264d57bae3e74dae2bbf61--601382034d7e4689b94af25abc165eb7 72ce3d3770e54b56a56521f6154e11cf X e6732f0a4e264d57bae3e74dae2bbf61--72ce3d3770e54b56a56521f6154e11cf 72ce3d3770e54b56a56521f6154e11cf--4d7846c402274836a5bfa268314b9e8d 72ce3d3770e54b56a56521f6154e11cf--98b8f1193b4040c6a75a554d34e3dd6f

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, '10': 254, '00': 251, '01': 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({'110': 30, '100': 28, '010': 22, '000': 20})]

For more details on QuantumModel, see here.