Spaces:
Running
Running
| """ | |
| Expressibility Score - Evaluates how well variational circuits can cover the Hilbert space. | |
| Research-level metric for Quantum Machine Learning (QML) applications. | |
| """ | |
| from typing import Any | |
| import numpy as np | |
| import math | |
| def score_expressibility( | |
| circuit_data: dict[str, Any], | |
| num_samples: int = 1000 | |
| ) -> dict[str, Any]: | |
| """ | |
| Score the expressibility of a variational quantum circuit. | |
| Expressibility measures how uniformly a parameterized circuit can sample | |
| from the space of quantum states. Higher expressibility = better for QML. | |
| Based on: "Expressibility and Entangling Capability of Parameterized | |
| Quantum Circuits for Hybrid Quantum-Classical Algorithms" | |
| Args: | |
| circuit_data: Circuit dictionary (should be parameterized) | |
| num_samples: Number of random parameter samples for estimation | |
| Returns: | |
| Expressibility analysis | |
| """ | |
| gates = circuit_data.get("gates", []) | |
| num_qubits = circuit_data.get("num_qubits", 0) | |
| parameters = circuit_data.get("parameters", []) | |
| # Analyze circuit structure | |
| structure_analysis = _analyze_ansatz_structure(gates, num_qubits) | |
| # Calculate theoretical expressibility bounds | |
| num_params = len(parameters) if parameters else structure_analysis["estimated_parameters"] | |
| # Expressibility capacity | |
| # Maximum expressibility requires 2^(2n) - 1 parameters for n qubits | |
| # (full SU(2^n) coverage) | |
| max_params_needed = 4 ** num_qubits - 1 | |
| param_sufficiency = min(1.0, num_params / max_params_needed) if max_params_needed > 0 else 0 | |
| # Entangling capability analysis | |
| entangling_score = _calculate_entangling_capability(gates, num_qubits) | |
| # Estimate expressibility score (0-1) | |
| # Based on: parameter count, entangling gates, layer structure | |
| expressibility_score = _estimate_expressibility( | |
| num_params, | |
| num_qubits, | |
| structure_analysis, | |
| entangling_score | |
| ) | |
| # Meyer-Wallach entanglement measure estimate | |
| mw_measure = _estimate_meyer_wallach(structure_analysis, num_qubits) | |
| # Classification | |
| if expressibility_score > 0.8: | |
| classification = "Highly Expressive" | |
| recommendation = "Suitable for complex learning tasks" | |
| elif expressibility_score > 0.5: | |
| classification = "Moderately Expressive" | |
| recommendation = "Good for moderate complexity problems" | |
| elif expressibility_score > 0.2: | |
| classification = "Low Expressibility" | |
| recommendation = "May struggle with complex patterns" | |
| else: | |
| classification = "Very Low Expressibility" | |
| recommendation = "Consider adding more parameters or entangling gates" | |
| return { | |
| "expressibility_score": round(expressibility_score, 4), | |
| "classification": classification, | |
| "recommendation": recommendation, | |
| "analysis": { | |
| "num_parameters": num_params, | |
| "max_parameters_for_full_coverage": max_params_needed, | |
| "parameter_sufficiency": round(param_sufficiency, 4), | |
| "entangling_capability": round(entangling_score, 4), | |
| "estimated_meyer_wallach": round(mw_measure, 4), | |
| }, | |
| "structure": { | |
| "num_layers": structure_analysis["num_layers"], | |
| "entangling_gates_per_layer": structure_analysis["entangling_per_layer"], | |
| "rotation_gates_per_layer": structure_analysis["rotations_per_layer"], | |
| "entanglement_pattern": structure_analysis["entanglement_pattern"], | |
| }, | |
| "qml_suitability": { | |
| "classification_tasks": "Good" if expressibility_score > 0.3 else "Limited", | |
| "regression_tasks": "Good" if expressibility_score > 0.4 else "Limited", | |
| "generative_tasks": "Good" if expressibility_score > 0.6 else "Limited", | |
| }, | |
| } | |
| def _analyze_ansatz_structure( | |
| gates: list[dict[str, Any]], | |
| num_qubits: int | |
| ) -> dict[str, Any]: | |
| """Analyze the structure of a variational ansatz.""" | |
| rotation_gates = {"rx", "ry", "rz", "u", "u1", "u2", "u3", "p"} | |
| entangling_gates = {"cx", "cnot", "cz", "cy", "swap", "crx", "cry", "crz", "cp"} | |
| rotation_count = 0 | |
| entangling_count = 0 | |
| barriers = 0 | |
| estimated_params = 0 | |
| # Track layers (separated by barriers or pattern) | |
| current_layer_rotations = 0 | |
| current_layer_entangling = 0 | |
| layers: list[dict[str, int]] = [] | |
| # Entanglement pattern detection | |
| entangling_pairs: list[tuple[int, int]] = [] | |
| for gate in gates: | |
| name = gate.get("name", "").lower() | |
| qubits = gate.get("qubits", []) | |
| if name == "barrier": | |
| if current_layer_rotations > 0 or current_layer_entangling > 0: | |
| layers.append({ | |
| "rotations": current_layer_rotations, | |
| "entangling": current_layer_entangling | |
| }) | |
| current_layer_rotations = 0 | |
| current_layer_entangling = 0 | |
| barriers += 1 | |
| continue | |
| if name in rotation_gates: | |
| rotation_count += 1 | |
| current_layer_rotations += 1 | |
| # Estimate parameters | |
| if name in ("rx", "ry", "rz", "p", "u1"): | |
| estimated_params += 1 | |
| elif name in ("u2",): | |
| estimated_params += 2 | |
| elif name in ("u", "u3"): | |
| estimated_params += 3 | |
| elif name in entangling_gates: | |
| entangling_count += 1 | |
| current_layer_entangling += 1 | |
| if len(qubits) == 2: | |
| entangling_pairs.append((qubits[0], qubits[1])) | |
| # Add final layer if exists | |
| if current_layer_rotations > 0 or current_layer_entangling > 0: | |
| layers.append({ | |
| "rotations": current_layer_rotations, | |
| "entangling": current_layer_entangling | |
| }) | |
| # Detect entanglement pattern | |
| pattern = _detect_entanglement_pattern(entangling_pairs, num_qubits) | |
| num_layers = len(layers) if layers else 1 | |
| return { | |
| "rotation_count": rotation_count, | |
| "entangling_count": entangling_count, | |
| "estimated_parameters": estimated_params, | |
| "num_layers": num_layers, | |
| "rotations_per_layer": rotation_count / num_layers if num_layers > 0 else 0, | |
| "entangling_per_layer": entangling_count / num_layers if num_layers > 0 else 0, | |
| "entanglement_pattern": pattern, | |
| "layers_detail": layers, | |
| } | |
| def _detect_entanglement_pattern( | |
| pairs: list[tuple[int, int]], | |
| num_qubits: int | |
| ) -> str: | |
| """Detect the entanglement pattern.""" | |
| if not pairs: | |
| return "none" | |
| # Check for linear pattern | |
| linear_pairs = [(i, i+1) for i in range(num_qubits - 1)] | |
| linear_pairs_rev = [(i+1, i) for i in range(num_qubits - 1)] | |
| if all(p in pairs or (p[1], p[0]) in pairs for p in linear_pairs): | |
| # Check if circular | |
| if (0, num_qubits - 1) in pairs or (num_qubits - 1, 0) in pairs: | |
| return "circular" | |
| return "linear" | |
| # Check for all-to-all | |
| all_pairs = [(i, j) for i in range(num_qubits) for j in range(i+1, num_qubits)] | |
| if all(p in pairs or (p[1], p[0]) in pairs for p in all_pairs): | |
| return "full" | |
| # Check for alternating | |
| even_pairs = [(i, i+1) for i in range(0, num_qubits - 1, 2)] | |
| odd_pairs = [(i, i+1) for i in range(1, num_qubits - 1, 2)] | |
| if all(p in pairs or (p[1], p[0]) in pairs for p in even_pairs): | |
| if all(p in pairs or (p[1], p[0]) in pairs for p in odd_pairs): | |
| return "alternating" | |
| return "custom" | |
| def _calculate_entangling_capability( | |
| gates: list[dict[str, Any]], | |
| num_qubits: int | |
| ) -> float: | |
| """Calculate entangling capability score.""" | |
| entangling_gates = {"cx", "cnot", "cz", "cy", "swap", "crx", "cry", "crz", "cp"} | |
| entangling_count = 0 | |
| qubit_pairs_entangled: set[tuple[int, int]] = set() | |
| for gate in gates: | |
| name = gate.get("name", "").lower() | |
| qubits = gate.get("qubits", []) | |
| if name in entangling_gates and len(qubits) >= 2: | |
| entangling_count += 1 | |
| pair = (min(qubits[0], qubits[1]), max(qubits[0], qubits[1])) | |
| qubit_pairs_entangled.add(pair) | |
| # Maximum possible pairs | |
| max_pairs = num_qubits * (num_qubits - 1) // 2 | |
| # Coverage score | |
| coverage = len(qubit_pairs_entangled) / max_pairs if max_pairs > 0 else 0 | |
| # Depth score (more entangling layers = more capability) | |
| depth_factor = min(1.0, entangling_count / (num_qubits * 2)) | |
| return (coverage + depth_factor) / 2 | |
| def _estimate_expressibility( | |
| num_params: int, | |
| num_qubits: int, | |
| structure: dict[str, Any], | |
| entangling_score: float | |
| ) -> float: | |
| """Estimate expressibility score.""" | |
| # Base score from parameter count | |
| dim = 4 ** num_qubits | |
| param_score = min(1.0, num_params / (dim / 4)) | |
| # Structure bonus | |
| structure_score = 0.0 | |
| if structure["num_layers"] >= 2: | |
| structure_score += 0.1 | |
| if structure["entangling_per_layer"] >= num_qubits - 1: | |
| structure_score += 0.2 | |
| if structure["entanglement_pattern"] in ("full", "circular"): | |
| structure_score += 0.1 | |
| # Combine scores | |
| score = 0.4 * param_score + 0.3 * entangling_score + 0.3 * min(1.0, structure_score) | |
| return min(1.0, score) | |
| def _estimate_meyer_wallach(structure: dict[str, Any], num_qubits: int) -> float: | |
| """Estimate Meyer-Wallach entanglement measure.""" | |
| # Simplified estimation based on entanglement structure | |
| entangling_count = structure["entangling_count"] | |
| if entangling_count == 0: | |
| return 0.0 | |
| # Rough estimate: MW measure approaches 0.5 for maximally entangling circuits | |
| max_entangling = num_qubits * (num_qubits - 1) | |
| coverage = min(1.0, entangling_count / max_entangling) | |
| return 0.5 * coverage | |
| def analyze_ansatz_trainability(circuit_data: dict[str, Any]) -> dict[str, Any]: | |
| """ | |
| Analyze potential trainability issues like barren plateaus. | |
| Barren plateaus occur when gradients vanish exponentially with | |
| circuit depth/width, making training difficult. | |
| """ | |
| gates = circuit_data.get("gates", []) | |
| num_qubits = circuit_data.get("num_qubits", 0) | |
| structure = _analyze_ansatz_structure(gates, num_qubits) | |
| # Risk factors for barren plateaus | |
| risk_factors = [] | |
| risk_score = 0.0 | |
| # Factor 1: Circuit depth | |
| depth_ratio = structure["num_layers"] / num_qubits if num_qubits > 0 else 0 | |
| if depth_ratio > 10: | |
| risk_factors.append("Very deep circuit (high barren plateau risk)") | |
| risk_score += 0.3 | |
| elif depth_ratio > 5: | |
| risk_factors.append("Deep circuit (moderate barren plateau risk)") | |
| risk_score += 0.15 | |
| # Factor 2: Global entanglement | |
| if structure["entanglement_pattern"] == "full": | |
| risk_factors.append("Full entanglement pattern increases gradient variance") | |
| risk_score += 0.2 | |
| # Factor 3: Many random parameters | |
| if structure["estimated_parameters"] > 4 ** num_qubits // 4: | |
| risk_factors.append("High parameter count relative to Hilbert space") | |
| risk_score += 0.15 | |
| # Factor 4: Large qubit count | |
| if num_qubits > 10: | |
| risk_factors.append(f"Large qubit count ({num_qubits}) increases plateau risk") | |
| risk_score += 0.2 | |
| # Recommendations | |
| recommendations = [] | |
| if risk_score > 0.3: | |
| recommendations.append("Consider parameter initialization strategies (e.g., identity initialization)") | |
| recommendations.append("Use local cost functions when possible") | |
| recommendations.append("Consider layer-wise training") | |
| if structure["entanglement_pattern"] == "full": | |
| recommendations.append("Consider using linear or alternating entanglement instead") | |
| if not risk_factors: | |
| risk_factors.append("No major barren plateau risk factors detected") | |
| return { | |
| "barren_plateau_risk": round(min(1.0, risk_score), 2), | |
| "risk_level": "High" if risk_score > 0.4 else "Moderate" if risk_score > 0.2 else "Low", | |
| "risk_factors": risk_factors, | |
| "recommendations": recommendations, | |
| "trainability_friendly_features": _identify_good_features(structure), | |
| } | |
| def _identify_good_features(structure: dict[str, Any]) -> list[str]: | |
| """Identify features that help trainability.""" | |
| good_features = [] | |
| if structure["entanglement_pattern"] == "linear": | |
| good_features.append("Linear entanglement pattern (good for avoiding barren plateaus)") | |
| if structure["num_layers"] <= 3: | |
| good_features.append("Shallow circuit depth") | |
| if structure["rotations_per_layer"] > 0: | |
| good_features.append("Sufficient rotation gates for expressivity") | |
| return good_features | |