| """Circuit conversion contracts for the qhybrid simulator payload.""" |
|
|
| from __future__ import annotations |
|
|
| import json |
| from dataclasses import dataclass |
| from typing import TYPE_CHECKING, Any, Callable |
|
|
|
|
| QHYBRID_PAYLOAD_VERSION = 1 |
|
|
| if TYPE_CHECKING: |
| from qiskit import QuantumCircuit as QiskitCircuit |
|
|
|
|
| @dataclass(frozen=True) |
| class QhybridGatePayload: |
| """Typed payload for one gate in qhybrid JSON.""" |
|
|
| gate_type: str | dict[str, list[float] | float] |
| qubits: list[int] |
| parameters: list[float] |
|
|
| def as_dict(self) -> dict[str, Any]: |
| return { |
| "gate_type": self.gate_type, |
| "qubits": self.qubits, |
| "parameters": self.parameters, |
| } |
|
|
|
|
| @dataclass(frozen=True) |
| class QhybridCircuitPayload: |
| """Typed payload for a full qhybrid circuit.""" |
|
|
| n_qubits: int |
| gates: list[QhybridGatePayload] |
| schema_version: int |
| name: str | None = None |
|
|
| def as_dict(self) -> dict[str, Any]: |
| return { |
| "schema_version": self.schema_version, |
| "n_qubits": self.n_qubits, |
| "gates": [gate.as_dict() for gate in self.gates], |
| "name": self.name, |
| } |
|
|
|
|
| GateConverter = Callable[[Any, list[int]], QhybridGatePayload] |
|
|
|
|
| def _default_gate_type(serde_gate: str, params: list[float]) -> str | dict[str, list[float] | float]: |
| if serde_gate in {"RX", "RY", "RZ", "P"}: |
| if len(params) != 1: |
| raise ValueError(f"{serde_gate} requires exactly one parameter") |
| return {serde_gate: params[0]} |
| if serde_gate == "U3": |
| if len(params) != 3: |
| raise ValueError("U3 requires exactly three parameters") |
| return {"U3": params} |
| if params: |
| raise ValueError(f"{serde_gate} does not accept parameters") |
| return serde_gate |
|
|
|
|
| def register_gate_converter( |
| qiskit_name: str, |
| serde_gate: str, |
| arity: int, |
| *, |
| expected_params: int | None = None, |
| ) -> GateConverter: |
| """Register a qiskit-name converter for a qhybrid gate type.""" |
| qiskit_name = qiskit_name.lower() |
|
|
| if arity < 1 or arity > 3: |
| raise ValueError(f"Unsupported arity for {qiskit_name}: {arity}") |
|
|
| def _converter(operation: Any, op_qubits: list[int]) -> QhybridGatePayload: |
| if len(op_qubits) != arity: |
| raise ValueError( |
| f"{qiskit_name} expects {arity} qubits, got {len(op_qubits)}" |
| ) |
| params = [float(value) for value in operation.params] |
| if expected_params is not None and len(params) != expected_params: |
| raise ValueError( |
| f"{qiskit_name} expects {expected_params} params, got {len(params)}" |
| ) |
| gate_type = _default_gate_type(serde_gate, params) |
| return QhybridGatePayload( |
| gate_type=gate_type, |
| qubits=op_qubits, |
| parameters=params, |
| ) |
|
|
| _GATE_CONVERTERS[qiskit_name] = _converter |
| return _converter |
|
|
|
|
| def _build_gate_converter_registry() -> dict[str, GateConverter]: |
| registry: dict[str, GateConverter] = {} |
| global _GATE_CONVERTERS |
| _GATE_CONVERTERS = registry |
|
|
| register_gate_converter("id", "I", arity=1) |
| register_gate_converter("x", "X", arity=1) |
| register_gate_converter("y", "Y", arity=1) |
| register_gate_converter("z", "Z", arity=1) |
| register_gate_converter("h", "H", arity=1) |
| register_gate_converter("s", "S", arity=1) |
| register_gate_converter("sdg", "Sdg", arity=1) |
| register_gate_converter("t", "T", arity=1) |
| register_gate_converter("tdg", "Tdg", arity=1) |
| register_gate_converter("rx", "RX", arity=1, expected_params=1) |
| register_gate_converter("ry", "RY", arity=1, expected_params=1) |
| register_gate_converter("rz", "RZ", arity=1, expected_params=1) |
| register_gate_converter("p", "P", arity=1, expected_params=1) |
| register_gate_converter("u", "U3", arity=1, expected_params=3) |
| register_gate_converter("u3", "U3", arity=1, expected_params=3) |
| register_gate_converter("cx", "CX", arity=2) |
| register_gate_converter("cy", "CY", arity=2) |
| register_gate_converter("cz", "CZ", arity=2) |
| register_gate_converter("ccx", "CCX", arity=3) |
|
|
| return registry |
|
|
|
|
| _GATE_CONVERTERS: dict[str, GateConverter] = _build_gate_converter_registry() |
|
|
|
|
| def get_supported_qiskit_gates() -> list[str]: |
| """Return supported qiskit gate names (lower-case canonical).""" |
| return sorted(_GATE_CONVERTERS.keys()) |
|
|
|
|
| def qiskit_to_qhybrid_json(qc: QiskitCircuit) -> str: |
| """Convert a Qiskit circuit to a versioned qhybrid JSON payload.""" |
| try: |
| from qiskit import transpile |
| except Exception as e: |
| raise ImportError("qiskit is required for qiskit_to_qhybrid_json") from e |
|
|
| qc = transpile(qc, basis_gates=["u", "cx"], optimization_level=0) |
|
|
| gates: list[QhybridGatePayload] = [] |
| for instruction in qc.data: |
| operation = instruction.operation |
| operation_name = str(operation.name).lower() |
| if operation_name in {"barrier", "measure"}: |
| continue |
|
|
| converter = _GATE_CONVERTERS.get(operation_name) |
| if converter is None: |
| supported = ", ".join(get_supported_qiskit_gates()) |
| raise NotImplementedError( |
| f"Unsupported gate for qhybrid: {operation.name}. " |
| f"Supported gates: {supported}" |
| ) |
|
|
| op_qubits = [qc.find_bit(q).index for q in instruction.qubits] |
| gates.append(converter(operation, op_qubits)) |
|
|
| payload = QhybridCircuitPayload( |
| schema_version=QHYBRID_PAYLOAD_VERSION, |
| n_qubits=qc.num_qubits, |
| gates=gates, |
| name=qc.name, |
| ) |
| return json.dumps(payload.as_dict()) |
|
|
|
|
| __all__ = [ |
| "QHYBRID_PAYLOAD_VERSION", |
| "QhybridCircuitPayload", |
| "QhybridGatePayload", |
| "GateConverter", |
| "get_supported_qiskit_gates", |
| "qiskit_to_qhybrid_json", |
| "register_gate_converter", |
| ] |
|
|
|
|