diff --git a/.mergify.yml b/.mergify.yml index 3b5ad2c9ed31..7d2ac03e4266 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -6,4 +6,4 @@ pull_request_rules: actions: backport: branches: - - stable/1.3 + - stable/1.4 diff --git a/crates/accelerate/src/circuit_duration.rs b/crates/accelerate/src/circuit_duration.rs new file mode 100644 index 000000000000..0fe7f998ac19 --- /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 e26689f4f0c3..76b26d22a771 100644 --- a/crates/accelerate/src/lib.rs +++ b/crates/accelerate/src/lib.rs @@ -17,6 +17,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 9df6047a494b..b03f2e95523f 100644 --- a/crates/accelerate/src/target_transpiler/mod.rs +++ b/crates/accelerate/src/target_transpiler/mod.rs @@ -977,6 +977,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 d2a0db8d19fd..f93f2af32a81 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 376881a56799..9ac2702f4cf2 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/instruction.py b/qiskit/circuit/instruction.py index cc51f2459b55..c53383446f23 100644 --- a/qiskit/circuit/instruction.py +++ b/qiskit/circuit/instruction.py @@ -63,14 +63,20 @@ class Instruction(Operation): def __init__(self, name, num_qubits, num_clbits, params, duration=None, unit="dt", label=None): """Create a new instruction. + .. deprecated:: 1.3 + The parameters ``duration`` and ``unit`` are deprecated since + Qiskit 1.3, and they will be removed in 2.0 or later. + An instruction's duration is defined in a backend's Target object. + Args: name (str): instruction name num_qubits (int): instruction's qubit width num_clbits (int): instruction's clbit width params (list[int|float|complex|str|ndarray|list|ParameterExpression]): list of parameters - duration (int or float): instruction's duration. it must be integer if ``unit`` is 'dt' - unit (str): time unit of duration + duration (int|float): (DEPRECATED) instruction's duration. it must be + an integer if ``unit`` is ``'dt'`` + unit (str): (DEPRECATED) time unit of duration label (str or None): An optional label for identifying the instruction. Raises: diff --git a/qiskit/circuit/quantumcircuit.py b/qiskit/circuit/quantumcircuit.py index 74822d50ce4e..fa199cbb86a2 100644 --- a/qiskit/circuit/quantumcircuit.py +++ b/qiskit/circuit/quantumcircuit.py @@ -41,6 +41,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 @@ -157,6 +158,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. @@ -909,8 +912,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 @@ -958,19 +962,6 @@ class QuantumCircuit: .. automethod:: decompose .. automethod:: reverse_bits - - Internal utilities - ================== - - These functions are not intended for public use, but were accidentally left documented in the - public API during the 1.0 release. They will be removed in Qiskit 2.0, but will be supported - until then. - - .. automethod:: cast - .. automethod:: cbit_argument_conversion - .. automethod:: cls_instances - .. automethod:: cls_prefix - .. automethod:: qbit_argument_conversion """ instances = 0 @@ -1452,35 +1443,12 @@ def __deepcopy__(self, memo=None): def _increment_instances(cls): cls.instances += 1 - @classmethod - @deprecate_func( - since=1.2, - removal_timeline="in the 2.0 release", - additional_msg="This method is only used as an internal helper " - "and will be removed with no replacement.", - ) - def cls_instances(cls) -> int: - """Return the current number of instances of this class, - useful for auto naming.""" - return cls.instances - @classmethod def _cls_instances(cls) -> int: """Return the current number of instances of this class, useful for auto naming.""" return cls.instances - @classmethod - @deprecate_func( - since=1.2, - removal_timeline="in the 2.0 release", - additional_msg="This method is only used as an internal helper " - "and will be removed with no replacement.", - ) - def cls_prefix(cls) -> str: - """Return the prefix to use for auto naming.""" - return cls.prefix - @classmethod def _cls_prefix(cls) -> str: """Return the prefix to use for auto naming.""" @@ -2343,20 +2311,6 @@ def __getitem__(self, item): """Return indexed operation.""" return self._data[item] - @staticmethod - @deprecate_func( - since=1.2, - removal_timeline="in the 2.0 release", - additional_msg="This method is only used as an internal helper " - "and will be removed with no replacement.", - ) - def cast(value: S, type_: Callable[..., T]) -> Union[S, T]: - """Best effort to cast value to type. Otherwise, returns the value.""" - try: - return type_(value) - except (ValueError, TypeError): - return value - @staticmethod def _cast(value: S, type_: Callable[..., T]) -> Union[S, T]: """Best effort to cast value to type. Otherwise, returns the value.""" @@ -2365,26 +2319,6 @@ def _cast(value: S, type_: Callable[..., T]) -> Union[S, T]: except (ValueError, TypeError): return value - @deprecate_func( - since=1.2, - removal_timeline="in the 2.0 release", - additional_msg="This method is only used as an internal helper " - "and will be removed with no replacement.", - ) - def qbit_argument_conversion(self, qubit_representation: QubitSpecifier) -> list[Qubit]: - """ - Converts several qubit representations (such as indexes, range, etc.) - into a list of qubits. - - Args: - qubit_representation: Representation to expand. - - Returns: - The resolved instances of the qubits. - """ - - return self._qbit_argument_conversion(qubit_representation) - def _qbit_argument_conversion(self, qubit_representation: QubitSpecifier) -> list[Qubit]: """ Converts several qubit representations (such as indexes, range, etc.) @@ -2400,25 +2334,6 @@ def _qbit_argument_conversion(self, qubit_representation: QubitSpecifier) -> lis qubit_representation, self.qubits, self._qubit_indices, Qubit ) - @deprecate_func( - since=1.2, - removal_timeline="in the 2.0 release", - additional_msg="This method is only used as an internal helper " - "and will be removed with no replacement.", - ) - def cbit_argument_conversion(self, clbit_representation: ClbitSpecifier) -> list[Clbit]: - """ - Converts several classical bit representations (such as indexes, range, etc.) - into a list of classical bits. - - Args: - clbit_representation : Representation to expand. - - Returns: - A list of tuples where each tuple is a classical bit. - """ - return self._cbit_argument_conversion(clbit_representation) - def _cbit_argument_conversion(self, clbit_representation: ClbitSpecifier) -> list[Clbit]: """ Converts several classical bit representations (such as indexes, range, etc.) @@ -3871,6 +3786,8 @@ def store(self, lvalue: typing.Any, rvalue: typing.Any, /) -> InstructionSet: """ # As a convenience, lift integer-literal rvalues to the matching width. lvalue = expr.lift(lvalue) + if lvalue.type.const: + raise CircuitError("const variables are not supported.") rvalue_type = ( lvalue.type if isinstance(rvalue, int) and not isinstance(rvalue, bool) else None ) @@ -6958,6 +6875,65 @@ def qubit_stop_time(self, *qubits: Union[Qubit, int]) -> float: else: 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/qiskit/pulse/utils.py b/qiskit/pulse/utils.py index 5f345917761e..2f2093817c8a 100644 --- a/qiskit/pulse/utils.py +++ b/qiskit/pulse/utils.py @@ -51,7 +51,7 @@ def format_parameter_value( decimal: Number of digit to round returned value. Returns: - Value casted to non-parameter data type, when possible. + Value cast to non-parameter data type, when possible. """ if isinstance(operand, ParameterExpression): try: diff --git a/qiskit/qasm3/exporter.py b/qiskit/qasm3/exporter.py index ef8d06bdaa45..10319f7031da 100644 --- a/qiskit/qasm3/exporter.py +++ b/qiskit/qasm3/exporter.py @@ -1287,7 +1287,7 @@ def __init__(self, lookup): def visit_var(self, node, /): return self.lookup(node) if node.standalone else self.lookup(node.var) - # pylint: disable=R0911 + # pylint: disable=too-many-return-statements def visit_value(self, node, /): if node.type.kind is types.Bool: return ast.BooleanLiteral(node.value) @@ -1296,17 +1296,16 @@ def visit_value(self, node, /): if node.type.kind is types.Float: return ast.FloatLiteral(node.value) if node.type.kind is types.Duration: - match node.value: - case Duration.dt(dt): - return ast.DurationLiteral(dt, ast.DurationUnit.SAMPLE) - case Duration.ns(ns): - return ast.DurationLiteral(ns, ast.DurationUnit.NANOSECOND) - case Duration.us(us): - return ast.DurationLiteral(us, ast.DurationUnit.MICROSECOND) - case Duration.ms(ms): - return ast.DurationLiteral(ms, ast.DurationUnit.MILLISECOND) - case Duration.s(sec): - return ast.DurationLiteral(sec, ast.DurationUnit.SECOND) + if isinstance(node.value, Duration.dt): + return ast.DurationLiteral(node.value[0], ast.DurationUnit.SAMPLE) + if isinstance(node.value, Duration.ns): + return ast.DurationLiteral(node.value[0], ast.DurationUnit.NANOSECOND) + if isinstance(node.value, Duration.us): + return ast.DurationLiteral(node.value[0], ast.DurationUnit.MICROSECOND) + if isinstance(node.value, Duration.ms): + return ast.DurationLiteral(node.value[0], ast.DurationUnit.MILLISECOND) + if isinstance(node.value, Duration.sec): + return ast.DurationLiteral(node.value[0], ast.DurationUnit.SECOND) raise RuntimeError(f"unhandled Value type '{node}'") def visit_cast(self, node, /): diff --git a/qiskit/qpy/binary_io/value.py b/qiskit/qpy/binary_io/value.py index bbef0621c609..4f7a8a1297f8 100644 --- a/qiskit/qpy/binary_io/value.py +++ b/qiskit/qpy/binary_io/value.py @@ -426,24 +426,23 @@ def _write_expr_type_v14(file_obj, type_: types.Type): def _write_duration(file_obj, duration: Duration): - match duration: - case Duration.dt(dt): - file_obj.write(type_keys.CircuitDuration.DT) - file_obj.write(struct.pack(formats.DURATION_DT_PACK, *formats.DURATION_DT(dt))) - case Duration.ns(ns): - file_obj.write(type_keys.CircuitDuration.NS) - file_obj.write(struct.pack(formats.DURATION_NS_PACK, *formats.DURATION_NS(ns))) - case Duration.us(us): - file_obj.write(type_keys.CircuitDuration.US) - file_obj.write(struct.pack(formats.DURATION_US_PACK, *formats.DURATION_US(us))) - case Duration.ms(ms): - file_obj.write(type_keys.CircuitDuration.MS) - file_obj.write(struct.pack(formats.DURATION_MS_PACK, *formats.DURATION_MS(ms))) - case Duration.s(sec): - file_obj.write(type_keys.CircuitDuration.S) - file_obj.write(struct.pack(formats.DURATION_S_PACK, *formats.DURATION_S(sec))) - case _: - raise exceptions.QpyError(f"unhandled Duration object '{duration};") + if isinstance(duration, Duration.dt): + file_obj.write(type_keys.CircuitDuration.DT) + file_obj.write(struct.pack(formats.DURATION_DT_PACK, *formats.DURATION_DT(duration[0]))) + elif isinstance(duration, Duration.ns): + file_obj.write(type_keys.CircuitDuration.NS) + file_obj.write(struct.pack(formats.DURATION_NS_PACK, *formats.DURATION_NS(duration[0]))) + elif isinstance(duration, Duration.us): + file_obj.write(type_keys.CircuitDuration.US) + file_obj.write(struct.pack(formats.DURATION_US_PACK, *formats.DURATION_US(duration[0]))) + elif isinstance(duration, Duration.ms): + file_obj.write(type_keys.CircuitDuration.MS) + file_obj.write(struct.pack(formats.DURATION_MS_PACK, *formats.DURATION_MS(duration[0]))) + elif isinstance(duration, Duration.sec): + file_obj.write(type_keys.CircuitDuration.S) + file_obj.write(struct.pack(formats.DURATION_S_PACK, *formats.DURATION_S(duration[0]))) + else: + raise exceptions.QpyError(f"unhandled Duration object '{duration};") def _read_parameter(file_obj): diff --git a/qiskit/qpy/type_keys.py b/qiskit/qpy/type_keys.py index 016b4bc6f5cb..20efa48a504b 100644 --- a/qiskit/qpy/type_keys.py +++ b/qiskit/qpy/type_keys.py @@ -575,20 +575,20 @@ class CircuitDuration(TypeKeyBase): @classmethod def assign(cls, obj): - match obj: - case Duration.dt(_): - return cls.DT - case Duration.ns(_): - return cls.NS - case Duration.us(_): - return cls.US - case Duration.ms(_): - return cls.MS - case Duration.s(_): - return cls.S - raise exceptions.QpyError( - f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace." - ) + if isinstance(obj, Duration.dt): + return cls.DT + elif isinstance(obj, Duration.ns): + return cls.NS + elif isinstance(obj, Duration.us): + return cls.US + elif isinstance(obj, Duration.ms): + return cls.MS + elif isinstance(obj, Duration.sec): + return cls.S + else: + raise exceptions.QpyError( + f"Object type '{type(obj)}' is not supported in {cls.__name__} namespace." + ) @classmethod def retrieve(cls, type_key): diff --git a/qiskit/quantum_info/operators/channel/transformations.py b/qiskit/quantum_info/operators/channel/transformations.py index 657ee62703ec..79619f798832 100644 --- a/qiskit/quantum_info/operators/channel/transformations.py +++ b/qiskit/quantum_info/operators/channel/transformations.py @@ -234,8 +234,23 @@ def _choi_to_kraus(data, input_dim, output_dim, atol=ATOL_DEFAULT): # # So the eigenvalues are on the diagonal, therefore the basis-transformation matrix must be # a spanning set of the eigenspace. + # + # In addition, to prevent `numpy.linalg` errors when the matrix A is ill-conditioned, + # we apply a small perturbation, replacing A by A + eI. Since (A + eI)x = kx is + # equivalent to Ax = (k-e)x, it means that the eigenvectors of A + eI and A are the same, + # and we can perfectly recover the eigenvalues of A from the eigenvalues of A + eI by + # subtracting e. + apply_perturbation = np.linalg.cond(data) >= 1e10 + + if apply_perturbation: + data += 1e-10 * np.eye(data.shape[0]) + triangular, vecs = scipy.linalg.schur(data) values = triangular.diagonal().real + + if apply_perturbation: + values = values - 1e-10 + # If we're not a CP map, fall-through back to the generalization handling. Since we needed # to get the eigenvalues anyway, we can do the CP check manually rather than deferring to a # separate re-calculation. diff --git a/qiskit/transpiler/passes/__init__.py b/qiskit/transpiler/passes/__init__.py index 1fd8454159a3..ae4bb218ef4a 100644 --- a/qiskit/transpiler/passes/__init__.py +++ b/qiskit/transpiler/passes/__init__.py @@ -115,10 +115,6 @@ ConstrainedReschedule InstructionDurationCheck SetIOLatency - ALAPSchedule - ASAPSchedule - DynamicalDecoupling - AlignMeasures Circuit Analysis ================ @@ -273,10 +269,6 @@ from .scheduling import ConstrainedReschedule from .scheduling import InstructionDurationCheck from .scheduling import SetIOLatency -from .scheduling import ALAPSchedule -from .scheduling import ASAPSchedule -from .scheduling import DynamicalDecoupling -from .scheduling import AlignMeasures # additional utility passes from .utils import CheckMap diff --git a/qiskit/transpiler/passes/scheduling/__init__.py b/qiskit/transpiler/passes/scheduling/__init__.py index 2eeb29661d5e..4b02b471b07e 100644 --- a/qiskit/transpiler/passes/scheduling/__init__.py +++ b/qiskit/transpiler/passes/scheduling/__init__.py @@ -12,9 +12,6 @@ """Module containing circuit scheduling passes.""" -from .alap import ALAPSchedule -from .asap import ASAPSchedule -from .dynamical_decoupling import DynamicalDecoupling from .scheduling import ALAPScheduleAnalysis, ASAPScheduleAnalysis, SetIOLatency from .time_unit_conversion import TimeUnitConversion from .padding import PadDelay, PadDynamicalDecoupling @@ -22,6 +19,3 @@ # For backward compatibility from . import alignments as instruction_alignments - -# TODO Deprecated pass. Will be removed after deprecation period. -from .alignments import AlignMeasures diff --git a/qiskit/transpiler/passes/scheduling/alap.py b/qiskit/transpiler/passes/scheduling/alap.py deleted file mode 100644 index 01b2bd0b502f..000000000000 --- a/qiskit/transpiler/passes/scheduling/alap.py +++ /dev/null @@ -1,156 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""ALAP Scheduling.""" - -from qiskit.circuit import Delay, Qubit, Measure -from qiskit.dagcircuit import DAGCircuit -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes.scheduling.base_scheduler import BaseSchedulerTransform -from qiskit.utils.deprecation import deprecate_func - - -class ALAPSchedule(BaseSchedulerTransform): - """ALAP Scheduling pass, which schedules the **stop** time of instructions as late as possible. - - See :class:`~qiskit.transpiler.passes.scheduling.base_scheduler.BaseSchedulerTransform` for the - detailed behavior of the control flow operation, i.e. ``c_if``. - """ - - @deprecate_func( - additional_msg=( - "Instead, use :class:`~.ALAPScheduleAnalysis`, which is an " - "analysis pass that requires a padding pass to later modify the circuit." - ), - since="1.1.0", - ) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def run(self, dag): - """Run the ALAPSchedule pass on `dag`. - - Args: - dag (DAGCircuit): DAG to schedule. - - Returns: - DAGCircuit: A scheduled DAG. - - Raises: - TranspilerError: if the circuit is not mapped on physical qubits. - TranspilerError: if conditional bit is added to non-supported instruction. - """ - if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: - raise TranspilerError("ALAP schedule runs on physical circuits only") - - time_unit = self.property_set["time_unit"] - if time_unit == "stretch": - raise TranspilerError("Scheduling cannot run on circuits with stretch durations.") - - new_dag = DAGCircuit() - for qreg in dag.qregs.values(): - new_dag.add_qreg(qreg) - for creg in dag.cregs.values(): - new_dag.add_creg(creg) - - idle_before = {q: 0 for q in dag.qubits + dag.clbits} - for node in reversed(list(dag.topological_op_nodes())): - op_duration = self._get_node_duration(node, dag) - - # compute t0, t1: instruction interval, note that - # t0: start time of instruction - # t1: end time of instruction - - # since this is alap scheduling, node is scheduled in reversed topological ordering - # and nodes are packed from the very end of the circuit. - # the physical meaning of t0 and t1 is flipped here. - if isinstance(node.op, self.CONDITIONAL_SUPPORTED): - t0q = max(idle_before[q] for q in node.qargs) - if node.op.condition_bits: - # conditional is bit tricky due to conditional_latency - t0c = max(idle_before[c] for c in node.op.condition_bits) - # Assume following case (t0c > t0q): - # - # |t0q - # Q ░░░░░░░░░░░░░▒▒▒ - # C ░░░░░░░░▒▒▒▒▒▒▒▒ - # |t0c - # - # In this case, there is no actual clbit read before gate. - # - # |t0q' = t0c - conditional_latency - # Q ░░░░░░░░▒▒▒░░▒▒▒ - # C ░░░░░░▒▒▒▒▒▒▒▒▒▒ - # |t1c' = t0c + conditional_latency - # - # rather than naively doing - # - # |t1q' = t0c + duration - # Q ░░░░░▒▒▒░░░░░▒▒▒ - # C ░░▒▒░░░░▒▒▒▒▒▒▒▒ - # |t1c' = t0c + duration + conditional_latency - # - t0 = max(t0q, t0c - op_duration) - t1 = t0 + op_duration - for clbit in node.op.condition_bits: - idle_before[clbit] = t1 + self.conditional_latency - else: - t0 = t0q - t1 = t0 + op_duration - else: - if node.op.condition_bits: - raise TranspilerError( - f"Conditional instruction {node.op.name} is not supported in ALAP scheduler." - ) - - if isinstance(node.op, Measure): - # clbit time is always right (alap) justified - t0 = max(idle_before[bit] for bit in node.qargs + node.cargs) - t1 = t0 + op_duration - # - # |t1 = t0 + duration - # Q ░░░░░▒▒▒▒▒▒▒▒▒▒▒ - # C ░░░░░░░░░▒▒▒▒▒▒▒ - # |t0 + (duration - clbit_write_latency) - # - for clbit in node.cargs: - idle_before[clbit] = t0 + (op_duration - self.clbit_write_latency) - else: - # It happens to be directives such as barrier - t0 = max(idle_before[bit] for bit in node.qargs + node.cargs) - t1 = t0 + op_duration - - for bit in node.qargs: - delta = t0 - idle_before[bit] - if delta > 0 and self._delay_supported(dag.find_bit(bit).index): - new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [], check=False) - idle_before[bit] = t1 - - new_dag.apply_operation_front(node.op, node.qargs, node.cargs, check=False) - - circuit_duration = max(idle_before.values()) - for bit, before in idle_before.items(): - delta = circuit_duration - before - if not (delta > 0 and isinstance(bit, Qubit)): - continue - if self._delay_supported(dag.find_bit(bit).index): - new_dag.apply_operation_front(Delay(delta, time_unit), [bit], [], check=False) - - new_dag.name = dag.name - new_dag.metadata = dag.metadata - new_dag._calibrations_prop = dag._calibrations_prop - - # set circuit duration and unit to indicate it is scheduled - new_dag.duration = circuit_duration - new_dag.unit = time_unit - - return new_dag diff --git a/qiskit/transpiler/passes/scheduling/alignments/__init__.py b/qiskit/transpiler/passes/scheduling/alignments/__init__.py index 8ecd68eacdbb..507fb95d3486 100644 --- a/qiskit/transpiler/passes/scheduling/alignments/__init__.py +++ b/qiskit/transpiler/passes/scheduling/alignments/__init__.py @@ -77,4 +77,3 @@ from .check_durations import InstructionDurationCheck from .reschedule import ConstrainedReschedule -from .align_measures import AlignMeasures diff --git a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py b/qiskit/transpiler/passes/scheduling/alignments/align_measures.py deleted file mode 100644 index 4bf8a9f4bc2e..000000000000 --- a/qiskit/transpiler/passes/scheduling/alignments/align_measures.py +++ /dev/null @@ -1,257 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. - -"""Align measurement instructions.""" -from __future__ import annotations -import itertools -import warnings -from collections import defaultdict -from collections.abc import Iterable -from typing import Type - -from qiskit.circuit.quantumcircuit import ClbitSpecifier, QubitSpecifier - -from qiskit.circuit.delay import Delay -from qiskit.circuit.measure import Measure -from qiskit.circuit.parameterexpression import ParameterExpression -from qiskit.dagcircuit import DAGCircuit -from qiskit.transpiler.basepasses import TransformationPass -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.utils.deprecation import deprecate_func - - -class AlignMeasures(TransformationPass): - """Measurement alignment. - - This is a control electronics aware optimization pass. - - In many quantum computing architectures gates (instructions) are implemented with - shaped analog stimulus signals. These signals are digitally stored in the - waveform memory of the control electronics and converted into analog voltage signals - by electronic components called digital to analog converters (DAC). - - In a typical hardware implementation of superconducting quantum processors, - a single qubit instruction is implemented by a - microwave signal with the duration of around several tens of ns with a per-sample - time resolution of ~0.1-10ns, as reported by ``backend.configuration().dt``. - In such systems requiring higher DAC bandwidth, control electronics often - defines a `pulse granularity`, in other words a data chunk, to allow the DAC to - perform the signal conversion in parallel to gain the bandwidth. - - Measurement alignment is required if a backend only allows triggering ``measure`` - instructions at a certain multiple value of this pulse granularity. - This value is usually provided by ``backend.configuration().timing_constraints``. - - In Qiskit SDK, the duration of delay can take arbitrary value in units of ``dt``, - thus circuits involving delays may violate the above alignment constraint (i.e. misalignment). - This pass shifts measurement instructions to a new time position to fix the misalignment, - by inserting extra delay right before the measure instructions. - The input of this pass should be scheduled :class:`~qiskit.dagcircuit.DAGCircuit`, - thus one should select one of the scheduling passes - (:class:`~qiskit.transpiler.passes.ALAPSchedule` or - :class:`~qiskit.trasnpiler.passes.ASAPSchedule`) before calling this. - - Examples: - We assume executing the following circuit on a backend with ``alignment=16``. - - .. code-block:: text - - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├ - └───┘└────────────────┘└╥┘ - c: 1/════════════════════════╩═ - 0 - - Note that delay of 100 dt induces a misalignment of 4 dt at the measurement. - This pass appends an extra 12 dt time shift to the input circuit. - - .. code-block:: text - - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├ - └───┘└────────────────┘└╥┘ - c: 1/════════════════════════╩═ - 0 - - This pass always inserts a positive delay before measurements - rather than reducing other delays. - - Notes: - The Backend may allow users to execute circuits violating the alignment constraint. - However, it may return meaningless measurement data mainly due to the phase error. - """ - - @deprecate_func( - additional_msg=( - "Instead, use :class:`~.ConstrainedReschedule`, which performs the same function " - "but also supports aligning to additional timing constraints." - ), - since="1.1.0", - ) - def __init__(self, alignment: int = 1): - """Create new pass. - - Args: - alignment: Integer number representing the minimum time resolution to - trigger measure instruction in units of ``dt``. This value depends on - the control electronics of your quantum processor. - """ - super().__init__() - self.alignment = alignment - - def run(self, dag: DAGCircuit): - """Run the measurement alignment pass on `dag`. - - Args: - dag (DAGCircuit): DAG to be checked. - - Returns: - DAGCircuit: DAG with consistent timing and op nodes annotated with duration. - - Raises: - TranspilerError: If circuit is not scheduled. - """ - time_unit = self.property_set["time_unit"] - if time_unit == "stretch": - raise TranspilerError("Scheduling cannot run on circuits with stretch durations.") - - if not _check_alignment_required(dag, self.alignment, Measure): - # return input as-is to avoid unnecessary scheduling. - # because following procedure regenerate new DAGCircuit, - # we should avoid continuing if not necessary from performance viewpoint. - return dag - - # if circuit is not yet scheduled, schedule with ALAP method - if dag.duration is None: - raise TranspilerError( - f"This circuit {dag.name} may involve a delay instruction violating the " - "pulse controller alignment. To adjust instructions to " - "right timing, you should call one of scheduling passes first. " - "This is usually done by calling transpiler with scheduling_method='alap'." - ) - - # the following lines are basically copied from ASAPSchedule pass - # - # * some validations for non-scheduled nodes are dropped, since we assume scheduled input - # * pad_with_delay is called only with non-delay node to avoid consecutive delay - new_dag = dag.copy_empty_like() - - qubit_time_available: dict[QubitSpecifier, int] = defaultdict(int) # to track op start time - qubit_stop_times: dict[QubitSpecifier, int] = defaultdict( - int - ) # to track delay start time for padding - clbit_readable: dict[ClbitSpecifier, int] = defaultdict(int) - clbit_writeable: dict[ClbitSpecifier, int] = defaultdict(int) - - def pad_with_delays(qubits: Iterable[QubitSpecifier], until, unit) -> None: - """Pad idle time-slots in ``qubits`` with delays in ``unit`` until ``until``.""" - for q in qubits: - if qubit_stop_times[q] < until: - idle_duration = until - qubit_stop_times[q] - new_dag.apply_operation_back(Delay(idle_duration, unit), (q,), check=False) - - for node in dag.topological_op_nodes(): - # choose appropriate clbit available time depending on op - clbit_time_available = ( - clbit_writeable if isinstance(node.op, Measure) else clbit_readable - ) - # correction to change clbit start time to qubit start time - delta = node.op.duration if isinstance(node.op, Measure) else 0 - start_time = max( - itertools.chain( - (qubit_time_available[q] for q in node.qargs), - ( - clbit_time_available[c] - delta - for c in node.cargs + tuple(node.op.condition_bits) - ), - ) - ) - - if isinstance(node.op, Measure): - if start_time % self.alignment != 0: - start_time = ((start_time // self.alignment) + 1) * self.alignment - - if not isinstance(node.op, Delay): # exclude delays for combining consecutive delays - pad_with_delays(node.qargs, until=start_time, unit=time_unit) - new_dag.apply_operation_back(node.op, node.qargs, node.cargs, check=False) - - stop_time = start_time + node.op.duration - # update time table - for q in node.qargs: - qubit_time_available[q] = stop_time - if not isinstance(node.op, Delay): - qubit_stop_times[q] = stop_time - for c in node.cargs: # measure - clbit_writeable[c] = clbit_readable[c] = stop_time - for c in node.op.condition_bits: # conditional op - clbit_writeable[c] = max(start_time, clbit_writeable[c]) - - working_qubits = qubit_time_available.keys() - circuit_duration = max(qubit_time_available[q] for q in working_qubits) - pad_with_delays(new_dag.qubits, until=circuit_duration, unit=time_unit) - - new_dag.name = dag.name - new_dag.metadata = dag.metadata - - # set circuit duration and unit to indicate it is scheduled - new_dag.duration = circuit_duration - new_dag.unit = time_unit - - return new_dag - - -def _check_alignment_required( - dag: DAGCircuit, - alignment: int, - instructions: Type | list[Type], -) -> bool: - """Check DAG nodes and return a boolean representing if instruction scheduling is necessary. - - Args: - dag: DAG circuit to check. - alignment: Instruction alignment condition. - instructions: Target instructions. - - Returns: - If instruction scheduling is necessary. - """ - if not isinstance(instructions, list): - instructions = [instructions] - - if alignment == 1: - # disable alignment if arbitrary t0 value can be used - return False - - if all(len(dag.op_nodes(inst)) == 0 for inst in instructions): - # disable alignment if target instruction is not involved - return False - - # check delay durations - for delay_node in dag.op_nodes(Delay): - duration = delay_node.op.duration - if isinstance(duration, ParameterExpression): - # duration is parametrized: - # raise user warning if backend alignment is not 1. - warnings.warn( - f"Parametrized delay with {repr(duration)} is found in circuit {dag.name}. " - f"This backend requires alignment={alignment}. " - "Please make sure all assigned values are multiple values of the alignment.", - UserWarning, - ) - else: - # duration is bound: - # check duration and trigger alignment if it violates constraint - if duration % alignment != 0: - return True - - # disable alignment if all delays are multiple values of the alignment - return False diff --git a/qiskit/transpiler/passes/scheduling/asap.py b/qiskit/transpiler/passes/scheduling/asap.py deleted file mode 100644 index b81e17857132..000000000000 --- a/qiskit/transpiler/passes/scheduling/asap.py +++ /dev/null @@ -1,177 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""ASAP Scheduling.""" - -from qiskit.circuit import Delay, Qubit, Measure -from qiskit.dagcircuit import DAGCircuit -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes.scheduling.base_scheduler import BaseSchedulerTransform -from qiskit.utils.deprecation import deprecate_func - - -class ASAPSchedule(BaseSchedulerTransform): - """ASAP Scheduling pass, which schedules the start time of instructions as early as possible.. - - See :class:`~qiskit.transpiler.passes.scheduling.base_scheduler.BaseSchedulerTransform` for the - detailed behavior of the control flow operation, i.e. ``c_if``. - - .. note:: - - This base class has been superseded by :class:`~.ASAPScheduleAnalysis` and - the new scheduling workflow. It will be deprecated and subsequently - removed in a future release. - """ - - @deprecate_func( - additional_msg=( - "Instead, use :class:`~.ASAPScheduleAnalysis`, which is an " - "analysis pass that requires a padding pass to later modify the circuit." - ), - since="1.1.0", - ) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - def run(self, dag): - """Run the ASAPSchedule pass on `dag`. - - Args: - dag (DAGCircuit): DAG to schedule. - - Returns: - DAGCircuit: A scheduled DAG. - - Raises: - TranspilerError: if the circuit is not mapped on physical qubits. - TranspilerError: if conditional bit is added to non-supported instruction. - """ - if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: - raise TranspilerError("ASAP schedule runs on physical circuits only") - - time_unit = self.property_set["time_unit"] - if time_unit == "stretch": - raise TranspilerError("Scheduling cannot run on circuits with stretch durations.") - - new_dag = DAGCircuit() - for qreg in dag.qregs.values(): - new_dag.add_qreg(qreg) - for creg in dag.cregs.values(): - new_dag.add_creg(creg) - - idle_after = {q: 0 for q in dag.qubits + dag.clbits} - for node in dag.topological_op_nodes(): - op_duration = self._get_node_duration(node, dag) - - # compute t0, t1: instruction interval, note that - # t0: start time of instruction - # t1: end time of instruction - if isinstance(node.op, self.CONDITIONAL_SUPPORTED): - t0q = max(idle_after[q] for q in node.qargs) - if node.op.condition_bits: - # conditional is bit tricky due to conditional_latency - t0c = max(idle_after[bit] for bit in node.op.condition_bits) - if t0q > t0c: - # This is situation something like below - # - # |t0q - # Q ▒▒▒▒▒▒▒▒▒░░ - # C ▒▒▒░░░░░░░░ - # |t0c - # - # In this case, you can insert readout access before tq0 - # - # |t0q - # Q ▒▒▒▒▒▒▒▒▒▒▒ - # C ▒▒▒░░░▒▒░░░ - # |t0q - conditional_latency - # - t0c = max(t0q - self.conditional_latency, t0c) - t1c = t0c + self.conditional_latency - for bit in node.op.condition_bits: - # Lock clbit until state is read - idle_after[bit] = t1c - # It starts after register read access - t0 = max(t0q, t1c) - else: - t0 = t0q - t1 = t0 + op_duration - else: - if node.op.condition_bits: - raise TranspilerError( - f"Conditional instruction {node.op.name} is not supported in ASAP scheduler." - ) - - if isinstance(node.op, Measure): - # measure instruction handling is bit tricky due to clbit_write_latency - t0q = max(idle_after[q] for q in node.qargs) - t0c = max(idle_after[c] for c in node.cargs) - # Assume following case (t0c > t0q) - # - # |t0q - # Q ▒▒▒▒░░░░░░░░░░░░ - # C ▒▒▒▒▒▒▒▒░░░░░░░░ - # |t0c - # - # In this case, there is no actual clbit access until clbit_write_latency. - # The node t0 can be push backward by this amount. - # - # |t0q' = t0c - clbit_write_latency - # Q ▒▒▒▒░░▒▒▒▒▒▒▒▒▒▒ - # C ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ - # |t0c' = t0c - # - # rather than naively doing - # - # |t0q' = t0c - # Q ▒▒▒▒░░░░▒▒▒▒▒▒▒▒ - # C ▒▒▒▒▒▒▒▒░░░▒▒▒▒▒ - # |t0c' = t0c + clbit_write_latency - # - t0 = max(t0q, t0c - self.clbit_write_latency) - t1 = t0 + op_duration - for clbit in node.cargs: - idle_after[clbit] = t1 - else: - # It happens to be directives such as barrier - t0 = max(idle_after[bit] for bit in node.qargs + node.cargs) - t1 = t0 + op_duration - - # Add delay to qubit wire - for bit in node.qargs: - delta = t0 - idle_after[bit] - if ( - delta > 0 - and isinstance(bit, Qubit) - and self._delay_supported(dag.find_bit(bit).index) - ): - new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) - idle_after[bit] = t1 - - new_dag.apply_operation_back(node.op, node.qargs, node.cargs) - - circuit_duration = max(idle_after.values()) - for bit, after in idle_after.items(): - delta = circuit_duration - after - if not (delta > 0 and isinstance(bit, Qubit)): - continue - if self._delay_supported(dag.find_bit(bit).index): - new_dag.apply_operation_back(Delay(delta, time_unit), [bit], []) - - new_dag.name = dag.name - new_dag.metadata = dag.metadata - new_dag._calibrations_prop = dag._calibrations_prop - - # set circuit duration and unit to indicate it is scheduled - new_dag.duration = circuit_duration - new_dag.unit = time_unit - return new_dag diff --git a/qiskit/transpiler/passes/scheduling/base_scheduler.py b/qiskit/transpiler/passes/scheduling/base_scheduler.py deleted file mode 100644 index c380c9f8c199..000000000000 --- a/qiskit/transpiler/passes/scheduling/base_scheduler.py +++ /dev/null @@ -1,310 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Base circuit scheduling pass.""" -import warnings - -from qiskit.circuit import Delay, Gate, Measure, Reset -from qiskit.circuit.parameterexpression import ParameterExpression -from qiskit.dagcircuit import DAGOpNode, DAGCircuit, DAGOutNode -from qiskit.transpiler.basepasses import TransformationPass -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.instruction_durations import InstructionDurations -from qiskit.transpiler.passes.scheduling.time_unit_conversion import TimeUnitConversion -from qiskit.transpiler.target import Target - - -class BaseSchedulerTransform(TransformationPass): - """Base scheduler pass. - - .. warning:: - - This base class is not part of the public interface for this module - it should not be used to develop new scheduling passes as the passes - which are using this are pending a future deprecation and subsequent - removal. If you are developing new scheduling passes look at the - ``BaseScheduler`` class instead which is used in the new scheduling - pass workflow. - - Policy of topological node ordering in scheduling - - The DAG representation of ``QuantumCircuit`` respects the node ordering also in the - classical register wires, though theoretically two conditional instructions - conditioned on the same register are commute, i.e. read-access to the - classical register doesn't change its state. - - .. code-block:: text - - qc = QuantumCircuit(2, 1) - qc.delay(100, 0) - qc.x(0).c_if(0, True) - qc.x(1).c_if(0, True) - - The scheduler SHOULD comply with above topological ordering policy of the DAG circuit. - Accordingly, the `asap`-scheduled circuit will become - - .. code-block:: text - - ┌────────────────┐ ┌───┐ - q_0: ┤ Delay(100[dt]) ├───┤ X ├────────────── - ├────────────────┤ └─╥─┘ ┌───┐ - q_1: ┤ Delay(100[dt]) ├─────╫────────┤ X ├─── - └────────────────┘ ║ └─╥─┘ - ┌────╨────┐┌────╨────┐ - c: 1/══════════════════╡ c_0=0x1 ╞╡ c_0=0x1 ╞ - └─────────┘└─────────┘ - - Note that this scheduling might be inefficient in some cases, - because the second conditional operation can start without waiting the delay of 100 dt. - However, such optimization should be done by another pass, - otherwise scheduling may break topological ordering of the original circuit. - - Realistic control flow scheduling respecting for microarchitecture - - In the dispersive QND readout scheme, qubit is measured with microwave stimulus to qubit (Q) - followed by resonator ring-down (depopulation). This microwave signal is recorded - in the buffer memory (B) with hardware kernel, then a discriminated (D) binary value - is moved to the classical register (C). - The sequence from t0 to t1 of the measure instruction interval might be modeled as follows: - - .. code-block:: text - - Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - B ░░▒▒▒▒▒▒▒▒░░░░░░░░░ - D ░░░░░░░░░░▒▒▒▒▒▒░░░ - C ░░░░░░░░░░░░░░░░▒▒░ - - However, ``QuantumCircuit`` representation is not enough accurate to represent - this model. In the circuit representation, thus ``Qubit`` is occupied by the - stimulus microwave signal during the first half of the interval, - and ``Clbit`` is only occupied at the very end of the interval. - - This precise model may induce weird edge case. - - .. code-block:: text - - ┌───┐ - q_0: ───┤ X ├────── - └─╥─┘ ┌─┐ - q_1: ─────╫─────┤M├ - ┌────╨────┐└╥┘ - c: 1/╡ c_0=0x1 ╞═╩═ - └─────────┘ 0 - - In this example, user may intend to measure the state of ``q_1``, after ``XGate`` is - applied to the ``q_0``. This is correct interpretation from viewpoint of - the topological node ordering, i.e. x gate node come in front of the measure node. - However, according to the measurement model above, the data in the register - is unchanged during the stimulus, thus two nodes are simultaneously operated. - If one `alap`-schedule this circuit, it may return following circuit. - - .. code-block:: text - - ┌────────────────┐ ┌───┐ - q_0: ┤ Delay(500[dt]) ├───┤ X ├────── - └────────────────┘ └─╥─┘ ┌─┐ - q_1: ───────────────────────╫─────┤M├ - ┌────╨────┐└╥┘ - c: 1/══════════════════╡ c_0=0x1 ╞═╩═ - └─────────┘ 0 - - Note that there is no delay on ``q_1`` wire, and the measure instruction immediately - start after t=0, while the conditional gate starts after the delay. - It looks like the topological ordering between the nodes are flipped in the scheduled view. - This behavior can be understood by considering the control flow model described above, - - .. code-block:: text - - : Quantum Circuit, first-measure - 0 ░░░░░░░░░░░░▒▒▒▒▒▒░ - 1 ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - - : In wire q0 - Q ░░░░░░░░░░░░░░░▒▒▒░ - C ░░░░░░░░░░░░▒▒░░░░░ - - : In wire q1 - Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - B ░░▒▒▒▒▒▒▒▒░░░░░░░░░ - D ░░░░░░░░░░▒▒▒▒▒▒░░░ - C ░░░░░░░░░░░░░░░░▒▒░ - - Since there is no qubit register (Q0, Q1) overlap, the node ordering is determined by the - shared classical register C. As you can see, the execution order is still - preserved on C, i.e. read C then apply ``XGate``, finally store the measured outcome in C. - Because ``DAGOpNode`` cannot define different durations for associated registers, - the time ordering of two nodes is inverted anyways. - - This behavior can be controlled by ``clbit_write_latency`` and ``conditional_latency``. - The former parameter determines the delay of the register write-access from - the beginning of the measure instruction t0, and another parameter determines - the delay of conditional gate operation from t0 which comes from the register read-access. - - Since we usually expect topological ordering and time ordering are identical - without the context of microarchitecture, both latencies are set to zero by default. - In this case, ``Measure`` instruction immediately locks the register C. - Under this configuration, the `alap`-scheduled circuit of above example may become - - .. code-block:: text - - ┌───┐ - q_0: ───┤ X ├────── - └─╥─┘ ┌─┐ - q_1: ─────╫─────┤M├ - ┌────╨────┐└╥┘ - c: 1/╡ c_0=0x1 ╞═╩═ - └─────────┘ 0 - - If the backend microarchitecture supports smart scheduling of the control flow, i.e. - it may separately schedule qubit and classical register, - insertion of the delay yields unnecessary longer total execution time. - - .. code-block:: text - - : Quantum Circuit, first-xgate - 0 ░▒▒▒░░░░░░░░░░░░░░░ - 1 ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - - : In wire q0 - Q ░▒▒▒░░░░░░░░░░░░░░░ - C ░░░░░░░░░░░░░░░░░░░ (zero latency) - - : In wire q1 - Q ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - C ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ (zero latency, scheduled after C0 read-access) - - However this result is much more intuitive in the topological ordering view. - If finite conditional latency is provided, for example, 30 dt, the circuit - is scheduled as follows. - - .. code-block:: text - - ┌───────────────┐ ┌───┐ - q_0: ┤ Delay(30[dt]) ├───┤ X ├────── - ├───────────────┤ └─╥─┘ ┌─┐ - q_1: ┤ Delay(30[dt]) ├─────╫─────┤M├ - └───────────────┘┌────╨────┐└╥┘ - c: 1/═════════════════╡ c_0=0x1 ╞═╩═ - └─────────┘ 0 - - with the timing model: - - .. code-block:: text - - : Quantum Circuit, first-xgate - 0 ░░▒▒▒░░░░░░░░░░░░░░░ - 1 ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - - : In wire q0 - Q ░░▒▒▒░░░░░░░░░░░░░░░ - C ░▒░░░░░░░░░░░░░░░░░░ (30dt latency) - - : In wire q1 - Q ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - C ░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░ - - See https://arxiv.org/abs/2102.01682 for more details. - - """ - - CONDITIONAL_SUPPORTED = (Gate, Delay) - - def __init__( - self, - durations: InstructionDurations = None, - clbit_write_latency: int = 0, - conditional_latency: int = 0, - target: Target = None, - ): - """Scheduler initializer. - - Args: - durations: Durations of instructions to be used in scheduling - clbit_write_latency: A control flow constraints. Because standard superconducting - quantum processor implement dispersive QND readout, the actual data transfer - to the clbit happens after the round-trip stimulus signal is buffered - and discriminated into quantum state. - The interval ``[t0, t0 + clbit_write_latency]`` is regarded as idle time - for clbits associated with the measure instruction. - This defaults to 0 dt which is identical to Qiskit Pulse scheduler. - conditional_latency: A control flow constraints. This value represents - a latency of reading a classical register for the conditional operation. - The gate operation occurs after this latency. This appears as a delay - in front of the DAGOpNode of the gate. - This defaults to 0 dt. - target: The :class:`~.Target` representing the target backend, if both - ``durations`` and this are specified then this argument will take - precedence and ``durations`` will be ignored. - """ - super().__init__() - self.durations = durations - # Ensure op node durations are attached and in consistent unit - if target is not None: - self.durations = target.durations() - self.requires.append(TimeUnitConversion(self.durations)) - - # Control flow constraints. - self.clbit_write_latency = clbit_write_latency - self.conditional_latency = conditional_latency - - self.target = target - - @staticmethod - def _get_node_duration( - node: DAGOpNode, - dag: DAGCircuit, - ) -> int: - """A helper method to get duration from node or calibration.""" - indices = [dag.find_bit(qarg).index for qarg in node.qargs] - - if dag._has_calibration_for(node): - # If node has calibration, this value should be the highest priority - cal_key = tuple(indices), tuple(float(p) for p in node.op.params) - duration = dag._calibrations_prop[node.op.name][cal_key].duration - else: - duration = node.op.duration - - if isinstance(node.op, Reset): - warnings.warn( - "Qiskit scheduler assumes Reset works similarly to Measure instruction. " - "Actual behavior depends on the control system of your quantum backend. " - "Your backend may provide a plugin scheduler pass." - ) - elif isinstance(node.op, Measure): - is_mid_circuit = not any( - isinstance(x, DAGOutNode) for x in dag.quantum_successors(node) - ) - if is_mid_circuit: - warnings.warn( - "Qiskit scheduler assumes mid-circuit measurement works as a standard instruction. " - "Actual backend may apply custom scheduling. " - "Your backend may provide a plugin scheduler pass." - ) - - if isinstance(duration, ParameterExpression): - raise TranspilerError( - f"Parameterized duration ({duration}) " - f"of {node.op.name} on qubits {indices} is not bounded." - ) - if duration is None: - raise TranspilerError(f"Duration of {node.op.name} on qubits {indices} is not found.") - - return duration - - def _delay_supported(self, qarg: int) -> bool: - """Delay operation is supported on the qubit (qarg) or not.""" - if self.target is None or self.target.instruction_supported("delay", qargs=(qarg,)): - return True - return False - - def run(self, dag: DAGCircuit): - raise NotImplementedError diff --git a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py b/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py deleted file mode 100644 index 08371c488422..000000000000 --- a/qiskit/transpiler/passes/scheduling/dynamical_decoupling.py +++ /dev/null @@ -1,313 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021, 2024. -# -# 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. - -"""Dynamical Decoupling insertion pass.""" - -import itertools -import warnings - -import numpy as np -from qiskit.circuit import Gate, Delay, Reset -from qiskit.circuit.library.standard_gates import IGate, UGate, U3Gate -from qiskit.dagcircuit import DAGOpNode, DAGInNode -from qiskit.quantum_info.operators.predicates import matrix_equal -from qiskit.synthesis.one_qubit import OneQubitEulerDecomposer -from qiskit.transpiler.basepasses import TransformationPass -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.instruction_durations import InstructionDurations -from qiskit.transpiler.passes.optimization import Optimize1qGates -from qiskit.utils.deprecation import deprecate_func - - -class DynamicalDecoupling(TransformationPass): - """Dynamical decoupling insertion pass. - - This pass works on a scheduled, physical circuit. It scans the circuit for - idle periods of time (i.e. those containing delay instructions) and inserts - a DD sequence of gates in those spots. These gates amount to the identity, - so do not alter the logical action of the circuit, but have the effect of - mitigating decoherence in those idle periods. - - As a special case, the pass allows a length-1 sequence (e.g. [XGate()]). - In this case the DD insertion happens only when the gate inverse can be - absorbed into a neighboring gate in the circuit (so we would still be - replacing Delay with something that is equivalent to the identity). - This can be used, for instance, as a Hahn echo. - - This pass ensures that the inserted sequence preserves the circuit exactly - (including global phase). - - .. plot:: - :alt: Output from the previous code. - :include-source: - - import numpy as np - from qiskit.circuit import QuantumCircuit - from qiskit.circuit.library import XGate - from qiskit.transpiler import PassManager, InstructionDurations - from qiskit.transpiler.passes import ALAPSchedule, DynamicalDecoupling - from qiskit.visualization import timeline_drawer - - # Because the legacy passes do not propagate the scheduling information correctly, it is - # necessary to run a no-op "re-schedule" before the output circuits can be drawn. - def draw(circuit): - from qiskit import transpile - - scheduled = transpile( - circuit, - optimization_level=0, - instruction_durations=InstructionDurations(), - scheduling_method="alap", - ) - return timeline_drawer(scheduled) - - circ = QuantumCircuit(4) - circ.h(0) - circ.cx(0, 1) - circ.cx(1, 2) - circ.cx(2, 3) - circ.measure_all() - durations = InstructionDurations( - [("h", 0, 50), ("cx", [0, 1], 700), ("reset", None, 10), - ("cx", [1, 2], 200), ("cx", [2, 3], 300), - ("x", None, 50), ("measure", None, 1000)] - ) - # balanced X-X sequence on all qubits - dd_sequence = [XGate(), XGate()] - pm = PassManager([ALAPSchedule(durations), - DynamicalDecoupling(durations, dd_sequence)]) - circ_dd = pm.run(circ) - draw(circ_dd) - - # Uhrig sequence on qubit 0 - n = 8 - dd_sequence = [XGate()] * n - def uhrig_pulse_location(k): - return np.sin(np.pi * (k + 1) / (2 * n + 2)) ** 2 - spacing = [] - for k in range(n): - spacing.append(uhrig_pulse_location(k) - sum(spacing)) - spacing.append(1 - sum(spacing)) - pm = PassManager( - [ - ALAPSchedule(durations), - DynamicalDecoupling(durations, dd_sequence, qubits=[0], spacing=spacing), - ] - ) - circ_dd = pm.run(circ) - draw(circ_dd) - """ - - @deprecate_func( - additional_msg=( - "Instead, use :class:`~.PadDynamicalDecoupling`, which performs the same " - "function but requires scheduling and alignment analysis passes to run prior to it." - ), - since="1.1.0", - ) - def __init__( - self, durations, dd_sequence, qubits=None, spacing=None, skip_reset_qubits=True, target=None - ): - """Dynamical decoupling initializer. - - Args: - durations (InstructionDurations): Durations of instructions to be - used in scheduling. - dd_sequence (list[Gate]): sequence of gates to apply in idle spots. - qubits (list[int]): physical qubits on which to apply DD. - If None, all qubits will undergo DD (when possible). - spacing (list[float]): a list of spacings between the DD gates. - The available slack will be divided according to this. - The list length must be one more than the length of dd_sequence, - and the elements must sum to 1. If None, a balanced spacing - will be used [d/2, d, d, ..., d, d, d/2]. - skip_reset_qubits (bool): if True, does not insert DD on idle - periods that immediately follow initialized/reset qubits (as - qubits in the ground state are less susceptible to decoherence). - target (Target): The :class:`~.Target` representing the target backend, if both - ``durations`` and this are specified then this argument will take - precedence and ``durations`` will be ignored. - """ - super().__init__() - self._durations = durations - self._dd_sequence = dd_sequence - self._qubits = qubits - self._spacing = spacing - self._skip_reset_qubits = skip_reset_qubits - self._target = target - if target is not None: - self._durations = target.durations() - for gate in dd_sequence: - if gate.name not in target.operation_names: - raise TranspilerError( - f"{gate.name} in dd_sequence is not supported in the target" - ) - - def run(self, dag): - """Run the DynamicalDecoupling pass on dag. - - Args: - dag (DAGCircuit): a scheduled DAG. - - Returns: - DAGCircuit: equivalent circuit with delays interrupted by DD, - where possible. - - Raises: - TranspilerError: if the circuit is not mapped on physical qubits. - """ - if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: - raise TranspilerError("DD runs on physical circuits only.") - - if dag.duration is None: - raise TranspilerError("DD runs after circuit is scheduled.") - - durations = self._update_inst_durations(dag) - - num_pulses = len(self._dd_sequence) - sequence_gphase = 0 - if num_pulses != 1: - if num_pulses % 2 != 0: - raise TranspilerError("DD sequence must contain an even number of gates (or 1).") - noop = np.eye(2) - for gate in self._dd_sequence: - noop = noop.dot(gate.to_matrix()) - if not matrix_equal(noop, IGate().to_matrix(), ignore_phase=True): - raise TranspilerError("The DD sequence does not make an identity operation.") - sequence_gphase = np.angle(noop[0][0]) - - if self._qubits is None: - self._qubits = set(range(dag.num_qubits())) - else: - self._qubits = set(self._qubits) - - if self._spacing: - if sum(self._spacing) != 1 or any(a < 0 for a in self._spacing): - raise TranspilerError( - "The spacings must be given in terms of fractions " - "of the slack period and sum to 1." - ) - else: # default to balanced spacing - mid = 1 / num_pulses - end = mid / 2 - self._spacing = [end] + [mid] * (num_pulses - 1) + [end] - - for qarg in list(self._qubits): - for gate in self._dd_sequence: - if not self.__gate_supported(gate, qarg): - self._qubits.discard(qarg) - break - - index_sequence_duration_map = {} - for physical_qubit in self._qubits: - dd_sequence_duration = 0 - for index, gate in enumerate(self._dd_sequence): - gate = gate.to_mutable() - self._dd_sequence[index] = gate - gate.duration = durations.get(gate, physical_qubit) - - dd_sequence_duration += gate.duration - index_sequence_duration_map[physical_qubit] = dd_sequence_duration - - new_dag = dag.copy_empty_like() - - for nd in dag.topological_op_nodes(): - if not isinstance(nd.op, Delay): - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) - continue - - dag_qubit = nd.qargs[0] - physical_qubit = dag.find_bit(dag_qubit).index - if physical_qubit not in self._qubits: # skip unwanted qubits - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) - continue - - pred = next(dag.predecessors(nd)) - succ = next(dag.successors(nd)) - if self._skip_reset_qubits: # discount initial delays - if isinstance(pred, DAGInNode) or isinstance(pred.op, Reset): - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) - continue - - dd_sequence_duration = index_sequence_duration_map[physical_qubit] - slack = nd.op.duration - dd_sequence_duration - if slack <= 0: # dd doesn't fit - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) - continue - - if num_pulses == 1: # special case of using a single gate for DD - u_inv = self._dd_sequence[0].inverse().to_matrix() - theta, phi, lam, phase = OneQubitEulerDecomposer().angles_and_phase(u_inv) - # absorb the inverse into the successor (from left in circuit) - if isinstance(succ, DAGOpNode) and isinstance(succ.op, (UGate, U3Gate)): - theta_r, phi_r, lam_r = succ.op.params - succ.op.params = Optimize1qGates.compose_u3( - theta_r, phi_r, lam_r, theta, phi, lam - ) - sequence_gphase += phase - # absorb the inverse into the predecessor (from right in circuit) - elif isinstance(pred, DAGOpNode) and isinstance(pred.op, (UGate, U3Gate)): - theta_l, phi_l, lam_l = pred.op.params - pred.op.params = Optimize1qGates.compose_u3( - theta, phi, lam, theta_l, phi_l, lam_l - ) - sequence_gphase += phase - # don't do anything if there's no single-qubit gate to absorb the inverse - else: - new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, check=False) - continue - - # insert the actual DD sequence - taus = [int(slack * a) for a in self._spacing] - unused_slack = slack - sum(taus) # unused, due to rounding to int multiples of dt - middle_index = int((len(taus) - 1) / 2) # arbitrary: redistribute to middle - taus[middle_index] += unused_slack # now we add up to original delay duration - - for tau, gate in itertools.zip_longest(taus, self._dd_sequence): - if tau > 0: - new_dag.apply_operation_back(Delay(tau), [dag_qubit], check=False) - if gate is not None: - new_dag.apply_operation_back(gate, [dag_qubit], check=False) - - new_dag.global_phase = new_dag.global_phase + sequence_gphase - - return new_dag - - def _update_inst_durations(self, dag): - """Update instruction durations with circuit information. If the dag contains gate - calibrations and no instruction durations were provided through the target or as a - standalone input, the circuit calibration durations will be used. - The priority order for instruction durations is: target > standalone > circuit. - """ - circ_durations = InstructionDurations() - - if dag._calibrations_prop: - cal_durations = [] - with warnings.catch_warnings(): - warnings.simplefilter(action="ignore", category=DeprecationWarning) - # `schedule.duration` emits pulse deprecation warnings which we don't want - # to see here - for gate, gate_cals in dag._calibrations_prop.items(): - for (qubits, parameters), schedule in gate_cals.items(): - cal_durations.append((gate, qubits, parameters, schedule.duration)) - circ_durations.update(cal_durations, circ_durations.dt) - - if self._durations is not None: - circ_durations.update(self._durations, getattr(self._durations, "dt", None)) - - return circ_durations - - def __gate_supported(self, gate: Gate, qarg: int) -> bool: - """A gate is supported on the qubit (qarg) or not.""" - if self._target is None or self._target.instruction_supported(gate.name, qargs=(qarg,)): - return True - return False diff --git a/qiskit/visualization/timeline/core.py b/qiskit/visualization/timeline/core.py index 71e2735559a8..a6f3ecff4c18 100644 --- a/qiskit/visualization/timeline/core.py +++ b/qiskit/visualization/timeline/core.py @@ -142,6 +142,15 @@ def add_data(self, data: drawings.ElementaryData): def load_program(self, program: circuit.QuantumCircuit, target: Target | None = None): """Load quantum circuit and create drawing.. + .. deprecated:: 1.3 + Visualization of unscheduled circuits with the timeline drawer has been + deprecated in Qiskit 1.3. + This circuit should be transpiled with a scheduler, despite having instructions + with explicit durations. + + .. deprecated:: 1.3 + Targets with duration-less operations are going to error in Qiskit 2.0. + Args: program: Scheduled circuit object to draw. target: The target the circuit is scheduled for. This contains backend information diff --git a/qiskit/visualization/timeline/interface.py b/qiskit/visualization/timeline/interface.py index 1818f78a9073..fa194403cbfa 100644 --- a/qiskit/visualization/timeline/interface.py +++ b/qiskit/visualization/timeline/interface.py @@ -52,6 +52,10 @@ def draw( ): r"""Generate visualization data for scheduled circuit programs. + .. deprecated:: 1.3 + The ``target`` parameter needs to be specified in Qiskit 2.0 in order to get the + instruction durations. + Args: program: Program to visualize. This program should be a `QuantumCircuit` which is transpiled with a scheduling_method, thus containing gate time information. 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/releasenotes/notes/choi-to-kraus-3ae7d49f0a27f639.yaml b/releasenotes/notes/choi-to-kraus-3ae7d49f0a27f639.yaml new file mode 100644 index 000000000000..bd055121aff3 --- /dev/null +++ b/releasenotes/notes/choi-to-kraus-3ae7d49f0a27f639.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Applied a small regularisation factor against ill-conditioned Hermitian matrices + in super-operator representations. diff --git a/releasenotes/notes/drop-legacy-scheduling-passes-ee1d593c41fe95c6.yaml b/releasenotes/notes/drop-legacy-scheduling-passes-ee1d593c41fe95c6.yaml new file mode 100644 index 000000000000..b57a6dcc8920 --- /dev/null +++ b/releasenotes/notes/drop-legacy-scheduling-passes-ee1d593c41fe95c6.yaml @@ -0,0 +1,9 @@ +--- +upgrade_transpiler: + - | + The deprecated transpiler passes ``ASAPSchedule``, ``ALAPSchedule``, + ``DynamicalDecoupling``, and ``AlignMeasures`` have been removed. These + passes were marked as deprecated. They have been replaced by the + :class:`.ALAPScheduleAnalysis`, :class:`.ASAPScheduleAnalysis`, + :class:`.PadDynamicalDecoupling`, and :class:`.ConstrainedReschedule` + passes respectively which can be used instead. diff --git a/releasenotes/notes/remove-circuit-cruft-30740668cf2f04e0.yaml b/releasenotes/notes/remove-circuit-cruft-30740668cf2f04e0.yaml new file mode 100644 index 000000000000..182545c4e6ed --- /dev/null +++ b/releasenotes/notes/remove-circuit-cruft-30740668cf2f04e0.yaml @@ -0,0 +1,13 @@ +--- +upgrade_circuits: + - | + The :class:`.QuantumCircuit` methods: + + * ``cast`` + * ``cbit_argument_conversion`` + * ``cls_instances`` + * ``cls_prefix`` + * ``qbit_argument_conversion`` + + have been removed, following their deprecation in Qiskit 1.2. These methods were internal + helper functions, and never intended to be public API. No replacement is provided. diff --git a/requirements.txt b/requirements.txt index 6eb5902ae9fd..845aea9c8f29 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ python-dateutil>=2.8.0 stevedore>=3.0.0 typing-extensions -# If updating the version range here, consider updating 'test/qpy_compat/run_tests.sh' to update the -# list of symengine dependencies used in the cross-version tests. +# If updating the version range here, consider updating the +# list of symengine dependencies used in the cross-version tests +# in 'test/qpy_compat/run_tests.sh' and 'test/qpy_compat/qpy_test_constraints.txt' symengine>=0.11,<0.14 diff --git a/test/python/circuit/test_scheduled_circuit.py b/test/python/circuit/test_scheduled_circuit.py index c09265d51b8b..494482811530 100644 --- a/test/python/circuit/test_scheduled_circuit.py +++ b/test/python/circuit/test_scheduled_circuit.py @@ -469,6 +469,159 @@ 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]) + + @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) + + 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) diff --git a/test/python/circuit/test_store.py b/test/python/circuit/test_store.py index 5f4d7de6702f..aaa590ff83b6 100644 --- a/test/python/circuit/test_store.py +++ b/test/python/circuit/test_store.py @@ -187,6 +187,13 @@ def test_implicitly_casts_const_scalars(self): Store(a, expr.Cast(expr.Value(1, types.Uint(8, const=True)), a.type, implicit=True)), ) + def test_rejects_const_target(self): + qc = QuantumCircuit() + with self.assertRaisesRegex(CircuitError, "const.*not supported"): + qc.store(expr.Var.new("a", types.Bool(const=True)), True) + with self.assertRaisesRegex(CircuitError, "const.*not supported"): + qc.store(expr.Var.new("a", types.Bool(const=True)), 1) + def test_does_not_widen_bool_literal(self): # `bool` is a subclass of `int` in Python (except some arithmetic operations have different # semantics...). It's not in Qiskit's value type system, though. diff --git a/test/python/transpiler/legacy_scheduling/__init__.py b/test/python/transpiler/legacy_scheduling/__init__.py deleted file mode 100644 index 3ce633ebcc4d..000000000000 --- a/test/python/transpiler/legacy_scheduling/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2022. -# -# 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. - -"""Qiskit legacy scheduling transpiler pass unit tests.""" diff --git a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py b/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py deleted file mode 100644 index aee567444cff..000000000000 --- a/test/python/transpiler/legacy_scheduling/test_instruction_alignments.py +++ /dev/null @@ -1,327 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# 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. - -"""Testing legacy instruction alignment pass.""" - -from qiskit import QuantumCircuit -from qiskit.transpiler import InstructionDurations -from qiskit.transpiler.passes import ( - AlignMeasures, - ALAPSchedule, - TimeUnitConversion, -) -from test import QiskitTestCase # pylint: disable=wrong-import-order - - -class TestAlignMeasures(QiskitTestCase): - """A test for measurement alignment pass.""" - - def setUp(self): - super().setUp() - instruction_durations = InstructionDurations() - instruction_durations.update( - [ - ("rz", (0,), 0), - ("rz", (1,), 0), - ("x", (0,), 160), - ("x", (1,), 160), - ("sx", (0,), 160), - ("sx", (1,), 160), - ("cx", (0, 1), 800), - ("cx", (1, 0), 800), - ("measure", None, 1600), - ] - ) - self.time_conversion_pass = TimeUnitConversion(inst_durations=instruction_durations) - # reproduce old behavior of 0.20.0 before #7655 - # currently default write latency is 0 - with self.assertWarns(DeprecationWarning): - self.scheduling_pass = ALAPSchedule( - durations=instruction_durations, - clbit_write_latency=1600, - conditional_latency=0, - ) - self.align_measure_pass = AlignMeasures(alignment=16) - - def test_t1_experiment_type(self): - """Test T1 experiment type circuit. - - (input) - - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├ - └───┘└────────────────┘└╥┘ - c: 1/════════════════════════╩═ - 0 - - (aligned) - - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├ - └───┘└────────────────┘└╥┘ - c: 1/════════════════════════╩═ - 0 - - This type of experiment slightly changes delay duration of interest. - However the quantization error should be less than alignment * dt. - """ - circuit = QuantumCircuit(1, 1) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} - ) - - ref_circuit = QuantumCircuit(1, 1) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.measure(0, 0) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_hanh_echo_experiment_type(self): - """Test Hahn echo experiment type circuit. - - (input) - - ┌────┐┌────────────────┐┌───┐┌────────────────┐┌────┐┌─┐ - q_0: ┤ √X ├┤ Delay(100[dt]) ├┤ X ├┤ Delay(100[dt]) ├┤ √X ├┤M├ - └────┘└────────────────┘└───┘└────────────────┘└────┘└╥┘ - c: 1/══════════════════════════════════════════════════════╩═ - 0 - - (output) - - ┌────┐┌────────────────┐┌───┐┌────────────────┐┌────┐┌──────────────┐┌─┐ - q_0: ┤ √X ├┤ Delay(100[dt]) ├┤ X ├┤ Delay(100[dt]) ├┤ √X ├┤ Delay(8[dt]) ├┤M├ - └────┘└────────────────┘└───┘└────────────────┘└────┘└──────────────┘└╥┘ - c: 1/══════════════════════════════════════════════════════════════════════╩═ - 0 - - This type of experiment doesn't change duration of interest (two in the middle). - However induces slight delay less than alignment * dt before measurement. - This might induce extra amplitude damping error. - """ - circuit = QuantumCircuit(1, 1) - circuit.sx(0) - circuit.delay(100, 0, unit="dt") - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.sx(0) - circuit.measure(0, 0) - - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} - ) - - ref_circuit = QuantumCircuit(1, 1) - ref_circuit.sx(0) - ref_circuit.delay(100, 0, unit="dt") - ref_circuit.x(0) - ref_circuit.delay(100, 0, unit="dt") - ref_circuit.sx(0) - ref_circuit.delay(8, 0, unit="dt") - ref_circuit.measure(0, 0) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_mid_circuit_measure(self): - """Test circuit with mid circuit measurement. - - (input) - - ┌───┐┌────────────────┐┌─┐┌───────────────┐┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├┤ Delay(10[dt]) ├┤ X ├┤ Delay(120[dt]) ├┤M├ - └───┘└────────────────┘└╥┘└───────────────┘└───┘└────────────────┘└╥┘ - c: 2/════════════════════════╩══════════════════════════════════════════╩═ - 0 1 - - (output) - - ┌───┐┌────────────────┐┌─┐┌───────────────┐┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(112[dt]) ├┤M├┤ Delay(10[dt]) ├┤ X ├┤ Delay(134[dt]) ├┤M├ - └───┘└────────────────┘└╥┘└───────────────┘└───┘└────────────────┘└╥┘ - c: 2/════════════════════════╩══════════════════════════════════════════╩═ - 0 1 - - Extra delay is always added to the existing delay right before the measurement. - Delay after measurement is unchanged. - """ - circuit = QuantumCircuit(1, 2) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - circuit.delay(10, 0, unit="dt") - circuit.x(0) - circuit.delay(120, 0, unit="dt") - circuit.measure(0, 1) - - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} - ) - - ref_circuit = QuantumCircuit(1, 2) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.measure(0, 0) - ref_circuit.delay(10, 0, unit="dt") - ref_circuit.x(0) - ref_circuit.delay(134, 0, unit="dt") - ref_circuit.measure(0, 1) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_mid_circuit_multiq_gates(self): - """Test circuit with mid circuit measurement and multi qubit gates. - - (input) - - ┌───┐┌────────────────┐┌─┐ ┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├──■───────■──┤M├ - └───┘└────────────────┘└╥┘┌─┴─┐┌─┐┌─┴─┐└╥┘ - q_1: ────────────────────────╫─┤ X ├┤M├┤ X ├─╫─ - ║ └───┘└╥┘└───┘ ║ - c: 2/════════════════════════╩═══════╩═══════╩═ - 0 1 0 - - (output) - - ┌───┐ ┌────────────────┐┌─┐ ┌─────────────────┐ ┌─┐» - q_0: ───────┤ X ├───────┤ Delay(112[dt]) ├┤M├──■──┤ Delay(1600[dt]) ├──■──┤M├» - ┌──────┴───┴──────┐└────────────────┘└╥┘┌─┴─┐└───────┬─┬───────┘┌─┴─┐└╥┘» - q_1: ┤ Delay(1872[dt]) ├───────────────────╫─┤ X ├────────┤M├────────┤ X ├─╫─» - └─────────────────┘ ║ └───┘ └╥┘ └───┘ ║ » - c: 2/══════════════════════════════════════╩═══════════════╩═══════════════╩═» - 0 1 0 » - « - «q_0: ─────────────────── - « ┌─────────────────┐ - «q_1: ┤ Delay(1600[dt]) ├ - « └─────────────────┘ - «c: 2/═══════════════════ - « - - Delay for the other channel paired by multi-qubit instruction is also scheduled. - Delay (1872dt) = X (160dt) + Delay (100dt + extra 12dt) + Measure (1600dt). - """ - circuit = QuantumCircuit(2, 2) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - circuit.cx(0, 1) - circuit.measure(1, 1) - circuit.cx(0, 1) - circuit.measure(0, 0) - - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} - ) - - ref_circuit = QuantumCircuit(2, 2) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.measure(0, 0) - ref_circuit.delay(160 + 112 + 1600, 1, unit="dt") - ref_circuit.cx(0, 1) - ref_circuit.delay(1600, 0, unit="dt") - ref_circuit.measure(1, 1) - ref_circuit.cx(0, 1) - ref_circuit.delay(1600, 1, unit="dt") - ref_circuit.measure(0, 0) - - self.assertEqual(aligned_circuit, ref_circuit) - - def test_alignment_is_not_processed(self): - """Test avoid pass processing if delay is aligned.""" - circuit = QuantumCircuit(2, 2) - circuit.x(0) - circuit.delay(160, 0, unit="dt") - circuit.measure(0, 0) - circuit.cx(0, 1) - circuit.measure(1, 1) - circuit.cx(0, 1) - circuit.measure(0, 0) - - # pre scheduling is not necessary because alignment is skipped - # this is to minimize breaking changes to existing code. - transpiled = self.align_measure_pass(circuit, property_set={"time_unit": "dt"}) - - self.assertEqual(transpiled, circuit) - - def test_circuit_using_clbit(self): - """Test a circuit with instructions using a common clbit. - - (input) - ┌───┐┌────────────────┐┌─┐ - q_0: ┤ X ├┤ Delay(100[dt]) ├┤M├────────────── - └───┘└────────────────┘└╥┘ ┌───┐ - q_1: ────────────────────────╫────┤ X ├────── - ║ └─╥─┘ ┌─┐ - q_2: ────────────────────────╫──────╫─────┤M├ - ║ ┌────╨────┐└╥┘ - c: 1/════════════════════════╩═╡ c_0 = T ╞═╩═ - 0 └─────────┘ 0 - - (aligned) - ┌───┐ ┌────────────────┐┌─┐┌────────────────┐ - q_0: ───────┤ X ├───────┤ Delay(112[dt]) ├┤M├┤ Delay(160[dt]) ├─── - ┌──────┴───┴──────┐└────────────────┘└╥┘└─────┬───┬──────┘ - q_1: ┤ Delay(1872[dt]) ├───────────────────╫───────┤ X ├────────── - └┬────────────────┤ ║ └─╥─┘ ┌─┐ - q_2: ─┤ Delay(432[dt]) ├───────────────────╫─────────╫─────────┤M├ - └────────────────┘ ║ ┌────╨────┐ └╥┘ - c: 1/══════════════════════════════════════╩════╡ c_0 = T ╞═════╩═ - 0 └─────────┘ 0 - - Looking at the q_0, the total schedule length T becomes - 160 (x) + 112 (aligned delay) + 1600 (measure) + 160 (delay) = 2032. - The last delay comes from ALAP scheduling called before the AlignMeasure pass, - which aligns stop times as late as possible, so the start time of x(1).c_if(0) - and the stop time of measure(0, 0) become T - 160. - """ - circuit = QuantumCircuit(3, 1) - circuit.x(0) - circuit.delay(100, 0, unit="dt") - circuit.measure(0, 0) - with self.assertWarns(DeprecationWarning): - circuit.x(1).c_if(0, 1) - circuit.measure(2, 0) - - timed_circuit = self.time_conversion_pass(circuit) - scheduled_circuit = self.scheduling_pass(timed_circuit, property_set={"time_unit": "dt"}) - aligned_circuit = self.align_measure_pass( - scheduled_circuit, property_set={"time_unit": "dt"} - ) - self.assertEqual(aligned_circuit.duration, 2032) - - ref_circuit = QuantumCircuit(3, 1) - ref_circuit.x(0) - ref_circuit.delay(112, 0, unit="dt") - ref_circuit.delay(1872, 1, unit="dt") # 2032 - 160 - ref_circuit.delay(432, 2, unit="dt") # 2032 - 1600 - ref_circuit.measure(0, 0) - with self.assertWarns(DeprecationWarning): - ref_circuit.x(1).c_if(0, 1) - ref_circuit.delay(160, 0, unit="dt") - ref_circuit.measure(2, 0) - - self.assertEqual(aligned_circuit, ref_circuit) diff --git a/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py b/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py deleted file mode 100644 index a13ca2e12de1..000000000000 --- a/test/python/transpiler/legacy_scheduling/test_scheduling_pass.py +++ /dev/null @@ -1,864 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2020. -# -# 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. - -"""Test the legacy Scheduling passes""" - -import unittest - -from ddt import ddt, data, unpack - -from qiskit import QuantumCircuit -from qiskit.circuit import Delay, Parameter -from qiskit.circuit.library.standard_gates import XGate, YGate, CXGate -from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.instruction_durations import InstructionDurations -from qiskit.transpiler.passes import ASAPSchedule, ALAPSchedule, DynamicalDecoupling -from qiskit.transpiler.passmanager import PassManager -from qiskit.transpiler.target import Target, InstructionProperties -from test import QiskitTestCase # pylint: disable=wrong-import-order - - -@ddt -class TestSchedulingPass(QiskitTestCase): - """Tests the Scheduling passes""" - - def test_alap_agree_with_reverse_asap_reverse(self): - """Test if ALAP schedule agrees with doubly-reversed ASAP schedule.""" - qc = QuantumCircuit(2) - qc.h(0) - qc.delay(500, 1) - qc.cx(0, 1) - qc.measure_all() - - durations = InstructionDurations( - [("h", 0, 200), ("cx", [0, 1], 700), ("measure", None, 1000)] - ) - - with self.assertWarns(DeprecationWarning): - pm = PassManager(ALAPSchedule(durations)) - alap_qc = pm.run(qc) - - with self.assertWarns(DeprecationWarning): - pm = PassManager(ASAPSchedule(durations)) - new_qc = pm.run(qc.reverse_ops()) - new_qc = new_qc.reverse_ops() - new_qc.name = new_qc.name - - self.assertEqual(alap_qc, new_qc) - - @data(ALAPSchedule, ASAPSchedule) - def test_classically_controlled_gate_after_measure(self, schedule_pass): - """Test if ALAP/ASAP schedules circuits with c_if after measure with a common clbit. - See: https://github.com/Qiskit/qiskit-terra/issues/7654 - - (input) - ┌─┐ - q_0: ┤M├─────────── - └╥┘ ┌───┐ - q_1: ─╫────┤ X ├─── - ║ └─╥─┘ - ║ ┌────╨────┐ - c: 1/═╩═╡ c_0 = T ╞ - 0 └─────────┘ - - (scheduled) - ┌─┐┌────────────────┐ - q_0: ───────────────────┤M├┤ Delay(200[dt]) ├ - ┌─────────────────┐└╥┘└─────┬───┬──────┘ - q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├─────── - └─────────────────┘ ║ └─╥─┘ - ║ ┌────╨────┐ - c: 1/════════════════════╩════╡ c_0=0x1 ╞════ - 0 └─────────┘ - """ - qc = QuantumCircuit(2, 1) - qc.measure(0, 0) - with self.assertWarns(DeprecationWarning): - qc.x(1).c_if(0, True) - - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - with self.assertWarns(DeprecationWarning): - pm = PassManager(schedule_pass(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(2, 1) - expected.measure(0, 0) - expected.delay(1000, 1) # x.c_if starts after measure - with self.assertWarns(DeprecationWarning): - expected.x(1).c_if(0, True) - expected.delay(200, 0) - - self.assertEqual(expected, scheduled) - - @data(ALAPSchedule, ASAPSchedule) - def test_measure_after_measure(self, schedule_pass): - """Test if ALAP/ASAP schedules circuits with measure after measure with a common clbit. - See: https://github.com/Qiskit/qiskit-terra/issues/7654 - - (input) - ┌───┐┌─┐ - q_0: ┤ X ├┤M├─── - └───┘└╥┘┌─┐ - q_1: ──────╫─┤M├ - ║ └╥┘ - c: 1/══════╩══╩═ - 0 0 - - (scheduled) - ┌───┐ ┌─┐┌─────────────────┐ - q_0: ───────┤ X ├───────┤M├┤ Delay(1000[dt]) ├ - ┌──────┴───┴──────┐└╥┘└───────┬─┬───────┘ - q_1: ┤ Delay(1200[dt]) ├─╫─────────┤M├──────── - └─────────────────┘ ║ └╥┘ - c: 1/════════════════════╩══════════╩═════════ - 0 0 - """ - qc = QuantumCircuit(2, 1) - qc.x(0) - qc.measure(0, 0) - qc.measure(1, 0) - - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - with self.assertWarns(DeprecationWarning): - pm = PassManager(schedule_pass(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(2, 1) - expected.x(0) - expected.measure(0, 0) - expected.delay(1200, 1) - expected.measure(1, 0) - expected.delay(1000, 0) - - self.assertEqual(expected, scheduled) - - @data(ALAPSchedule, ASAPSchedule) - def test_c_if_on_different_qubits(self, schedule_pass): - """Test if ALAP/ASAP schedules circuits with `c_if`s on different qubits. - - (input) - ┌─┐ - q_0: ┤M├────────────────────── - └╥┘ ┌───┐ - q_1: ─╫────┤ X ├────────────── - ║ └─╥─┘ ┌───┐ - q_2: ─╫──────╫────────┤ X ├─── - ║ ║ └─╥─┘ - ║ ┌────╨────┐┌────╨────┐ - c: 1/═╩═╡ c_0 = T ╞╡ c_0 = T ╞ - 0 └─────────┘└─────────┘ - - (scheduled) - - ┌─┐┌────────────────┐ - q_0: ───────────────────┤M├┤ Delay(200[dt]) ├─────────── - ┌─────────────────┐└╥┘└─────┬───┬──────┘ - q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├────────────────── - ├─────────────────┤ ║ └─╥─┘ ┌───┐ - q_2: ┤ Delay(1000[dt]) ├─╫─────────╫────────────┤ X ├─── - └─────────────────┘ ║ ║ └─╥─┘ - ║ ┌────╨────┐ ┌────╨────┐ - c: 1/════════════════════╩════╡ c_0=0x1 ╞════╡ c_0=0x1 ╞ - 0 └─────────┘ └─────────┘ - """ - qc = QuantumCircuit(3, 1) - qc.measure(0, 0) - with self.assertWarns(DeprecationWarning): - qc.x(1).c_if(0, True) - with self.assertWarns(DeprecationWarning): - qc.x(2).c_if(0, True) - - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - with self.assertWarns(DeprecationWarning): - pm = PassManager(schedule_pass(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(3, 1) - expected.measure(0, 0) - expected.delay(1000, 1) - expected.delay(1000, 2) - with self.assertWarns(DeprecationWarning): - expected.x(1).c_if(0, True) - with self.assertWarns(DeprecationWarning): - expected.x(2).c_if(0, True) - expected.delay(200, 0) - - self.assertEqual(expected, scheduled) - - @data(ALAPSchedule, ASAPSchedule) - def test_shorter_measure_after_measure(self, schedule_pass): - """Test if ALAP/ASAP schedules circuits with shorter measure after measure with a common clbit. - - (input) - ┌─┐ - q_0: ┤M├─── - └╥┘┌─┐ - q_1: ─╫─┤M├ - ║ └╥┘ - c: 1/═╩══╩═ - 0 0 - - (scheduled) - ┌─┐┌────────────────┐ - q_0: ───────────────────┤M├┤ Delay(700[dt]) ├ - ┌─────────────────┐└╥┘└──────┬─┬───────┘ - q_1: ┤ Delay(1000[dt]) ├─╫────────┤M├──────── - └─────────────────┘ ║ └╥┘ - c: 1/════════════════════╩═════════╩═════════ - 0 0 - """ - qc = QuantumCircuit(2, 1) - qc.measure(0, 0) - qc.measure(1, 0) - - durations = InstructionDurations([("measure", [0], 1000), ("measure", [1], 700)]) - with self.assertWarns(DeprecationWarning): - pm = PassManager(schedule_pass(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(2, 1) - expected.measure(0, 0) - expected.delay(1000, 1) - expected.measure(1, 0) - expected.delay(700, 0) - - self.assertEqual(expected, scheduled) - - @data(ALAPSchedule, ASAPSchedule) - def test_measure_after_c_if(self, schedule_pass): - """Test if ALAP/ASAP schedules circuits with c_if after measure with a common clbit. - - (input) - ┌─┐ - q_0: ┤M├────────────── - └╥┘ ┌───┐ - q_1: ─╫────┤ X ├────── - ║ └─╥─┘ ┌─┐ - q_2: ─╫──────╫─────┤M├ - ║ ┌────╨────┐└╥┘ - c: 1/═╩═╡ c_0 = T ╞═╩═ - 0 └─────────┘ 0 - - (scheduled) - ┌─┐┌─────────────────┐ - q_0: ───────────────────┤M├┤ Delay(1000[dt]) ├────────────────── - ┌─────────────────┐└╥┘└──────┬───┬──────┘┌────────────────┐ - q_1: ┤ Delay(1000[dt]) ├─╫────────┤ X ├───────┤ Delay(800[dt]) ├ - ├─────────────────┤ ║ └─╥─┘ └──────┬─┬───────┘ - q_2: ┤ Delay(1000[dt]) ├─╫──────────╫────────────────┤M├──────── - └─────────────────┘ ║ ┌────╨────┐ └╥┘ - c: 1/════════════════════╩═════╡ c_0=0x1 ╞════════════╩═════════ - 0 └─────────┘ 0 - """ - qc = QuantumCircuit(3, 1) - qc.measure(0, 0) - with self.assertWarns(DeprecationWarning): - qc.x(1).c_if(0, 1) - qc.measure(2, 0) - - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - with self.assertWarns(DeprecationWarning): - pm = PassManager(schedule_pass(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(3, 1) - expected.delay(1000, 1) - expected.delay(1000, 2) - expected.measure(0, 0) - with self.assertWarns(DeprecationWarning): - expected.x(1).c_if(0, 1) - expected.measure(2, 0) - expected.delay(1000, 0) - expected.delay(800, 1) - - self.assertEqual(expected, scheduled) - - def test_parallel_gate_different_length(self): - """Test circuit having two parallel instruction with different length. - - (input) - ┌───┐┌─┐ - q_0: ┤ X ├┤M├─── - ├───┤└╥┘┌─┐ - q_1: ┤ X ├─╫─┤M├ - └───┘ ║ └╥┘ - c: 2/══════╩══╩═ - 0 1 - - (expected, ALAP) - ┌────────────────┐┌───┐┌─┐ - q_0: ┤ Delay(200[dt]) ├┤ X ├┤M├ - └─────┬───┬──────┘└┬─┬┘└╥┘ - q_1: ──────┤ X ├────────┤M├──╫─ - └───┘ └╥┘ ║ - c: 2/════════════════════╩═══╩═ - 1 0 - - (expected, ASAP) - ┌───┐┌─┐┌────────────────┐ - q_0: ┤ X ├┤M├┤ Delay(200[dt]) ├ - ├───┤└╥┘└──────┬─┬───────┘ - q_1: ┤ X ├─╫────────┤M├──────── - └───┘ ║ └╥┘ - c: 2/══════╩═════════╩═════════ - 0 1 - - """ - qc = QuantumCircuit(2, 2) - qc.x(0) - qc.x(1) - qc.measure(0, 0) - qc.measure(1, 1) - - durations = InstructionDurations( - [("x", [0], 200), ("x", [1], 400), ("measure", None, 1000)] - ) - with self.assertWarns(DeprecationWarning): - pm = PassManager(ALAPSchedule(durations)) - qc_alap = pm.run(qc) - - alap_expected = QuantumCircuit(2, 2) - alap_expected.delay(200, 0) - alap_expected.x(0) - alap_expected.x(1) - alap_expected.measure(0, 0) - alap_expected.measure(1, 1) - - self.assertEqual(qc_alap, alap_expected) - - with self.assertWarns(DeprecationWarning): - pm = PassManager(ASAPSchedule(durations)) - qc_asap = pm.run(qc) - - asap_expected = QuantumCircuit(2, 2) - asap_expected.x(0) - asap_expected.x(1) - asap_expected.measure(0, 0) # immediately start after X gate - asap_expected.measure(1, 1) - asap_expected.delay(200, 0) - - self.assertEqual(qc_asap, asap_expected) - - def test_parallel_gate_different_length_with_barrier(self): - """Test circuit having two parallel instruction with different length with barrier. - - (input) - ┌───┐┌─┐ - q_0: ┤ X ├┤M├─── - ├───┤└╥┘┌─┐ - q_1: ┤ X ├─╫─┤M├ - └───┘ ║ └╥┘ - c: 2/══════╩══╩═ - 0 1 - - (expected, ALAP) - ┌────────────────┐┌───┐ ░ ┌─┐ - q_0: ┤ Delay(200[dt]) ├┤ X ├─░─┤M├─── - └─────┬───┬──────┘└───┘ ░ └╥┘┌─┐ - q_1: ──────┤ X ├─────────────░──╫─┤M├ - └───┘ ░ ║ └╥┘ - c: 2/═══════════════════════════╩══╩═ - 0 1 - - (expected, ASAP) - ┌───┐┌────────────────┐ ░ ┌─┐ - q_0: ┤ X ├┤ Delay(200[dt]) ├─░─┤M├─── - ├───┤└────────────────┘ ░ └╥┘┌─┐ - q_1: ┤ X ├───────────────────░──╫─┤M├ - └───┘ ░ ║ └╥┘ - c: 2/═══════════════════════════╩══╩═ - 0 1 - """ - qc = QuantumCircuit(2, 2) - qc.x(0) - qc.x(1) - qc.barrier() - qc.measure(0, 0) - qc.measure(1, 1) - - durations = InstructionDurations( - [("x", [0], 200), ("x", [1], 400), ("measure", None, 1000)] - ) - with self.assertWarns(DeprecationWarning): - pm = PassManager(ALAPSchedule(durations)) - qc_alap = pm.run(qc) - - alap_expected = QuantumCircuit(2, 2) - alap_expected.delay(200, 0) - alap_expected.x(0) - alap_expected.x(1) - alap_expected.barrier() - alap_expected.measure(0, 0) - alap_expected.measure(1, 1) - - self.assertEqual(qc_alap, alap_expected) - - with self.assertWarns(DeprecationWarning): - pm = PassManager(ASAPSchedule(durations)) - qc_asap = pm.run(qc) - - asap_expected = QuantumCircuit(2, 2) - asap_expected.x(0) - asap_expected.delay(200, 0) - asap_expected.x(1) - asap_expected.barrier() - asap_expected.measure(0, 0) - asap_expected.measure(1, 1) - - self.assertEqual(qc_asap, asap_expected) - - def test_measure_after_c_if_on_edge_locking(self): - """Test if ALAP/ASAP schedules circuits with c_if after measure with a common clbit. - - The scheduler is configured to reproduce behavior of the 0.20.0, - in which clbit lock is applied to the end-edge of measure instruction. - See https://github.com/Qiskit/qiskit-terra/pull/7655 - - (input) - ┌─┐ - q_0: ┤M├────────────── - └╥┘ ┌───┐ - q_1: ─╫────┤ X ├────── - ║ └─╥─┘ ┌─┐ - q_2: ─╫──────╫─────┤M├ - ║ ┌────╨────┐└╥┘ - c: 1/═╩═╡ c_0 = T ╞═╩═ - 0 └─────────┘ 0 - - (ASAP scheduled) - ┌─┐┌────────────────┐ - q_0: ───────────────────┤M├┤ Delay(200[dt]) ├───────────────────── - ┌─────────────────┐└╥┘└─────┬───┬──────┘ - q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├──────────────────────────── - └─────────────────┘ ║ └─╥─┘ ┌─┐┌────────────────┐ - q_2: ────────────────────╫─────────╫─────────┤M├┤ Delay(200[dt]) ├ - ║ ┌────╨────┐ └╥┘└────────────────┘ - c: 1/════════════════════╩════╡ c_0=0x1 ╞═════╩═══════════════════ - 0 └─────────┘ 0 - - (ALAP scheduled) - ┌─┐┌────────────────┐ - q_0: ───────────────────┤M├┤ Delay(200[dt]) ├─── - ┌─────────────────┐└╥┘└─────┬───┬──────┘ - q_1: ┤ Delay(1000[dt]) ├─╫───────┤ X ├────────── - └┬────────────────┤ ║ └─╥─┘ ┌─┐ - q_2: ─┤ Delay(200[dt]) ├─╫─────────╫─────────┤M├ - └────────────────┘ ║ ┌────╨────┐ └╥┘ - c: 1/════════════════════╩════╡ c_0=0x1 ╞═════╩═ - 0 └─────────┘ 0 - - """ - qc = QuantumCircuit(3, 1) - qc.measure(0, 0) - with self.assertWarns(DeprecationWarning): - qc.x(1).c_if(0, 1) - qc.measure(2, 0) - - durations = InstructionDurations([("x", None, 200), ("measure", None, 1000)]) - - # lock at the end edge - with self.assertWarns(DeprecationWarning): - actual_asap = PassManager(ASAPSchedule(durations, clbit_write_latency=1000)).run(qc) - actual_alap = PassManager(ALAPSchedule(durations, clbit_write_latency=1000)).run(qc) - - # start times of 2nd measure depends on ASAP/ALAP - expected_asap = QuantumCircuit(3, 1) - expected_asap.measure(0, 0) - expected_asap.delay(1000, 1) - with self.assertWarns(DeprecationWarning): - expected_asap.x(1).c_if(0, 1) - expected_asap.measure(2, 0) - expected_asap.delay(200, 0) - expected_asap.delay(200, 2) - self.assertEqual(expected_asap, actual_asap) - - expected_alap = QuantumCircuit(3, 1) - expected_alap.measure(0, 0) - expected_alap.delay(1000, 1) - with self.assertWarns(DeprecationWarning): - expected_alap.x(1).c_if(0, 1) - expected_alap.delay(200, 2) - expected_alap.measure(2, 0) - expected_alap.delay(200, 0) - self.assertEqual(expected_alap, actual_alap) - - @data([100, 200], [500, 0], [1000, 200]) - @unpack - def test_active_reset_circuit(self, write_lat, cond_lat): - """Test practical example of reset circuit. - - Because of the stimulus pulse overlap with the previous XGate on the q register, - measure instruction is always triggered after XGate regardless of write latency. - Thus only conditional latency matters in the scheduling. - - (input) - ┌─┐ ┌───┐ ┌─┐ ┌───┐ ┌─┐ ┌───┐ - q: ┤M├───┤ X ├───┤M├───┤ X ├───┤M├───┤ X ├─── - └╥┘ └─╥─┘ └╥┘ └─╥─┘ └╥┘ └─╥─┘ - ║ ┌────╨────┐ ║ ┌────╨────┐ ║ ┌────╨────┐ - c: 1/═╩═╡ c_0=0x1 ╞═╩═╡ c_0=0x1 ╞═╩═╡ c_0=0x1 ╞ - 0 └─────────┘ 0 └─────────┘ 0 └─────────┘ - - """ - qc = QuantumCircuit(1, 1) - qc.measure(0, 0) - with self.assertWarns(DeprecationWarning): - qc.x(0).c_if(0, 1) - qc.measure(0, 0) - with self.assertWarns(DeprecationWarning): - qc.x(0).c_if(0, 1) - qc.measure(0, 0) - with self.assertWarns(DeprecationWarning): - qc.x(0).c_if(0, 1) - - durations = InstructionDurations([("x", None, 100), ("measure", None, 1000)]) - with self.assertWarns(DeprecationWarning): - actual_asap = PassManager( - ASAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) - ).run(qc) - actual_alap = PassManager( - ALAPSchedule(durations, clbit_write_latency=write_lat, conditional_latency=cond_lat) - ).run(qc) - - expected = QuantumCircuit(1, 1) - expected.measure(0, 0) - if cond_lat > 0: - expected.delay(cond_lat, 0) - with self.assertWarns(DeprecationWarning): - expected.x(0).c_if(0, 1) - expected.measure(0, 0) - if cond_lat > 0: - expected.delay(cond_lat, 0) - with self.assertWarns(DeprecationWarning): - expected.x(0).c_if(0, 1) - expected.measure(0, 0) - if cond_lat > 0: - expected.delay(cond_lat, 0) - with self.assertWarns(DeprecationWarning): - expected.x(0).c_if(0, 1) - - self.assertEqual(expected, actual_asap) - self.assertEqual(expected, actual_alap) - - def test_random_complicated_circuit(self): - """Test scheduling complicated circuit with control flow. - - (input) - ┌────────────────┐ ┌───┐ ░ ┌───┐ » - q_0: ┤ Delay(100[dt]) ├───┤ X ├────░──────────────────┤ X ├───» - └────────────────┘ └─╥─┘ ░ ┌───┐ └─╥─┘ » - q_1: ───────────────────────╫──────░───────┤ X ├────────╫─────» - ║ ░ ┌─┐ └─╥─┘ ║ » - q_2: ───────────────────────╫──────░─┤M├─────╫──────────╫─────» - ┌────╨────┐ ░ └╥┘┌────╨────┐┌────╨────┐» - c: 1/══════════════════╡ c_0=0x1 ╞════╩═╡ c_0=0x0 ╞╡ c_0=0x0 ╞» - └─────────┘ 0 └─────────┘└─────────┘» - « ┌────────────────┐┌───┐ - «q_0: ┤ Delay(300[dt]) ├┤ X ├─────■───── - « └────────────────┘└───┘ ┌─┴─┐ - «q_1: ────────■─────────────────┤ X ├─── - « ┌─┴─┐ ┌─┐ └─╥─┘ - «q_2: ──────┤ X ├────────┤M├──────╫───── - « └───┘ └╥┘ ┌────╨────┐ - «c: 1/════════════════════╩══╡ c_0=0x0 ╞ - « 0 └─────────┘ - - (ASAP scheduled) duration = 2800 dt - ┌────────────────┐┌────────────────┐ ┌───┐ ░ ┌─────────────────┐» - q_0: ┤ Delay(100[dt]) ├┤ Delay(100[dt]) ├───┤ X ├────░─┤ Delay(1400[dt]) ├» - ├────────────────┤└────────────────┘ └─╥─┘ ░ ├─────────────────┤» - q_1: ┤ Delay(300[dt]) ├───────────────────────╫──────░─┤ Delay(1200[dt]) ├» - ├────────────────┤ ║ ░ └───────┬─┬───────┘» - q_2: ┤ Delay(300[dt]) ├───────────────────────╫──────░─────────┤M├────────» - └────────────────┘ ┌────╨────┐ ░ └╥┘ » - c: 1/════════════════════════════════════╡ c_0=0x1 ╞════════════╩═════════» - └─────────┘ 0 » - « ┌───┐ ┌────────────────┐» - «q_0: ────────────────────────────────┤ X ├───┤ Delay(300[dt]) ├» - « ┌───┐ └─╥─┘ └────────────────┘» - «q_1: ───┤ X ├──────────────────────────╫─────────────■─────────» - « └─╥─┘ ┌────────────────┐ ║ ┌─┴─┐ » - «q_2: ─────╫─────┤ Delay(300[dt]) ├─────╫───────────┤ X ├───────» - « ┌────╨────┐└────────────────┘┌────╨────┐ └───┘ » - «c: 1/╡ c_0=0x0 ╞══════════════════╡ c_0=0x0 ╞══════════════════» - « └─────────┘ └─────────┘ » - « ┌───┐ ┌────────────────┐ - «q_0: ──────┤ X ├────────────■─────┤ Delay(700[dt]) ├ - « ┌─────┴───┴──────┐ ┌─┴─┐ ├────────────────┤ - «q_1: ┤ Delay(400[dt]) ├───┤ X ├───┤ Delay(700[dt]) ├ - « ├────────────────┤ └─╥─┘ └──────┬─┬───────┘ - «q_2: ┤ Delay(300[dt]) ├─────╫────────────┤M├──────── - « └────────────────┘┌────╨────┐ └╥┘ - «c: 1/══════════════════╡ c_0=0x0 ╞════════╩═════════ - « └─────────┘ 0 - - (ALAP scheduled) duration = 3100 - ┌────────────────┐┌────────────────┐ ┌───┐ ░ ┌─────────────────┐» - q_0: ┤ Delay(100[dt]) ├┤ Delay(100[dt]) ├───┤ X ├────░─┤ Delay(1400[dt]) ├» - ├────────────────┤└────────────────┘ └─╥─┘ ░ ├─────────────────┤» - q_1: ┤ Delay(300[dt]) ├───────────────────────╫──────░─┤ Delay(1200[dt]) ├» - ├────────────────┤ ║ ░ └───────┬─┬───────┘» - q_2: ┤ Delay(300[dt]) ├───────────────────────╫──────░─────────┤M├────────» - └────────────────┘ ┌────╨────┐ ░ └╥┘ » - c: 1/════════════════════════════════════╡ c_0=0x1 ╞════════════╩═════════» - └─────────┘ 0 » - « ┌───┐ ┌────────────────┐» - «q_0: ────────────────────────────────┤ X ├───┤ Delay(300[dt]) ├» - « ┌───┐ ┌────────────────┐ └─╥─┘ └────────────────┘» - «q_1: ───┤ X ├───┤ Delay(300[dt]) ├─────╫─────────────■─────────» - « └─╥─┘ ├────────────────┤ ║ ┌─┴─┐ » - «q_2: ─────╫─────┤ Delay(600[dt]) ├─────╫───────────┤ X ├───────» - « ┌────╨────┐└────────────────┘┌────╨────┐ └───┘ » - «c: 1/╡ c_0=0x0 ╞══════════════════╡ c_0=0x0 ╞══════════════════» - « └─────────┘ └─────────┘ » - « ┌───┐ ┌────────────────┐ - «q_0: ──────┤ X ├────────────■─────┤ Delay(700[dt]) ├ - « ┌─────┴───┴──────┐ ┌─┴─┐ ├────────────────┤ - «q_1: ┤ Delay(100[dt]) ├───┤ X ├───┤ Delay(700[dt]) ├ - « └──────┬─┬───────┘ └─╥─┘ └────────────────┘ - «q_2: ───────┤M├─────────────╫─────────────────────── - « └╥┘ ┌────╨────┐ - «c: 1/════════╩═════════╡ c_0=0x0 ╞══════════════════ - « 0 └─────────┘ - - """ - qc = QuantumCircuit(3, 1) - qc.delay(100, 0) - with self.assertWarns(DeprecationWarning): - qc.x(0).c_if(0, 1) - qc.barrier() - qc.measure(2, 0) - with self.assertWarns(DeprecationWarning): - qc.x(1).c_if(0, 0) - with self.assertWarns(DeprecationWarning): - qc.x(0).c_if(0, 0) - qc.delay(300, 0) - qc.cx(1, 2) - qc.x(0) - with self.assertWarns(DeprecationWarning): - qc.cx(0, 1).c_if(0, 0) - qc.measure(2, 0) - - durations = InstructionDurations( - [("x", None, 100), ("measure", None, 1000), ("cx", None, 200)] - ) - - with self.assertWarns(DeprecationWarning): - actual_asap = PassManager( - ASAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) - ).run(qc) - actual_alap = PassManager( - ALAPSchedule(durations, clbit_write_latency=100, conditional_latency=200) - ).run(qc) - - expected_asap = QuantumCircuit(3, 1) - expected_asap.delay(100, 0) - expected_asap.delay(100, 0) # due to conditional latency of 200dt - expected_asap.delay(300, 1) - expected_asap.delay(300, 2) - with self.assertWarns(DeprecationWarning): - expected_asap.x(0).c_if(0, 1) - expected_asap.barrier() - expected_asap.delay(1400, 0) - expected_asap.delay(1200, 1) - expected_asap.measure(2, 0) - with self.assertWarns(DeprecationWarning): - expected_asap.x(1).c_if(0, 0) - with self.assertWarns(DeprecationWarning): - expected_asap.x(0).c_if(0, 0) - expected_asap.delay(300, 0) - expected_asap.x(0) - expected_asap.delay(300, 2) - expected_asap.cx(1, 2) - expected_asap.delay(400, 1) - with self.assertWarns(DeprecationWarning): - expected_asap.cx(0, 1).c_if(0, 0) - expected_asap.delay(700, 0) # creg is released at t0 of cx(0,1).c_if(0,0) - expected_asap.delay( - 700, 1 - ) # no creg write until 100dt. thus measure can move left by 300dt. - expected_asap.delay(300, 2) - expected_asap.measure(2, 0) - self.assertEqual(expected_asap, actual_asap) - self.assertEqual(actual_asap.duration, 3100) - - expected_alap = QuantumCircuit(3, 1) - expected_alap.delay(100, 0) - expected_alap.delay(100, 0) # due to conditional latency of 200dt - expected_alap.delay(300, 1) - expected_alap.delay(300, 2) - with self.assertWarns(DeprecationWarning): - expected_alap.x(0).c_if(0, 1) - expected_alap.barrier() - expected_alap.delay(1400, 0) - expected_alap.delay(1200, 1) - expected_alap.measure(2, 0) - with self.assertWarns(DeprecationWarning): - expected_alap.x(1).c_if(0, 0) - with self.assertWarns(DeprecationWarning): - expected_alap.x(0).c_if(0, 0) - expected_alap.delay(300, 0) - expected_alap.x(0) - expected_alap.delay(300, 1) - expected_alap.delay(600, 2) - expected_alap.cx(1, 2) - expected_alap.delay(100, 1) - with self.assertWarns(DeprecationWarning): - expected_alap.cx(0, 1).c_if(0, 0) - expected_alap.measure(2, 0) - expected_alap.delay(700, 0) - expected_alap.delay(700, 1) - self.assertEqual(expected_alap, actual_alap) - self.assertEqual(actual_alap.duration, 3100) - - def test_dag_introduces_extra_dependency_between_conditionals(self): - """Test dependency between conditional operations in the scheduling. - - In the below example circuit, the conditional x on q1 could start at time 0, - however it must be scheduled after the conditional x on q0 in ASAP scheduling. - That is because circuit model used in the transpiler passes (DAGCircuit) - interprets instructions acting on common clbits must be run in the order - given by the original circuit (QuantumCircuit). - - (input) - ┌────────────────┐ ┌───┐ - q_0: ┤ Delay(100[dt]) ├───┤ X ├─── - └─────┬───┬──────┘ └─╥─┘ - q_1: ──────┤ X ├────────────╫───── - └─╥─┘ ║ - ┌────╨────┐ ┌────╨────┐ - c: 1/═══╡ c_0=0x1 ╞════╡ c_0=0x1 ╞ - └─────────┘ └─────────┘ - - (ASAP scheduled) - ┌────────────────┐ ┌───┐ - q_0: ┤ Delay(100[dt]) ├───┤ X ├────────────── - ├────────────────┤ └─╥─┘ ┌───┐ - q_1: ┤ Delay(100[dt]) ├─────╫────────┤ X ├─── - └────────────────┘ ║ └─╥─┘ - ┌────╨────┐┌────╨────┐ - c: 1/══════════════════╡ c_0=0x1 ╞╡ c_0=0x1 ╞ - └─────────┘└─────────┘ - """ - qc = QuantumCircuit(2, 1) - qc.delay(100, 0) - with self.assertWarns(DeprecationWarning): - qc.x(0).c_if(0, True) - with self.assertWarns(DeprecationWarning): - qc.x(1).c_if(0, True) - - durations = InstructionDurations([("x", None, 160)]) - with self.assertWarns(DeprecationWarning): - pm = PassManager(ASAPSchedule(durations)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(2, 1) - expected.delay(100, 0) - expected.delay(100, 1) # due to extra dependency on clbits - with self.assertWarns(DeprecationWarning): - expected.x(0).c_if(0, True) - with self.assertWarns(DeprecationWarning): - expected.x(1).c_if(0, True) - - self.assertEqual(expected, scheduled) - - @data(ALAPSchedule, ASAPSchedule) - def test_respect_target_instruction_constraints(self, schedule_pass): - """Test if ALAP/ASAP does not pad delays for qubits that do not support delay instructions. - See: https://github.com/Qiskit/qiskit-terra/issues/9993 - """ - target = Target(dt=1) - target.add_instruction(XGate(), {(1,): InstructionProperties(duration=200)}) - # delays are not supported - - qc = QuantumCircuit(2) - qc.x(1) - - with self.assertWarns(DeprecationWarning): - pm = PassManager(schedule_pass(target=target)) - scheduled = pm.run(qc) - - expected = QuantumCircuit(2) - expected.x(1) - # no delay on qubit 0 - - self.assertEqual(expected, scheduled) - - def test_dd_respect_target_instruction_constraints(self): - """Test if DD pass does not pad delays for qubits that do not support delay instructions - and does not insert DD gates for qubits that do not support necessary gates. - See: https://github.com/Qiskit/qiskit-terra/issues/9993 - """ - qc = QuantumCircuit(3) - qc.cx(0, 1) - qc.cx(1, 2) - - target = Target(dt=1) - # Y is partially supported (not supported on qubit 2) - target.add_instruction( - XGate(), {(q,): InstructionProperties(duration=100) for q in range(2)} - ) - target.add_instruction( - CXGate(), - { - (0, 1): InstructionProperties(duration=1000), - (1, 2): InstructionProperties(duration=1000), - }, - ) - # delays are not supported - - # No DD instructions nor delays are padded due to no delay support in the target - with self.assertWarns(DeprecationWarning): - pm_scheduler = PassManager( - [ - ALAPSchedule(target=target), - DynamicalDecoupling( - durations=None, dd_sequence=[XGate(), XGate()], target=target - ), - ] - ) - scheduled = pm_scheduler.run(qc) - self.assertEqual(qc, scheduled) - - # Fails since Y is not supported in the target - with self.assertWarns(DeprecationWarning): - with self.assertRaises(TranspilerError): - PassManager( - [ - ALAPSchedule(target=target), - DynamicalDecoupling( - durations=None, - dd_sequence=[XGate(), YGate(), XGate(), YGate()], - target=target, - ), - ] - ) - - # Add delay support to the target - target.add_instruction(Delay(Parameter("t")), {(q,): None for q in range(3)}) - # No error but no DD on qubit 2 (just delay is padded) since X is not supported on it - scheduled = pm_scheduler.run(qc) - - expected = QuantumCircuit(3) - expected.delay(1000, [2]) - expected.cx(0, 1) - expected.cx(1, 2) - expected.delay(200, [0]) - expected.x([0]) - expected.delay(400, [0]) - expected.x([0]) - expected.delay(200, [0]) - self.assertEqual(expected, scheduled) - - -if __name__ == "__main__": - unittest.main() diff --git a/test/qpy_compat/qpy_test_constraints.txt b/test/qpy_compat/qpy_test_constraints.txt index 03f798b3c01a..9b407a59b6ba 100644 --- a/test/qpy_compat/qpy_test_constraints.txt +++ b/test/qpy_compat/qpy_test_constraints.txt @@ -1,2 +1,7 @@ numpy===1.24.4 scipy===1.10.1 + +# This is a loose constraint because we want to test different versions, +# as defined in 'test/qpy_compat/run_tests.sh', but any symengine version +# above (and including) 0.14 will be incompatible with qpy. +symengine<0.14 diff --git a/test/qpy_compat/run_tests.sh b/test/qpy_compat/run_tests.sh index f1c770809c77..fd9e17c58071 100755 --- a/test/qpy_compat/run_tests.sh +++ b/test/qpy_compat/run_tests.sh @@ -57,6 +57,8 @@ popd # This will likely duplicate the base dev-compatibility test, but the tests are fairly fast, and # it's better safe than sorry with the serialisation tests. +# Note that the constraint in the range of symengine versions is logically duplicated +# in `qpy_test_constraints.txt` symengine_versions=( '>=0.11,<0.12' '>=0.13,<0.14'