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 27c4b8e81d3545bb8aa3d461c51cc45c 0 6c69e6303c3d44e897eb8caf51f55824 X 27c4b8e81d3545bb8aa3d461c51cc45c--6c69e6303c3d44e897eb8caf51f55824 2a7d2b196b4643a7917989233e5ba74a 1 37f40327a5e04be492d6e1eca498b01a 6c69e6303c3d44e897eb8caf51f55824--37f40327a5e04be492d6e1eca498b01a 69783fb32ada40f3a0c3de26b366ab16 febf73fbb7704085a02b1eac72cb2ecf Y 2a7d2b196b4643a7917989233e5ba74a--febf73fbb7704085a02b1eac72cb2ecf febf73fbb7704085a02b1eac72cb2ecf--69783fb32ada40f3a0c3de26b366ab16

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 b1ec0c5ac2434be986243c20e6122422 0 8f6aef5f8e3b4a05b013c4e8cf42b1c6 RX(0.5) b1ec0c5ac2434be986243c20e6122422--8f6aef5f8e3b4a05b013c4e8cf42b1c6 f028917b67c2409b81da3c7cf7469a9c 8f6aef5f8e3b4a05b013c4e8cf42b1c6--f028917b67c2409b81da3c7cf7469a9c
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 9597fd4c45fc4efea46d965eafd4279b 0 2f9614c538ac4d3eaf4bcd3332ba07cb 9597fd4c45fc4efea46d965eafd4279b--2f9614c538ac4d3eaf4bcd3332ba07cb 3d16b8144ea9421f85658cadb863a85f 1 96a20306e43f4349979aa5aea16e7cb2 2f9614c538ac4d3eaf4bcd3332ba07cb--96a20306e43f4349979aa5aea16e7cb2 64e1f59e156644afa864eea0eab3aff8 aa0aaebcf4e24f0bba202c7210daf802 X 3d16b8144ea9421f85658cadb863a85f--aa0aaebcf4e24f0bba202c7210daf802 aa0aaebcf4e24f0bba202c7210daf802--2f9614c538ac4d3eaf4bcd3332ba07cb aa0aaebcf4e24f0bba202c7210daf802--64e1f59e156644afa864eea0eab3aff8

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 bac22a57564942a5839f0f25037bed24 0 a0e8b0f0adcf4ae582403d874cd0a741 X bac22a57564942a5839f0f25037bed24--a0e8b0f0adcf4ae582403d874cd0a741 86fcb7f858654559ab263d118434650a X a0e8b0f0adcf4ae582403d874cd0a741--86fcb7f858654559ab263d118434650a d9e8e79d74ec4fc6b51479c853dfc4b8 86fcb7f858654559ab263d118434650a--d9e8e79d74ec4fc6b51479c853dfc4b8
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 6a8bf7fd1df648bea9da30d9f5cf58a9 0 eef88cf2ea25453db346521fc24610c5 X 6a8bf7fd1df648bea9da30d9f5cf58a9--eef88cf2ea25453db346521fc24610c5 60133b8de1ef4890a9e9b2320a64f7ae 1 beb302e9ed8d460e95d9018bd2fc1ca3 eef88cf2ea25453db346521fc24610c5--beb302e9ed8d460e95d9018bd2fc1ca3 e7103985f577417494eb68b23b03998a beb302e9ed8d460e95d9018bd2fc1ca3--e7103985f577417494eb68b23b03998a c4b0da5bd591455a88b509ecbae4d10e 524da1efd7a14db59281b3c1e016935b 60133b8de1ef4890a9e9b2320a64f7ae--524da1efd7a14db59281b3c1e016935b 6f4fee5902e24e9da1a5042205c07459 X 524da1efd7a14db59281b3c1e016935b--6f4fee5902e24e9da1a5042205c07459 6f4fee5902e24e9da1a5042205c07459--c4b0da5bd591455a88b509ecbae4d10e

  • 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 26d83d638d724c1a980b9b38f330be7a 0 494bfb168df8479096104b65b32518f1 X 26d83d638d724c1a980b9b38f330be7a--494bfb168df8479096104b65b32518f1 2d82117415e545beba650419386c3a84 1 5c1a71d60a8b46c08a05a55b8fe54d44 494bfb168df8479096104b65b32518f1--5c1a71d60a8b46c08a05a55b8fe54d44 bceb0a102dda401990f7833f0e680e96 39bed7f29e4143b4a742273f9d504114 X 2d82117415e545beba650419386c3a84--39bed7f29e4143b4a742273f9d504114 39bed7f29e4143b4a742273f9d504114--bceb0a102dda401990f7833f0e680e96

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_5b6c38bd6de24af981d2a99735f4fdc6 subblock cluster_89be04ea15744cd3bc23c2b89b46e05b subblock 3ea76a2bfba7434bbcb8785257bdd321 0 a86f45ca5bd244549fc772c155024ab2 X 3ea76a2bfba7434bbcb8785257bdd321--a86f45ca5bd244549fc772c155024ab2 83b87a79234845a7a3534e30d7749180 1 f3918dc416f045d195a3db7ea50ca60d X a86f45ca5bd244549fc772c155024ab2--f3918dc416f045d195a3db7ea50ca60d 6d1b24bd5212410c9747ccf9a3d222b0 f3918dc416f045d195a3db7ea50ca60d--6d1b24bd5212410c9747ccf9a3d222b0 b8802f4ce3354125a32d49999c68fc94 812dd5518fc548fa865a6fa37cbc49bf Y 83b87a79234845a7a3534e30d7749180--812dd5518fc548fa865a6fa37cbc49bf d0a710ed5c0d42259dd48f3b1afe0437 2 58fb09fb3c624ac6a111db37e707661f Y 812dd5518fc548fa865a6fa37cbc49bf--58fb09fb3c624ac6a111db37e707661f 58fb09fb3c624ac6a111db37e707661f--b8802f4ce3354125a32d49999c68fc94 3d4e2f3136a54c32a2d0c2720c5852dd 30ac548164fe43a98a43c11966d58d3d d0a710ed5c0d42259dd48f3b1afe0437--30ac548164fe43a98a43c11966d58d3d 1fad7b5e95e64990a35dfa22bb11e5bd 3 85d67e2e6da64c2998010a5c623ac771 30ac548164fe43a98a43c11966d58d3d--85d67e2e6da64c2998010a5c623ac771 85d67e2e6da64c2998010a5c623ac771--3d4e2f3136a54c32a2d0c2720c5852dd 95a8f3600c284250a4818b387f59362c bedabf7151af4986a927f06ce2ad80b5 1fad7b5e95e64990a35dfa22bb11e5bd--bedabf7151af4986a927f06ce2ad80b5 60a446689e854e4b9d83dabaa713043b 4 85ddb71f9aa543f4a21f0e73fe3e9bb3 bedabf7151af4986a927f06ce2ad80b5--85ddb71f9aa543f4a21f0e73fe3e9bb3 85ddb71f9aa543f4a21f0e73fe3e9bb3--95a8f3600c284250a4818b387f59362c 4ab651b30cfb4a62b14d6b439c02cf45 942d582c2525477b91dd90cfc031b62e X 60a446689e854e4b9d83dabaa713043b--942d582c2525477b91dd90cfc031b62e 942d582c2525477b91dd90cfc031b62e--bedabf7151af4986a927f06ce2ad80b5 ab9f39bd6af74709ae611519f9a14f72 X 942d582c2525477b91dd90cfc031b62e--ab9f39bd6af74709ae611519f9a14f72 ab9f39bd6af74709ae611519f9a14f72--85ddb71f9aa543f4a21f0e73fe3e9bb3 ab9f39bd6af74709ae611519f9a14f72--4ab651b30cfb4a62b14d6b439c02cf45

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.