From 558ff63bff791a756dd5689f483c75d2cfe84e09 Mon Sep 17 00:00:00 2001 From: Gadi Aleksandrowicz Date: Wed, 25 Mar 2026 17:17:29 +0200 Subject: [PATCH 1/6] Correctly load ParameterExpression with empty replay --- crates/qpy/src/params.rs | 14 +++++++++++++- test/python/circuit/test_circuit_load_from_qpy.py | 15 +++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/qpy/src/params.rs b/crates/qpy/src/params.rs index 4de6537e20e3..4c8bbfa0e902 100644 --- a/crates/qpy/src/params.rs +++ b/crates/qpy/src/params.rs @@ -16,7 +16,7 @@ use qiskit_circuit::operations::Param; use qiskit_circuit::parameter::parameter_expression::{ OPReplay, OpCode, ParameterExpression, ParameterValueType, PyParameter, }; -use qiskit_circuit::parameter::symbol_expr::Symbol; +use qiskit_circuit::parameter::symbol_expr::{Symbol, SymbolExpr, Value}; use std::sync::Arc; use uuid::Uuid; @@ -343,6 +343,18 @@ pub(crate) fn unpack_parameter_expression( let parameter_expression_data = deserialize_vec::( ¶meter_expression_pack.expression_data, )?; + if parameter_expression_data.is_empty() { + // special case: we can't reconstruct the expression from replay, we'll instantiate it directly + // empty expressions are basically Value(0), but we also need to preserve the symbol table + let expr = SymbolExpr::Value(Value::Int(0)); + let mut name_map = HashMap::new(); + for value in param_uuid_map.values() { + if let GenericValue::ParameterExpressionSymbol(symbol) = &value { + name_map.insert(symbol.repr(false), symbol.clone()); + } + } + return Ok(ParameterExpression::new(expr, name_map)); + } // we now convert the parameter_expression_data into Vec that can be used via ParameterExpression::from_qpy let mut replay: Vec = Vec::new(); diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index 03ab5e5d427d..62bbce895888 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -365,6 +365,21 @@ def test_parameter_expression(self): self.assertEqual(qc, new_circuit) self.assertDeprecatedBitProperties(qc, new_circuit) + def test_degenerate_parameter_expression(self): + """Test a circuit with a parameter expression that simplifies to 0.""" + x = Parameter("x") + cases = [0 * x, x - x] + for case in cases: + qc = QuantumCircuit(1) + qc.rz(case, 0) + qpy_file = io.BytesIO() + dump(qc, qpy_file) + qpy_file.seek(0) + new_circuit = load(qpy_file)[0] + self.assertEqual(qc, new_circuit) + # should still have the same parameters even if they are not used + self.assertEqual(qc.parameters, new_circuit.parameters) + def test_string_parameter(self): """Test a PauliGate instruction that has string parameters.""" From 93db998050d67b085bfd730288933c6731772a22 Mon Sep 17 00:00:00 2001 From: Gadi Aleksandrowicz Date: Wed, 25 Mar 2026 17:42:16 +0200 Subject: [PATCH 2/6] Slightly more extensive tests - also covers parameter vetors and parameter expresssions --- test/python/circuit/test_circuit_load_from_qpy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index 62bbce895888..d1ff614da48c 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -368,7 +368,8 @@ def test_parameter_expression(self): def test_degenerate_parameter_expression(self): """Test a circuit with a parameter expression that simplifies to 0.""" x = Parameter("x") - cases = [0 * x, x - x] + y_vec = ParameterVector("y", 2) + cases = [0 * x, x - x, 0 * y_vec[0], 0 * (x + y_vec[1])] for case in cases: qc = QuantumCircuit(1) qc.rz(case, 0) From d8713ff8938f01cade5d5a7956725466e45b5035 Mon Sep 17 00:00:00 2001 From: Gadi Aleksandrowicz Date: Fri, 27 Mar 2026 15:51:00 +0300 Subject: [PATCH 3/6] Handle more complex situations --- .../src/parameter/parameter_expression.rs | 31 +++++-- crates/qpy/src/params.rs | 82 ++++++++++++++++++- .../circuit/test_circuit_load_from_qpy.py | 3 +- 3 files changed, 106 insertions(+), 10 deletions(-) diff --git a/crates/circuit/src/parameter/parameter_expression.rs b/crates/circuit/src/parameter/parameter_expression.rs index fb184378d71d..e93fd53a6970 100644 --- a/crates/circuit/src/parameter/parameter_expression.rs +++ b/crates/circuit/src/parameter/parameter_expression.rs @@ -137,6 +137,10 @@ impl ParameterExpression { qpy_replay(self, &self.name_map, &mut replay); replay } + + pub fn num_of_symbols(&self) -> usize { + self.name_map.len() + } } // This needs to be implemented manually, because PyO3 does not provide built-in // conversions for the subclasses of ParameterExpression in Python. Specifically @@ -695,6 +699,17 @@ impl ParameterExpression { } Ok(merged) } + + /// Extend the symbol table with additional symbols + pub fn extend_symbols(&mut self, symbols: I) + where + I: IntoIterator, + { + for symbol in symbols { + let name = symbol.repr(false); + self.name_map.entry(name).or_insert(symbol); + } + } } /// A parameter expression. @@ -1871,14 +1886,20 @@ pub enum ParameterValueType { VectorElement(PyParameterVectorElement), } +impl From for ParameterValueType { + fn from(value: Value) -> Self { + match value { + Value::Int(i) => ParameterValueType::Int(i), + Value::Real(r) => ParameterValueType::Float(r), + Value::Complex(c) => ParameterValueType::Complex(c), + } + } +} + impl ParameterValueType { fn extract_from_expr(expr: &SymbolExpr) -> Option { if let Some(value) = expr.eval(true) { - match value { - Value::Int(i) => Some(ParameterValueType::Int(i)), - Value::Real(r) => Some(ParameterValueType::Float(r)), - Value::Complex(c) => Some(ParameterValueType::Complex(c)), - } + Some(value.into()) } else if let SymbolExpr::Symbol(symbol) = expr { match symbol.index { None => { diff --git a/crates/qpy/src/params.rs b/crates/qpy/src/params.rs index 4c8bbfa0e902..4b29faa3ce68 100644 --- a/crates/qpy/src/params.rs +++ b/crates/qpy/src/params.rs @@ -15,6 +15,7 @@ use qiskit_circuit::imports; use qiskit_circuit::operations::Param; use qiskit_circuit::parameter::parameter_expression::{ OPReplay, OpCode, ParameterExpression, ParameterValueType, PyParameter, + PyParameterVectorElement, }; use qiskit_circuit::parameter::symbol_expr::{Symbol, SymbolExpr, Value}; use std::sync::Arc; @@ -224,10 +225,61 @@ fn pack_symbol_table_element( } } +// In case the parameter expression reduces to a constant value/symbol +// we still want to pack it as an expression to save the symbol table data, +// even though the symbols are not used. So we turn the constant to an equivalent expression. +fn pack_parameter_expression_with_empty_replay( + exp: &ParameterExpression, +) -> Result, QpyError> { + // Try constant value first + if let Ok(value) = exp.try_to_value(false) { + let synthetic_op = OPReplay { + op: OpCode::ADD, + lhs: Some(value.into()), + rhs: Some(ParameterValueType::Int(0)), + }; + return pack_parameter_expression_element(&synthetic_op); + } + + // Check if it's a bare parameter or parameter vector element + if let Ok(symbol) = exp.try_to_symbol_ref() { + let param_value = match symbol.index { + None => ParameterValueType::Parameter(PyParameter { + symbol: symbol.clone(), + }), + Some(_) => ParameterValueType::VectorElement(PyParameterVectorElement { + symbol: symbol.clone(), + }), + }; + let synthetic_op = OPReplay { + op: OpCode::ADD, + lhs: Some(param_value), + rhs: Some(ParameterValueType::Int(0)), + }; + return pack_parameter_expression_element(&synthetic_op); + } + Err(QpyError::InvalidParameter(format!( + "Cannot encode parameter expression {:?}", + exp + ))) +} + +// fn pack_parameter_expression_with_empty_replay(exp: &ParameterExpression) -> Result, QpyError> { +// if let Ok(value) = exp.try_to_value(false) { +// return Ok(pack_parameter_expression_element(&constant_value_op_replay(value))?;); +// } else { +// return Err(QpyError::InvalidParameter(format!("Cannot encode parameter expression {:?}", exp))); +// } +// } + fn pack_parameter_expression_elements( exp: &ParameterExpression, ) -> Result, QpyError> { - let mut result = Vec::new(); + let replay = exp.qpy_replay(); + if replay.is_empty() { + return pack_parameter_expression_with_empty_replay(exp); + } + let mut result: Vec = Vec::new(); for replay_obj in exp.qpy_replay().iter() { let packed_parameter = pack_parameter_expression_element(replay_obj)?; result.extend(packed_parameter); @@ -470,9 +522,15 @@ pub(crate) fn unpack_parameter_expression( replay.push(OPReplay { op, lhs, rhs }); }; } - ParameterExpression::from_qpy(&replay, Some(sub_operations)).map_err(|_| { + let mut exp = ParameterExpression::from_qpy(&replay, Some(sub_operations)).map_err(|_| { QpyError::ConversionError("Failure while loading parameter expression".to_string()) - }) + })?; + // add parameters not present in the replay but part of the original expression (in case they were optimized away) + exp.extend_symbols(param_uuid_map.values().filter_map(|v| match v { + GenericValue::ParameterExpressionSymbol(s) => Some(s.clone()), + _ => None, + })); + Ok(exp) } pub(crate) fn pack_symbol(symbol: &Symbol) -> formats::ParameterSymbolPack { @@ -585,13 +643,29 @@ pub(crate) fn unpack_parameter_vector( }) } +// exp should be a symbol (for parameter/parameter vector element) +// but more than that: it should no contain other symbols in its symbol table +// since if, e.g. exp was `0*x+y` and got simplified to `y` we still need +// to store `x`, so we must treat exp as an expression +fn expression_as_single_symbol(exp: &ParameterExpression) -> Option { + if let Ok(symbol) = exp.try_to_symbol() { + if exp.num_of_symbols() == 1 { + Some(symbol) + } else { + None + } + } else { + None + } +} + pub(crate) fn pack_param_expression( exp: &ParameterExpression, qpy_data: &QPYWriteData, ) -> Result { // if the parameter expression is a single symbol, we should treat it like a parameter // or a parameter vector, depending on whether the `vector` field exists - if let Ok(symbol) = exp.try_to_symbol() { + if let Some(symbol) = expression_as_single_symbol(exp) { match symbol.vector { None => pack_generic_value(&GenericValue::ParameterExpressionSymbol(symbol), qpy_data), Some(_) => pack_generic_value( diff --git a/test/python/circuit/test_circuit_load_from_qpy.py b/test/python/circuit/test_circuit_load_from_qpy.py index d1ff614da48c..0cb9fcb30214 100644 --- a/test/python/circuit/test_circuit_load_from_qpy.py +++ b/test/python/circuit/test_circuit_load_from_qpy.py @@ -369,7 +369,8 @@ def test_degenerate_parameter_expression(self): """Test a circuit with a parameter expression that simplifies to 0.""" x = Parameter("x") y_vec = ParameterVector("y", 2) - cases = [0 * x, x - x, 0 * y_vec[0], 0 * (x + y_vec[1])] + z = Parameter("z") + cases = [0 * x, 0 * x + 2, 0 * x + z, x - x, 0 * y_vec[0], 0 * (x + y_vec[1])] for case in cases: qc = QuantumCircuit(1) qc.rz(case, 0) From 73c359b809a4ccba78a7bda995dd2ab324dd52e0 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 27 Mar 2026 09:50:24 -0400 Subject: [PATCH 4/6] Enable missing argument in docstring lint (#15721) * Enable missing argument in docstring lint In the recently merged #15603 we migrated from pylint to use ruff. During the development of that PR the rule group "D" for rules derived from pydocstyle was investigated but the full rule group was overly pedantic and even with disabling the most egregious rules also changed the docs for the worse (at least by autofixing many rules) so that sphinx would not succesfully build after applying the fixes. So during #15603 the changes for this rule group were reverted. However, one of the useful rules from that group D417 which checks that all arguments are documented. This rule found real issues in the documentation and it is worthwhile to enable it. This commit enables the rule in our ruff checks and fixes the issues associated with. During this process ruff flagged one place in the `__call__` method docstring for the `TwoQubitControlledUDecomposer` class has not documented several arguments on the method. However, these arguments are unused currently. It is apparently trying to match the `__call__` signature of the other two qubit decomposers but is not using most of the arguments. Most could be conceivably supported by `TwoQubitControlledUDecomposer` and it's not clear why they were added. But, for this PR I didn't want to look into adding the support as it was out of scope. For the time being this PR suppresses the ruff rule and adds a TODO comment to start using the unused arguments. * Update filename docstring * Revise wording on state visualization filename arg --- pyproject.toml | 2 ++ qiskit/circuit/controlflow/box.py | 3 +++ qiskit/circuit/controlflow/switch_case.py | 1 + qiskit/circuit/duration.py | 2 +- .../circuit/library/arithmetic/adders/adder.py | 3 +++ .../arithmetic/linear_pauli_rotations.py | 2 +- .../arithmetic/multipliers/multiplier.py | 2 +- .../arithmetic/polynomial_pauli_rotations.py | 2 +- qiskit/circuit/library/n_local/n_local.py | 4 ++-- qiskit/circuit/library/standard_gates/x.py | 5 ++++- qiskit/circuit/quantumcircuit.py | 10 +++++++--- qiskit/dagcircuit/dagdependency.py | 2 +- qiskit/dagcircuit/dagdependency_v2.py | 2 +- qiskit/passmanager/passmanager.py | 8 ++++++++ qiskit/primitives/containers/bindings_array.py | 2 +- qiskit/primitives/primitive_job.py | 2 ++ qiskit/primitives/statevector_sampler.py | 2 +- qiskit/qasm3/printer.py | 2 ++ qiskit/quantum_info/analysis/distance.py | 4 ++-- qiskit/result/distributions/probability.py | 2 +- qiskit/result/distributions/quasi.py | 6 +++--- qiskit/result/sampled_expval.py | 2 +- .../synthesis/two_qubit/two_qubit_decompose.py | 2 +- .../aqc/fast_gradient/fast_grad_utils.py | 2 +- .../passes/basis/unroll_custom_definitions.py | 4 ++-- .../transpiler/passes/layout/dense_layout.py | 4 ++++ .../commuting_2q_gate_router.py | 2 ++ .../pauli_2q_evolution_commutation.py | 2 +- .../passes/scheduling/padding/base_padding.py | 3 +++ .../passes/scheduling/padding/pad_delay.py | 3 +++ qiskit/transpiler/passmanager_config.py | 3 +++ qiskit/utils/lazy_tester.py | 12 ++++++++++++ qiskit/visualization/circuit/_utils.py | 2 ++ qiskit/visualization/circuit/latex.py | 1 + qiskit/visualization/counts_visualization.py | 2 +- qiskit/visualization/state_visualization.py | 18 +++++++++++++++++- 36 files changed, 102 insertions(+), 28 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e62abe405040..43238bb0d262 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -347,6 +347,8 @@ select = [ "INP", # flake8-print "T20", + # Missing arguments in docstring + "D417", ] ignore = [ "E402", # module-import-not-at-top-of-file false positives with module docs diff --git a/qiskit/circuit/controlflow/box.py b/qiskit/circuit/controlflow/box.py index 29636c378f0d..1034345ea9b7 100644 --- a/qiskit/circuit/controlflow/box.py +++ b/qiskit/circuit/controlflow/box.py @@ -154,6 +154,9 @@ def __init__( duration: the final duration of the box. unit: the unit of ``duration``. label: an optional label for the box. + annotations: any :class:`.Annotation`\\ s to apply to the box. In cases where order + is important, annotations are to be interpreted in the same order they appear in + the iterable. """ self._circuit = circuit self._duration = duration diff --git a/qiskit/circuit/controlflow/switch_case.py b/qiskit/circuit/controlflow/switch_case.py index d9e0ceee3172..e3b82903ad8a 100644 --- a/qiskit/circuit/controlflow/switch_case.py +++ b/qiskit/circuit/controlflow/switch_case.py @@ -70,6 +70,7 @@ def __init__( cases: an ordered iterable of the corresponding value of the ``target`` and the circuit block that should be executed if this is matched. There is no fall-through between blocks, and the order matters. + label: An optional label for identifying the instruction. """ from qiskit.circuit import QuantumCircuit diff --git a/qiskit/circuit/duration.py b/qiskit/circuit/duration.py index 60672e3373d5..e519381f32d6 100644 --- a/qiskit/circuit/duration.py +++ b/qiskit/circuit/duration.py @@ -47,7 +47,7 @@ def convert_durations_to_dt(qc: QuantumCircuit, dt_in_sec: float, inplace=True): Returns a new circuit if `inplace=False`. - Parameters: + Args: qc (QuantumCircuit): Duration of dt in seconds used for conversion. dt_in_sec (float): Duration of dt in seconds used for conversion. inplace (bool): All durations are converted inplace or return new circuit. diff --git a/qiskit/circuit/library/arithmetic/adders/adder.py b/qiskit/circuit/library/arithmetic/adders/adder.py index c6ae59eca06f..eaea86145803 100644 --- a/qiskit/circuit/library/arithmetic/adders/adder.py +++ b/qiskit/circuit/library/arithmetic/adders/adder.py @@ -100,6 +100,7 @@ def __init__(self, num_state_qubits: int, label: str | None = None) -> None: Args: num_state_qubits: The number of qubits in each of the registers. name: The name of the circuit. + label: An optional label for identifying the instruction. """ if num_state_qubits < 1: raise ValueError("Need at least 1 state qubit.") @@ -155,6 +156,7 @@ def __init__(self, num_state_qubits: int, label: str | None = None) -> None: Args: num_state_qubits: The number of qubits in each of the registers. name: The name of the circuit. + label: An optional label for identifying the instruction. """ if num_state_qubits < 1: raise ValueError("Need at least 1 state qubit.") @@ -211,6 +213,7 @@ def __init__(self, num_state_qubits: int, label: str | None = None) -> None: Args: num_state_qubits: The number of qubits in each of the registers. name: The name of the circuit. + label: An optional label for identifying the instruction. """ if num_state_qubits < 1: raise ValueError("Need at least 1 state qubit.") diff --git a/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py b/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py index 1a1f00fa188e..bdea473d9454 100644 --- a/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py +++ b/qiskit/circuit/library/arithmetic/linear_pauli_rotations.py @@ -93,7 +93,7 @@ def slope(self, slope: float) -> None: """Set the multiplicative factor of the rotation angles. Args: - The slope of the rotation angles. + slope: The slope of the rotation angles. """ if self._slope is None or slope != self._slope: self._invalidate() diff --git a/qiskit/circuit/library/arithmetic/multipliers/multiplier.py b/qiskit/circuit/library/arithmetic/multipliers/multiplier.py index 449952590189..81eee96b87f4 100644 --- a/qiskit/circuit/library/arithmetic/multipliers/multiplier.py +++ b/qiskit/circuit/library/arithmetic/multipliers/multiplier.py @@ -151,7 +151,7 @@ def __init__( num_result_qubits: The number of result qubits to limit the output to. Default value is ``2 * num_state_qubits`` to represent any possible result from the multiplication of the two inputs. - name: The name of the circuit. + label: The optional string label to apply to the instruction. Raises: ValueError: If ``num_state_qubits`` is smaller than 1. ValueError: If ``num_result_qubits`` is smaller than ``num_state_qubits``. diff --git a/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py b/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py index f4d4d501e77a..c700233f72ac 100644 --- a/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py +++ b/qiskit/circuit/library/arithmetic/polynomial_pauli_rotations.py @@ -206,7 +206,7 @@ def coeffs(self, coeffs: list[float]) -> None: ``coeffs[i]`` is the coefficient of the i-th power of x. Args: - The coefficients of the polynomial. + coeffs: The coefficients of the polynomial. """ self._invalidate() self._coeffs = coeffs diff --git a/qiskit/circuit/library/n_local/n_local.py b/qiskit/circuit/library/n_local/n_local.py index 889ac4d593e1..430b5275b974 100644 --- a/qiskit/circuit/library/n_local/n_local.py +++ b/qiskit/circuit/library/n_local/n_local.py @@ -446,7 +446,7 @@ def num_qubits(self, num_qubits: int) -> None: """Set the number of qubits for the n-local circuit. Args: - The new number of qubits. + num_qubits: The new number of qubits. """ if self._num_qubits != num_qubits: # invalidate the circuit @@ -666,7 +666,7 @@ def ordered_parameters(self, parameters: ParameterVector | list[Parameter]) -> N """Set the parameters used in the underlying circuit. Args: - The parameters to be used in the underlying circuit. + parameters: The parameters to be used in the underlying circuit. Raises: ValueError: If the length of ordered parameters does not match the number of diff --git a/qiskit/circuit/library/standard_gates/x.py b/qiskit/circuit/library/standard_gates/x.py index b81891579e81..a3aea0d384eb 100644 --- a/qiskit/circuit/library/standard_gates/x.py +++ b/qiskit/circuit/library/standard_gates/x.py @@ -1243,15 +1243,18 @@ def __init__( ): """ Args: + num_ctrl_qubits: Number of controls to add. Defaults to ``1``. dirty_ancillas: when set to ``True``, the method applies an optimized multicontrolled-X gate up to a relative phase using dirty ancillary qubits with the properties of lemmas 7 and 8 from arXiv:1501.06911, with at most 8*k - 6 CNOT gates. For k within the range {1, ..., ceil(n/2)}. And for n representing the total number of qubits. + label: Optional gate label. Defaults to ``None``. + ctrl_state: The control state of the gate, specified either as an integer or a bitstring + (e.g. ``"110"``). If ``None``, defaults to the all-ones state ``2**num_ctrl_qubits - 1`` relative_phase: when set to ``True``, the method applies the optimized multicontrolled-X gate up to a relative phase, in a way that, by lemma 7 of arXiv:1501.06911, the relative phases of the ``action part`` cancel out with the phases of the ``reset part``. - action_only: when set to ``True``, the method applies only the action part of lemma 8 from arXiv:1501.06911. diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 5820bb01050f..ddb84bdfe492 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -5857,7 +5857,8 @@ def ecr(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet: For the full matrix form of this gate, see the underlying gate documentation. Args: - qubit1, qubit2: The qubits to apply the gate to. + qubit1: The first qubit to apply the gate to. + qubit2: The second qubit to apply the gate to. Returns: A handle to the instructions created. @@ -5970,7 +5971,8 @@ def swap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSet For the full matrix form of this gate, see the underlying gate documentation. Args: - qubit1, qubit2: The qubits to apply the gate to. + qubit1: The first qubit to apply the gate to. + qubit2: The second qubit to apply the gate to. Returns: A handle to the instructions created. @@ -5987,7 +5989,8 @@ def iswap(self, qubit1: QubitSpecifier, qubit2: QubitSpecifier) -> InstructionSe For the full matrix form of this gate, see the underlying gate documentation. Args: - qubit1, qubit2: The qubits to apply the gate to. + qubit1: The first qubit to apply the gate to. + qubit2: The second qubit to apply the gate to. Returns: A handle to the instructions created. @@ -6914,6 +6917,7 @@ def _push_scope( Args: qubits: Any qubits that this scope should automatically use. clbits: Any clbits that this scope should automatically use. + registers: Any registers that this scope should automatically use. allow_jumps: Whether this scope allows jumps to be used within it. forbidden_message: If given, all attempts to add instructions to this scope will raise a :exc:`.CircuitError` with this message. diff --git a/qiskit/dagcircuit/dagdependency.py b/qiskit/dagcircuit/dagdependency.py index 5fbb3ba39d46..992fe1cc66f6 100644 --- a/qiskit/dagcircuit/dagdependency.py +++ b/qiskit/dagcircuit/dagdependency.py @@ -128,7 +128,7 @@ def global_phase(self, angle: float | ParameterExpression): """Set the global phase of the circuit. Args: - angle (float, ParameterExpression) + angle (float, ParameterExpression): The angle to set the global phase to. """ from qiskit.circuit.parameterexpression import ParameterExpression # needed? diff --git a/qiskit/dagcircuit/dagdependency_v2.py b/qiskit/dagcircuit/dagdependency_v2.py index 41f0c11b7e30..dc46f7c82409 100644 --- a/qiskit/dagcircuit/dagdependency_v2.py +++ b/qiskit/dagcircuit/dagdependency_v2.py @@ -127,7 +127,7 @@ def global_phase(self, angle): """Set the global phase of the circuit. Args: - angle (float, ParameterExpression) + angle (float, ParameterExpression): The angle to set the global phase to. """ if isinstance(angle, ParameterExpression): self._global_phase = angle diff --git a/qiskit/passmanager/passmanager.py b/qiskit/passmanager/passmanager.py index 133c92e3c034..3aefdc93d23c 100644 --- a/qiskit/passmanager/passmanager.py +++ b/qiskit/passmanager/passmanager.py @@ -143,6 +143,7 @@ def _passmanager_frontend( Args: input_program: Input program. + **kwargs: Keyword arguments for the pass manager frontend. Returns: Pass manager IR. @@ -162,6 +163,7 @@ def _passmanager_backend( in_program: The input program, this can be used if you need any metadata about the original input for the output. It should not be mutated. + **kwargs: Keyword arguments for the pass manager backend. Returns: Output program. @@ -290,6 +292,8 @@ def _run_workflow( Args: program: Arbitrary program to optimize. pass_manager: Pass manager with scheduled passes. + initial_property_set: An optional dictionary to preseed the + property set in the pass manager with. **kwargs: Keyword arguments for IR conversion. Returns: @@ -342,6 +346,10 @@ def _run_workflow_in_new_process( Args: program: Arbitrary program to optimize. pass_manager_bin: Binary of the pass manager with scheduled passes. + initial_property_set: An optional dictionary to preseed the + property set in the pass manager with. + callback: An optional callable that will be called after each pass + executes. Returns: Optimized program. diff --git a/qiskit/primitives/containers/bindings_array.py b/qiskit/primitives/containers/bindings_array.py index 5715a36e32e8..9d3b9d93633c 100644 --- a/qiskit/primitives/containers/bindings_array.py +++ b/qiskit/primitives/containers/bindings_array.py @@ -174,7 +174,7 @@ def as_array(self, parameters: Iterable[ParameterLike] | None = None) -> np.ndar The parameters are indexed along the last dimension of the returned array. - Parameters: + Args: parameters: Optional parameters that determine the order of the output. Returns: diff --git a/qiskit/primitives/primitive_job.py b/qiskit/primitives/primitive_job.py index 8a85f27f4cc0..59af1ff26b02 100644 --- a/qiskit/primitives/primitive_job.py +++ b/qiskit/primitives/primitive_job.py @@ -36,6 +36,8 @@ def __init__(self, function, *args, **kwargs): """ Args: function: A callable function to execute the job. + args: any additional positional arguments + kwargs: any additional keyword arguments """ super().__init__(str(uuid.uuid4())) self._future = None diff --git a/qiskit/primitives/statevector_sampler.py b/qiskit/primitives/statevector_sampler.py index f535348843d2..c154a08884cd 100644 --- a/qiskit/primitives/statevector_sampler.py +++ b/qiskit/primitives/statevector_sampler.py @@ -254,7 +254,7 @@ def _samples_to_packed_array( def _final_measurement_mapping(circuit: QuantumCircuit) -> dict[tuple[ClassicalRegister, int], int]: """Return the final measurement mapping for the circuit. - Parameters: + Args: circuit: Input quantum circuit. Returns: diff --git a/qiskit/qasm3/printer.py b/qiskit/qasm3/printer.py index 918fe964d0aa..f4f71231ac21 100644 --- a/qiskit/qasm3/printer.py +++ b/qiskit/qasm3/printer.py @@ -120,6 +120,8 @@ def __init__( default. While the output of this printer is always unambiguous, using ``else`` without immediately opening an explicit scope with ``{ }`` in nested contexts can cause issues, in the general case, which is why it is sometimes less supported. + experimental: any experimental features to enable during the export. See + :class:`ExperimentalFeatures` for more details. """ self.stream = stream self.indent = indent diff --git a/qiskit/quantum_info/analysis/distance.py b/qiskit/quantum_info/analysis/distance.py index a714835392b7..e845d23abfa4 100644 --- a/qiskit/quantum_info/analysis/distance.py +++ b/qiskit/quantum_info/analysis/distance.py @@ -19,7 +19,7 @@ def hellinger_distance(dist_p: dict, dist_q: dict) -> float: """Computes the Hellinger distance between two counts distributions. - Parameters: + Args: dist_p (dict): First dict of counts. dist_q (dict): Second dict of counts. @@ -65,7 +65,7 @@ def hellinger_fidelity(dist_p: dict, dist_q: dict) -> float: :math:`F(Q,P)=\\left(\\sum_{i}\\sqrt{p_{i}q_{i}}\\right)^{2}` that in turn is equal to the quantum state fidelity for diagonal density matrices. - Parameters: + Args: dist_p (dict): First dict of counts. dist_q (dict): Second dict of counts. diff --git a/qiskit/result/distributions/probability.py b/qiskit/result/distributions/probability.py index 50e5b1bf8e8f..eb792ca573fe 100644 --- a/qiskit/result/distributions/probability.py +++ b/qiskit/result/distributions/probability.py @@ -75,7 +75,7 @@ def __init__(self, data, shots=None): def binary_probabilities(self, num_bits=None): """Build a probabilities dictionary with binary string keys - Parameters: + Args: num_bits (int): number of bits in the binary bitstrings (leading zeros will be padded). If None, a default value will be used. If keys are given as integers or strings with binary or hex prefix, diff --git a/qiskit/result/distributions/quasi.py b/qiskit/result/distributions/quasi.py index e4ebfbab0c26..4cc09ddf821c 100644 --- a/qiskit/result/distributions/quasi.py +++ b/qiskit/result/distributions/quasi.py @@ -39,7 +39,7 @@ def __init__(self, data, shots=None, stddev_upper_bound=None): and the parameter ``ndigits`` can be manipulated with the class attribute ``__ndigits__``. The default is ``15``. - Parameters: + Args: data (dict): Input quasiprobability data. Where the keys represent a measured classical value and the value is a float for the quasiprobability of that result. @@ -90,7 +90,7 @@ def nearest_probability_distribution(self, return_distance=False): it to the closest probability distribution as defined by the L2-norm. - Parameters: + Args: return_distance (bool): Return the L2 distance between distributions. Returns: @@ -121,7 +121,7 @@ def nearest_probability_distribution(self, return_distance=False): def binary_probabilities(self, num_bits=None): """Build a quasi-probabilities dictionary with binary string keys - Parameters: + Args: num_bits (int): number of bits in the binary bitstrings (leading zeros will be padded). If None, a default value will be used. If keys are given as integers or strings with binary or hex prefix, diff --git a/qiskit/result/sampled_expval.py b/qiskit/result/sampled_expval.py index efca38dbd7b6..1b1c7966bf44 100644 --- a/qiskit/result/sampled_expval.py +++ b/qiskit/result/sampled_expval.py @@ -42,7 +42,7 @@ def sampled_expectation_value( Note that passing a raw dict requires bit-string keys. - Parameters: + Args: dist: Input sampled distribution. oper: The operator for the observable. diff --git a/qiskit/synthesis/two_qubit/two_qubit_decompose.py b/qiskit/synthesis/two_qubit/two_qubit_decompose.py index bf575b167807..2ea71ed3f0d0 100644 --- a/qiskit/synthesis/two_qubit/two_qubit_decompose.py +++ b/qiskit/synthesis/two_qubit/two_qubit_decompose.py @@ -299,7 +299,7 @@ def __init__(self, rxx_equivalent_gate: type[Gate], euler_basis: str = "ZXZ"): self.scale = self._inner_decomposer.scale self.euler_basis = euler_basis - def __call__( + def __call__( # noqa: D417 TODO: Add support for the undocumented arguments self, unitary: Operator | np.ndarray, approximate=False, use_dag=False, *, atol=DEFAULT_ATOL ) -> QuantumCircuit: """Returns the Weyl decomposition in circuit form. diff --git a/qiskit/synthesis/unitary/aqc/fast_gradient/fast_grad_utils.py b/qiskit/synthesis/unitary/aqc/fast_gradient/fast_grad_utils.py index 5252699b638b..9e0819150b12 100644 --- a/qiskit/synthesis/unitary/aqc/fast_gradient/fast_grad_utils.py +++ b/qiskit/synthesis/unitary/aqc/fast_gradient/fast_grad_utils.py @@ -23,7 +23,7 @@ def is_permutation(x: np.ndarray) -> bool: Checks if array is really an index permutation. Args: - 1D-array of integers that supposedly represents a permutation. + x: 1D-array of integers that supposedly represents a permutation. Returns: True, if array is really a permutation of indices. diff --git a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py index 78b654477043..0feebb039810 100644 --- a/qiskit/transpiler/passes/basis/unroll_custom_definitions.py +++ b/qiskit/transpiler/passes/basis/unroll_custom_definitions.py @@ -33,8 +33,8 @@ def __init__(self, equivalence_library, basis_gates=None, target=None, min_qubit Ignored if ``target`` is also specified. target (Optional[Target]): The :class:`~.Target` object corresponding to the compilation target. When specified, any argument specified for ``basis_gates`` is ignored. - min_qubits (int): The minimum number of qubits for operations in the input - dag to translate. + min_qubits (int): The minimum number of qubits for operations in the input + dag to translate. """ super().__init__() diff --git a/qiskit/transpiler/passes/layout/dense_layout.py b/qiskit/transpiler/passes/layout/dense_layout.py index b9575a6b0636..3e50a09cfa7c 100644 --- a/qiskit/transpiler/passes/layout/dense_layout.py +++ b/qiskit/transpiler/passes/layout/dense_layout.py @@ -128,6 +128,10 @@ def _best_subset(self, num_qubits, num_meas, num_cx, coupling_map): Args: num_qubits (int): Number of subset qubits to consider. + num_meas (int): The number of measure measurement operations in the circuit. + num_cx (int): The number of CXGates that are in the circuit + coupling_map (CouplingMap): The coupling map representing the connectivity of + the QPU. Returns: ndarray: Array of qubits to use for best connectivity mapping. diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py index 50f49bb7014e..818306f74090 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/commuting_2q_gate_router.py @@ -226,6 +226,7 @@ def _position_in_cmap(self, dag: DAGCircuit, j: int, k: int, layout: Layout) -> """A helper function to track the movement of virtual qubits through the swaps. Args: + dag: The dag the pass is being run on. j: The index of decision variable j (i.e. virtual qubit). k: The index of decision variable k (i.e. virtual qubit). layout: The current layout that takes into account previous swap gates. @@ -377,6 +378,7 @@ def _check_edges(self, dag: DAGCircuit, node: DAGOpNode, swap_strategy: SwapStra """Check if the swap strategy can create the required connectivity. Args: + dag: The dag to check edges from. node: The dag node for which to check if the swap strategy provides enough connectivity. swap_strategy: The swap strategy that is being used. diff --git a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py index 67e0c5700c2a..605288b5347a 100644 --- a/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py +++ b/qiskit/transpiler/passes/routing/commuting_2q_gate_routing/pauli_2q_evolution_commutation.py @@ -31,7 +31,7 @@ def run(self, dag: DAGCircuit) -> DAGCircuit: """Check for :class:`.PauliEvolutionGate` objects where the summands all commute. Args: - The DAG circuit in which to look for the commuting evolutions. + dag: The DAG circuit in which to look for the commuting evolutions. Returns: The dag in which :class:`.PauliEvolutionGate` objects made of commuting two-qubit Paulis diff --git a/qiskit/transpiler/passes/scheduling/padding/base_padding.py b/qiskit/transpiler/passes/scheduling/padding/base_padding.py index f05d4b2efb2e..612418098372 100644 --- a/qiskit/transpiler/passes/scheduling/padding/base_padding.py +++ b/qiskit/transpiler/passes/scheduling/padding/base_padding.py @@ -66,6 +66,9 @@ def __init__( target: The :class:`~.Target` representing the target backend. If it supplied and it does not support delay instruction on a qubit, padding passes do not pad any idle time of the qubit. + durations: The instruction durations. This is mostly for legacy applications without + a :class:`.Target`. The ``target`` argument should typically be used instead of + this and if both are specified ``target`` will supersede this argument. """ super().__init__() self.target = target diff --git a/qiskit/transpiler/passes/scheduling/padding/pad_delay.py b/qiskit/transpiler/passes/scheduling/padding/pad_delay.py index 1192fbc89c81..2a91a3b8690c 100644 --- a/qiskit/transpiler/passes/scheduling/padding/pad_delay.py +++ b/qiskit/transpiler/passes/scheduling/padding/pad_delay.py @@ -70,6 +70,9 @@ def __init__( target: The :class:`~.Target` representing the target backend. If it is supplied and does not support delay instruction on a qubit, padding passes do not pad any idle time of the qubit. + durations: The instruction durations. This is mostly for legacy applications without + a :class:`.Target`. The ``target`` argument should typically be used instead of + this and if both are specified ``target`` will supersede this argument. """ super().__init__(target=target, durations=durations) self.fill_very_end = fill_very_end diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 9fafd7e42b9e..dc0dfc4813a7 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -67,6 +67,9 @@ def __init__( :class:`~qiskit.transpiler.passes.UnitarySynthesis` pass. Will search installed plugins for a valid method. You can see a list of installed plugins with :func:`.unitary_synthesis_plugin_names`. + unitary_synthesis_plugin_config (dict): The configuration dictionary that will + be passed to the specified unitary synthesis plugin. Refer to + the plugin documentation for how to use this. target (Target): The backend target hls_config (HLSConfig): An optional configuration class to use for :class:`~qiskit.transpiler.passes.HighLevelSynthesis` pass. diff --git a/qiskit/utils/lazy_tester.py b/qiskit/utils/lazy_tester.py index 65fa00b181a3..604a876f79c9 100644 --- a/qiskit/utils/lazy_tester.py +++ b/qiskit/utils/lazy_tester.py @@ -261,6 +261,12 @@ def __init__( module. It should be valid to write ``from import , , ...``. If simply a string or iterable of strings, then it should be valid to write ``import `` for each of them. + name: the name of this optional dependency. + callback: a callback that is called immediately after the availability of the library is + tested with the result. This will only be called once. + install: how to install this optional dependency. Passed to + :class:`.MissingOptionalLibraryError` as the ``pip_install`` parameter. + msg: an extra message to include in the error raised if this is required. Raises: ValueError: if no modules are given. @@ -344,6 +350,12 @@ def __init__( Args: command: the strings that make up the command to be run. For example, ``["pdflatex", "-version"]``. + name: the name of this optional dependency. + callback: a callback that is called immediately after the availability of the library is + tested with the result. This will only be called once. + install: how to install this optional dependency. Passed to + :class:`.MissingOptionalLibraryError` as the ``pip_install`` parameter. + msg: an extra message to include in the error raised if this is required. Raises: ValueError: if an empty command is given. diff --git a/qiskit/visualization/circuit/_utils.py b/qiskit/visualization/circuit/_utils.py index aefe34001ad1..b70837f4d560 100644 --- a/qiskit/visualization/circuit/_utils.py +++ b/qiskit/visualization/circuit/_utils.py @@ -434,6 +434,8 @@ def _get_layered_instructions( default `left` will be used. idle_wires (bool): Include idle wires. Default is True. wire_order (list): A list of ints that modifies the order of the bits. + wire_map (dict): The wire map + measure_arrows (bool): whether do draw arrows from measurements Returns: Tuple(list,list,list): To be consumed by the visualizer directly. diff --git a/qiskit/visualization/circuit/latex.py b/qiskit/visualization/circuit/latex.py index 2de84f52fc0b..4bbd10e9e6f0 100644 --- a/qiskit/visualization/circuit/latex.py +++ b/qiskit/visualization/circuit/latex.py @@ -78,6 +78,7 @@ def __init__( circuit. Defaults to True. initial_state (bool): Optional. Adds |0> in the beginning of the line. Default: `False`. cregbundle (bool): Optional. If set True bundle classical registers. + with_layout (bool): Optional. If set to True display the layout in the circuit. circuit (QuantumCircuit): the circuit that's being displayed barrier_label_len (int): Optional. The number of characters to display for barrier labels. If this number is exceeded, the string will be truncated. diff --git a/qiskit/visualization/counts_visualization.py b/qiskit/visualization/counts_visualization.py index 7f34a9fa6525..b14f6ec01d29 100644 --- a/qiskit/visualization/counts_visualization.py +++ b/qiskit/visualization/counts_visualization.py @@ -440,7 +440,7 @@ def _unify_labels(data): def _plot_data(data, labels, number_to_keep, kind="counts"): """Generate the data needed for plotting counts. - Parameters: + Args: data (list or dict): This is either a list of dictionaries or a single dict containing the values to represent (ex {'001': 130}) labels (list): The list of bitstring labels for the plot. diff --git a/qiskit/visualization/state_visualization.py b/qiskit/visualization/state_visualization.py index 3af9eb54b7e3..985aff888657 100644 --- a/qiskit/visualization/state_visualization.py +++ b/qiskit/visualization/state_visualization.py @@ -47,7 +47,9 @@ def plot_state_hinton(state, title="", figsize=None, ax_real=None, ax_imag=None, state (Statevector or DensityMatrix or ndarray): An N-qubit quantum state. title (str): a string that represents the plot title figsize (tuple): Figure size in inches. - filename (str): file path to save image to. + filename (str | None): The optional file path to save image to. If not specified + no file is created for the visualization. If this is set the return + from this function will be ``None``. ax_real (matplotlib.axes.Axes): An optional Axes object to be used for the visualization output. If none is specified a new matplotlib Figure will be created and used. If this is specified without an @@ -281,6 +283,9 @@ def plot_bloch_multivector( title_font_size (float): Font size for the title. title_pad (float): Padding for the title (suptitle ``y`` position is ``0.98`` and the image height will be extended by ``1 + title_pad/100``). + filename (str | None): The optional file path to save image to. If not specified + no file is created for the visualization. If this is set the return + from this function will be ``None``. Returns: :class:`matplotlib:matplotlib.figure.Figure` : @@ -403,6 +408,9 @@ def plot_state_city( ax_real only the imaginary component plot will be generated. Additionally, if specified there will be no returned Figure since it is redundant. + filename (str | None): The optional file path to save image to. If not specified + no file is created for the visualization. If this is set the return + from this function will be ``None``. Returns: :class:`matplotlib:matplotlib.figure.Figure` : @@ -642,6 +650,9 @@ def plot_state_paulivec(state, title="", figsize=None, color=None, ax=None, *, f the visualization output. If none is specified a new matplotlib Figure will be created and used. Additionally, if specified there will be no returned Figure since it is redundant. + filename (str | None): The optional file path to save image to. If not specified + no file is created for the visualization. If this is set the return + from this function will be ``None``. Returns: :class:`matplotlib:matplotlib.figure.Figure` : @@ -826,6 +837,10 @@ def plot_state_qsphere( show the phase for each basis state. use_degrees (bool): An optional boolean indicating whether to use radians or degrees for the phase values in the plot. + filename (str | None): The optional file path to save image to. If not specified + no file is created for the visualization. If this is set the return + from this function will be ``None``. + Returns: :class:`matplotlib:matplotlib.figure.Figure` : @@ -1437,6 +1452,7 @@ def state_drawer(state, output=None, **drawer_args): **paulivec**: Matplotlib figure, rendering of statevector using `plot_state_paulivec()`. Args: + state: State to be drawn output (str): Select the output method to use for drawing the circuit. Valid choices are ``text``, ``latex``, ``latex_source``, ``qsphere``, ``hinton``, ``bloch``, ``city`` or ``paulivec``. From f7585e2233cb473d241d6ad38a50658adcdaf378 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Fri, 27 Mar 2026 11:22:19 -0400 Subject: [PATCH 5/6] Use nalgebra matrices for uncommon paths in consolidate blocks (#15881) In the recently merged #15871 we updated the consolidate blocks pass to use Matrix4 in the common path of a 2q block being consolidated. This is like > 90% of what the pass does when run in the preset pass manager. However, there were uncommon cases in the pass around the handling of blocks of a single gate that are outside of the target which were not updated to use nalgebra arrays if it's a fixed size 1q or 2q gate. This commit updates these uncommon paths so that we're always returning an nalgebra matrix in the output UnitaryGate if the block being consolidated is a single qubit or two qubits. --- .../src/convert_2q_block_matrix.rs | 40 ++++++++++++++++- .../src/passes/consolidate_blocks.rs | 44 ++++++++++++++----- 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/crates/quantum_info/src/convert_2q_block_matrix.rs b/crates/quantum_info/src/convert_2q_block_matrix.rs index 581d7623e2c2..d9e9c71c1889 100644 --- a/crates/quantum_info/src/convert_2q_block_matrix.rs +++ b/crates/quantum_info/src/convert_2q_block_matrix.rs @@ -16,7 +16,7 @@ use pyo3::prelude::*; use num_complex::Complex64; use numpy::PyReadonlyArray2; -use numpy::nalgebra::{Matrix4, MatrixViewMut4}; +use numpy::nalgebra::{Matrix2, Matrix4, MatrixViewMut4}; use numpy::ndarray::{Array2, ArrayView2}; use rustworkx_core::petgraph::stable_graph::NodeIndex; @@ -52,6 +52,16 @@ fn matrix4_from_pyreadonly(array: &PyReadonlyArray2) -> Matrix4) -> Matrix2 { + Matrix2::new( + *array.get((0, 0)).unwrap(), + *array.get((0, 1)).unwrap(), + *array.get((1, 0)).unwrap(), + *array.get((1, 1)).unwrap(), + ) +} + #[inline] pub fn get_matrix_from_inst(inst: &PackedInstruction) -> PyResult> { if let Some(mat) = inst.try_matrix() { @@ -109,6 +119,34 @@ pub fn get_2q_matrix_from_inst(inst: &PackedInstruction) -> PyResult PyResult> { + if let Some(mat) = inst.try_matrix_as_nalgebra_1q() { + return Ok(mat); + } + if inst.op.try_standard_gate().is_some() { + return Err(QiskitError::new_err( + "Parameterized gates can't be consolidated", + )); + } + let OperationRef::Gate(gate) = inst.op.view() else { + return Err(QiskitError::new_err( + "Can't compute matrix of non-unitary op", + )); + }; + // If the operation is a custom python gate, we will acquire the gil and use an + // Operator. Otherwise, using op.matrix() should work. A user should not be + // able to reach this condition in Rust standalone mode. + Python::attach(|py| { + let res = QI_OPERATOR + .get_bound(py) + .call1((gate.instruction.clone_ref(py),))? + .getattr(intern!(py, "data"))? + .extract()?; + Ok(matrix2_from_pyreadonly(&res)) + }) +} + /// Quaternion-based collect of two parallel runs of 1q gates. #[derive(Clone, Debug)] struct Separable1q { diff --git a/crates/transpiler/src/passes/consolidate_blocks.rs b/crates/transpiler/src/passes/consolidate_blocks.rs index 0f52d4b90d70..d65e8f833b0f 100644 --- a/crates/transpiler/src/passes/consolidate_blocks.rs +++ b/crates/transpiler/src/passes/consolidate_blocks.rs @@ -31,7 +31,9 @@ use qiskit_circuit::interner::Interned; use qiskit_circuit::operations::StandardGate; use qiskit_circuit::operations::{ArrayType, Operation, Param, UnitaryGate}; use qiskit_circuit::packed_instruction::PackedOperation; -use qiskit_quantum_info::convert_2q_block_matrix::{blocks_to_matrix, get_matrix_from_inst}; +use qiskit_quantum_info::convert_2q_block_matrix::{ + blocks_to_matrix, get_1q_matrix_from_inst, get_2q_matrix_from_inst, get_matrix_from_inst, +}; use qiskit_synthesis::linalg::nalgebra_array_view; use qiskit_synthesis::two_qubit_decompose::RXXEquivalent; use qiskit_synthesis::two_qubit_decompose::{ @@ -289,14 +291,31 @@ fn py_run_consolidate_blocks( phys_qargs.get(dag, inst.qubits), ) { all_block_gates.insert(inst_node); - let matrix = match get_matrix_from_inst(inst) { - Ok(mat) => mat, - Err(_) => continue, - }; - // TODO: Use Matrix2/ArrayType::OneQ when we're using nalgebra - // for consolidation - let unitary_gate = UnitaryGate { - array: ArrayType::NDArray(matrix), + 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, + Err(_) => continue, + }; + UnitaryGate { + array: ArrayType::OneQ(matrix), + } + } else if num_qubits == 2 { + let matrix = match get_2q_matrix_from_inst(inst) { + Ok(mat) => mat, + Err(_) => continue, + }; + UnitaryGate { + array: ArrayType::TwoQ(matrix), + } + } else { + let matrix = match get_matrix_from_inst(inst) { + Ok(mat) => mat, + Err(_) => continue, + }; + UnitaryGate { + array: ArrayType::NDArray(matrix), + } }; dag.substitute_op( inst_node, @@ -450,12 +469,15 @@ fn py_run_consolidate_blocks( first_qubits, ) { - let matrix = match get_matrix_from_inst(first_inst) { + // Runs are necessarily single qubit gates so if it has a matrix then it + // must have a single qubit matrix or it's not a run and the blocks handling above + // would have covered it + let matrix = match get_1q_matrix_from_inst(first_inst) { Ok(mat) => mat, Err(_) => continue, }; let unitary_gate = UnitaryGate { - array: ArrayType::NDArray(matrix), + array: ArrayType::OneQ(matrix), }; dag.substitute_op( first_inst_node, From 552fd4fb7a549cb0969173d3f63f283e2d302efa Mon Sep 17 00:00:00 2001 From: Gadi Aleksandrowicz Date: Sun, 29 Mar 2026 15:21:53 +0300 Subject: [PATCH 6/6] More explicit error messages in qpy_compat --- test/qpy_compat/test_qpy.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index 3c5745592e95..f76b5b26216f 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -1109,8 +1109,13 @@ def load_qpy(qpy_files, version_parts): # See https://github.com/Qiskit/qiskit/pull/13814 continue print(f"Loading qpy file: {path}") # noqa: T201 - with open(path, "rb") as fd: - qpy_circuits = load(fd) + try: + with open(path, "rb") as fd: + qpy_circuits = load(fd) + except Exception as ex: + msg = f"****QPY Load error****: Failed to load {path} with the exception: {ex}" + sys.stderr.write(msg) + sys.exit(1) equivalent = path in {"open_controlled_gates.qpy", "controlled_gates.qpy"} for i, circuit in enumerate(circuits): bind = None