From 3e2908b49f2281af74a9cd9879a837276276420d Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 19 Feb 2025 04:29:59 -0500 Subject: [PATCH 1/5] Add method to compute estimated duration of scheduled circuit (#13783) * Add method to compute estimated duration of scheduled circuit This commit adds a new QuantumCircuit method to compute the estimated duration of a scheduled circuit. This is to replace the deprecated duration attribute that the transpiler was potentially setting during the scheduling stage. This method computes the longest duration path in the dag view of the circuit internally. This method should have been included in the 1.2.0 release prior to the deprecation of the `QuantumCircuit.duration` attribute in 1.3.0. But, this was an oversight in the path to deprecation, as it was part of larger deprecation of numerous scheduling pieces in the 1.3.0. We should definitely backport this for the 1.4.0 release for inclusion in that release prior to the Qiskit 2.0.0 release which removes the deprecated attribute * Simplify dag node indexing * Expand docs * Fix handling for StandardInstruction in rust and add first test * Expand test coverage * Fix lint (cherry picked from commit 8c50ce46316a38898884449ace5411122bf5c7ba) --- crates/accelerate/src/circuit_duration.rs | 109 ++++++++++++++++ crates/accelerate/src/lib.rs | 1 + .../accelerate/src/target_transpiler/mod.rs | 11 ++ crates/pyext/src/lib.rs | 1 + qiskit/__init__.py | 1 + qiskit/circuit/quantumcircuit.py | 65 +++++++++- ...mate_duration-method-a35bf8eef4b2f210.yaml | 7 + test/python/circuit/test_scheduled_circuit.py | 120 ++++++++++++++++++ 8 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 crates/accelerate/src/circuit_duration.rs create mode 100644 releasenotes/notes/add-estimate_duration-method-a35bf8eef4b2f210.yaml diff --git a/crates/accelerate/src/circuit_duration.rs b/crates/accelerate/src/circuit_duration.rs new file mode 100644 index 000000000000..0e9738822f84 --- /dev/null +++ b/crates/accelerate/src/circuit_duration.rs @@ -0,0 +1,109 @@ +// This code is part of Qiskit. +// +// (C) Copyright IBM 2025 +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE.txt file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +use pyo3::prelude::*; +use pyo3::wrap_pyfunction; + +use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, Wire}; +use qiskit_circuit::operations::{DelayUnit, Operation, OperationRef, Param, StandardInstruction}; + +use crate::nlayout::PhysicalQubit; +use crate::target_transpiler::Target; +use crate::QiskitError; +use rustworkx_core::dag_algo::longest_path; +use rustworkx_core::petgraph::stable_graph::StableDiGraph; +use rustworkx_core::petgraph::visit::{EdgeRef, IntoEdgeReferences}; + +/// Estimate the duration of a scheduled circuit in seconds +#[pyfunction] +pub(crate) fn compute_estimated_duration(dag: &DAGCircuit, target: &Target) -> PyResult { + let dt = target.dt; + + let get_duration = + |edge: <&StableDiGraph as IntoEdgeReferences>::EdgeRef| -> PyResult { + let node_weight = &dag[edge.target()]; + match node_weight { + NodeType::Operation(inst) => { + let name = inst.op.name(); + let qubits = dag.get_qargs(inst.qubits); + let physical_qubits: Vec = + qubits.iter().map(|x| PhysicalQubit::new(x.0)).collect(); + + if let OperationRef::StandardInstruction(op) = inst.op.view() { + if let StandardInstruction::Delay(unit) = op { + let dur = &inst.params.as_ref().unwrap()[0]; + return if unit == DelayUnit::DT { + if let Some(dt) = dt { + match dur { + Param::Float(val) => + { + Ok(val / dt) + + }, + Param::Obj(val) => { + Python::with_gil(|py| { + let dur_float: f64 = val.extract(py)?; + Ok(dur_float * dt) + }) + }, + Param::ParameterExpression(_) => Err(QiskitError::new_err( + "Circuit contains parameterized delays, can't compute a duration estimate with this circuit" + )), + } + } else { + Err(QiskitError::new_err( + "Circuit contains delays in dt but the target doesn't specify dt" + )) + } + } else if unit == DelayUnit::S { + match dur { + Param::Float(val) => Ok(*val), + _ => Err(QiskitError::new_err( + "Invalid type for parameter value for delay in circuit", + )), + } + } else { + Err(QiskitError::new_err( + "Circuit contains delays in units other then seconds or dt, the circuit is not scheduled." + )) + }; + } else if let StandardInstruction::Barrier(_) = op { + return Ok(0.); + } + } + match target.get_duration(name, &physical_qubits) { + Some(dur) => Ok(dur), + None => Err(QiskitError::new_err(format!( + "Duration not found for {} on qubits: {:?}", + name, qubits + ))), + } + } + NodeType::QubitOut(_) | NodeType::ClbitOut(_) => Ok(0.), + NodeType::ClbitIn(_) | NodeType::QubitIn(_) => { + Err(QiskitError::new_err("Invalid circuit provided")) + } + _ => Err(QiskitError::new_err( + "Circuit contains Vars, duration can't be calculated with classical variables", + )), + } + }; + match longest_path(dag.dag(), get_duration)? { + Some((_, weight)) => Ok(weight), + None => Err(QiskitError::new_err("Invalid circuit provided")), + } +} + +pub fn compute_duration(m: &Bound) -> PyResult<()> { + m.add_wrapped(wrap_pyfunction!(compute_estimated_duration))?; + Ok(()) +} diff --git a/crates/accelerate/src/lib.rs b/crates/accelerate/src/lib.rs index 45cf047a6808..1270c00f5c53 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -21,6 +21,7 @@ use pyo3::import_exception; pub mod barrier_before_final_measurement; pub mod basis; pub mod check_map; +pub mod circuit_duration; pub mod circuit_library; pub mod commutation_analysis; pub mod commutation_cancellation; diff --git a/crates/accelerate/src/target_transpiler/mod.rs b/crates/accelerate/src/target_transpiler/mod.rs index 8eb9f385a672..568eb256503b 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -951,6 +951,17 @@ impl Target { }) } + /// Get the duration of a given instruction in the target + pub fn get_duration(&self, name: &str, qargs: &[PhysicalQubit]) -> Option { + self.gate_map.get(name).and_then(|gate_props| { + let qargs_key: Qargs = qargs.iter().cloned().collect(); + match gate_props.get(Some(&qargs_key)) { + Some(props) => props.as_ref().and_then(|inst_props| inst_props.duration), + None => None, + } + }) + } + /// Get an iterator over the indices of all physical qubits of the target pub fn physical_qubits(&self) -> impl ExactSizeIterator { 0..self.num_qubits.unwrap_or_default() diff --git a/crates/pyext/src/lib.rs b/crates/pyext/src/lib.rs index d8e59e04e51e..a2a63e79a223 100644 --- a/crates/pyext/src/lib.rs +++ b/crates/pyext/src/lib.rs @@ -31,6 +31,7 @@ fn _accelerate(m: &Bound) -> PyResult<()> { add_submodule(m, ::qiskit_accelerate::barrier_before_final_measurement::barrier_before_final_measurements_mod, "barrier_before_final_measurement")?; add_submodule(m, ::qiskit_accelerate::basis::basis, "basis")?; add_submodule(m, ::qiskit_accelerate::check_map::check_map_mod, "check_map")?; + add_submodule(m, ::qiskit_accelerate::circuit_duration::compute_duration, "circuit_duration")?; add_submodule(m, ::qiskit_accelerate::circuit_library::circuit_library, "circuit_library")?; add_submodule(m, ::qiskit_accelerate::commutation_analysis::commutation_analysis, "commutation_analysis")?; add_submodule(m, ::qiskit_accelerate::commutation_cancellation::commutation_cancellation, "commutation_cancellation")?; diff --git a/qiskit/__init__.py b/qiskit/__init__.py index af543bea80c7..2ce5476ec569 100644 --- a/qiskit/__init__.py +++ b/qiskit/__init__.py @@ -110,6 +110,7 @@ sys.modules["qiskit._accelerate.twirling"] = _accelerate.twirling sys.modules["qiskit._accelerate.high_level_synthesis"] = _accelerate.high_level_synthesis sys.modules["qiskit._accelerate.remove_identity_equiv"] = _accelerate.remove_identity_equiv +sys.modules["qiskit._accelerate.circuit_duration"] = _accelerate.circuit_duration from qiskit.exceptions import QiskitError, MissingOptionalLibraryError diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 2c96ec533f3f..2f225aec99a2 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -40,6 +40,7 @@ import numpy as np from qiskit._accelerate.circuit import CircuitData from qiskit._accelerate.circuit import StandardGate +from qiskit._accelerate.circuit_duration import compute_estimated_duration from qiskit.exceptions import QiskitError from qiskit.utils.multiprocessing import is_main_process from qiskit.circuit.instruction import Instruction @@ -156,6 +157,8 @@ class QuantumCircuit: :attr:`data` List of individual :class:`CircuitInstruction`\\ s that make up the circuit. :attr:`duration` Total duration of the circuit, added by scheduling transpiler passes. + This attribute is deprecated and :meth:`.estimate_duration` should + be used instead. :attr:`layout` Hardware layout and routing information added by the transpiler. :attr:`num_ancillas` The number of ancilla qubits in the circuit. @@ -921,8 +924,9 @@ class QuantumCircuit: If a :class:`QuantumCircuit` has been scheduled as part of a transpilation pipeline, the timing information for individual qubits can be accessed. The whole-circuit timing information is - available through the :attr:`duration`, :attr:`unit` and :attr:`op_start_times` attributes. + available through the :meth:`estimate_duration` method and :attr:`op_start_times` attribute. + .. automethod:: estimate_duration .. automethod:: qubit_duration .. automethod:: qubit_start_time .. automethod:: qubit_stop_time @@ -6654,6 +6658,65 @@ def qubit_stop_time(self, *qubits: Union[Qubit, int]) -> float: return 0 # If there are no instructions over bits + def estimate_duration(self, target, unit: str = "s") -> int | float: + """Estimate the duration of a scheduled circuit + + This method computes the estimate of the circuit duration by finding + the longest duration path in the circuit based on the durations + provided by a given target. This method only works for simple circuits + that have no control flow or other classical feed-forward operations. + + Args: + target (Target): The :class:`.Target` instance that contains durations for + the instructions if the target is missing duration data for any of the + instructions in the circuit an :class:`.QiskitError` will be raised. This + should be the same target object used as the target for transpilation. + unit: The unit to return the duration in. This defaults to "s" for seconds + but this can be a supported SI prefix for seconds returns. For example + setting this to "n" will return in unit of nanoseconds. Supported values + of this type are "f", "p", "n", "u", "µ", "m", "k", "M", "G", "T", and + "P". Additionally, a value of "dt" is also accepted to output an integer + in units of "dt". For this to function "dt" must be specified in the + ``target``. + + Returns: + The estimated duration for the execution of a single shot of the circuit in + the specified unit. + + Raises: + QiskitError: If the circuit is not scheduled or contains other + details that prevent computing an estimated duration from + (such as parameterized delay). + """ + from qiskit.converters import circuit_to_dag + + dur = compute_estimated_duration(circuit_to_dag(self), target) + if unit == "s": + return dur + if unit == "dt": + from qiskit.circuit.duration import duration_in_dt # pylint: disable=cyclic-import + + return duration_in_dt(dur, target.dt) + + prefix_dict = { + "f": 1e-15, + "p": 1e-12, + "n": 1e-9, + "u": 1e-6, + "µ": 1e-6, + "m": 1e-3, + "k": 1e3, + "M": 1e6, + "G": 1e9, + "T": 1e12, + "P": 1e15, + } + if unit not in prefix_dict: + raise QiskitError( + f"Specified unit: {unit} is not a valid/supported SI prefix, 's', or 'dt'" + ) + return dur / prefix_dict[unit] + class _OuterCircuitScopeInterface(CircuitScopeInterface): # This is an explicit interface-fulfilling object friend of QuantumCircuit that acts as its diff --git a/releasenotes/notes/add-estimate_duration-method-a35bf8eef4b2f210.yaml b/releasenotes/notes/add-estimate_duration-method-a35bf8eef4b2f210.yaml new file mode 100644 index 000000000000..ae6bff75d89e --- /dev/null +++ b/releasenotes/notes/add-estimate_duration-method-a35bf8eef4b2f210.yaml @@ -0,0 +1,7 @@ +--- +features_circuits: + - | + Added a new method, :meth:`.QuantumCircuit.estimate_duration`, to compute + the estimated duration of a scheduled circuit output from the :mod:`.transpiler`. + This should be used if you need an estimate of the full circuit duration instead + of the deprecated :attr:`.QuantumCircuit.duration` attribute. diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index 7ebed694a8db..29a79534822a 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -476,6 +476,126 @@ def test_convert_duration_to_dt(self): ref_unit, ) + @data("s", "dt", "f", "p", "n", "u", "µ", "m", "k", "M", "G", "T", "P") + def test_estimate_duration(self, unit): + """Test the circuit duration is computed correctly.""" + backend = GenericBackendV2(num_qubits=3, seed=42) + + circ = QuantumCircuit(2) + circ.cx(0, 1) + circ.measure_all() + + circuit_dt = transpile(circ, backend, scheduling_method="asap") + duration = circuit_dt.estimate_duration(backend.target, unit=unit) + expected_in_sec = 1.815516e-06 + expected_val = { + "s": expected_in_sec, + "dt": int(expected_in_sec / backend.target.dt), + "f": expected_in_sec / 1e-15, + "p": expected_in_sec / 1e-12, + "n": expected_in_sec / 1e-9, + "u": expected_in_sec / 1e-6, + "µ": expected_in_sec / 1e-6, + "m": expected_in_sec / 1e-3, + "k": expected_in_sec / 1e3, + "M": expected_in_sec / 1e6, + "G": expected_in_sec / 1e9, + "T": expected_in_sec / 1e12, + "P": expected_in_sec / 1e15, + } + self.assertEqual(duration, expected_val[unit]) + + @data("s", "dt", "f", "p", "n", "u", "µ", "m", "k", "M", "G", "T", "P") + def test_estimate_duration_with_long_delay(self, unit): + """Test the circuit duration is computed correctly.""" + backend = GenericBackendV2(num_qubits=3, seed=42) + + circ = QuantumCircuit(3) + circ.cx(0, 1) + circ.measure_all() + circ.delay(1e15, 2) + + circuit_dt = transpile(circ, backend, scheduling_method="asap") + duration = circuit_dt.estimate_duration(backend.target, unit=unit) + expected_in_sec = 222000.00000139928 + expected_val = { + "s": expected_in_sec, + "dt": int(expected_in_sec / backend.target.dt), + "f": expected_in_sec / 1e-15, + "p": expected_in_sec / 1e-12, + "n": expected_in_sec / 1e-9, + "u": expected_in_sec / 1e-6, + "µ": expected_in_sec / 1e-6, + "m": expected_in_sec / 1e-3, + "k": expected_in_sec / 1e3, + "M": expected_in_sec / 1e6, + "G": expected_in_sec / 1e9, + "T": expected_in_sec / 1e12, + "P": expected_in_sec / 1e15, + } + self.assertEqual(duration, expected_val[unit]) + + def test_estimate_duration_invalid_unit(self): + backend = GenericBackendV2(num_qubits=3, seed=42) + + circ = QuantumCircuit(2) + circ.cx(0, 1) + circ.measure_all() + + circuit_dt = transpile(circ, backend, scheduling_method="asap") + with self.assertRaises(QiskitError): + circuit_dt.estimate_duration(backend.target, unit="jiffy") + + def test_delay_circ(self): + backend = GenericBackendV2(num_qubits=3, seed=42) + + circ = QuantumCircuit(2) + circ.delay(100, 0, unit="dt") + + circuit_dt = transpile(circ, backend, scheduling_method="asap") + res = circuit_dt.estimate_duration(backend.target, unit="dt") + self.assertIsInstance(res, int) + self.assertEqual(res, 100) + + def test_estimate_duration_control_flow(self): + backend = GenericBackendV2(num_qubits=3, seed=42, control_flow=True) + + circ = QuantumCircuit(2) + circ.cx(0, 1) + circ.measure_all() + with circ.if_test((0, True)): + circ.x(0) + with self.assertRaises(QiskitError): + circ.estimate_duration(backend.target) + + def test_estimate_duration_with_var(self): + backend = GenericBackendV2(num_qubits=3, seed=42, control_flow=True) + + circ = QuantumCircuit(2) + circ.cx(0, 1) + circ.measure_all() + circ.add_var("a", False) + with self.assertRaises(QiskitError): + circ.estimate_duration(backend.target) + + def test_estimate_duration_parameterized_delay(self): + backend = GenericBackendV2(num_qubits=3, seed=42, control_flow=True) + + circ = QuantumCircuit(2) + circ.cx(0, 1) + circ.measure_all() + circ.delay(Parameter("t"), 0) + with self.assertRaises(QiskitError): + circ.estimate_duration(backend.target) + + def test_estimate_duration_dt_delay_no_dt(self): + backend = GenericBackendV2(num_qubits=3, seed=42) + circ = QuantumCircuit(1) + circ.delay(100, 0) + backend.target.dt = None + with self.assertRaises(QiskitError): + circ.estimate_duration(backend.target) + def test_change_dt_in_transpile(self): qc = QuantumCircuit(1, 1) qc.x(0) From 78110e7e3b037121e5cfc80d1b9971f33d94665f Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 19 Feb 2025 07:16:14 -0500 Subject: [PATCH 2/5] Update rust code to work with 1.4.0 rust data model --- crates/accelerate/src/circuit_duration.rs | 67 ++++++++++++----------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/crates/accelerate/src/circuit_duration.rs b/crates/accelerate/src/circuit_duration.rs index 0e9738822f84..539080945ae3 100644 --- a/crates/accelerate/src/circuit_duration.rs +++ b/crates/accelerate/src/circuit_duration.rs @@ -10,11 +10,12 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. +use pyo3::intern; use pyo3::prelude::*; use pyo3::wrap_pyfunction; use qiskit_circuit::dag_circuit::{DAGCircuit, NodeType, Wire}; -use qiskit_circuit::operations::{DelayUnit, Operation, OperationRef, Param, StandardInstruction}; +use qiskit_circuit::operations::{Operation, OperationRef, Param}; use crate::nlayout::PhysicalQubit; use crate::target_transpiler::Target; @@ -30,7 +31,7 @@ pub(crate) fn compute_estimated_duration(dag: &DAGCircuit, target: &Target) -> P let get_duration = |edge: <&StableDiGraph as IntoEdgeReferences>::EdgeRef| -> PyResult { - let node_weight = &dag[edge.target()]; + let node_weight = &dag.dag()[edge.target()]; match node_weight { NodeType::Operation(inst) => { let name = inst.op.name(); @@ -38,33 +39,34 @@ pub(crate) fn compute_estimated_duration(dag: &DAGCircuit, target: &Target) -> P let physical_qubits: Vec = qubits.iter().map(|x| PhysicalQubit::new(x.0)).collect(); - if let OperationRef::StandardInstruction(op) = inst.op.view() { - if let StandardInstruction::Delay(unit) = op { - let dur = &inst.params.as_ref().unwrap()[0]; - return if unit == DelayUnit::DT { + if name == "delay" { + let dur = &inst.params.as_ref().unwrap()[0]; + let OperationRef::Instruction(op) = inst.op.view() else { + unreachable!("Invalid type for delay instruction"); + }; + Python::with_gil(|py| { + let unit: String = op + .instruction + .getattr(py, intern!(py, "unit"))? + .extract(py)?; + if unit == "dt" { if let Some(dt) = dt { match dur { - Param::Float(val) => - { - Ok(val / dt) - - }, - Param::Obj(val) => { - Python::with_gil(|py| { - let dur_float: f64 = val.extract(py)?; - Ok(dur_float * dt) - }) - }, - Param::ParameterExpression(_) => Err(QiskitError::new_err( - "Circuit contains parameterized delays, can't compute a duration estimate with this circuit" - )), - } + Param::Float(val) => Ok(val / dt), + Param::Obj(val) => { + let dur_float: f64 = val.extract(py)?; + Ok(dur_float / dt) + }, + Param::ParameterExpression(_) => Err(QiskitError::new_err( + "Circuit contains parameterized delays, can't compute a duration estimate with this circuit" + )), + } } else { Err(QiskitError::new_err( "Circuit contains delays in dt but the target doesn't specify dt" )) } - } else if unit == DelayUnit::S { + } else if unit == "s" { match dur { Param::Float(val) => Ok(*val), _ => Err(QiskitError::new_err( @@ -75,18 +77,19 @@ pub(crate) fn compute_estimated_duration(dag: &DAGCircuit, target: &Target) -> P Err(QiskitError::new_err( "Circuit contains delays in units other then seconds or dt, the circuit is not scheduled." )) - }; - } else if let StandardInstruction::Barrier(_) = op { - return Ok(0.); + } + }) + } else if name == "barrier" { + Ok(0.) + } else { + match target.get_duration(name, &physical_qubits) { + Some(dur) => Ok(dur), + None => Err(QiskitError::new_err(format!( + "Duration not found for {} on qubits: {:?}", + name, qubits + ))), } } - match target.get_duration(name, &physical_qubits) { - Some(dur) => Ok(dur), - None => Err(QiskitError::new_err(format!( - "Duration not found for {} on qubits: {:?}", - name, qubits - ))), - } } NodeType::QubitOut(_) | NodeType::ClbitOut(_) => Ok(0.), NodeType::ClbitIn(_) | NodeType::QubitIn(_) => { From dac65bb75ed2d8bd7ece395f0d3895d47f7adef8 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 19 Feb 2025 08:34:57 -0500 Subject: [PATCH 3/5] Fix bug in arithmetic for converting dt to sec --- crates/accelerate/src/circuit_duration.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/accelerate/src/circuit_duration.rs b/crates/accelerate/src/circuit_duration.rs index 539080945ae3..272075a98c7b 100644 --- a/crates/accelerate/src/circuit_duration.rs +++ b/crates/accelerate/src/circuit_duration.rs @@ -52,10 +52,10 @@ pub(crate) fn compute_estimated_duration(dag: &DAGCircuit, target: &Target) -> P if unit == "dt" { if let Some(dt) = dt { match dur { - Param::Float(val) => Ok(val / dt), + Param::Float(val) => Ok(val * dt), Param::Obj(val) => { let dur_float: f64 = val.extract(py)?; - Ok(dur_float / dt) + Ok(dur_float * dt) }, Param::ParameterExpression(_) => Err(QiskitError::new_err( "Circuit contains parameterized delays, can't compute a duration estimate with this circuit" From 6443a7527019f44fcf2c52c9ce07e7cb138b3775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= <57907331+ElePT@users.noreply.github.com> Date: Thu, 20 Feb 2025 10:18:19 +0100 Subject: [PATCH 4/5] Add fix test --- test/python/circuit/test_scheduled_circuit.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index 29a79534822a..0c9402edaabc 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -534,7 +534,38 @@ def test_estimate_duration_with_long_delay(self, unit): "P": expected_in_sec / 1e15, } self.assertEqual(duration, expected_val[unit]) + @data("s", "dt", "f", "p", "n", "u", "µ", "m", "k", "M", "G", "T", "P") + def test_estimate_duration_with_dt_float(self, unit): + # This is not a valid use case, but it is still expressible currently + # since we don't disallow fractional dt values. This should not be assumed + # to be a part of an api contract. If there is a refactor and this test + # breaks remove the test it is not valid. This was only added to provide + # explicit test coverage for a rust code path. + backend = GenericBackendV2(num_qubits=3, seed=42) + circ = QuantumCircuit(3) + circ.cx(0, 1) + circ.measure_all() + circ.delay(1.23e15, 2, unit="dt") + circuit_dt = transpile(circ, backend, scheduling_method="asap") + duration = circuit_dt.estimate_duration(backend.target, unit=unit) + expected_in_sec = 273060.0000013993 + expected_val = { + "s": expected_in_sec, + "dt": int(expected_in_sec / backend.target.dt), + "f": expected_in_sec / 1e-15, + "p": expected_in_sec / 1e-12, + "n": expected_in_sec / 1e-9, + "u": expected_in_sec / 1e-6, + "µ": expected_in_sec / 1e-6, + "m": expected_in_sec / 1e-3, + "k": expected_in_sec / 1e3, + "M": expected_in_sec / 1e6, + "G": expected_in_sec / 1e9, + "T": expected_in_sec / 1e12, + "P": expected_in_sec / 1e15, + } + self.assertEqual(duration, expected_val[unit]) def test_estimate_duration_invalid_unit(self): backend = GenericBackendV2(num_qubits=3, seed=42) From bc61e6eea054a86c374c3d7ab502bee73ac7c840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elena=20Pe=C3=B1a=20Tapia?= Date: Thu, 20 Feb 2025 11:52:20 +0100 Subject: [PATCH 5/5] Fix lint --- test/python/circuit/test_scheduled_circuit.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index 0c9402edaabc..0513d47b8d2c 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -534,6 +534,7 @@ def test_estimate_duration_with_long_delay(self, unit): "P": expected_in_sec / 1e15, } self.assertEqual(duration, expected_val[unit]) + @data("s", "dt", "f", "p", "n", "u", "µ", "m", "k", "M", "G", "T", "P") def test_estimate_duration_with_dt_float(self, unit): # This is not a valid use case, but it is still expressible currently @@ -566,6 +567,7 @@ def test_estimate_duration_with_dt_float(self, unit): "P": expected_in_sec / 1e15, } self.assertEqual(duration, expected_val[unit]) + def test_estimate_duration_invalid_unit(self): backend = GenericBackendV2(num_qubits=3, seed=42)