Source code for uniqc.algorithmics.measurement.basis_rotation

"""Basis-rotation measurement for arbitrary single-qubit measurement bases."""

__all__ = ["basis_rotation_measurement"]

from typing import Optional, List, Union, Dict
import numpy as np

from uniqc.circuit_builder import Circuit
from uniqc.simulator.qasm_simulator import QASM_Simulator


[docs] def basis_rotation_measurement( circuit: Circuit, qubits: Optional[List[int]] = None, basis: Optional[Union[str, List[str]]] = None, shots: Optional[int] = None, ) -> Union[Dict[str, float], List[float]]: """Measure a circuit by applying basis-rotation gates and then measuring in the computational (Z) basis. For each qubit, the rotation applied before measurement is determined by the corresponding entry in ``basis``: - ``"Z"`` — no rotation (Z basis, default) - ``"X"`` — Hadamard gate (H), measures X basis - ``"Y"`` — ``S^dagger H``, measures Y basis - ``"I"`` — no rotation (Z basis, identity) When ``shots`` is ``None``, the statevector simulator is used to return the exact probability distribution. When ``shots`` is given, the distribution is estimated from that many samples. Args: circuit: Quantum circuit (must contain MEASURE instructions). qubits: Indices of qubits to include. ``None`` means all qubits. basis: Per-qubit measurement basis. Accepts a single string such as ``"XYZ"`` (applied left-to-right to ``qubits``, with each character one of ``"I"`` / ``"X"`` / ``"Y"`` / ``"Z"``), a list of strings such as ``["X", "Y", "Z"]``, or ``None`` (default, Z basis for all qubits). shots: Number of measurement shots. ``None`` returns the exact probability vector from the statevector simulator. Returns: When ``shots`` is ``None``, a ``dict`` mapping each computational-basis outcome string (e.g. ``"01"``) to its probability. When ``shots`` is given, a ``dict`` mapping outcome strings to integer counts (frequency). Raises: ValueError: ``len(basis)`` does not match ``len(qubits)``. ValueError: ``shots`` is not a positive integer. ValueError: ``basis`` contains invalid characters. Example: >>> from uniqc.circuit_builder import Circuit >>> from uniqc.algorithmics.measurement import basis_rotation_measurement >>> c = Circuit() >>> c.h(0) # |0⟩ → (|0⟩+|1⟩)/√2 >>> c.cx(0, 1) # Bell state (|00⟩+|11⟩)/√2 >>> c.measure(0, 1) >>> # Measure qubit 0 in X basis, qubit 1 in Z basis >>> probs = basis_rotation_measurement(c, basis="XZ") >>> abs(probs["00"] - 0.5) < 1e-6 # P(0) in X basis for |+⟩ is 0.5 True """ n_qubits = circuit.max_qubit + 1 if qubits is None: qubits = list(range(n_qubits)) else: qubits = list(qubits) n = len(qubits) # Parse basis argument if basis is None: basis_strs: list[str] = ["Z"] * n elif isinstance(basis, str): basis_strs = list(basis.upper()) if len(basis_strs) != n: raise ValueError( f"basis string length ({len(basis_strs)}) must match " f"len(qubits) ({n})" ) elif isinstance(basis, list): if len(basis) != n: raise ValueError( f"len(basis) ({len(basis)}) must match len(qubits) ({n})" ) basis_strs = [b.upper() for b in basis] else: raise TypeError(f"basis must be str, list, or None, got {type(basis).__name__}") for b in basis_strs: if b not in ("I", "X", "Y", "Z"): raise ValueError( f"basis must only contain I/X/Y/Z, got: {b!r}" ) if shots is not None and (not isinstance(shots, int) or shots <= 0): raise ValueError(f"shots must be a positive integer, got {shots}") # Build rotation gate injection map per qubit index rot_gates: dict[int, list[str]] = {i: [] for i in range(n)} for i, b in enumerate(basis_strs): if b == "X": rot_gates[i].append(f"h q[{i}];") elif b == "Y": # Sdg then H maps Y eigenstates → Z eigenstates rot_gates[i].append(f"sdg q[{i}];") rot_gates[i].append(f"h q[{i}];") # Inject rotations before each MEASURE line lines = circuit.qasm.splitlines() new_lines: list[str] = [] for line in lines: stripped = line.strip() if stripped.startswith("measure "): left = stripped.split("->")[0].strip() qi = int(left.split("[")[1].split("]")[0]) for gate in rot_gates[qi]: new_lines.append(gate) new_lines.append(line) modified_qasm = "\n".join(new_lines) # Simulate sim = QASM_Simulator(least_qubit_remapping=False) if shots is None: probs = sim.simulate_pmeasure(modified_qasm) return {f"{i:0{n}b}": float(p) for i, p in enumerate(probs)} else: counts = sim.simulate_shots(modified_qasm, shots=shots) return {f"{k:0{n}b}": v for k, v in counts.items()}