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 ddf296c55cf14fca88ebd1ba9aad3d92 0 eb9ab8ebb12d4550b19a6f06b454531c X ddf296c55cf14fca88ebd1ba9aad3d92--eb9ab8ebb12d4550b19a6f06b454531c 9d5900b4b0d24211bea9d8f112ee6b18 1 b00f0f86c98b4024bd1757e9bea2e365 eb9ab8ebb12d4550b19a6f06b454531c--b00f0f86c98b4024bd1757e9bea2e365 a9eb05ae5f934cdcbd35091be6387f63 ee46585badb544469c4e7eb81b704d9b Y 9d5900b4b0d24211bea9d8f112ee6b18--ee46585badb544469c4e7eb81b704d9b ee46585badb544469c4e7eb81b704d9b--a9eb05ae5f934cdcbd35091be6387f63

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 55feadabbf4649d78a01ea050c6a9349 0 2eed580dabf84cf88a8bba5af5b83db3 RX(0.5) 55feadabbf4649d78a01ea050c6a9349--2eed580dabf84cf88a8bba5af5b83db3 a4f9a1d64b75447ca2361c8636fbcacb 2eed580dabf84cf88a8bba5af5b83db3--a4f9a1d64b75447ca2361c8636fbcacb
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 2ed7d9adc75e4b0a9658efea15070bb2 0 c5f7bbcc11d748f79a142f74c50e8449 2ed7d9adc75e4b0a9658efea15070bb2--c5f7bbcc11d748f79a142f74c50e8449 a713ae1c60954cf093558e837d02f595 1 59191b28caad450ea53255ee564f2301 c5f7bbcc11d748f79a142f74c50e8449--59191b28caad450ea53255ee564f2301 8cb3443e9a3b4d4db7dacb401f461aae 70212fe1303a4cd087540669af28b977 X a713ae1c60954cf093558e837d02f595--70212fe1303a4cd087540669af28b977 70212fe1303a4cd087540669af28b977--c5f7bbcc11d748f79a142f74c50e8449 70212fe1303a4cd087540669af28b977--8cb3443e9a3b4d4db7dacb401f461aae

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 a1a4634b0dad49f0a5c41626911bcfd4 0 b02925473c9f4a34aba7d7ca2f3b6503 X a1a4634b0dad49f0a5c41626911bcfd4--b02925473c9f4a34aba7d7ca2f3b6503 b6a2739e63064d0895c219448acacf18 X b02925473c9f4a34aba7d7ca2f3b6503--b6a2739e63064d0895c219448acacf18 b1a3646883d6408fb5b899fa4c11899d b6a2739e63064d0895c219448acacf18--b1a3646883d6408fb5b899fa4c11899d
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 0868254c167a4688a3a14629d5c9b867 0 f3290feb94b444be9368f497d9c5643a X 0868254c167a4688a3a14629d5c9b867--f3290feb94b444be9368f497d9c5643a 04548315ac37481a93613c527d6d7b9e 1 cc175a817f1643dfb1b37ebb5870d602 f3290feb94b444be9368f497d9c5643a--cc175a817f1643dfb1b37ebb5870d602 00f9692b66554ae4a4c8f14105885633 cc175a817f1643dfb1b37ebb5870d602--00f9692b66554ae4a4c8f14105885633 03272a0fc2704f9a9fccbb8446c6403b 9b39026ae1ba40eab2743c5dc886f9bb 04548315ac37481a93613c527d6d7b9e--9b39026ae1ba40eab2743c5dc886f9bb eb21811473d048dcb5e3f87c3e039125 X 9b39026ae1ba40eab2743c5dc886f9bb--eb21811473d048dcb5e3f87c3e039125 eb21811473d048dcb5e3f87c3e039125--03272a0fc2704f9a9fccbb8446c6403b

  • 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 29c2005560494561afcf4ccb7607b492 0 c6c5d8d09a024dfc9a6756d65f117cb7 X 29c2005560494561afcf4ccb7607b492--c6c5d8d09a024dfc9a6756d65f117cb7 ee2f4df6ceb24fcabcc32e6efec1c08a 1 ea6027cafb984761ad3e59f67a5a03b8 c6c5d8d09a024dfc9a6756d65f117cb7--ea6027cafb984761ad3e59f67a5a03b8 234864402e57409ca6c3611cc00bbb62 8948d31ef6e745d28b0b732abd123245 X ee2f4df6ceb24fcabcc32e6efec1c08a--8948d31ef6e745d28b0b732abd123245 8948d31ef6e745d28b0b732abd123245--234864402e57409ca6c3611cc00bbb62

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_5a36c4bfd17e4007b7f6cba21d0eaf10 subblock cluster_0291a883aa2b419a974115c64b6763e0 subblock 3b34a6e6818743be9340bf25aeacd0b7 0 7cca348f5b49489099832aa20cf18a5b X 3b34a6e6818743be9340bf25aeacd0b7--7cca348f5b49489099832aa20cf18a5b eeea29fd34174d679d69bec4ac51ea61 1 f0e7e9e29947449187de4da0ed6edf5d X 7cca348f5b49489099832aa20cf18a5b--f0e7e9e29947449187de4da0ed6edf5d ff758855e776409abf9eee8a0d14136a f0e7e9e29947449187de4da0ed6edf5d--ff758855e776409abf9eee8a0d14136a 7fc713b054bd4aeebbfe019e53d5f185 1522a0767fe745339a79bcbece7f31a0 Y eeea29fd34174d679d69bec4ac51ea61--1522a0767fe745339a79bcbece7f31a0 8ae09c1f3b7b4352857e4e3e74453492 2 71b3f948b41e496cb8c0ace147446f0e Y 1522a0767fe745339a79bcbece7f31a0--71b3f948b41e496cb8c0ace147446f0e 71b3f948b41e496cb8c0ace147446f0e--7fc713b054bd4aeebbfe019e53d5f185 c20ad54d8e1d442a8cbdc65582fad90a 8d40d83f3923452eb2803ccfe68cb2c3 8ae09c1f3b7b4352857e4e3e74453492--8d40d83f3923452eb2803ccfe68cb2c3 ced12c67065b461ca6e1bff68d8e23d2 3 27fedd5420a94fbb85d141183a935b39 8d40d83f3923452eb2803ccfe68cb2c3--27fedd5420a94fbb85d141183a935b39 27fedd5420a94fbb85d141183a935b39--c20ad54d8e1d442a8cbdc65582fad90a b5689c01312c4b8bbef4261356e74a0c 67cc40b198de4f4a9d7c810c77f50683 ced12c67065b461ca6e1bff68d8e23d2--67cc40b198de4f4a9d7c810c77f50683 c6e5ea61d3a74970b33bd5bc42ef92b3 4 d17a320dad02473c9d591ef9cebb1b94 67cc40b198de4f4a9d7c810c77f50683--d17a320dad02473c9d591ef9cebb1b94 d17a320dad02473c9d591ef9cebb1b94--b5689c01312c4b8bbef4261356e74a0c 6a681cb97a574981b1a9a4099e089406 d5f52fcbb16c4bc1a4342de52241f883 X c6e5ea61d3a74970b33bd5bc42ef92b3--d5f52fcbb16c4bc1a4342de52241f883 d5f52fcbb16c4bc1a4342de52241f883--67cc40b198de4f4a9d7c810c77f50683 7a71ee2a8e0746c381d761dc6c871566 X d5f52fcbb16c4bc1a4342de52241f883--7a71ee2a8e0746c381d761dc6c871566 7a71ee2a8e0746c381d761dc6c871566--d17a320dad02473c9d591ef9cebb1b94 7a71ee2a8e0746c381d761dc6c871566--6a681cb97a574981b1a9a4099e089406

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.