---
title: Cross Platform Programming
tocdepth: 1
marimo-version: 0.24.0
width: medium
---
```python {.marimo}
import marimo as mo
```
## Cross-platform programming
One of the main reasons for building hybridlane was to unlock the ability to define a quantum circuit once, and then reuse its definition across multiple devices or backends. We'll demonstrate how to do this for a calibration circuit that we'll simulate and then cross-compile for an ion trap. The workflow works the same for other backends in principle.
['RZ(np.float64(6.283185307179586), wires=[5])', 'ConditionalXDisplacement(2.0, 0.0, wires=[5, Qumode(manifold=1, index=5)])', 'RZ(np.float64(6.283185307179586), wires=[5])', 'RZ(3.141592653589793, wires=[4])', 'RY(1.5707963267948966, wires=[4])', 'ConditionalXDisplacement(2.0, 1.5707963267948966, wires=[4, ' 'Qumode(manifold=1, index=5)])', 'RZ(np.float64(6.283185307179586), wires=[4])', 'ConditionalXDisplacement(-2.0, 0.0, wires=[5, Qumode(manifold=1, index=5)])', 'RZ(np.float64(6.283185307179586), wires=[5])', 'ConditionalXDisplacement(-2.0, 1.5707963267948966, wires=[4, ' 'Qumode(manifold=1, index=5)])', 'RZ(3.141592653589793, wires=[4])', 'RY(1.5707963267948966, wires=[4])', 'GlobalPhase(-25.132741228718352, wires=[])']While this notebook doesn't actually run the resulting circuit on hardware, we've successfully replicated the workflow you find in mature qubit-based software. And if you're working on quantum hardware or a simulator, building a `Device` will let you integrate your project into hybridlane so that you can program it at a high level. ## OpenQASM Suppose you need to serialize your circuit -- maybe you're building a device and have to send it over the network. hybridlane provides an intermediate representation (IR) based on OpenQASM 3.0 to facilitate this, with extensions to support CV-DV quantum programs. As an example, we'll serialize the above compiled circuit with [hl.to_openqasm](https://pnnl.github.io/hybridlane/_autoapi/hybridlane/index.html#hybridlane.to_openqasm). The first step will be to teach hybridlane how to serialize the $xCD$ gate because it's not part of our standard library, which you can view [here](https://github.com/pnnl/hybridlane/blob/main/examples/cvstdgates.inc). The other gates are qubit gates that are part of the regular OpenQASM standard library. To define a serializer for a gate, you register an implementation for `format_gate_as_qasm`. We'll reuse the definition for $CD$ and just change the op code: ```python {.marimo} from typing import Any from hybridlane.io.openqasm import format_gate_as_qasm @format_gate_as_qasm.register def _(op: hl.XCD, wire_to_str: dict[Any, str], precision: int | None = None) -> str: if precision: params = [f"{p:.{precision}f}" for p in op.parameters] else: params = list(map(str, op.parameters)) gate_name = "cv_xcd" wires = [wire_to_str[w] for w in op.wires] param_str = "(" + ", ".join(params) + ")" if params else "" wire_str = ", ".join(wires) gate_str = f"{gate_name}{param_str} {wire_str};" return gate_str ``` The arguments to the implementation are: - `op`: The operator that is being encoded. - `wire_to_str`: A dictionary mapping the wires in the circuit to their OpenQASM labels (e.g. `m -> m[3]`). - `precision`: An optional number of decimal places to use when writing out angle parameters. Additionally, the implementation must follow the rules of [functools.singledispatch](https://docs.python.org/3/library/functools.html#functools.singledispatch) in order for it to work properly, for which you have two options: - The primary format is to type-hint the variable `op: hl.XCD`. You can reuse an implementation for multiple gates by using a union type like `hl.XCD | hl.YCD`. - You could choose to pass the operator type to the decorator instead like `@format_gate_as_qasm.register(hl.XCD)`, particularly if you're programmatically generating some implementations. With this accomplised, let's encode our circuit. `hl.to_openqasm` uses the familiar functional style you've seen in many PennyLane functions like `qp.specs`. ```python {.marimo} ir = hl.to_openqasm(hw_qnode, level="device", precision=5)(2.0) print(ir) ```
OPENQASM 3.0;
include "stdgates.inc";
include "cvstdgates.inc";
qubit[2] q;
qumode[1] m;
def state_prep() {
reset q;
reset m;
rz(6.28319) q[0];
cv_xcd(2.00000, 0.00000) q[0], m[0];
rz(6.28319) q[0];
rz(3.14159) q[1];
ry(1.57080) q[1];
cv_xcd(2.00000, 1.57080) q[1], m[0];
rz(6.28319) q[1];
cv_xcd(-2.00000, 0.00000) q[0], m[0];
rz(6.28319) q[0];
cv_xcd(-2.00000, 1.57080) q[1], m[0];
rz(3.14159) q[1];
ry(1.57080) q[1];
gphase(-25.13274) ;
}
state_prep();
bit[1] c0;
c0[0] = measure q[0];
Evident from the above example, hybridlane's OpenQASM added a few modifications:
- It includes both the OpenQASM standard library of qubit gates (`stdgates.inc`) and our custom CV-DV standard library (`cvstdgates.inc`).
- The register definitions preserved the type information, with a dedicated qumode register `m`.
- Our non-standard $xCD$ gate was encoded as `cv_xcd` in the `state_prep()` routine.
Parsing this would require a modified OpenQASM parser, but that could be created relatively easily by adjusting the grammar definition.