[CQT-461] Make CircuitAnalyzer metrics configurable - #703
Conversation
SoufiTNO
commented
Jul 21, 2026
- Add metrics and exclude_metrics parameters to (de)select metrics
- Add configurable per-metric timeout parameter
- Add available_metrics() class method
- Add documentation for the CircuitAnalyzer under a new Analysis section in the compilation passes docs
- Add tests for metric selection and timeout
- Update CHANGELOG
- Add metrics and exclude_metrics parameters to (de)select metrics - Add configurable per-metric timeout parameter - Add available_metrics() class method - Add documentation for the CircuitAnalyzer under a new Analysis section in the compilation passes docs - Add tests for metric selection and timeout - Update CHANGELOG
elenbaasc
left a comment
There was a problem hiding this comment.
Very nice implementation, thanks for the effort!
My main concern is about using thread pool timeout, as the calculation will/might continue in the background, instead of being killed.
|
|
||
| if TYPE_CHECKING: | ||
| from collections.abc import Iterable | ||
|
|
| @@ -0,0 +1,7 @@ | |||
| Analyzer passe in OpenSquirrel is used to compute statistics that describe the structure of a quantum circuit. | |||
There was a problem hiding this comment.
| Analyzer passe in OpenSquirrel is used to compute statistics that describe the structure of a quantum circuit. | |
| Analyzer passes are used in OpenSquirrel to compute statistics that describe the structure of a quantum circuit. |
| @@ -0,0 +1,7 @@ | |||
| Analyzer passe in OpenSquirrel is used to compute statistics that describe the structure of a quantum circuit. | |||
| Unlike most other passes, an analyzer does not modify the circuit: it inspects it and returns a set of metrics. | |||
| These metrics can be used, for instance, to characterise a circuit prior to compilation, to compare circuits, or as input features for machine-learning models that predict compilation performance. | |||
There was a problem hiding this comment.
| These metrics can be used, for instance, to characterise a circuit prior to compilation, to compare circuits, or as input features for machine-learning models that predict compilation performance. | |
| These metrics can be used, for instance, | |
| - to characterize a circuit prior to compilation, | |
| - to compare circuits, or | |
| - as input features for machine-learning models that predict compilation performance. |
| @@ -0,0 +1,113 @@ | |||
| This pass computes a collection of structural metrics that describe a quantum circuit. | |||
| The metrics follow the circuit profiling approach proposed by Bandić _et al._[^1] and are grouped into four categories: | |||
There was a problem hiding this comment.
| The metrics follow the circuit profiling approach proposed by Bandić _et al._[^1] and are grouped into four categories: | |
| The metrics follow the circuit profiling approach proposed by Bandić _et al._ (2025)$^1$ and are grouped into four categories: |
| so it may continue in the background until it completes on its own. | ||
| A positive value is required for `timeout`; a non-positive value raises a `ValueError`. | ||
|
|
||
| [^1]: |
There was a problem hiding this comment.
| [^1]: | |
| $^1$ |
| except FuturesTimeoutError: | ||
| metrics[name] = None | ||
| warnings.warn( | ||
| f"computation of metric '{name}' exceeded the time-out of {self.timeout} s; " |
There was a problem hiding this comment.
| f"computation of metric '{name}' exceeded the time-out of {self.timeout} s; " | |
| f"computation of metric '{name}' exceeded the time-out of {self.timeout} s: " |
| requested = set(self._METRIC_REGISTRY) if metrics is None else set(metrics) | ||
| excluded = set(exclude_metrics) if exclude_metrics is not None else set() |
There was a problem hiding this comment.
| requested = set(self._METRIC_REGISTRY) if metrics is None else set(metrics) | |
| excluded = set(exclude_metrics) if exclude_metrics is not None else set() | |
| requested = set(self._METRIC_REGISTRY) if not metrics else set(metrics) | |
| excluded = set(exclude_metrics) if exclude_metrics else set() |
| _METRIC_REGISTRY: ClassVar[dict[str, Callable[[CircuitAnalyzer], Any]]] = { | ||
| # Size | ||
| "n_qubits": _metric_n_qubits, | ||
| "n_gates": _metric_n_gates, | ||
| "n_two_qubit_gates": _metric_n_two_qubit_gates, | ||
| "two_qubit_pct": _metric_two_qubit_pct, | ||
| "depth": _metric_depth, | ||
| # Interaction graph | ||
| "ig_avg_shortest_path": _metric_ig_avg_shortest_path, | ||
| "ig_std_adjacency": _metric_ig_std_adjacency, | ||
| "ig_diameter": _metric_ig_diameter, | ||
| "ig_central_dominance": _metric_ig_central_dominance, | ||
| "ig_avg_degree": _metric_ig_avg_degree, | ||
| "ig_n_maximal_cliques": _metric_ig_n_maximal_cliques, | ||
| "ig_clustering_coefficient": _metric_ig_clustering_coefficient, | ||
| # Gate dependency graph | ||
| "gdg_critical_path_length": _metric_gdg_critical_path_length, | ||
| "gdg_path_length_mean": _metric_gdg_path_length_mean, | ||
| "gdg_path_length_std": _metric_gdg_path_length_std, | ||
| "gdg_pct_gates_in_critical_path": _metric_gdg_pct_gates_in_critical_path, | ||
| # Density | ||
| "density_score": _metric_density_score, | ||
| "idling_score": _metric_idling_score, | ||
| } |
There was a problem hiding this comment.
Any particular reason why this (class variable) is not defined before the __init__?
| variance = statistics.pstdev(path_lengths) | ||
| return mean_length, math.sqrt(variance) |
There was a problem hiding this comment.
Missed this one last time. Just to double check, doesn't pstdev already return the standard deviation (i.e. not the variance), so why is the square-root returned?
| def _analyze_with_timeout(self) -> dict[str, Any]: | ||
| """Compute each selected metric in a worker thread, bounded by the configured time-out.""" | ||
| metrics: dict[str, Any] = {} | ||
| executor = ThreadPoolExecutor(max_workers=1) |
There was a problem hiding this comment.
Python cannot kill a running thread. So if the timeout error is raised the calculation will/might still run in the background. You could consider using multiprocessing, to replace thread pool timeout with terminating a process.