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 cb04d3abc47941f29b9974566147995b 0 d4efb23addcb4155a096ed129690b29b X cb04d3abc47941f29b9974566147995b--d4efb23addcb4155a096ed129690b29b 4061badf9a98411b9290d4d363fdaaa6 1 41a0a392e9b2472099894cd3e7357cbe d4efb23addcb4155a096ed129690b29b--41a0a392e9b2472099894cd3e7357cbe 29025db6c53c4e70abff071f242df748 2d6d8f37955945ceb6a938b6986d1699 Y 4061badf9a98411b9290d4d363fdaaa6--2d6d8f37955945ceb6a938b6986d1699 2d6d8f37955945ceb6a938b6986d1699--29025db6c53c4e70abff071f242df748

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 709087a8db4542249b1a79e689fb7383 0 b8aaee1166da480b88c142d70f83230f RX(0.5) 709087a8db4542249b1a79e689fb7383--b8aaee1166da480b88c142d70f83230f 74b903e6e3264fc09aa4d7a828266373 b8aaee1166da480b88c142d70f83230f--74b903e6e3264fc09aa4d7a828266373
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 e5d3f2c5c0124e2bb8951dad91c2182c 0 d40ff151b4904adabded1d5e1406c0b8 e5d3f2c5c0124e2bb8951dad91c2182c--d40ff151b4904adabded1d5e1406c0b8 be90977e5676434e8fef9e3800c93bab 1 8b2df78552d24aad942f3aa719c97898 d40ff151b4904adabded1d5e1406c0b8--8b2df78552d24aad942f3aa719c97898 081f6825034748d99c506432a56552ee 79d3792f9eeb40bbab415b40ce44a914 X be90977e5676434e8fef9e3800c93bab--79d3792f9eeb40bbab415b40ce44a914 79d3792f9eeb40bbab415b40ce44a914--d40ff151b4904adabded1d5e1406c0b8 79d3792f9eeb40bbab415b40ce44a914--081f6825034748d99c506432a56552ee

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 d9f9506f7bc14709925fc7740187d0ec 0 580c8eea9cf24609b33d72bb2baaacb3 X d9f9506f7bc14709925fc7740187d0ec--580c8eea9cf24609b33d72bb2baaacb3 f9b6fa8dd94c4153bf361d609f23c086 X 580c8eea9cf24609b33d72bb2baaacb3--f9b6fa8dd94c4153bf361d609f23c086 01de1ef67ef8469aa1316917f2402b75 f9b6fa8dd94c4153bf361d609f23c086--01de1ef67ef8469aa1316917f2402b75
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 56ff2c85a6fd4bfbace20b412aa6d5f6 0 92c2d1c71b6e41928079bb831d685cb0 X 56ff2c85a6fd4bfbace20b412aa6d5f6--92c2d1c71b6e41928079bb831d685cb0 679a2fb83140491aaa0a1c0af8e0e4e5 1 7ed7af06b5314427a8a18a9e7ddb9cd9 92c2d1c71b6e41928079bb831d685cb0--7ed7af06b5314427a8a18a9e7ddb9cd9 c8d9b965d672494487af1bb023189a88 7ed7af06b5314427a8a18a9e7ddb9cd9--c8d9b965d672494487af1bb023189a88 5a4ace73f0544b93bf2e56fce337380f 88c9f337d2cd4482a17d543f92cbdf0f 679a2fb83140491aaa0a1c0af8e0e4e5--88c9f337d2cd4482a17d543f92cbdf0f 8882ba44990c437e87768d5316d9c424 X 88c9f337d2cd4482a17d543f92cbdf0f--8882ba44990c437e87768d5316d9c424 8882ba44990c437e87768d5316d9c424--5a4ace73f0544b93bf2e56fce337380f

  • 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 2e5a827019d847ea8c16611b5e3543b2 0 60fc0e8344db4697aa999e5b474e8624 X 2e5a827019d847ea8c16611b5e3543b2--60fc0e8344db4697aa999e5b474e8624 209ec6ef619d42c58d1ef7f610ee95f2 1 13f4667bbb9148ed99dd5d94aff84066 60fc0e8344db4697aa999e5b474e8624--13f4667bbb9148ed99dd5d94aff84066 f460d81b27274ee593601b148fce6d58 10a16a78c3a7490dba9877ec5ccab847 X 209ec6ef619d42c58d1ef7f610ee95f2--10a16a78c3a7490dba9877ec5ccab847 10a16a78c3a7490dba9877ec5ccab847--f460d81b27274ee593601b148fce6d58

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_7717bdd1f8164e38b01b3549e0f1ca69 subblock cluster_bbf70e4aa067470b94267dc85ffbcc88 subblock 873325dfce0643999863f752716c2e7b 0 78431007ab4b4641810a8c93598f97c9 X 873325dfce0643999863f752716c2e7b--78431007ab4b4641810a8c93598f97c9 339885b0d46c46b88dced994701ae4ad 1 a889129398a14a28939bd70aadfd502e X 78431007ab4b4641810a8c93598f97c9--a889129398a14a28939bd70aadfd502e c8294c6a12884bd5a3be6d1670ca016e a889129398a14a28939bd70aadfd502e--c8294c6a12884bd5a3be6d1670ca016e f3e9728231444fcba5ed3a2caace87d9 595cf708c27849fe926b9babee5ba424 Y 339885b0d46c46b88dced994701ae4ad--595cf708c27849fe926b9babee5ba424 9f764e8548a141b28b804bc7a0eeb425 2 be883be41c1a4264900b3cc153b1af20 Y 595cf708c27849fe926b9babee5ba424--be883be41c1a4264900b3cc153b1af20 be883be41c1a4264900b3cc153b1af20--f3e9728231444fcba5ed3a2caace87d9 ec3ff8b6e4d04bcfaa620008b31f0c9a b93275bf43ed4c228d3e79de358af2ad 9f764e8548a141b28b804bc7a0eeb425--b93275bf43ed4c228d3e79de358af2ad 840646dd90e545a888eeaba9bf76eeeb 3 e73ab4b56dae41298640bc6f95271582 b93275bf43ed4c228d3e79de358af2ad--e73ab4b56dae41298640bc6f95271582 e73ab4b56dae41298640bc6f95271582--ec3ff8b6e4d04bcfaa620008b31f0c9a 24586bfca64d40fbae3d363465feaee6 deeaf964dd4645f0bacd7a347ae336d7 840646dd90e545a888eeaba9bf76eeeb--deeaf964dd4645f0bacd7a347ae336d7 c57a6eec72c64952bf88d678c61084f8 4 040e5ba6e87144838632e3e874e0fe44 deeaf964dd4645f0bacd7a347ae336d7--040e5ba6e87144838632e3e874e0fe44 040e5ba6e87144838632e3e874e0fe44--24586bfca64d40fbae3d363465feaee6 22bbc7b8e7c6497080a31daa8b9add47 90d0236bc86f44a0bbdac09dc562b118 X c57a6eec72c64952bf88d678c61084f8--90d0236bc86f44a0bbdac09dc562b118 90d0236bc86f44a0bbdac09dc562b118--deeaf964dd4645f0bacd7a347ae336d7 7a14c6d816fc4433b4f8137528ff2bf7 X 90d0236bc86f44a0bbdac09dc562b118--7a14c6d816fc4433b4f8137528ff2bf7 7a14c6d816fc4433b4f8137528ff2bf7--040e5ba6e87144838632e3e874e0fe44 7a14c6d816fc4433b4f8137528ff2bf7--22bbc7b8e7c6497080a31daa8b9add47

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({'00': 271, '10': 252, '01': 244, '11': 233})]
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, '110': 25, '000': 24, '010': 21})]

For more details on QuantumModel, see here.