Source code for qutip_qip.circuit.instruction

from abc import ABC, abstractmethod
from typing import Type
from dataclasses import dataclass, field
import warnings
from qutip_qip.operations import Gate, Measurement


def _validate_non_negative_int_tuple(T: any, txt: str = ""):
    if type(T) is not tuple:
        raise TypeError(f"Must pass a tuple for {txt}, got {type(T)}")

    for q in T:
        if type(q) is not int:
            raise ValueError(f"All {txt} indices must be an int, found {q}")

        if q < 0:
            raise ValueError(f"{txt} indices must be non-negative, found {q}")


[docs] @dataclass(frozen=True, slots=True) class CircuitInstruction(ABC): operation: Gate | Type[Gate] | Measurement qubits: tuple[int, ...] = tuple() cbits: tuple[int, ...] = tuple() style: dict = field(default_factory=dict) def __post_init__(self): """Basic validation for all instructions.""" if not len(self.qubits) and not len(self.cbits): raise ValueError( "Circuit Instruction must operate on at least one qubit or cbit." ) _validate_non_negative_int_tuple(self.qubits, "qubit") _validate_non_negative_int_tuple(self.cbits, "cbit") if len(self.qubits) != len(set(self.qubits)): raise ValueError("Found repeated qubits") if len(self.cbits) != len(set(self.cbits)): raise ValueError("Found repeated cbits") @staticmethod def is_gate_instruction() -> bool: return False @staticmethod def is_measurement_instruction() -> bool: return False @abstractmethod def to_qasm(self, qasm_out) -> None: raise NotImplementedError @abstractmethod def __str__(self) -> str: raise NotImplementedError def __repr__(self) -> str: return str(self)
[docs] @dataclass(frozen=True, slots=True) class GateInstruction(CircuitInstruction): operation: Gate | Type[Gate] cbits_ctrl_value: int | None = None def __post_init__(self) -> None: super(GateInstruction, self).__post_init__() # Don't make it super(), it will throw an error because slots=True # destroys __class__ reference to the original class until Python 3.13 # Check CPython Issue #90562, this has been resolved in Python 3.14 if not (isinstance(self.operation, Gate) or issubclass(self.operation, Gate)): raise TypeError(f"Operation must be a Gate, got {self.operation}") if len(self.qubits) != self.operation.num_qubits: raise ValueError( f"Gate '{self.operation.name}' requires {self.operation.num_qubits} qubits." f" But got {len(self.qubits)}." ) if len(self.cbits) > 0 and self.cbits_ctrl_value is None: raise ValueError( "cbits_ctrl_value can't be None if classical controls are provided." ) if self.cbits_ctrl_value is not None: if self.cbits_ctrl_value < 0: raise ValueError( f"Classical Control value can't be negative, got {self.cbits_ctrl_value}" ) if self.cbits_ctrl_value > 2 ** len(self.cbits) - 1: raise ValueError( f"Classical Control value can't be greater than 2^num_cbits -1, got {self.cbits_ctrl_value}." ) @property def controls(self) -> tuple[int, ...]: if self.operation.is_controlled: return self.qubits[: self.operation.num_ctrl_qubits] return () @property def targets(self) -> tuple[int, ...]: if self.operation.is_controlled: return self.qubits[self.operation.num_ctrl_qubits :] return self.qubits @staticmethod def is_gate_instruction() -> bool: return True def __getattr__(self, name): """ Temporary backward compatibility layer to: forward selected old Gate attributes to ``operation`` with a deprecation warning. """ if name in ("name", "num_qubits", "arg_value"): warnings.warn( f"Gate object in a circuit has been replaced by GateInstructions." f"use GateInstruction.operation.{name} instead.", DeprecationWarning, stacklevel=2, ) value = getattr(self.operation, name) if name == "arg_value" and isinstance(value, tuple) and len(value) == 1: return value[0] return value raise AttributeError( f"'{type(self).__name__}' object has no attribute '{name}'" ) def to_qasm(self, qasm_out) -> None: gate = self.operation args = None if gate.is_parametric: args = gate.arg_value qasm_gate = qasm_out.qasm_name(gate.name) if not qasm_gate: error_str = f"{gate.name} gate's qasm defn is not specified" raise NotImplementedError(error_str) if self.cbits: err_msg = "Exporting controlled gates is not implemented yet." raise NotImplementedError(err_msg) else: qasm_out.output( qasm_out._qasm_str( q_name=qasm_gate, q_targets=list(self.targets), q_controls=list(self.controls), q_args=args, ) ) def __str__(self) -> str: return f"Gate({self.operation}), qubits({self.qubits}),\ cbits({self.cbits}), style({self.style})"
[docs] @dataclass(frozen=True, slots=True) class MeasurementInstruction(CircuitInstruction): operation: Measurement def __post_init__(self) -> None: super(MeasurementInstruction, self).__post_init__() if not isinstance(self.operation, Measurement): raise TypeError(f"Operation must be a measurement, got {self.operation}") if len(self.qubits) != len(self.cbits): raise ValueError("Measurement requires equal number of qubits and cbits.") @staticmethod def is_measurement_instruction() -> bool: return True def to_qasm(self, qasm_out) -> None: for qubit, cbit in zip(self.qubits, self.cbits): qasm_out.output(f"measure q[{qubit}] -> c[{cbit}];") def __str__(self) -> str: return f"Measure(q{self.qubits} -> c{self.cbits})"