Source code for uniqc.circuit_adapter

"""Circuit adapter layer for converting UnifiedQuantum circuits to provider-native formats.

This module provides adapter classes for converting UnifiedQuantum Circuit objects
to native circuit formats used by different quantum computing platforms:
- OriginQ (pyqpanda)
- Quafu (pyquafu)
- IBM (qiskit)

Usage::

    from uniqc.circuit_adapter import OriginQCircuitAdapter, QuafuCircuitAdapter, IBMCircuitAdapter
    from uniqc.circuit_builder import Circuit

    # Create a UnifiedQuantum circuit
    circuit = Circuit()
    circuit.h(0)
    circuit.cnot(0, 1)
    circuit.measure(0, 1)

    # Convert to provider-native circuits
    originq_adapter = OriginQCircuitAdapter()
    pyqpanda_circuit = originq_adapter.adapt(circuit)

    quafu_adapter = QuafuCircuitAdapter()
    quafu_circuit = quafu_adapter.adapt(circuit)

    ibm_adapter = IBMCircuitAdapter()
    qiskit_circuit = ibm_adapter.adapt(circuit)
"""

from __future__ import annotations

__all__ = [
    "CircuitAdapter",
    "OriginQCircuitAdapter",
    "QuafuCircuitAdapter",
    "IBMCircuitAdapter",
]

import abc
from typing import TYPE_CHECKING, Any, List, TypeVar, Generic, Optional

if TYPE_CHECKING:
    from uniqc.circuit_builder.qcircuit import Circuit

# Type variable for provider-native circuit types
T = TypeVar("T")


[docs] class CircuitAdapter(abc.ABC, Generic[T]): """Abstract base class for circuit adapters. Provides a unified interface for converting UnifiedQuantum Circuit objects to provider-native circuit formats. """
[docs] @abc.abstractmethod def adapt(self, circuit: "Circuit") -> T: """Convert a UnifiedQuantum Circuit to the provider's native circuit format. Args: circuit: UnifiedQuantum Circuit object. Returns: Provider-native circuit object. """ ...
[docs] def adapt_batch(self, circuits: List["Circuit"]) -> List[T]: """Convert multiple UnifiedQuantum Circuits to provider-native format. Args: circuits: List of UnifiedQuantum Circuit objects. Returns: List of provider-native circuit objects. """ return [self.adapt(c) for c in circuits]
[docs] @abc.abstractmethod def get_supported_gates(self) -> List[str]: """Return the list of gate names supported by this adapter. Returns: List of supported gate names (uppercase strings). """ ...
def _get_originir(self, circuit: "Circuit") -> str: """Extract OriginIR string from a UnifiedQuantum Circuit. Args: circuit: UnifiedQuantum Circuit object. Returns: OriginIR string representation of the circuit. """ return circuit.originir
[docs] class OriginQCircuitAdapter(CircuitAdapter[Any]): """Adapter for converting UnifiedQuantum Circuit to pyqpanda (OriginQ) format. Uses pyqpanda3's intermediate compiler to convert OriginIR to QProg. """ # Gate mapping from OriginIR names to pyqpanda supported gates SUPPORTED_GATES = [ "H", "X", "Y", "Z", "S", "T", "SX", "RX", "RY", "RZ", "RPhi", "RPhi90", "RPhi180", "U1", "U2", "U3", "U4", "CNOT", "CZ", "SWAP", "ISWAP", "TOFFOLI", "CSWAP", "XX", "YY", "ZZ", "XY", "PHASE2Q", "UU15", "I", "BARRIER", "MEASURE", ] def __init__(self) -> None: self._pyqpanda3: Any = None self._convert_originir: Any = None def _ensure_imports(self) -> None: """Lazily import pyqpanda3 modules.""" if self._pyqpanda3 is None or self._convert_originir is None: try: from pyqpanda3 import core as pyqpanda3_core from pyqpanda3.intermediate_compiler import ( convert_originir_string_to_qprog, ) self._pyqpanda3 = pyqpanda3_core self._convert_originir = convert_originir_string_to_qprog except ImportError as e: raise RuntimeError( "pyqpanda3 is required for OriginQCircuitAdapter. " "Install it with: pip install pyqpanda3" ) from e
[docs] def adapt(self, circuit: "Circuit") -> Any: """Convert UnifiedQuantum Circuit to pyqpanda QProg. Args: circuit: UnifiedQuantum Circuit object. Returns: pyqpanda3.core.QProg object. """ self._ensure_imports() originir = self._get_originir(circuit) return self._convert_originir(originir)
[docs] def get_supported_gates(self) -> List[str]: """Return the list of gate names supported by this adapter.""" return self.SUPPORTED_GATES.copy()
[docs] class QuafuCircuitAdapter(CircuitAdapter[Any]): """Adapter for converting UnifiedQuantum Circuit to pyquafu (Quafu) format. Translates OriginIR gate by gate to quafu.QuantumCircuit. """ # Gate mapping from OriginIR to Quafu SUPPORTED_GATES = [ "H", "X", "Y", "Z", "RX", "RY", "RZ", "CNOT", "CZ", "MEASURE", # Note: Quafu supports more gates, these are the most common ones ] def __init__(self) -> None: self._quafu: Any = None self._QuantumCircuit: Any = None def _ensure_imports(self) -> None: """Lazily import quafu modules.""" if self._quafu is None: try: import quafu from quafu import QuantumCircuit self._quafu = quafu self._QuantumCircuit = QuantumCircuit except ImportError as e: raise RuntimeError( "quafu is required for QuafuCircuitAdapter. " "Install it with: pip install pyquafu" ) from e
[docs] def adapt(self, circuit: "Circuit") -> Any: """Convert UnifiedQuantum Circuit to quafu QuantumCircuit. Args: circuit: UnifiedQuantum Circuit object. Returns: quafu.QuantumCircuit object. """ self._ensure_imports() from uniqc.originir.originir_line_parser import OriginIR_LineParser originir = self._get_originir(circuit) lines = originir.splitlines() qc: Any = None # Track control structure state control_stack: list[list[int]] = [] # Stack of control qubit lists dagger_count = 0 # Nested DAGGER counter (odd = dagger active) for line in lines: line = line.strip() if not line: continue try: ( operation, qubit, cbit, parameter, dagger_flag, control_qubits, ) = OriginIR_LineParser.parse_line(line) except NotImplementedError: raise RuntimeError( f"Unknown OriginIR operation in Quafu adapter: {line}" ) from None # Initialize circuit on QINIT if operation == "QINIT": num_qubits = int(qubit) qc = self._QuantumCircuit(num_qubits) continue if qc is None: raise RuntimeError("QINIT must appear before any gate operation.") # Skip CREG (handled implicitly) if operation == "CREG": continue # Handle control structure markers if operation == "CONTROL": # Parse control qubits from the line ctrl_qubits = qubit if isinstance(qubit, list) else [int(qubit)] control_stack.append(ctrl_qubits) continue elif operation == "ENDCONTROL": if control_stack: control_stack.pop() continue elif operation == "DAGGER": dagger_count += 1 continue elif operation == "ENDDAGGER": dagger_count -= 1 continue # Merge control qubits from stack effective_control_qubits: list[int] = [] if control_qubits: effective_control_qubits.extend(control_qubits) for ctrl in control_stack: effective_control_qubits.extend(ctrl) # Apply dagger from DAGGER block effective_dagger = dagger_flag or (dagger_count % 2 == 1) # Apply gates qc = self._apply_gate( qc, operation, qubit, cbit, parameter, effective_dagger, effective_control_qubits if effective_control_qubits else None ) if qc is None: raise RuntimeError("OriginIR string produced no circuit.") return qc
def _apply_gate( self, qc: Any, operation: Optional[str], qubit: Any, cbit: Any, parameter: Any, dagger_flag: Optional[bool], control_qubits: Any, ) -> Any: """Apply a single gate to the Quafu QuantumCircuit. Args: qc: Quafu QuantumCircuit object. operation: Gate operation name. qubit: Target qubit(s). cbit: Classical bit for measurement. parameter: Gate parameter(s). dagger_flag: Whether the gate is daggered. control_qubits: Control qubits for controlled gates. Returns: Modified QuantumCircuit object. """ if operation is None: return qc # Normalize control qubits has_control = control_qubits is not None and len(control_qubits) > 0 ctrl_qubits = control_qubits if has_control else [] # Helper function to apply controlled gate def apply_controlled(gate_fn, target_qubit, *args): """Apply gate with control qubits if present.""" if has_control: # For multi-controlled gates, use Quafu's mcx or controlled methods if len(ctrl_qubits) == 1: # Single control: use cnot-like control qc.cnot(int(ctrl_qubits[0]), int(target_qubit)) gate_fn(int(target_qubit), *args) qc.cnot(int(ctrl_qubits[0]), int(target_qubit)) else: # Multi-control: use mcx if available if hasattr(qc, 'mcx'): qc.mcx([int(c) for c in ctrl_qubits], int(target_qubit)) gate_fn(int(target_qubit), *args) qc.mcx([int(c) for c in ctrl_qubits], int(target_qubit)) else: raise NotImplementedError( f"Multi-controlled gates with {len(ctrl_qubits)} controls " "not supported by this Quafu version" ) else: gate_fn(int(target_qubit), *args) # Single-qubit gates if operation == "H": if has_control: qc.ch(int(ctrl_qubits[0]), int(qubit)) if len(ctrl_qubits) == 1 else apply_controlled(qc.h, qubit) else: qc.h(int(qubit)) elif operation == "X": if has_control: if len(ctrl_qubits) == 1: qc.cnot(int(ctrl_qubits[0]), int(qubit)) else: qc.mcx([int(c) for c in ctrl_qubits], int(qubit)) else: qc.x(int(qubit)) elif operation == "Y": apply_controlled(qc.y, qubit) elif operation == "Z": if has_control: qc.cz(int(ctrl_qubits[0]), int(qubit)) if len(ctrl_qubits) == 1 else apply_controlled(qc.z, qubit) else: qc.z(int(qubit)) elif operation == "S": if dagger_flag: apply_controlled(qc.sdg, qubit) else: apply_controlled(qc.s, qubit) elif operation == "T": if dagger_flag: apply_controlled(qc.tdg, qubit) else: apply_controlled(qc.t, qubit) elif operation == "SX": if dagger_flag: apply_controlled(qc.sxdg, qubit) else: apply_controlled(qc.sx, qubit) # Single-qubit rotation gates elif operation == "RX": apply_controlled(lambda q: qc.rx(q, float(parameter)), qubit) elif operation == "RY": apply_controlled(lambda q: qc.ry(q, float(parameter)), qubit) elif operation == "RZ": apply_controlled(lambda q: qc.rz(q, float(parameter)), qubit) # Two-qubit gates elif operation == "CNOT": qubits = qubit if isinstance(qubit, list) else [qubit] if has_control: # Add additional controls to existing CNOT all_controls = list(ctrl_qubits) + [int(qubits[0])] qc.mcx(all_controls, int(qubits[1])) else: qc.cnot(int(qubits[0]), int(qubits[1])) elif operation == "CZ": qubits = qubit if isinstance(qubit, list) else [qubit] if has_control: qc.mcx([int(ctrl_qubits[0])], int(qubits[0])) qc.cz(int(qubits[0]), int(qubits[1])) qc.mcx([int(ctrl_qubits[0])], int(qubits[0])) else: qc.cz(int(qubits[0]), int(qubits[1])) elif operation == "SWAP": qubits = qubit if isinstance(qubit, list) else [qubit] qc.swap(int(qubits[0]), int(qubits[1])) elif operation == "ISWAP": qubits = qubit if isinstance(qubit, list) else [qubit] qc.iswap(int(qubits[0]), int(qubits[1])) # Measurement elif operation == "MEASURE": # Quafu measure takes lists of qubits and cbits if cbit is not None: qc.measure([int(qubit)], [int(cbit)]) else: qc.measure([int(qubit)]) # Barrier elif operation == "BARRIER": if isinstance(qubit, list): qc.barrier(qubit) else: qc.barrier([qubit]) # Ignore control structure markers elif operation in ("CONTROL", "ENDCONTROL", "DAGGER", "ENDDAGGER"): pass else: # For unsupported gates, raise a clear error raise NotImplementedError( f"Gate '{operation}' is not supported by QuafuCircuitAdapter. " f"Supported gates: {self.SUPPORTED_GATES}" ) return qc
[docs] def get_supported_gates(self) -> List[str]: """Return the list of gate names supported by this adapter.""" return self.SUPPORTED_GATES.copy()
[docs] class IBMCircuitAdapter(CircuitAdapter[Any]): """Adapter for converting UnifiedQuantum Circuit to qiskit (IBM) format. Converts Circuit -> OriginIR -> QASM -> Qiskit QuantumCircuit. """ # QASM 2.0 standard gates supported by qiskit SUPPORTED_GATES = [ "H", "X", "Y", "Z", "S", "T", "SX", "RX", "RY", "RZ", "U1", "U2", "U3", "CNOT", "CX", "CZ", "SWAP", "ISWAP", "TOFFOLI", "CCX", "CSWAP", "Fredkin", "MEASURE", "BARRIER", "I", "ID", ] def __init__(self) -> None: self._qiskit: Any = None def _ensure_imports(self) -> None: """Lazily import qiskit modules.""" if self._qiskit is None: try: import qiskit self._qiskit = qiskit except ImportError as e: raise RuntimeError( "qiskit is required for IBMCircuitAdapter. " "Install it with: pip install qiskit" ) from e
[docs] def adapt(self, circuit: "Circuit") -> Any: """Convert UnifiedQuantum Circuit to qiskit QuantumCircuit. The conversion path is: UnifiedQuantum Circuit -> OriginIR -> QASM -> Qiskit QuantumCircuit Args: circuit: UnifiedQuantum Circuit object. Returns: qiskit.QuantumCircuit object. """ self._ensure_imports() # Get QASM representation (via OriginIR -> QASM conversion) qasm_str = circuit.qasm # Parse QASM to Qiskit QuantumCircuit return self._qiskit.QuantumCircuit.from_qasm_str(qasm_str)
[docs] def adapt_with_transpilation( self, circuit: "Circuit", backend: Any = None, optimization_level: int = 1, **kwargs: Any, ) -> Any: """Convert and transpile the circuit for a specific backend. Args: circuit: UnifiedQuantum Circuit object. backend: Qiskit backend to transpile for. optimization_level: Transpiler optimization level (0-3). **kwargs: Additional arguments for qiskit.compiler.transpile. Returns: Transpiled qiskit.QuantumCircuit object. """ self._ensure_imports() qiskit_circuit = self.adapt(circuit) if backend is not None: return self._qiskit.compiler.transpile( qiskit_circuit, backend=backend, optimization_level=optimization_level, **kwargs ) return qiskit_circuit
[docs] def get_supported_gates(self) -> List[str]: """Return the list of gate names supported by this adapter.""" return self.SUPPORTED_GATES.copy()