Exporting Circuits ================== To facilitate the integration of simulators and hardware devices, Hybridlane provides an intermediate representation (IR) format based on `OpenQASM 3.0 `_, with a few (minimal) modifications to capture hybrid CV-DV programs. We detail the extensions to OpenQASM in a later section to first focus on introducing how to use it. A quantum program can be exported using the :py:func:`~hybridlane.to_openqasm` function. This function inspects the circuit for qumodes and qubits, and declares them separately in registers `m` and `q`, respectively. Based on the measurements and their observables, it also infers whether to use homodyne (``hqml.QuadX``) or Fock number (``hqml.NumberOperator``) measurements. Finally, noncommuting measurements are run on separate calls to the state preparation circuit, so a single OpenQASM program may contain multiple circuit executions using the function invocation feature of OpenQASM 3.0. Example ------- Here we give an example of exporting a basic circuit to OpenQASM. Consider the following circuit .. code:: python dev = qml.device("bosonicqiskit.hybrid") @qml.qnode(dev) def circuit(n): for j in range(n): qml.X(0) hqml.JaynesCummings(np.pi / (2 * np.sqrt(j + 1)), np.pi / 2, [0, 1]) return ( hqml.var(hqml.QuadP(1)), hqml.expval(qml.PauliZ(0)), ) Note that it has DV gates, hybrid gates, and DV and CV measurements. Furthermore, the ``QuadP`` observable is not diagonal in the position basis. This can be exported with .. code:: python >>> hqml.to_openqasm(circuit, precision=5)(5) which produces the following IR .. code:: OPENQASM 3.0; include "stdgates.inc"; const int homodyne_precision_bits = 32; const int fock_readout_precision_bits = 32; include "cvstdgates.inc"; qubit[1] q; qumode[1] m; def state_prep() { reset q; reset m; x q[0]; cv_jc(1.5708, 1.5708) q[0], m[0]; x q[0]; cv_jc(1.1107, 1.5708) q[0], m[0]; x q[0]; cv_jc(0.9069, 1.5708) q[0], m[0]; x q[0]; cv_jc(0.7854, 1.5708) q[0], m[0]; x q[0]; cv_jc(0.70248, 1.5708) q[0], m[0]; } state_prep(); cv_r(1.5708) m[0]; float[homodyne_precision_bits] c0 = measure_x m[0]; bit[1] c1; c1[0] = measure q[0]; To measure the momentum operator :math:`\hat{p}`, you can see that the export function added the diagonalizing gates (:math:`R(\pi/2)`). To disable this behavior, you can set ``rotations = False``. Finally, the variables ``int_bits`` and ``float_bits`` control how much precision is used for Fock and homodyne measurements, respectively. Their values are inserted into the constants ``fock_readout_precision_bits`` and ``homodyne_precision_bits``, then subsequently used in each CV measurement. This custom IR format is not compatible with the official OpenQASM parser. If, for some reason, your application needs to erase the type information to be compatible with OpenQASM, you can pass the ``strict=True`` flag to produce OpenQASM-compatible circuits. This will remove the custom CV-DV extensions. .. code:: python >>> hqml.to_openqasm(circuit, precision=5, strict=True)(5) produces .. code:: OPENQASM 3.0; include "stdgates.inc"; const int homodyne_precision_bits = 32; const int fock_readout_precision_bits = 32; include "cvstdgates.inc"; // Position measurement x defcal measure_x m -> float[homodyne_precision_bits] {} // Fock measurement n defcal measure_n m -> uint[fock_readout_precision_bits] {} qubit[1] q; qubit[1] m; def state_prep() { reset q; reset m; x q[0]; cv_jc(1.5708, 1.5708) q[0], m[0]; x q[0]; cv_jc(1.1107, 1.5708) q[0], m[0]; x q[0]; cv_jc(0.9069, 1.5708) q[0], m[0]; x q[0]; cv_jc(0.7854, 1.5708) q[0], m[0]; x q[0]; cv_jc(0.70248, 1.5708) q[0], m[0]; } state_prep(); cv_r(1.5708) m[0]; float[homodyne_precision_bits] c0 = measure_x(m[0]); bit[1] c1; c1[0] = measure q[0]; Notice how the register declaration ``qumode m[1]`` became ``qubit m[1]``, and the ``measure_x`` keyword was replaced with a corresponding ``defcal`` and function call. OpenQASM Modifications ---------------------- Our superset of OpenQASM contains the following extra features: 1. The ``qumode`` keyword has the same semantics as ``qubit``, just telling the compiler that a register specifically contains qumodes instead. This enables a compiler to perform type checking on gates and measurements. Example: .. code:: // Can declare a register of qumodes qumode[3] m; // and reset them reset m; // or use them in a subroutine definition def pmeasure(qumode m) -> float[32] { cv_r(pi/2) m; return measure_x m; } 2. The ``measure_x`` keyword has the same syntax as the qubit ``measure`` keyword, but it performs homodyne measurement of a qumode and stores the result in a ``float`` variable. The bit width of the result dictates the precision of the measurement. Example: .. code:: // Performs position readout into 32 bit precision float[32] c = measure_x m[0]; // or can do a lower-precision measurement float[5] c2 = measure_x m[1]; 3. Similarly, the ``measure_n`` keyword performs a Fock readout of a qumode, and stores the result in a ``uint`` variable. Again, the bit width of the resulting variable determines the precision of the measurement. Example: .. code:: // Performs fock readout into 32 bit precision uint[32] c = measure_n m[0]; // or can do a lower-precision measurement uint[5] c2 = measure_n m[1]; 4. We introduce a CV-DV standard gate library based on Liu et al., 2024 (`arXiv:2407.10381 `_). This library should be handled by compilers using the statement ``include "cvstdgates.inc";``, and we include its definitions in the file ``examples/cvstdgates.inc``. All of our gates follow the definitions of this library, so you can use the documentation of ``hqml.ops`` as a reference.