Introduction

hybridlane implements heterogeneous quantum programming using PennyLane – circuits composed of both continuous-variable (CV) and discrete-variable (DV) degrees of freedom. We’ll focus on qumodes (CV) and qubits (DV), but in principle hybridlane can handle qudits as well.

Note

As hybridlane builds extensively on PennyLane, we highly recommend reading the PennyLane documentation.

hybridlane circuits are just PennyLane circuits with our additional type checking procedure. In PennyLane’s common “quantum function” format, circuits are defined in a functional manner. Each quantum function f(x) accepts a set of classical inputs x, invokes a series of quantum operations, and returns a set of classical outputs obtained by measuring the quantum state after the operations.

Simple example

As a simple example, here’s a circuit that produces a definite Fock state \(\ket{n}\) by repeatedly shuffling quanta from a qubit to a qumode:

import numpy as np
import pennylane as qp
import hybridlane as hl

dev = qp.device('default.hybrid', fock_level=8)

@qp.qnode(dev)
def circuit(n):
    for j in range(n):
        qp.X("q")
        hl.JC(np.pi / (2 * np.sqrt(j + 1)), np.pi / 2, wires=["q", "m"])

    return hl.expval(hl.N("m"))

Warning

For our experimentalist friends – hybridlane uses the quantum information convention that \(Z\ket{0} = +1\ket{0}\) is the initial (ground) state of the quantum processor. This may differ from other libraries like QuTiP and Dynamiqs, but it’s to ensure consistency with PennyLane.

The input to the quantum function circuit is a single integer n, determining which state to prepare. Then, a sequence of interleaving \(X\) and \(JC\) gates are applied, and finally the mean photon number of the qumode, \(\langle \hat{n} \rangle\), is returned. The circuit can then be evaluated for different values of n:

>>> circuit(5)
5

Note

You may have noticed the @qp.qnode(dev) decorator. This is required to bind the quantum function to a particular device – in this case, the default.hybrid simulator. Without it, the function won’t be executable.

Upon invoking the circuit, the default.hybrid device invokes hybridlane’s type checker to validate that the circuit is well-formed, and it determines which wires are qubits and qumodes by inspecting the gates used in the circuit. The first gate encountered was the Pauli X gate, which constrains wire q to be a qubit. Then, the next gate is a hybrid JC gate, which is defined to act on a qubit and a qumode, in that order. So, the type checker determines that m is a qumode, and it also validates the previous assignment of q as a qubit.

You can check the results yourself using hl.type_check, which returns a function that you then call with the same arguments as the original quantum function:

>>> hl.type_check(circuit)(5)
TypeCheckResult(wire_types=OrderedDict({'q': Qubit(), 'm': Qumode()}), basis_maps=[BasisMap({'m': ComputationalBasis.Discrete})])

In practice, you’ll almost never need to do this.

Tip

You can see the full list of qubit operations at pennylane and CV/hybrid operations at hybridlane.