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:
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
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)
from qadence import CNOT
# A CNOT gate with control on qubit 0 and target on qubit 1.
cnot_gate = CNOT(0, 1)
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
ChainBlocktype. 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))
# Chaining on different qubits using the operator overload.
# Identical to the kron operation.
chain_xx = X(0) * X(1)
- kron applies a set of blocks in parallel (simultaneously) on disjoint qubit support and results in a
KronBlocktype. This is akin to applying a tensor product of the sub-blocks with the@operator.
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
AddBlocktype which can be used to construct Pauli operators. Please note thatAddBlockcan 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.
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)
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)
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:
- Build a
QuantumCircuitwhich ties together a composite block and a register. - Define a
QuantumModelwhich 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)))
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)
For more details on QuantumModel, see here.