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 73efbcb250c241ec81b6b13aacfd5026 0 49a0d0f61d87483f899e49e518aaa240 X 73efbcb250c241ec81b6b13aacfd5026--49a0d0f61d87483f899e49e518aaa240 ae3fb3ab089c4384abfe0e9f957149d2 1 e93c320de676421681fc3fbef4fafea5 49a0d0f61d87483f899e49e518aaa240--e93c320de676421681fc3fbef4fafea5 242b389da08342998e266583c680b5bf 2a6c48ba942e4b198096f9f930f31edc Y ae3fb3ab089c4384abfe0e9f957149d2--2a6c48ba942e4b198096f9f930f31edc 2a6c48ba942e4b198096f9f930f31edc--242b389da08342998e266583c680b5bf

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 4727bfe993ad48928a8eaeeb2f17f70f 0 b414f58382c943c6a09695d149d81a3b RX(0.5) 4727bfe993ad48928a8eaeeb2f17f70f--b414f58382c943c6a09695d149d81a3b b39e8144b32c4f4ba88ce44a9f71a28e b414f58382c943c6a09695d149d81a3b--b39e8144b32c4f4ba88ce44a9f71a28e
from qadence import CNOT

# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
%3 8d53e9769a9e41cf8f1340f7a52e24c1 0 5293f805ce8c478b9780720af6d86c18 8d53e9769a9e41cf8f1340f7a52e24c1--5293f805ce8c478b9780720af6d86c18 245aae1a2b6942da81d1969029794fe9 1 28c14d76a62c4cabad38ce1198f2a8b7 5293f805ce8c478b9780720af6d86c18--28c14d76a62c4cabad38ce1198f2a8b7 041b770c26ff4a07aa51d20d96fae961 cda4fe40246248358aecb3bfa3a57aac X 245aae1a2b6942da81d1969029794fe9--cda4fe40246248358aecb3bfa3a57aac cda4fe40246248358aecb3bfa3a57aac--5293f805ce8c478b9780720af6d86c18 cda4fe40246248358aecb3bfa3a57aac--041b770c26ff4a07aa51d20d96fae961

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 1ddf573d9d2f48d8adfa8659bfc7116f 0 cc99e35c174e495c84f49bbea64ddaaa X 1ddf573d9d2f48d8adfa8659bfc7116f--cc99e35c174e495c84f49bbea64ddaaa 2c370563ff9a4ebd915e4ccc35f9c655 X cc99e35c174e495c84f49bbea64ddaaa--2c370563ff9a4ebd915e4ccc35f9c655 0108e6357b694cd9a09a2dd221c9bb62 2c370563ff9a4ebd915e4ccc35f9c655--0108e6357b694cd9a09a2dd221c9bb62
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
%3 f83767b5b99f4764a55fca34847e7e4c 0 64f8237e4cda4b308566915f7befdb9d X f83767b5b99f4764a55fca34847e7e4c--64f8237e4cda4b308566915f7befdb9d c35636db3e67407fb9e355f50e4f5cdc 1 d6a580738a4c4f3d92c31f2276b8a1d8 64f8237e4cda4b308566915f7befdb9d--d6a580738a4c4f3d92c31f2276b8a1d8 745d189745f14668924a82f9b2e25bb7 d6a580738a4c4f3d92c31f2276b8a1d8--745d189745f14668924a82f9b2e25bb7 d74353ea03c347a784b95b8c5ff49fb1 8a4cf6f024f2417d9396eca53484ff57 c35636db3e67407fb9e355f50e4f5cdc--8a4cf6f024f2417d9396eca53484ff57 9119aae4d95e4030b6e037c59726983f X 8a4cf6f024f2417d9396eca53484ff57--9119aae4d95e4030b6e037c59726983f 9119aae4d95e4030b6e037c59726983f--d74353ea03c347a784b95b8c5ff49fb1

  • 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 e16bd90eee02433d9f18ce3be1260f47 0 5f96cbcdd44c4fbb9a0cda83ccd6c1fd X e16bd90eee02433d9f18ce3be1260f47--5f96cbcdd44c4fbb9a0cda83ccd6c1fd 4ac2db0c4ae442dda62642312c69a906 1 ebc8fb1f38dc499c961af57516724e04 5f96cbcdd44c4fbb9a0cda83ccd6c1fd--ebc8fb1f38dc499c961af57516724e04 ab97ec8af12e4d29b97df1fffdfd5108 674b16f05ea847b8b47d6faac90cc90a X 4ac2db0c4ae442dda62642312c69a906--674b16f05ea847b8b47d6faac90cc90a 674b16f05ea847b8b47d6faac90cc90a--ab97ec8af12e4d29b97df1fffdfd5108

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_c23ef849b8cc4a9a8b2e8807e6c3f508 subblock cluster_660a714a073e4dd0a7ce3240cda20e4a subblock 60a9ecd7fa5446ec93ef7098b6fa5ac8 0 a471c39a49f94ac2848bc899b029a64a X 60a9ecd7fa5446ec93ef7098b6fa5ac8--a471c39a49f94ac2848bc899b029a64a 8335265ca36e4924bb6a2a399c3efda3 1 3ebff76d60c9422889b794914f6c80e3 X a471c39a49f94ac2848bc899b029a64a--3ebff76d60c9422889b794914f6c80e3 e3f2c58345dd4709903062f89c58773a 3ebff76d60c9422889b794914f6c80e3--e3f2c58345dd4709903062f89c58773a b30b7cc7f0654f2385b614185bf221a0 cb471e16a34744eeafa202e3b7159c62 Y 8335265ca36e4924bb6a2a399c3efda3--cb471e16a34744eeafa202e3b7159c62 9e97ab002b1f41cbb88a679f0333dac7 2 974e7fd419854e1093fc460b31d84964 Y cb471e16a34744eeafa202e3b7159c62--974e7fd419854e1093fc460b31d84964 974e7fd419854e1093fc460b31d84964--b30b7cc7f0654f2385b614185bf221a0 2486f22eb31a4710b36a32ccd241bab6 d71e4a6ca80e405ab1ae021ae4e5e8ce 9e97ab002b1f41cbb88a679f0333dac7--d71e4a6ca80e405ab1ae021ae4e5e8ce 81909f7b80be4dc9a243c69cd64b96e5 3 7669bc9b6212493e9811dd233e718b81 d71e4a6ca80e405ab1ae021ae4e5e8ce--7669bc9b6212493e9811dd233e718b81 7669bc9b6212493e9811dd233e718b81--2486f22eb31a4710b36a32ccd241bab6 a911e7fc6a4e48c6aa7a55471ff7b195 7f80cd968be3478abd7ebde162408b35 81909f7b80be4dc9a243c69cd64b96e5--7f80cd968be3478abd7ebde162408b35 afbd9bbffd444793b70d192e5bbbfe96 4 7fbee7bd63ee49ba8845359adb61dc2a 7f80cd968be3478abd7ebde162408b35--7fbee7bd63ee49ba8845359adb61dc2a 7fbee7bd63ee49ba8845359adb61dc2a--a911e7fc6a4e48c6aa7a55471ff7b195 116a5d794a44494eb51ead0342dafaf9 4ecbbf180f114d869f93b5f1e1ec410d X afbd9bbffd444793b70d192e5bbbfe96--4ecbbf180f114d869f93b5f1e1ec410d 4ecbbf180f114d869f93b5f1e1ec410d--7f80cd968be3478abd7ebde162408b35 580d0229e3d44623973164813644ba7b X 4ecbbf180f114d869f93b5f1e1ec410d--580d0229e3d44623973164813644ba7b 580d0229e3d44623973164813644ba7b--7fbee7bd63ee49ba8845359adb61dc2a 580d0229e3d44623973164813644ba7b--116a5d794a44494eb51ead0342dafaf9

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.