Spaces:
No application file
No application file
| #!/usr/bin/env python3 | |
| """ | |
| Copyright (c) 2025 Joshua Hendricks Cole (DBA: Corporation of Light). | |
| All Rights Reserved. PATENT PENDING. | |
| QuantumLabSimulator - Main quantum laboratory interface | |
| Wraps and extends existing quantum simulators with unified API | |
| """ | |
| import numpy as np | |
| import sys | |
| import os | |
| from typing import Dict, List, Tuple, Optional, Union, Callable, Any | |
| from dataclasses import dataclass | |
| from enum import Enum | |
| try: | |
| from core.base_lab import BaseLab | |
| except ImportError: | |
| # This is a fallback for script execution | |
| import sys | |
| import os | |
| sys.path.append(os.path.join(os.path.dirname(__file__), '..')) | |
| from core.base_lab import BaseLab | |
| # Import existing quantum simulators | |
| sys.path.append('/Users/noone/repos/consciousness/ech0_modules') | |
| try: | |
| from quantum_circuit_simulator import QuantumCircuitSimulator as CircuitSim | |
| from quantum_cognition import QuantumCognitionSystem | |
| SIMULATORS_AVAILABLE = True | |
| except ImportError: | |
| SIMULATORS_AVAILABLE = False | |
| print("[WARN] Existing quantum simulators not found, using fallback mode") | |
| class SimulationBackend(Enum): | |
| """Available simulation backends""" | |
| STATEVECTOR_EXACT = "statevector" # 1-30 qubits, exact | |
| TENSOR_NETWORK = "tensor_network" # 30-50 qubits, approximate | |
| MPS = "mps" # 30-50 qubits, Matrix Product State | |
| DENSITY_MATRIX = "density_matrix" # For mixed states | |
| COGNITION = "cognition" # Quantum-inspired cognition | |
| class SimulationConfig: | |
| """Configuration for quantum simulation""" | |
| num_qubits: int | |
| backend: SimulationBackend = SimulationBackend.STATEVECTOR_EXACT | |
| optimize_for_m4: bool = True | |
| max_memory_gb: float = 20.0 | |
| error_correction: bool = False | |
| noise_model: Optional[Dict] = None | |
| class QuantumLabSimulator(BaseLab): | |
| """ | |
| Unified quantum laboratory simulator. | |
| Features: | |
| - 30-qubit exact statevector simulation | |
| - Tensor network approximation for >30 qubits | |
| - Quantum chemistry integration | |
| - Materials science quantum calculations | |
| - Quantum sensor modeling | |
| - Cognition-inspired algorithms | |
| Integration: | |
| - Wraps existing CircuitSimulator (30-qubit exact) | |
| - Wraps QuantumCognition (quantum-inspired) | |
| - Extends with chemistry, materials, sensors | |
| ECH0 Usage Examples: | |
| ```python | |
| # Basic circuit simulation | |
| lab = QuantumLabSimulator(num_qubits=5) | |
| lab.h(0) # Hadamard on qubit 0 | |
| lab.cnot(0, 1) # Entangle qubits | |
| results = lab.measure_all() | |
| # Chemistry calculation | |
| from quantum_chemistry import Molecule | |
| h2 = Molecule.hydrogen_molecule(bond_length=0.74) | |
| energy = lab.chemistry.compute_ground_state_energy(h2) | |
| # Materials property | |
| energy_gap = lab.materials.compute_band_gap("silicon") | |
| # Quantum sensing | |
| sensitivity = lab.sensors.magnetometry_sensitivity(num_qubits=10) | |
| ``` | |
| """ | |
| def __init__( | |
| self, | |
| num_qubits: int = 5, | |
| backend: Union[str, SimulationBackend] = SimulationBackend.STATEVECTOR_EXACT, | |
| optimize_for_m4: bool = True, | |
| verbose: bool = True, | |
| config: Dict[str, Any] = None | |
| ): | |
| """ | |
| Initialize quantum laboratory. | |
| Args: | |
| num_qubits: Number of qubits (1-30 exact, 30-50 approximate) | |
| backend: Simulation backend to use (string or enum) | |
| optimize_for_m4: Use M4 Mac optimizations | |
| verbose: Print initialization info | |
| """ | |
| super().__init__(config) | |
| self.num_qubits = num_qubits | |
| if isinstance(backend, str): | |
| self.backend = SimulationBackend(backend) | |
| else: | |
| self.backend = backend | |
| self.optimize_for_m4 = optimize_for_m4 | |
| self.verbose = verbose | |
| # Select backend | |
| self._initialize_backend() | |
| # Initialize sub-systems (lazy loading) | |
| self._chemistry = None | |
| self._materials = None | |
| self._sensors = None | |
| self._cognition = None | |
| if verbose: | |
| self._print_initialization_summary() | |
| def run_experiment(self, experiment_spec: Dict[str, Any]) -> Dict[str, Any]: | |
| """ | |
| Run a quantum experiment. | |
| Example spec: | |
| { | |
| "experiment_type": "bell_pair", | |
| } | |
| { | |
| "experiment_type": "ghz_state", | |
| "num_qubits": 4 | |
| } | |
| { | |
| "experiment_type": "custom_circuit", | |
| "operations": [ | |
| {"gate": "h", "qubit": 0}, | |
| {"gate": "cnot", "control": 0, "target": 1} | |
| ] | |
| } | |
| """ | |
| exp_type = experiment_spec.get("experiment_type") | |
| if not exp_type: | |
| raise ValueError("'experiment_type' is required.") | |
| if exp_type == "bell_pair": | |
| self.reset() | |
| self.h(0).cnot(0, 1) | |
| return {"status": "completed", "probabilities": self.get_probabilities()} | |
| elif exp_type == "ghz_state": | |
| num_qubits = experiment_spec.get("num_qubits", self.num_qubits) | |
| if num_qubits != self.num_qubits: | |
| # Re-initialize if necessary, though not ideal | |
| self.__init__(num_qubits=num_qubits, backend=self.backend, optimize_for_m4=self.optimize_for_m4) | |
| self.reset() | |
| self.h(0) | |
| for i in range(num_qubits - 1): | |
| self.cnot(i, i + 1) | |
| return {"status": "completed", "probabilities": self.get_probabilities()} | |
| elif exp_type == "custom_circuit": | |
| self.reset() | |
| operations = experiment_spec.get("operations", []) | |
| for op in operations: | |
| gate = op.get("gate") | |
| if gate == "h": | |
| self.h(op["qubit"]) | |
| elif gate == "x": | |
| self.x(op["qubit"]) | |
| elif gate == "cnot": | |
| self.cnot(op["control"], op["target"]) | |
| # ... add other gates as needed | |
| return {"status": "completed", "probabilities": self.get_probabilities()} | |
| else: | |
| raise ValueError(f"Unknown experiment type: {exp_type}") | |
| def get_status(self) -> Dict[str, Any]: | |
| """Get information about current backend and state.""" | |
| status = self.get_backend_info() | |
| status["probabilities"] = self.get_probabilities() | |
| return status | |
| def _initialize_backend(self): | |
| """Initialize simulation backend""" | |
| if self.backend == SimulationBackend.STATEVECTOR_EXACT: | |
| if self.num_qubits > 30: | |
| print(f"[WARN] {self.num_qubits} qubits exceeds statevector limit (30)") | |
| print(f"[INFO] Switching to tensor network backend") | |
| self.backend = SimulationBackend.TENSOR_NETWORK | |
| self._initialize_tensor_network_backend() | |
| else: | |
| if SIMULATORS_AVAILABLE: | |
| self.circuit = CircuitSim(self.num_qubits, self.optimize_for_m4) | |
| else: | |
| self._initialize_fallback_backend() | |
| elif self.backend == SimulationBackend.TENSOR_NETWORK: | |
| self._initialize_tensor_network_backend() | |
| elif self.backend == SimulationBackend.MPS: | |
| self._initialize_mps_backend() | |
| elif self.backend == SimulationBackend.COGNITION: | |
| if SIMULATORS_AVAILABLE: | |
| self.cognition_system = QuantumCognitionSystem() | |
| else: | |
| print("[WARN] Quantum cognition system not available") | |
| def _initialize_tensor_network_backend(self): | |
| """Initialize tensor network approximation backend""" | |
| print(f"[INFO] Tensor network backend for {self.num_qubits} qubits") | |
| print(f"[INFO] Using Matrix Product State (MPS) approximation") | |
| # Simplified MPS representation | |
| # Each qubit represented as 2×D×D tensor (D = bond dimension) | |
| self.bond_dimension = min(64, 2**(self.num_qubits // 2)) | |
| self.mps_tensors = [] | |
| # Initialize MPS in |0⟩^n state | |
| for i in range(self.num_qubits): | |
| if i == 0: | |
| # First tensor: 2×1×D | |
| tensor = np.zeros((2, 1, self.bond_dimension), dtype=np.complex128) | |
| tensor[0, 0, 0] = 1.0 # |0⟩ state | |
| elif i == self.num_qubits - 1: | |
| # Last tensor: 2×D×1 | |
| tensor = np.zeros((2, self.bond_dimension, 1), dtype=np.complex128) | |
| tensor[0, 0, 0] = 1.0 | |
| else: | |
| # Middle tensors: 2×D×D | |
| tensor = np.zeros((2, self.bond_dimension, self.bond_dimension), dtype=np.complex128) | |
| tensor[0, 0, 0] = 1.0 | |
| self.mps_tensors.append(tensor) | |
| print(f"[INFO] MPS bond dimension: {self.bond_dimension}") | |
| print(f"[INFO] Memory usage: ~{self._estimate_mps_memory():.2f} GB") | |
| def _initialize_mps_backend(self): | |
| """Initialize pure MPS backend""" | |
| self._initialize_tensor_network_backend() | |
| def _initialize_fallback_backend(self): | |
| """Fallback implementation if simulators unavailable""" | |
| print("[INFO] Using fallback statevector implementation") | |
| self.dim = 2 ** self.num_qubits | |
| self.statevector = np.zeros(self.dim, dtype=np.complex128) | |
| self.statevector[0] = 1.0 + 0j | |
| def _estimate_mps_memory(self) -> float: | |
| """Estimate MPS memory usage in GB""" | |
| # Each tensor: 2 * D^2 * 16 bytes (complex128) | |
| tensor_size = 2 * self.bond_dimension**2 * 16 | |
| total_size = tensor_size * self.num_qubits | |
| return total_size / (1024**3) | |
| def _print_initialization_summary(self): | |
| """Print initialization summary""" | |
| print(f"\n{'='*60}") | |
| print(f"QULAB INFINITE - QUANTUM LABORATORY SIMULATOR") | |
| print(f"{'='*60}") | |
| print(f"\n⚛️ Configuration:") | |
| print(f" Qubits: {self.num_qubits}") | |
| print(f" Backend: {self.backend.value}") | |
| if self.backend == SimulationBackend.STATEVECTOR_EXACT: | |
| memory_gb = (2**self.num_qubits * 16) / (1024**3) | |
| print(f" Hilbert space: {2**self.num_qubits:,} dimensions") | |
| print(f" Memory: {memory_gb:.2f} GB") | |
| elif self.backend in [SimulationBackend.TENSOR_NETWORK, SimulationBackend.MPS]: | |
| print(f" Bond dimension: {self.bond_dimension}") | |
| print(f" Memory: ~{self._estimate_mps_memory():.2f} GB") | |
| print(f" M4 optimization: {'✅ Enabled' if self.optimize_for_m4 else '❌ Disabled'}") | |
| print(f"\n✅ Quantum laboratory initialized\n") | |
| # ========== GATE OPERATIONS (delegate to backend) ========== | |
| def h(self, qubit: int): | |
| """Apply Hadamard gate""" | |
| if hasattr(self, 'circuit'): | |
| self.circuit.h(qubit) | |
| elif hasattr(self, 'mps_tensors'): | |
| self._apply_mps_gate('H', qubit) | |
| elif hasattr(self, 'statevector'): | |
| gate = np.array([[1, 1], [1, -1]], dtype=np.complex128) / np.sqrt(2) | |
| self._apply_statevector_gate(gate, qubit) | |
| return self | |
| def x(self, qubit: int): | |
| """Apply Pauli-X gate""" | |
| if hasattr(self, 'circuit'): | |
| self.circuit.x(qubit) | |
| elif hasattr(self, 'mps_tensors'): | |
| self._apply_mps_gate('X', qubit) | |
| elif hasattr(self, 'statevector'): | |
| gate = np.array([[0, 1], [1, 0]], dtype=np.complex128) | |
| self._apply_statevector_gate(gate, qubit) | |
| return self | |
| def y(self, qubit: int): | |
| """Apply Pauli-Y gate""" | |
| if hasattr(self, 'circuit'): | |
| self.circuit.y(qubit) | |
| elif hasattr(self, 'mps_tensors'): | |
| self._apply_mps_gate('Y', qubit) | |
| elif hasattr(self, 'statevector'): | |
| gate = np.array([[0, -1j], [1j, 0]], dtype=np.complex128) | |
| self._apply_statevector_gate(gate, qubit) | |
| return self | |
| def z(self, qubit: int): | |
| """Apply Pauli-Z gate""" | |
| if hasattr(self, 'circuit'): | |
| self.circuit.z(qubit) | |
| elif hasattr(self, 'mps_tensors'): | |
| self._apply_mps_gate('Z', qubit) | |
| elif hasattr(self, 'statevector'): | |
| gate = np.array([[1, 0], [0, -1]], dtype=np.complex128) | |
| self._apply_statevector_gate(gate, qubit) | |
| return self | |
| def rx(self, qubit: int, theta: float): | |
| """Apply RX rotation""" | |
| if hasattr(self, 'circuit'): | |
| self.circuit.rx(qubit, theta) | |
| elif hasattr(self, 'mps_tensors'): | |
| self._apply_mps_rotation('RX', qubit, theta) | |
| elif hasattr(self, 'statevector'): | |
| gate = np.array([ | |
| [np.cos(theta / 2), -1j * np.sin(theta / 2)], | |
| [-1j * np.sin(theta / 2), np.cos(theta / 2)], | |
| ], dtype=np.complex128) | |
| self._apply_statevector_gate(gate, qubit) | |
| return self | |
| def ry(self, qubit: int, theta: float): | |
| """Apply RY rotation""" | |
| if hasattr(self, 'circuit'): | |
| self.circuit.ry(qubit, theta) | |
| elif hasattr(self, 'mps_tensors'): | |
| self._apply_mps_rotation('RY', qubit, theta) | |
| elif hasattr(self, 'statevector'): | |
| gate = np.array([ | |
| [np.cos(theta / 2), -np.sin(theta / 2)], | |
| [np.sin(theta / 2), np.cos(theta / 2)], | |
| ], dtype=np.complex128) | |
| self._apply_statevector_gate(gate, qubit) | |
| return self | |
| def rz(self, qubit: int, theta: float): | |
| """Apply RZ rotation""" | |
| if hasattr(self, 'circuit'): | |
| self.circuit.rz(qubit, theta) | |
| elif hasattr(self, 'mps_tensors'): | |
| self._apply_mps_rotation('RZ', qubit, theta) | |
| elif hasattr(self, 'statevector'): | |
| gate = np.array([ | |
| [np.exp(-1j * theta / 2), 0], | |
| [0, np.exp(1j * theta / 2)], | |
| ], dtype=np.complex128) | |
| self._apply_statevector_gate(gate, qubit) | |
| return self | |
| def cnot(self, control: int, target: int): | |
| """Apply CNOT gate""" | |
| if hasattr(self, 'circuit'): | |
| self.circuit.cnot(control, target) | |
| elif hasattr(self, 'mps_tensors'): | |
| self._apply_mps_two_qubit_gate('CNOT', control, target) | |
| elif hasattr(self, 'statevector'): | |
| self._apply_statevector_cnot(control, target) | |
| return self | |
| def cz(self, control: int, target: int): | |
| """Apply CZ gate""" | |
| if hasattr(self, 'circuit'): | |
| self.circuit.cz(control, target) | |
| elif hasattr(self, 'mps_tensors'): | |
| self._apply_mps_two_qubit_gate('CZ', control, target) | |
| elif hasattr(self, 'statevector'): | |
| self._apply_statevector_cz(control, target) | |
| return self | |
| # ========== MPS GATE OPERATIONS ========== | |
| def _apply_mps_gate(self, gate_name: str, qubit: int): | |
| """Apply single-qubit gate to MPS""" | |
| # Gate matrices | |
| gates = { | |
| 'H': np.array([[1, 1], [1, -1]], dtype=np.complex128) / np.sqrt(2), | |
| 'X': np.array([[0, 1], [1, 0]], dtype=np.complex128), | |
| 'Y': np.array([[0, -1j], [1j, 0]], dtype=np.complex128), | |
| 'Z': np.array([[1, 0], [0, -1]], dtype=np.complex128), | |
| } | |
| gate = gates[gate_name] | |
| # Apply gate to MPS tensor at position 'qubit' | |
| # Tensor shape: [physical_dim=2, left_bond, right_bond] | |
| tensor = self.mps_tensors[qubit] | |
| new_tensor = np.einsum('ij,jkl->ikl', gate, tensor) | |
| self.mps_tensors[qubit] = new_tensor | |
| def _apply_mps_rotation(self, gate_name: str, qubit: int, theta: float): | |
| """Apply rotation gate to MPS""" | |
| if gate_name == 'RX': | |
| gate = np.array([ | |
| [np.cos(theta/2), -1j*np.sin(theta/2)], | |
| [-1j*np.sin(theta/2), np.cos(theta/2)] | |
| ], dtype=np.complex128) | |
| elif gate_name == 'RY': | |
| gate = np.array([ | |
| [np.cos(theta/2), -np.sin(theta/2)], | |
| [np.sin(theta/2), np.cos(theta/2)] | |
| ], dtype=np.complex128) | |
| elif gate_name == 'RZ': | |
| gate = np.array([ | |
| [np.exp(-1j*theta/2), 0], | |
| [0, np.exp(1j*theta/2)] | |
| ], dtype=np.complex128) | |
| tensor = self.mps_tensors[qubit] | |
| new_tensor = np.einsum('ij,jkl->ikl', gate, tensor) | |
| self.mps_tensors[qubit] = new_tensor | |
| def _apply_mps_two_qubit_gate(self, gate_name: str, q1: int, q2: int): | |
| """ | |
| Apply a two-qubit gate to adjacent qubits in an MPS. | |
| This implementation uses tensor contraction followed by SVD. | |
| """ | |
| # Ensure qubits are adjacent for simplicity | |
| if abs(q1 - q2) != 1: | |
| raise NotImplementedError("Two-qubit gates are only supported for adjacent qubits in this MPS implementation.") | |
| # Ensure q1 is the lower index | |
| if q1 > q2: | |
| q1, q2 = q2, q1 | |
| # Get the gate matrix | |
| if gate_name == 'CNOT': | |
| gate = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]], dtype=np.complex128).reshape(2, 2, 2, 2) | |
| elif gate_name == 'CZ': | |
| gate = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]], dtype=np.complex128).reshape(2, 2, 2, 2) | |
| else: | |
| raise ValueError(f"Unknown two-qubit gate: {gate_name}") | |
| # 1. Contract the two tensors | |
| tensor1 = self.mps_tensors[q1] | |
| tensor2 = self.mps_tensors[q2] | |
| # Shapes: (phys1, left1, right1), (phys2, left2, right2) | |
| # Contract right bond of tensor1 with left bond of tensor2 | |
| combined_tensor = np.einsum('pli,qij->pqlj', tensor1, tensor2) | |
| # New shape: (phys1, phys2, left1, right2) | |
| # 2. Apply the gate | |
| # Gate shape: (out1, out2, in1, in2) | |
| # Combined tensor shape: (in1, in2, left1, right2) | |
| # np.einsum notation for clarity: | |
| # ijkl: gate indices, klmn: combined_tensor indices | |
| # We contract gate indices kl with tensor indices kl (phys1, phys2) | |
| # Resulting tensor has shape (i, j, m, n) -> (out1, out2, left1, right2) | |
| result_tensor = np.einsum('ijkl,klmn->ijmn', gate, combined_tensor) | |
| # 3. Reshape for SVD | |
| # Combine (out1, left1) and (out2, right2) to form a matrix | |
| # Shape: (out1*left1, out2*right2) | |
| left_dim = result_tensor.shape[0] * result_tensor.shape[2] | |
| right_dim = result_tensor.shape[1] * result_tensor.shape[3] | |
| reshaped_tensor = result_tensor.transpose(0, 2, 1, 3).reshape(left_dim, right_dim) | |
| # 4. SVD and truncate | |
| U, S, Vh = np.linalg.svd(reshaped_tensor, full_matrices=False) | |
| # Truncate to the original bond dimension | |
| new_bond_dim = min(len(S), self.bond_dimension) | |
| U = U[:, :new_bond_dim] | |
| S = np.diag(S[:new_bond_dim]) | |
| Vh = Vh[:new_bond_dim, :] | |
| # 5. Reshape back into two tensors | |
| # New tensor for q1 | |
| new_tensor1 = U.reshape(tensor1.shape[0], tensor1.shape[1], new_bond_dim) | |
| # New tensor for q2 (needs to absorb S and Vh) | |
| # First, combine S and Vh | |
| svh = S @ Vh | |
| # Reshape into tensor form | |
| new_tensor2 = svh.reshape(new_bond_dim, tensor2.shape[0], tensor2.shape[2]) | |
| # Transpose to match our (phys, left, right) convention | |
| new_tensor2 = new_tensor2.transpose(1, 0, 2) | |
| # Update the MPS chain | |
| self.mps_tensors[q1] = new_tensor1 | |
| self.mps_tensors[q2] = new_tensor2 | |
| def _apply_statevector_gate(self, gate: np.ndarray, qubit: int): | |
| """Apply a single-qubit gate directly to the fallback statevector.""" | |
| if not hasattr(self, 'statevector'): | |
| return | |
| mask = 1 << (self.num_qubits - 1 - qubit) | |
| new_state = self.statevector.copy() | |
| for idx in range(self.dim): | |
| if idx & mask: | |
| continue # Handle paired basis states in the branch where bit = 0 | |
| partner = idx | mask | |
| amp0 = self.statevector[idx] | |
| amp1 = self.statevector[partner] | |
| new_state[idx] = gate[0, 0] * amp0 + gate[0, 1] * amp1 | |
| new_state[partner] = gate[1, 0] * amp0 + gate[1, 1] * amp1 | |
| self.statevector = new_state | |
| def _apply_statevector_cnot(self, control: int, target: int): | |
| """Apply a CNOT gate directly to the fallback statevector.""" | |
| if not hasattr(self, 'statevector'): | |
| return | |
| control_mask = 1 << (self.num_qubits - 1 - control) | |
| target_mask = 1 << (self.num_qubits - 1 - target) | |
| original = self.statevector.copy() | |
| for idx in range(self.dim): | |
| if idx & control_mask: | |
| toggled = idx ^ target_mask | |
| self.statevector[idx] = original[toggled] | |
| else: | |
| self.statevector[idx] = original[idx] | |
| def _apply_statevector_cz(self, control: int, target: int): | |
| """Apply a CZ gate directly to the fallback statevector.""" | |
| if not hasattr(self, 'statevector'): | |
| return | |
| control_mask = 1 << (self.num_qubits - 1 - control) | |
| target_mask = 1 << (self.num_qubits - 1 - target) | |
| for idx in range(self.dim): | |
| if (idx & control_mask) and (idx & target_mask): | |
| self.statevector[idx] *= -1.0 | |
| # ========== MEASUREMENT ========== | |
| def measure(self, qubit: int) -> int: | |
| """Measure single qubit""" | |
| if hasattr(self, 'circuit'): | |
| return self.circuit.measure(qubit) | |
| elif hasattr(self, 'mps_tensors'): | |
| return self._measure_mps(qubit) | |
| elif hasattr(self, 'statevector'): | |
| mask = 1 << (self.num_qubits - 1 - qubit) | |
| prob_one = sum( | |
| abs(self.statevector[idx]) ** 2 | |
| for idx in range(self.dim) | |
| if idx & mask | |
| ) | |
| return int(prob_one >= 0.5) | |
| else: | |
| return 0 | |
| def measure_all(self) -> List[int]: | |
| """Measure all qubits""" | |
| return [self.measure(i) for i in range(self.num_qubits)] | |
| def _measure_mps(self, qubit: int) -> int: | |
| """Measure qubit in MPS representation""" | |
| tensor = self.mps_tensors[qubit] | |
| amp_zero = np.linalg.norm(tensor[0]) ** 2 | |
| amp_one = np.linalg.norm(tensor[1]) ** 2 | |
| total = amp_zero + amp_one | |
| if total == 0: | |
| return 0 | |
| prob_one = amp_one / total | |
| return int(prob_one >= 0.5) | |
| def get_probabilities(self) -> Dict[str, float]: | |
| """Get probability distribution""" | |
| if hasattr(self, 'circuit'): | |
| return self.circuit.get_probabilities() | |
| elif hasattr(self, 'statevector'): | |
| probs = {} | |
| for i in range(self.dim): | |
| bitstring = format(i, f'0{self.num_qubits}b') | |
| prob = abs(self.statevector[i])**2 | |
| if prob > 1e-10: | |
| probs[bitstring] = prob | |
| return probs | |
| else: | |
| # MPS: approximate for the first few qubits to verify entanglement | |
| # Contract first two tensors to check Bell state | |
| if self.num_qubits >= 2: | |
| t0 = self.mps_tensors[0] | |
| t1 = self.mps_tensors[1] | |
| # Contract the shared bond dimension | |
| combined = np.einsum('pli,qij->pqlj', t0, t1) # p,q = physical; l,j = outer bonds | |
| # Trace out the outer bond dimensions to get the reduced density matrix for first 2 qubits | |
| density_matrix = np.einsum('pqlj,rslj->pqrs', combined, combined.conj()) | |
| probs = {} | |
| for i in range(2): | |
| for j in range(2): | |
| prob = np.abs(density_matrix[i,j,i,j]) | |
| if prob > 1e-9: | |
| # This only gives diagonal elements, which is what we need for probabilities | |
| bitstring = f"{i}{j}" | |
| probs[bitstring] = prob | |
| # Normalize because we traced out the rest of the chain | |
| total_prob = sum(probs.values()) | |
| if total_prob > 0: | |
| for k in probs: | |
| probs[k] /= total_prob | |
| return probs | |
| return {"0" * self.num_qubits: 1.0} # Placeholder for < 2 qubits | |
| # ========== ADVANCED FEATURES ========== | |
| def expectation_value(self, observable: str) -> float: | |
| """ | |
| Compute expectation value of observable. | |
| Args: | |
| observable: Pauli string like 'Z0' or 'X0Y1Z2' | |
| Returns: | |
| Expectation value <ψ|O|ψ> | |
| """ | |
| if hasattr(self, 'circuit'): | |
| # Save current state | |
| original_state = self.circuit.get_statevector() | |
| # Parse observable and compute expectation | |
| # Simplified: just return 0.0 for now | |
| # Full implementation would apply observable and compute <ψ|O|ψ> | |
| return 0.0 | |
| else: | |
| return 0.0 | |
| def fidelity(self, target_state: np.ndarray) -> float: | |
| """ | |
| Compute fidelity with target state. | |
| F = |<ψ|φ>|² | |
| """ | |
| if hasattr(self, 'circuit'): | |
| current = self.circuit.get_statevector() | |
| overlap = np.abs(np.vdot(current, target_state))**2 | |
| return overlap | |
| return 0.0 | |
| # ========== SUBSYSTEM PROPERTIES ========== | |
| def chemistry(self): | |
| """Access quantum chemistry module""" | |
| if self._chemistry is None: | |
| import quantum_chemistry | |
| self._chemistry = quantum_chemistry.QuantumChemistry(self) | |
| return self._chemistry | |
| def materials(self): | |
| """Access quantum materials module""" | |
| if self._materials is None: | |
| import quantum_materials | |
| self._materials = quantum_materials.QuantumMaterials(self) | |
| return self._materials | |
| def sensors(self): | |
| """Access quantum sensors module""" | |
| if self._sensors is None: | |
| import quantum_sensors | |
| self._sensors = quantum_sensors.QuantumSensors(self) | |
| return self._sensors | |
| # ========== UTILITY METHODS ========== | |
| def reset(self): | |
| """Reset quantum state to |0⟩^n""" | |
| if hasattr(self, 'circuit'): | |
| self.circuit = CircuitSim(self.num_qubits, self.optimize_for_m4) | |
| elif hasattr(self, 'statevector'): | |
| self.statevector = np.zeros(self.dim, dtype=np.complex128) | |
| self.statevector[0] = 1.0 + 0j | |
| elif hasattr(self, 'mps_tensors'): | |
| self._initialize_mps_backend() | |
| return self | |
| def print_state(self, top_n: int = 10): | |
| """Print quantum state""" | |
| if hasattr(self, 'circuit'): | |
| self.circuit.print_state(top_n) | |
| else: | |
| print(f"\nQuantum State (backend: {self.backend.value}):") | |
| probs = self.get_probabilities() | |
| sorted_probs = sorted(probs.items(), key=lambda x: x[1], reverse=True) | |
| for i, (state, prob) in enumerate(sorted_probs[:top_n]): | |
| print(f" |{state}⟩: {prob*100:.2f}%") | |
| def get_backend_info(self) -> Dict: | |
| """Get information about current backend""" | |
| info = { | |
| "backend": self.backend.value, | |
| "num_qubits": self.num_qubits, | |
| "optimize_for_m4": self.optimize_for_m4 | |
| } | |
| if self.backend == SimulationBackend.STATEVECTOR_EXACT: | |
| info["hilbert_dim"] = 2**self.num_qubits | |
| info["memory_gb"] = (2**self.num_qubits * 16) / (1024**3) | |
| elif self.backend in [SimulationBackend.TENSOR_NETWORK, SimulationBackend.MPS]: | |
| info["bond_dimension"] = self.bond_dimension | |
| info["memory_gb"] = self._estimate_mps_memory() | |
| return info | |
| # ========== CONVENIENCE FUNCTIONS ========== | |
| def create_bell_pair(verbose: bool = False) -> QuantumLabSimulator: | |
| """Create Bell state (maximally entangled pair)""" | |
| lab = QuantumLabSimulator(num_qubits=2, verbose=verbose) | |
| lab.h(0).cnot(0, 1) | |
| return lab | |
| def create_ghz_state(num_qubits: int, verbose: bool = False) -> QuantumLabSimulator: | |
| """Create GHZ state (maximally entangled N qubits)""" | |
| lab = QuantumLabSimulator(num_qubits=num_qubits, verbose=verbose) | |
| lab.h(0) | |
| for i in range(num_qubits - 1): | |
| lab.cnot(i, i + 1) | |
| return lab | |
| def create_w_state(num_qubits: int, verbose: bool = False) -> QuantumLabSimulator: | |
| """Create W state (symmetric superposition)""" | |
| lab = QuantumLabSimulator(num_qubits=num_qubits, verbose=verbose) | |
| # W state creation circuit (simplified) | |
| # Full implementation requires specific angle rotations | |
| lab.h(0) | |
| for i in range(1, num_qubits): | |
| lab.cnot(0, i) | |
| return lab | |
| # ========== DEMO ========== | |
| if __name__ == "__main__": | |
| print("\n" + "="*60) | |
| print("QULAB INFINITE - QUANTUM LABORATORY DEMONSTRATION") | |
| print("="*60) | |
| # Demo 1: Small circuit (statevector) | |
| print("\n\n1️⃣ STATEVECTOR SIMULATION (5 qubits)") | |
| lab5 = QuantumLabSimulator(num_qubits=5) | |
| lab5.h(0).cnot(0, 1).cnot(1, 2) | |
| lab5.print_state() | |
| # Demo 2: Large circuit (tensor network) | |
| print("\n\n2️⃣ TENSOR NETWORK SIMULATION (35 qubits)") | |
| lab35 = QuantumLabSimulator( | |
| num_qubits=35, | |
| backend=SimulationBackend.TENSOR_NETWORK, | |
| verbose=False | |
| ) | |
| lab35.h(0).cnot(0, 1) | |
| print(f" ✅ 35-qubit circuit operational with MPS") | |
| print(f" Memory usage: ~{lab35._estimate_mps_memory():.2f} GB") | |
| # Verify entanglement | |
| probs = lab35.get_probabilities() | |
| print(f" Probabilities after CNOT(0,1): {probs}") | |
| # Demo 3: Bell state | |
| print("\n\n3️⃣ BELL STATE GENERATION") | |
| bell = create_bell_pair(verbose=False) | |
| bell.print_state() | |
| # Demo 4: Backend info | |
| print("\n\n4️⃣ BACKEND INFORMATION") | |
| info = lab5.get_backend_info() | |
| print(f" Backend: {info['backend']}") | |
| print(f" Qubits: {info['num_qubits']}") | |
| print(f" Hilbert space: {info.get('hilbert_dim', 'N/A')}") | |
| print(f" Memory: {info.get('memory_gb', 0):.2f} GB") | |
| print("\n\n✅ Quantum laboratory demonstration complete!") | |
| print(" Ready for chemistry, materials, and sensor simulations") | |