From bd7229fe0af245bac78be59c43483cdf821508a1 Mon Sep 17 00:00:00 2001 From: Pranava Kumar Date: Tue, 14 Jul 2026 01:08:10 +0530 Subject: [PATCH] fix: Improve ConsolidateBlocks error messages for NaN/Inf parameters (#16236) Add explicit NaN/Inf detection in the ConsolidateBlocks pass and include block context (gate name, parameters, qubits) in error messages, instead of propagating a non-descript NoConvergence from scipy. The error now shows which gate has NaN/Inf parameters, making debugging significantly easier. Fixes #16236 --- .../src/passes/consolidate_blocks.rs | 91 ++++++++++++++++++- ...ix-consolidate-blocks-nan-error-16236.yaml | 8 ++ .../transpiler/test_consolidate_blocks.py | 17 ++++ 3 files changed, 111 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/fix-consolidate-blocks-nan-error-16236.yaml diff --git a/crates/transpiler/src/passes/consolidate_blocks.rs b/crates/transpiler/src/passes/consolidate_blocks.rs index 3684ae079d34..1b9f76541bee 100644 --- a/crates/transpiler/src/passes/consolidate_blocks.rs +++ b/crates/transpiler/src/passes/consolidate_blocks.rs @@ -48,6 +48,7 @@ use qiskit_synthesis::two_qubit_decompose::{ use rustworkx_core::petgraph::stable_graph::NodeIndex; use smallvec::SmallVec; +use crate::QiskitError; use crate::passes::unitary_synthesis::{PARAM_SET, TWO_QUBIT_BASIS_SET}; use crate::target::{Qargs, Target}; use qiskit_circuit::PhysicalQubit; @@ -118,6 +119,48 @@ fn get_matrix(gate: &StandardGate) -> ArrayView2<'_, Complex64> { } } +/// Format a block of nodes as a human-readable string for error messages. +fn format_block_context(dag: &DAGCircuit, block: &[NodeIndex]) -> String { + block + .iter() + .map(|node| { + let inst = dag[*node].unwrap_operation(); + let params = inst + .params_view() + .iter() + .map(|param| match param { + Param::Float(value) => format!("{value}"), + Param::ParameterExpression(expr) => expr.to_string(), + Param::Obj(_) => "".to_string(), + }) + .collect::>(); + let params = if params.is_empty() { + String::new() + } else { + format!("({})", params.join(", ")) + }; + let qargs = dag + .get_qargs(inst.qubits) + .iter() + .map(|qubit| qubit.index().to_string()) + .collect::>() + .join(", "); + format!("{}{params} on qubits [{qargs}]", inst.op.name()) + }) + .collect::>() + .join("; ") +} + +/// Check if any element of a 2x2 complex matrix is NaN or infinite. +fn matrix_2x2_has_nan_or_inf(matrix: &Matrix2) -> bool { + matrix.iter().any(|x| !x.re.is_finite() || !x.im.is_finite()) +} + +/// Check if any element of a 4x4 complex matrix is NaN or infinite. +fn matrix_4x4_has_nan_or_inf(matrix: &Matrix4) -> bool { + matrix.iter().any(|x| !x.re.is_finite() || !x.im.is_finite()) +} + /// Helper function that extracts the decomposer and basis gate directly from the [Target]. #[inline] fn get_decomposer_and_basis_gate( @@ -267,7 +310,15 @@ fn should_substitute( let num_qubits = inst.op.num_qubits(); let unitary_gate = if num_qubits == 1 { let matrix = match get_1q_matrix_from_inst(inst) { - Ok(mat) => mat, + Ok(mat) => { + if matrix_2x2_has_nan_or_inf(&mat) { + let block_desc = format_block_context(dag, block); + return Err(QiskitError::new_err(format!( + "ConsolidateBlocks failed because a 1-qubit gate has NaN or Inf parameters: {block_desc}" + ))); + } + mat + } Err(_) => return Ok(ConsolidateResult::NoConsolidate), }; UnitaryGate { @@ -275,7 +326,15 @@ fn should_substitute( } } else if num_qubits == 2 { let matrix = match get_2q_matrix_from_inst(inst) { - Ok(mat) => mat, + Ok(mat) => { + if matrix_4x4_has_nan_or_inf(&mat) { + let block_desc = format_block_context(dag, block); + return Err(QiskitError::new_err(format!( + "ConsolidateBlocks failed because a 2-qubit gate has NaN or Inf parameters: {block_desc}" + ))); + } + mat + } Err(_) => return Ok(ConsolidateResult::NoConsolidate), }; UnitaryGate { @@ -283,7 +342,15 @@ fn should_substitute( } } else { let matrix = match get_matrix_from_inst(inst) { - Ok(mat) => mat, + Ok(mat) => { + if mat.iter().any(|x| !x.re.is_finite() || !x.im.is_finite()) { + let block_desc = format_block_context(dag, block); + return Err(QiskitError::new_err(format!( + "ConsolidateBlocks failed because a gate has NaN or Inf parameters: {block_desc}" + ))); + } + mat + } Err(_) => return Ok(ConsolidateResult::NoConsolidate), }; UnitaryGate { @@ -363,6 +430,12 @@ fn should_substitute( ]; let matrix = blocks_to_matrix(dag, block, block_index_map).ok(); if let Some(matrix) = matrix { + if matrix_4x4_has_nan_or_inf(&matrix) { + let block_desc = format_block_context(dag, block); + return Err(QiskitError::new_err(format!( + "ConsolidateBlocks failed because the 2-qubit block contains a circuit gate with NaN or Inf parameters: {block_desc}" + ))); + } let consolidate = if force_consolidate || block.len() > MAX_2Q_DEPTH || (basis_gates.is_some() && outside_basis) @@ -374,11 +447,19 @@ fn should_substitute( match decomposer { DecomposerType::TwoQubitBasis(decomp) => decomp.num_basis_gates_inner( nalgebra_array_view::(matrix.as_view()), - )?, + ).map_err(|err| QiskitError::new_err(format!( + "ConsolidateBlocks failed on block: {}. Error: {}", + format_block_context(dag, block), + err + )))?, DecomposerType::TwoQubitControlledU(decomp) => decomp .num_basis_gates_inner(nalgebra_array_view::( matrix.as_view(), - ))?, + )).map_err(|err| QiskitError::new_err(format!( + "ConsolidateBlocks failed on block: {}. Error: {}", + format_block_context(dag, block), + err + )))?, } } else { unreachable!("A decomposer is always set unless force_consolidate is true"); diff --git a/releasenotes/notes/fix-consolidate-blocks-nan-error-16236.yaml b/releasenotes/notes/fix-consolidate-blocks-nan-error-16236.yaml new file mode 100644 index 000000000000..03af110159b4 --- /dev/null +++ b/releasenotes/notes/fix-consolidate-blocks-nan-error-16236.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + :meth:`~qiskit.transpiler.passes.ConsolidateBlocks` now raises a clear error + message with context when a gate parameter is NaN or Inf, instead of failing + with a non-descript ``NoConvergence`` error. The error message includes the + gate name and qubit index of the offending gate. + Fixed `#16236 `__. diff --git a/test/python/transpiler/test_consolidate_blocks.py b/test/python/transpiler/test_consolidate_blocks.py index bbc278bd9627..689ab3da2390 100644 --- a/test/python/transpiler/test_consolidate_blocks.py +++ b/test/python/transpiler/test_consolidate_blocks.py @@ -32,6 +32,7 @@ ) from qiskit.circuit.classical import expr from qiskit.converters import circuit_to_dag +from qiskit.exceptions import QiskitError from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.operators.measures import process_fidelity from qiskit.transpiler import PassManager, Target, generate_preset_pass_manager @@ -894,3 +895,19 @@ def at_least_four_gates(_dag, run): pm.run(qc) blocks = pm.property_set.get("block_list", []) self.assertEqual(len(blocks), 0) + + def test_consolidate_nan_parameters_in_2q_block(self): + """Test that ConsolidateBlocks gives a clear error when a 2-qubit block has NaN parameters.""" + qc = QuantumCircuit(2) + qc.rz(float("nan"), 0) + qc.cx(0, 1) + + dag = circuit_to_dag(qc) + pm = PassManager([ + Collect1qRuns(), + Collect2qBlocks(), + ConsolidateBlocks(basis_gates=["cx", "rz"]), + ]) + with self.assertRaises(QiskitError) as ctx: + pm.run(dag) + self.assertIn("NaN", str(ctx.exception))