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 a8dbf12fb833466eb6e0a6deedcafc2e 0 449962ec1a5e43cfa415d208d02ee506 X a8dbf12fb833466eb6e0a6deedcafc2e--449962ec1a5e43cfa415d208d02ee506 02afda36a6424262bf9b7f61bd501da5 1 13dd76bd16a046ad9b83f1bf5ec76c1f 449962ec1a5e43cfa415d208d02ee506--13dd76bd16a046ad9b83f1bf5ec76c1f cc9511806e544bb8a06c1f488e65a8fe a2a030487e1449fa879e9598b27186ce Y 02afda36a6424262bf9b7f61bd501da5--a2a030487e1449fa879e9598b27186ce a2a030487e1449fa879e9598b27186ce--cc9511806e544bb8a06c1f488e65a8fe

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 1f729016a12e49d0a1f8b7e5b4fcb443 0 821feaa4b1004a4b8c03270873cd4453 RX(0.5) 1f729016a12e49d0a1f8b7e5b4fcb443--821feaa4b1004a4b8c03270873cd4453 819383e6eb2d41439c986569eae90ad1 821feaa4b1004a4b8c03270873cd4453--819383e6eb2d41439c986569eae90ad1
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 71eddd39ade748a5a216e5ec67324f85 0 40fca2664a754b32b7901bd396640ce5 71eddd39ade748a5a216e5ec67324f85--40fca2664a754b32b7901bd396640ce5 9410954774c3461597fb8972a121e4b0 1 34411ab375964ab19f873c783596f178 40fca2664a754b32b7901bd396640ce5--34411ab375964ab19f873c783596f178 5e2e8bd0114044cc92b2f78b4ddd693d 5bb42f38cfb64b829816b14a1e1d2760 X 9410954774c3461597fb8972a121e4b0--5bb42f38cfb64b829816b14a1e1d2760 5bb42f38cfb64b829816b14a1e1d2760--40fca2664a754b32b7901bd396640ce5 5bb42f38cfb64b829816b14a1e1d2760--5e2e8bd0114044cc92b2f78b4ddd693d

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 86f275b4122044a39c2f7081c5879c42 0 44d5c8dff0154929ae2deb9fb32d30fb X 86f275b4122044a39c2f7081c5879c42--44d5c8dff0154929ae2deb9fb32d30fb 0593c383e5a245d986ae5f84c05bf4ea X 44d5c8dff0154929ae2deb9fb32d30fb--0593c383e5a245d986ae5f84c05bf4ea 7eff0286f97449abb6d6698eadc09243 0593c383e5a245d986ae5f84c05bf4ea--7eff0286f97449abb6d6698eadc09243
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 bdac6dd46fb14efbb9ebc3dd9121adaa 0 a5be5cdfb5df4d0cb064b26e2d3ec4d3 X bdac6dd46fb14efbb9ebc3dd9121adaa--a5be5cdfb5df4d0cb064b26e2d3ec4d3 88451d995c174916a5531f4d3fe68b3f 1 3b3af9cdf44e4c0c92f4a9b682fcfd9b a5be5cdfb5df4d0cb064b26e2d3ec4d3--3b3af9cdf44e4c0c92f4a9b682fcfd9b 11adad48135b480ca8939cc65c7997b0 3b3af9cdf44e4c0c92f4a9b682fcfd9b--11adad48135b480ca8939cc65c7997b0 00e4b3a2f6ee4ecb8158122f8413fa71 21aa5a361cc04cf08f8f774776a4197c 88451d995c174916a5531f4d3fe68b3f--21aa5a361cc04cf08f8f774776a4197c 6f2376e52b6d4112bdef73f19c46400c X 21aa5a361cc04cf08f8f774776a4197c--6f2376e52b6d4112bdef73f19c46400c 6f2376e52b6d4112bdef73f19c46400c--00e4b3a2f6ee4ecb8158122f8413fa71

  • 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 1d42a0df3ea947fa95019c7ed46a2234 0 e9e658a3832644f7afb33dccc944331d X 1d42a0df3ea947fa95019c7ed46a2234--e9e658a3832644f7afb33dccc944331d 88a392812f1c4f24b2ca339465c6acc8 1 8a4bbfb4ca684bf090c429d21c37e070 e9e658a3832644f7afb33dccc944331d--8a4bbfb4ca684bf090c429d21c37e070 c00e2da450db44628b5c64ed518fbdd4 49f8544fd80d467e959e6c3ab2c71265 X 88a392812f1c4f24b2ca339465c6acc8--49f8544fd80d467e959e6c3ab2c71265 49f8544fd80d467e959e6c3ab2c71265--c00e2da450db44628b5c64ed518fbdd4

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_96f2fc774f75464faace0440def9cfae subblock cluster_a1866e57d3354ba6bee8d59b6ba494e7 subblock 0b6ab2b9ae90435086de29783b152dc2 0 621c9236632b49f08645d5c3c5db1a4b X 0b6ab2b9ae90435086de29783b152dc2--621c9236632b49f08645d5c3c5db1a4b 594f54eeaf2c4514a546e1ec376e294f 1 0f7f9dbfcb2e4db4bcf6299e8f799625 X 621c9236632b49f08645d5c3c5db1a4b--0f7f9dbfcb2e4db4bcf6299e8f799625 7771d9b44ba44513b15b4d039364a67f 0f7f9dbfcb2e4db4bcf6299e8f799625--7771d9b44ba44513b15b4d039364a67f c41f8341b5904f9e8dddc74c225f39df 34a7c84e0ffc4085a8ec57eaff04ea0c Y 594f54eeaf2c4514a546e1ec376e294f--34a7c84e0ffc4085a8ec57eaff04ea0c e680ebf4ec3647eeb57342320b4c8308 2 4377861de89c4cfebd2d4ca959a44f5b Y 34a7c84e0ffc4085a8ec57eaff04ea0c--4377861de89c4cfebd2d4ca959a44f5b 4377861de89c4cfebd2d4ca959a44f5b--c41f8341b5904f9e8dddc74c225f39df 111f3827c4304f95ac5297ebf0c92f38 7025191d6e774b10b8f52c8c1761f1a3 e680ebf4ec3647eeb57342320b4c8308--7025191d6e774b10b8f52c8c1761f1a3 d5487813ec6b4d46b3619bb2c6cc4239 3 86f6b2050b2e40e4a1aadcab9a47d094 7025191d6e774b10b8f52c8c1761f1a3--86f6b2050b2e40e4a1aadcab9a47d094 86f6b2050b2e40e4a1aadcab9a47d094--111f3827c4304f95ac5297ebf0c92f38 81823328854947ac8962b8bf955fc341 1a2439bd5bef4594a11328af13dcd86a d5487813ec6b4d46b3619bb2c6cc4239--1a2439bd5bef4594a11328af13dcd86a 3fcfb9d7d0714a00a1c815c087acde35 4 43245b0f6c944c76b82f49832e751b2b 1a2439bd5bef4594a11328af13dcd86a--43245b0f6c944c76b82f49832e751b2b 43245b0f6c944c76b82f49832e751b2b--81823328854947ac8962b8bf955fc341 f31a0760f6b04a1085fed68cfab33a42 afc30fc64dd742cb92b1f3a996a143a1 X 3fcfb9d7d0714a00a1c815c087acde35--afc30fc64dd742cb92b1f3a996a143a1 afc30fc64dd742cb92b1f3a996a143a1--1a2439bd5bef4594a11328af13dcd86a 706505b6ad3c40209225650d1374c215 X afc30fc64dd742cb92b1f3a996a143a1--706505b6ad3c40209225650d1374c215 706505b6ad3c40209225650d1374c215--43245b0f6c944c76b82f49832e751b2b 706505b6ad3c40209225650d1374c215--f31a0760f6b04a1085fed68cfab33a42

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.