"""
Complexity metrics implementation for QWARD.
This module provides the ComplexityMetrics class for analyzing quantum circuits
and extracting various complexity metrics as described in research literature.
The metrics include gate-based metrics, entanglement metrics, standardized metrics,
advanced metrics, and derived metrics.
Reference:
[1] D. Shami, "Character Complexity: A Novel Measure for Quantum Circuit Analysis,"
Sep. 18, 2024, arXiv: arXiv:2408.09641. doi: 10.48550/arXiv.2408.09641.
"""
from typing import Any, Dict, List
from qiskit import QuantumCircuit
from qward.metrics.base_metric import MetricCalculator
from qward.metrics.types import MetricsType, MetricsId
# Import schemas for structured data validation
try:
from qward.schemas.complexity_metrics_schema import (
ComplexityMetricsSchema,
GateBasedMetricsSchema,
EntanglementMetricsSchema,
StandardizedMetricsSchema,
AdvancedMetricsSchema,
DerivedMetricsSchema,
)
SCHEMAS_AVAILABLE = True
except ImportError:
SCHEMAS_AVAILABLE = False
# =============================================================================
# Gate Classification Constants
# =============================================================================
# Single-qubit gates
SINGLE_QUBIT_GATES = [
"id",
"x",
"y",
"z",
"h",
"s",
"sdg",
"t",
"tdg",
"rx",
"ry",
"rz",
"u1",
"u2",
"u3",
"p",
]
# Two-qubit gates
TWO_QUBIT_GATES = [
"cx",
"cz",
"swap",
"iswap",
"cp",
"cu",
"rxx",
"ryy",
"rzz",
"crx",
"cry",
"crz",
]
# Clifford gates (can be efficiently simulated classically)
CLIFFORD_GATES = ["h", "s", "sdg", "cx", "cz", "x", "y", "z"]
# Non-computational gates (don't affect quantum state)
NON_COMPUTATIONAL_GATES = ["barrier", "measure"]
# Gate complexity weights for weighted complexity calculation
GATE_WEIGHTS = {
# Single-qubit gates (basic)
"id": 1,
"x": 1,
"y": 1,
"z": 1,
"h": 1,
"s": 1,
"sdg": 1,
# Single-qubit gates (parameterized)
"t": 2,
"tdg": 2,
"rx": 2,
"ry": 2,
"rz": 2,
"p": 2,
"u1": 2,
"u2": 3,
"u3": 4,
# Two-qubit gates
"cx": 10,
"cz": 10,
"swap": 12,
"cp": 12,
# Multi-qubit gates
"ccx": 30,
"cswap": 32,
"mcx": 40,
# Default weight for unknown gates
}
DEFAULT_GATE_WEIGHT = 5
[docs]
class ComplexityMetrics(MetricCalculator):
"""
Calculate complexity metrics from QuantumCircuit objects.
This class provides methods for analyzing quantum circuits and extracting
various complexity metrics including gate-based metrics, entanglement metrics,
standardized metrics, advanced metrics, and derived metrics.
The complexity metrics help characterize the computational difficulty and
resource requirements of quantum circuits.
"""
def _get_metric_type(self) -> MetricsType:
"""Get the type of this metric."""
return MetricsType.PRE_RUNTIME
def _get_metric_id(self) -> MetricsId:
"""Get the ID of this metric."""
return MetricsId.COMPLEXITY
[docs]
def is_ready(self) -> bool:
"""Check if the metric is ready to be calculated."""
return self.circuit is not None
def _ensure_schemas_available(self) -> None:
"""Ensure Pydantic schemas are available, raise ImportError if not."""
if not SCHEMAS_AVAILABLE:
raise ImportError(
"Pydantic schemas are not available. Install pydantic to use structured metrics."
)
# =============================================================================
# Main API Methods
# =============================================================================
[docs]
def get_metrics(self) -> "ComplexityMetricsSchema":
"""
Get all complexity metrics as a structured, validated schema object.
Returns:
ComplexityMetricsSchema: Complete validated complexity metrics schema
Raises:
ImportError: If Pydantic schemas are not available
ValidationError: If metrics data doesn't match schema constraints
"""
self._ensure_schemas_available()
return ComplexityMetricsSchema(
gate_based_metrics=self.get_gate_based_metrics(),
entanglement_metrics=self.get_entanglement_metrics(),
standardized_metrics=self.get_standardized_metrics(),
advanced_metrics=self.get_advanced_metrics(),
derived_metrics=self.get_derived_metrics(),
)
# =============================================================================
# Gate-Based Metrics
# =============================================================================
[docs]
def get_gate_based_metrics(self) -> "GateBasedMetricsSchema":
"""
Get gate-based complexity metrics as a validated schema object.
Returns:
GateBasedMetricsSchema: Validated gate-based metrics
"""
self._ensure_schemas_available()
op_counts = self.circuit.count_ops()
gate_count = self.circuit.size()
circuit_depth = self.circuit.depth()
# T-count (number of T gates) - important for fault-tolerant quantum computing
t_count = op_counts.get("t", 0) + op_counts.get("tdg", 0)
# CNOT count - most common two-qubit gate
cnot_count = op_counts.get("cx", 0)
# Two-qubit gate count
two_qubit_count = self._count_gates_by_type(op_counts, TWO_QUBIT_GATES)
# Multi-qubit gate ratio
single_qubit_count = self._count_gates_by_type(op_counts, SINGLE_QUBIT_GATES)
multi_qubit_count = self._calculate_multi_qubit_count(
gate_count, single_qubit_count, op_counts
)
multi_qubit_ratio = multi_qubit_count / gate_count if gate_count > 0 else 0
return GateBasedMetricsSchema(
gate_count=gate_count,
circuit_depth=circuit_depth,
t_count=t_count,
cnot_count=cnot_count,
two_qubit_count=two_qubit_count,
multi_qubit_ratio=round(multi_qubit_ratio, 3),
)
[docs]
def get_gate_based_metrics_dict(self) -> Dict[str, Any]:
"""
Get gate-based complexity metrics as a dictionary.
Returns:
Dict[str, Any]: Dictionary containing gate counts and ratios
"""
return self.get_gate_based_metrics().model_dump()
# =============================================================================
# Entanglement Metrics
# =============================================================================
[docs]
def get_entanglement_metrics(self) -> "EntanglementMetricsSchema":
"""
Get entanglement-based complexity metrics as a validated schema object.
Returns:
EntanglementMetricsSchema: Validated entanglement metrics
"""
self._ensure_schemas_available()
op_counts = self.circuit.count_ops()
gate_count = self.circuit.size()
width = self.circuit.num_qubits
# Two-qubit gate count (entangling operations)
two_qubit_count = self._count_gates_by_type(op_counts, TWO_QUBIT_GATES)
# Entangling gate density
entangling_gate_density = two_qubit_count / gate_count if gate_count > 0 else 0
# Entangling width (estimate of qubits involved in entanglement)
entangling_width = min(width, two_qubit_count + 1) if two_qubit_count > 0 else 1
return EntanglementMetricsSchema(
entangling_gate_density=round(entangling_gate_density, 3),
entangling_width=entangling_width,
)
[docs]
def get_entanglement_metrics_dict(self) -> Dict[str, Any]:
"""
Get entanglement-based complexity metrics as a dictionary.
Returns:
Dict[str, Any]: Dictionary containing entanglement-related metrics
"""
return self.get_entanglement_metrics().model_dump()
# =============================================================================
# Standardized Metrics
# =============================================================================
[docs]
def get_standardized_metrics(self) -> "StandardizedMetricsSchema":
"""
Get standardized complexity metrics as a validated schema object.
Returns:
StandardizedMetricsSchema: Validated standardized metrics
"""
self._ensure_schemas_available()
depth = self.circuit.depth()
width = self.circuit.num_qubits
op_counts = self.circuit.count_ops()
# Use sum of operation counts instead of circuit.size() to handle all operations consistently
total_operations = sum(op_counts.values())
# Circuit volume (depth × width)
circuit_volume = depth * width
# Gate density (gates per qubit-time-step)
gate_density = total_operations / circuit_volume if circuit_volume > 0 else 0
# Clifford vs non-Clifford ratio (only considering computational gates)
clifford_count = self._count_gates_by_type(op_counts, CLIFFORD_GATES)
non_computational_count = self._count_gates_by_type(op_counts, NON_COMPUTATIONAL_GATES)
computational_gate_count = total_operations - non_computational_count
non_clifford_count = max(0, computational_gate_count - clifford_count)
# Calculate ratios based on computational gates only
clifford_ratio = (
clifford_count / computational_gate_count if computational_gate_count > 0 else 0
)
non_clifford_ratio = (
non_clifford_count / computational_gate_count if computational_gate_count > 0 else 0
)
return StandardizedMetricsSchema(
circuit_volume=circuit_volume,
gate_density=round(gate_density, 3),
clifford_ratio=round(clifford_ratio, 3),
non_clifford_ratio=round(non_clifford_ratio, 3),
)
[docs]
def get_standardized_metrics_dict(self) -> Dict[str, Any]:
"""
Get standardized complexity metrics as a dictionary.
Returns:
Dict[str, Any]: Dictionary containing standardized circuit metrics
"""
return self.get_standardized_metrics().model_dump()
# =============================================================================
# Advanced Metrics
# =============================================================================
[docs]
def get_advanced_metrics(self) -> "AdvancedMetricsSchema":
"""
Get advanced complexity metrics as a validated schema object.
Returns:
AdvancedMetricsSchema: Validated advanced metrics
"""
self._ensure_schemas_available()
depth = self.circuit.depth()
width = self.circuit.num_qubits
gate_count = self.circuit.size()
# Parallelism factor (average gates per time step)
parallelism_factor = gate_count / depth if depth > 0 else 0
max_parallelism = width # Maximum gates that could be executed in parallel
parallelism_efficiency = parallelism_factor / max_parallelism if max_parallelism > 0 else 0
# Circuit efficiency (utilization of qubit-time resources)
circuit_efficiency = gate_count / (width * depth) if (width * depth) > 0 else 0
# Quantum resource utilization (balanced metric considering both dimensions)
quantum_resource_utilization = 0.5 * (
gate_count / (width * width) if width > 0 else 0
) + 0.5 * (gate_count / (depth * depth) if depth > 0 else 0)
# Ensure quantum_resource_utilization doesn't exceed 1.0 (schema constraint)
quantum_resource_utilization = min(quantum_resource_utilization, 1.0)
return AdvancedMetricsSchema(
parallelism_factor=round(parallelism_factor, 3),
parallelism_efficiency=round(parallelism_efficiency, 3),
circuit_efficiency=round(circuit_efficiency, 3),
quantum_resource_utilization=round(quantum_resource_utilization, 3),
)
[docs]
def get_advanced_metrics_dict(self) -> Dict[str, Any]:
"""
Get advanced complexity metrics as a dictionary.
Returns:
Dict[str, Any]: Dictionary containing advanced circuit analysis metrics
"""
return self.get_advanced_metrics().model_dump()
# =============================================================================
# Derived Metrics
# =============================================================================
[docs]
def get_derived_metrics(self) -> "DerivedMetricsSchema":
"""
Get derived complexity metrics as a validated schema object.
Returns:
DerivedMetricsSchema: Validated derived metrics
"""
self._ensure_schemas_available()
depth = self.circuit.depth()
width = self.circuit.num_qubits
op_counts = self.circuit.count_ops()
# Square circuit factor (how close to square the circuit is)
square_ratio = min(depth, width) / max(depth, width) if max(depth, width) > 0 else 1.0
# Weighted gate complexity
weighted_complexity = sum(
count * GATE_WEIGHTS.get(gate, DEFAULT_GATE_WEIGHT) for gate, count in op_counts.items()
)
# Normalized weighted complexity (per qubit)
normalized_weighted_complexity = weighted_complexity / width if width > 0 else 0
return DerivedMetricsSchema(
square_ratio=round(square_ratio, 3),
weighted_complexity=weighted_complexity,
normalized_weighted_complexity=round(normalized_weighted_complexity, 3),
)
[docs]
def get_derived_metrics_dict(self) -> Dict[str, Any]:
"""
Get derived complexity metrics as a dictionary.
Returns:
Dict[str, Any]: Dictionary containing derived circuit metrics
"""
return self.get_derived_metrics().model_dump()
# =============================================================================
# Helper Methods
# =============================================================================
def _count_gates_by_type(self, op_counts: Dict[str, int], gate_list: List[str]) -> int:
"""Count gates of specific types from operation counts."""
return sum(op_counts.get(gate, 0) for gate in gate_list)
def _calculate_multi_qubit_count(
self, gate_count: int, single_qubit_count: int, op_counts: Dict[str, int]
) -> int:
"""Calculate the number of multi-qubit gates."""
non_computational_count = self._count_gates_by_type(op_counts, NON_COMPUTATIONAL_GATES)
return gate_count - single_qubit_count - non_computational_count