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 e6ddae30bbf3417e8534b7b4da3ff0c8 0 d95aeb2a31f24edd9ec9920b22932dc5 X e6ddae30bbf3417e8534b7b4da3ff0c8--d95aeb2a31f24edd9ec9920b22932dc5 aea3a476549a471482d97c6bcaa2259c 1 e97fff15999345e3852d318d2fafdacb d95aeb2a31f24edd9ec9920b22932dc5--e97fff15999345e3852d318d2fafdacb 319a40c828a4467f8eec7f426c1265fe 0a8e12d4d69f4696b6b5c229207ad2e1 Y aea3a476549a471482d97c6bcaa2259c--0a8e12d4d69f4696b6b5c229207ad2e1 0a8e12d4d69f4696b6b5c229207ad2e1--319a40c828a4467f8eec7f426c1265fe

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 18e6ad7a8e3b42b8ad437357e0632d71 0 b8a7988be527471db9919040edbb7127 RX(0.5) 18e6ad7a8e3b42b8ad437357e0632d71--b8a7988be527471db9919040edbb7127 f571d2671d98452d8c4969b11ba19e1f b8a7988be527471db9919040edbb7127--f571d2671d98452d8c4969b11ba19e1f
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 035e41fe86e94690aac6e97f629dd23a 0 0af6930c897845c884a61ed7bb6011c1 035e41fe86e94690aac6e97f629dd23a--0af6930c897845c884a61ed7bb6011c1 a1be6c3a0b7149f7b8e5cb505d3b1c10 1 181325c25eb844cb8d6859ed0bfea4e6 0af6930c897845c884a61ed7bb6011c1--181325c25eb844cb8d6859ed0bfea4e6 9c2c6d0f352645b08ce357d87c85d82a fc5e5442d1024e06a434af5d434bd3f0 X a1be6c3a0b7149f7b8e5cb505d3b1c10--fc5e5442d1024e06a434af5d434bd3f0 fc5e5442d1024e06a434af5d434bd3f0--0af6930c897845c884a61ed7bb6011c1 fc5e5442d1024e06a434af5d434bd3f0--9c2c6d0f352645b08ce357d87c85d82a

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 994885b3920d44dca92667d215950631 0 4b5cc474a6bb4df9aa4525eca798a363 X 994885b3920d44dca92667d215950631--4b5cc474a6bb4df9aa4525eca798a363 3cf1d3ca9889416dbba48a484fced502 X 4b5cc474a6bb4df9aa4525eca798a363--3cf1d3ca9889416dbba48a484fced502 6cc9aa0d4d744e808c92bceb442d08e5 3cf1d3ca9889416dbba48a484fced502--6cc9aa0d4d744e808c92bceb442d08e5
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 8be62adc4a224a92809a3c44e47e8136 0 ffa726eb460c4f4da9be465fc1b12ea2 X 8be62adc4a224a92809a3c44e47e8136--ffa726eb460c4f4da9be465fc1b12ea2 45cda5aa62da4d63a6246d912161e65a 1 a77cf0ffa88c41798e686dac524c77c5 ffa726eb460c4f4da9be465fc1b12ea2--a77cf0ffa88c41798e686dac524c77c5 9c662be1b7f24c06aeff5622accdbf57 a77cf0ffa88c41798e686dac524c77c5--9c662be1b7f24c06aeff5622accdbf57 1be8c353ca4045d8a3f681963e3303e8 d982eed8c7aa4f399e3821a9ba29d0f9 45cda5aa62da4d63a6246d912161e65a--d982eed8c7aa4f399e3821a9ba29d0f9 f257679c9e38499f96dd7a3751172097 X d982eed8c7aa4f399e3821a9ba29d0f9--f257679c9e38499f96dd7a3751172097 f257679c9e38499f96dd7a3751172097--1be8c353ca4045d8a3f681963e3303e8

  • 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 754909d9ca0a4ef88620264ceeddb0e7 0 6e78fc9e19164d6ea63263697cc32492 X 754909d9ca0a4ef88620264ceeddb0e7--6e78fc9e19164d6ea63263697cc32492 c222196cf28b48a79f117ccae94a55ef 1 8d68845f93424e088359df0980a4c20a 6e78fc9e19164d6ea63263697cc32492--8d68845f93424e088359df0980a4c20a 5bcbe96571e64cbd9af942affc25dbb2 620a7f30e5884c6c970d5d469e756dc0 X c222196cf28b48a79f117ccae94a55ef--620a7f30e5884c6c970d5d469e756dc0 620a7f30e5884c6c970d5d469e756dc0--5bcbe96571e64cbd9af942affc25dbb2

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_d1b9fcec1b534c95a4579cef44b2cb08 subblock cluster_2c7d5247679d408bbf79c22570a6ccfa subblock ca8e1fe35e964a33a4849a494ba4cfbd 0 46f4f4a787314101a3e06627f0090341 X ca8e1fe35e964a33a4849a494ba4cfbd--46f4f4a787314101a3e06627f0090341 72a84628f7f8419dade743c9f3c49923 1 07385f2024e249e5baa96842534b76ea X 46f4f4a787314101a3e06627f0090341--07385f2024e249e5baa96842534b76ea a5389aba5d7b41ab9a8842e55822fceb 07385f2024e249e5baa96842534b76ea--a5389aba5d7b41ab9a8842e55822fceb b47bdd1920be4cdfb235ec8adedbcd75 6e1796fc99c64acebcf1662685fdf7aa Y 72a84628f7f8419dade743c9f3c49923--6e1796fc99c64acebcf1662685fdf7aa f9c5a8472e4e40f2a636b8e64a20ffed 2 1360e205970841d8bd174b4ae6e9865a Y 6e1796fc99c64acebcf1662685fdf7aa--1360e205970841d8bd174b4ae6e9865a 1360e205970841d8bd174b4ae6e9865a--b47bdd1920be4cdfb235ec8adedbcd75 357344c5bc364915b6b5022294d2aa58 c2fbbe5baab045c8bb1460ef71de228c f9c5a8472e4e40f2a636b8e64a20ffed--c2fbbe5baab045c8bb1460ef71de228c 19b4b50aab264412a9c9a365fff0602d 3 dde429a1a7814c799474e1445a48f827 c2fbbe5baab045c8bb1460ef71de228c--dde429a1a7814c799474e1445a48f827 dde429a1a7814c799474e1445a48f827--357344c5bc364915b6b5022294d2aa58 be8650714a6348dba24282f3007b348c 5aa7f05b87694783856be6ffa8989ecc 19b4b50aab264412a9c9a365fff0602d--5aa7f05b87694783856be6ffa8989ecc 29b418ba89e446929cd96f80cc1a8ab4 4 6fc525c59e14483396aefaa7177deaf8 5aa7f05b87694783856be6ffa8989ecc--6fc525c59e14483396aefaa7177deaf8 6fc525c59e14483396aefaa7177deaf8--be8650714a6348dba24282f3007b348c 7dec0f92e98c4330bec73ff5563a9bc4 8efc31e98a794184bda3cc8e08b08c19 X 29b418ba89e446929cd96f80cc1a8ab4--8efc31e98a794184bda3cc8e08b08c19 8efc31e98a794184bda3cc8e08b08c19--5aa7f05b87694783856be6ffa8989ecc 2c3c7143cb2b4c41ada4d030c2bee427 X 8efc31e98a794184bda3cc8e08b08c19--2c3c7143cb2b4c41ada4d030c2bee427 2c3c7143cb2b4c41ada4d030c2bee427--6fc525c59e14483396aefaa7177deaf8 2c3c7143cb2b4c41ada4d030c2bee427--7dec0f92e98c4330bec73ff5563a9bc4

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': 262, '00': 249, '10': 248, '01': 241})]
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({'100': 30, '010': 27, '000': 24, '110': 19})]

For more details on QuantumModel, see here.