Gyanateet Dutta
Fix Space loading: direct Streamlit, lazy imports, ReNova page, fix deps
9f8cf99
Raw
History Blame Contribute Delete
9.03 kB
"""Public exports and compatibility shims for qhybrid_kernels."""
import json
import math
import numpy as np
from .conversions import (
complex_matrix_to_ri,
complex_statevector_to_ri,
kraus_1q_to_ri,
ri_to_complex_matrix,
ri_to_complex_statevector,
)
from .noise import (
apply_kraus_1q_density_matrix,
apply_pauli_channel_statevector,
apply_correlated_pauli_noise_statevector,
apply_cnot_error_statevector,
expectation_value_pauli_string_py,
)
from .qiskit_adapter import (
kraus_1q_from_qiskit,
apply_qiskit_kraus_1q_to_density_matrix,
)
from .circuit import (
GateConverter,
QHYBRID_PAYLOAD_VERSION,
QhybridCircuitPayload,
QhybridGatePayload,
get_supported_qiskit_gates,
qiskit_to_qhybrid_json,
register_gate_converter,
)
try:
from .rust_kernels import (
execute_quantum_circuit,
apply_correlated_pauli_noise_statevector as _apply_correlated_pauli_noise_statevector,
apply_cnot_error_statevector as _apply_cnot_error_statevector,
expectation_value_pauli_string_py as _expectation_value_pauli_string_py,
QuantumCircuit,
)
apply_correlated_pauli_noise_statevector = _apply_correlated_pauli_noise_statevector
apply_cnot_error_statevector = _apply_cnot_error_statevector
expectation_value_pauli_string_py = _expectation_value_pauli_string_py
except ImportError:
QuantumCircuit = None
def _normalise_gate_name(gate_type) -> str:
if isinstance(gate_type, str):
return gate_type
if not isinstance(gate_type, dict) or len(gate_type) != 1:
raise TypeError("Invalid qhybrid gate payload")
return next(iter(gate_type.keys()))
def _normalise_gate_params(gate_type) -> list[float]:
if isinstance(gate_type, str):
return []
if not isinstance(gate_type, dict) or len(gate_type) != 1:
raise TypeError("Invalid qhybrid gate payload")
params = next(iter(gate_type.values()))
if isinstance(params, (list, tuple)):
return [float(v) for v in params]
return [float(params)]
def _coerce_gate_matrix(gate_name: str, params: list[float]) -> np.ndarray:
if gate_name == "I":
return np.array([[1.0, 0.0], [0.0, 1.0]], dtype=np.complex128)
if gate_name == "X":
return np.array([[0.0, 1.0], [1.0, 0.0]], dtype=np.complex128)
if gate_name == "Y":
return np.array([[0.0, -1j], [1j, 0.0]], dtype=np.complex128)
if gate_name == "Z":
return np.array([[1.0, 0.0], [0.0, -1.0]], dtype=np.complex128)
if gate_name == "H":
inv_root2 = 1.0 / math.sqrt(2.0)
return np.array(
[[inv_root2, inv_root2], [inv_root2, -inv_root2]],
dtype=np.complex128,
)
if gate_name == "S":
return np.array([[1.0, 0.0], [0.0, 1j]], dtype=np.complex128)
if gate_name == "Sdg":
return np.array([[1.0, 0.0], [0.0, -1j]], dtype=np.complex128)
if gate_name == "T":
return np.array([[1.0, 0.0], [0.0, np.exp(1j * np.pi / 4)]], dtype=np.complex128)
if gate_name == "Tdg":
return np.array(
[[1.0, 0.0], [0.0, np.exp(-1j * np.pi / 4)]],
dtype=np.complex128,
)
if gate_name == "RX":
theta = float(params[0])
c = math.cos(theta / 2.0)
s = math.sin(theta / 2.0)
return np.array([[c, -1j * s], [-1j * s, c]], dtype=np.complex128)
if gate_name == "RY":
theta = float(params[0])
c = math.cos(theta / 2.0)
s = math.sin(theta / 2.0)
return np.array([[c, -s], [s, c]], dtype=np.complex128)
if gate_name in {"RZ", "P"}:
theta = float(params[0])
return np.array(
[[np.exp(-1j * theta / 2.0), 0.0], [0.0, np.exp(1j * theta / 2.0)]],
dtype=np.complex128,
)
if gate_name == "U3":
if len(params) != 3:
raise ValueError("U3 gate expects 3 parameters")
theta, phi, lam = params
c = math.cos(theta / 2.0)
s = math.sin(theta / 2.0)
return np.array(
[
[c, -np.exp(1j * lam) * s],
[np.exp(1j * phi) * s, np.exp(1j * (phi + lam)) * c],
],
dtype=np.complex128,
)
raise ValueError(f"Unsupported qhybrid gate: {gate_name}")
def _apply_single_qubit(state: np.ndarray, matrix: np.ndarray, qubit: int) -> np.ndarray:
dim = state.shape[0]
for idx in range(dim):
if (idx >> qubit) & 1:
continue
pair_idx = idx | (1 << qubit)
a = state[idx]
b = state[pair_idx]
state[idx] = matrix[0, 0] * a + matrix[0, 1] * b
state[pair_idx] = matrix[1, 0] * a + matrix[1, 1] * b
return state
def _apply_cx(state: np.ndarray, control: int, target: int) -> np.ndarray:
for idx in range(state.shape[0]):
if ((idx >> control) & 1) == 1 and ((idx >> target) & 1) == 0:
partner = idx | (1 << target)
state[idx], state[partner] = state[partner], state[idx]
return state
def execute_quantum_circuit(circuit_json: str) -> np.ndarray:
payload = json.loads(circuit_json)
schema_version = payload.get("schema_version")
if schema_version is None:
raise ValueError("Missing circuit schema_version")
if schema_version != QHYBRID_PAYLOAD_VERSION:
raise ValueError(f"Unsupported circuit schema_version: {schema_version}")
n_qubits = int(payload["n_qubits"])
dim = 1 << n_qubits
state = np.zeros(dim, dtype=np.complex128)
state[0] = 1.0 + 0.0j
for gate in payload.get("gates", []):
gate_name = _normalise_gate_name(gate["gate_type"])
gate_params = _normalise_gate_params(gate["gate_type"])
qubits = list(gate["qubits"])
if gate_name in {"CX", "CNOT"}:
state = _apply_cx(state, qubits[0], qubits[1])
elif gate_name == "CY":
for idx in range(state.shape[0]):
if ((idx >> qubits[0]) & 1) == 1 and ((idx >> qubits[1]) & 1) == 0:
partner = idx | (1 << qubits[1])
y_matrix = _coerce_gate_matrix("Y", [])
a = state[idx]
b = state[partner]
state[idx] = y_matrix[0, 0] * a + y_matrix[0, 1] * b
state[partner] = y_matrix[1, 0] * a + y_matrix[1, 1] * b
elif gate_name == "CZ":
for idx in range(state.shape[0]):
if ((idx >> qubits[0]) & 1) == 1 and ((idx >> qubits[1]) & 1) == 1:
state[idx] = -state[idx]
# no-op for all other basis states
else:
matrix = _coerce_gate_matrix(gate_name, gate_params)
state = _apply_single_qubit(state, matrix, qubits[0])
return complex_statevector_to_ri(state)
__all__ = [
"complex_statevector_to_ri",
"ri_to_complex_statevector",
"complex_matrix_to_ri",
"ri_to_complex_matrix",
"kraus_1q_to_ri",
"apply_pauli_channel_statevector",
"apply_kraus_1q_density_matrix",
"apply_correlated_pauli_noise_statevector",
"apply_cnot_error_statevector",
"expectation_value_pauli_string_py",
"kraus_1q_from_qiskit",
"apply_qiskit_kraus_1q_to_density_matrix",
"GateConverter",
"QHYBRID_PAYLOAD_VERSION",
"QhybridCircuitPayload",
"QhybridGatePayload",
"get_supported_qiskit_gates",
"execute_quantum_circuit",
"register_gate_converter",
]
def _patch_qiskit_kraus_compat() -> None:
"""Patch qiskit Kraus constructor to accept NumPy Kraus stacks.
Newer Qiskit versions are stricter about Kraus input shape and may reject
a 3-D NumPy array directly. The project tests still use this legacy input
form in some places, so we add a small compatibility shim at import time.
"""
try:
from qiskit.quantum_info import Kraus
except Exception:
return
if getattr(Kraus, "_qhybrid_kernels_numpy_compat", False):
return
_original_init = Kraus.__init__
def _compatible_init(self, data, *args, **kwargs): # type: ignore[no-redef]
if (
isinstance(data, np.ndarray)
and data.ndim == 3
and data.shape[1:] == (2, 2)
):
data = [np.asarray(data[idx], dtype=np.complex128) for idx in range(data.shape[0])]
return _original_init(self, data, *args, **kwargs)
Kraus.__init__ = _compatible_init # type: ignore[assignment]
Kraus._qhybrid_kernels_numpy_compat = True # type: ignore[attr-defined]
_patch_qiskit_kraus_compat()