| """Pure-Python compatibility layer for qhybrid_kernels Rust extensions.""" |
|
|
| from __future__ import annotations |
|
|
| import numpy as np |
|
|
| from .conversions import complex_statevector_to_ri, ri_to_complex_matrix, ri_to_complex_statevector |
|
|
|
|
| class QuantumCircuit: |
| """Placeholder class to satisfy optional extension imports.""" |
|
|
|
|
| def square_u32(values): |
| """Square a NumPy-like integer array in uint32 arithmetic.""" |
| return np.asarray(values, dtype=np.uint32) ** 2 |
|
|
|
|
| def _to_complex_state(psi_ri): |
| return ri_to_complex_statevector(psi_ri) |
|
|
|
|
| def _to_complex_kraus(kraus_ri): |
| kraus = np.asarray(kraus_ri, dtype=np.float64) |
| if kraus.ndim == 4 and kraus.shape[-1] == 2: |
| return kraus[..., 0] + 1j * kraus[..., 1] |
| return np.asarray(kraus, dtype=np.complex128) |
|
|
|
|
| def _apply_single_qubit_gate(state: np.ndarray, matrix: np.ndarray, qubit: int) -> np.ndarray: |
| out = state.copy() |
| dim = out.shape[0] |
| for idx in range(dim): |
| if (idx >> qubit) & 1: |
| continue |
| partner = idx | (1 << qubit) |
| a = out[idx] |
| b = out[partner] |
| out[idx] = matrix[0, 0] * a + matrix[0, 1] * b |
| out[partner] = matrix[1, 0] * a + matrix[1, 1] * b |
| return out |
|
|
|
|
| def _pauli_matrix(gate_index: int): |
| if gate_index == 0: |
| return np.eye(2, dtype=np.complex128) |
| if gate_index == 1: |
| return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=np.complex128) |
| if gate_index == 2: |
| return np.array([[0.0, -1j], [1j, 0.0]], dtype=np.complex128) |
| if gate_index == 3: |
| return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=np.complex128) |
| raise ValueError("unsupported_pauli_index") |
|
|
|
|
| def _single_kraus_step_density(rho: np.ndarray, op: np.ndarray, target: int, n_qubits: int) -> np.ndarray: |
| dim = rho.shape[0] |
| out = np.zeros_like(rho) |
| for i in range(dim): |
| ti = (i >> target) & 1 |
| rest_i = i & ~(1 << target) |
| for j in range(dim): |
| tj = (j >> target) & 1 |
| rest_j = j & ~(1 << target) |
| if rest_i != rest_j: |
| continue |
|
|
| value = 0.0 + 0.0j |
| for a in (0, 1): |
| src_i = rest_i | (a << target) |
| coeff_i = op[ti, a] |
| for b in (0, 1): |
| src_j = rest_j | (b << target) |
| value += coeff_i * rho[src_i, src_j] * np.conj(op[tj, b]) |
| out[i, j] = value |
| return out |
|
|
|
|
| def apply_pauli_channel_statevector( |
| psi_ri: np.ndarray, |
| n_qubits: int, |
| target_qubit: int, |
| probs: np.ndarray, |
| seed: int = 0, |
| ) -> np.ndarray: |
| psi = _to_complex_state(psi_ri) |
| probs_arr = np.asarray(probs, dtype=np.float64).reshape(-1) |
| if probs_arr.size < 4: |
| raise ValueError("probs must contain four probabilities for I/X/Y/Z") |
|
|
| total = float(np.sum(probs_arr)) |
| if total <= 0.0: |
| gate = _pauli_matrix(0) |
| else: |
| normalized = probs_arr / total |
| gate_idx = int(np.random.default_rng(seed).choice(4, p=normalized)) |
| gate = _pauli_matrix(gate_idx) |
|
|
| out = _apply_single_qubit_gate(psi, gate, int(target_qubit)) |
| return complex_statevector_to_ri(out) |
|
|
|
|
| def apply_kraus_1q_density_matrix( |
| rho_ri: np.ndarray, |
| n_qubits: int, |
| target_qubit: int, |
| kraus_ri: np.ndarray, |
| ) -> np.ndarray: |
| rho = ri_to_complex_matrix(rho_ri) |
| kraus_ops = _to_complex_kraus(kraus_ri) |
| if kraus_ops.ndim != 3 or kraus_ops.shape[1:] != (2, 2): |
| raise ValueError("kraus_ri must have shape (k, 2, 2, 2)") |
|
|
| dim = 1 << int(n_qubits) |
| if rho.shape != (dim, dim): |
| rho = rho.reshape(dim, dim) |
|
|
| out = np.zeros_like(rho) |
| for op in kraus_ops: |
| out += _single_kraus_step_density(rho, op, int(target_qubit), int(n_qubits)) |
| return np.stack((out.real, out.imag), axis=-1) |
|
|
|
|
| def apply_correlated_pauli_noise_statevector( |
| psi_ri: np.ndarray, |
| n_qubits: int, |
| error_probs: np.ndarray, |
| seed: int, |
| ) -> np.ndarray: |
| |
| return np.asarray(psi_ri, dtype=np.float64) |
|
|
|
|
| def apply_cnot_error_statevector( |
| psi_ri: np.ndarray, |
| n_qubits: int, |
| control: int, |
| target: int, |
| error_prob: float, |
| seed: int, |
| ) -> np.ndarray: |
| psi = _to_complex_state(psi_ri) |
| if error_prob <= 0.0: |
| return complex_statevector_to_ri(psi) |
| if error_prob >= 1.0: |
| return complex_statevector_to_ri(_apply_single_qubit_gate(psi, np.array([[0, 1], [1, 0]], dtype=np.complex128), int(target))) |
|
|
| rng = np.random.default_rng(seed) |
| if rng.random() > error_prob: |
| return complex_statevector_to_ri(psi) |
|
|
| |
| out = psi.copy() |
| x = np.array([[0, 1], [1, 0]], dtype=np.complex128) |
| for idx in range(out.shape[0]): |
| if ((idx >> control) & 1) == 1 and ((idx >> target) & 1) == 0: |
| partner = idx | (1 << target) |
| out[idx], out[partner] = out[partner], out[idx] |
| return complex_statevector_to_ri(out) |
|
|
|
|
| def expectation_value_pauli_string_py(state_ri: np.ndarray, pauli_string: str) -> float: |
| |
| |
| if pauli_string in ("", "I", "i"): |
| return 1.0 |
| |
| return 0.0 |
|
|
|
|