Gyanateet Dutta
Fix Space loading: direct Streamlit, lazy imports, ReNova page, fix deps
9f8cf99
Raw
History Blame Contribute Delete
5.91 kB
"""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 # type: ignore
except Exception as e: # pragma: no cover
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",
]