-
Notifications
You must be signed in to change notification settings - Fork 2.9k
[Stretch] Support stretch and duration expressions for Delay instruction.
#13853
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 200 commits
af4ba52
e423bf9
0a5917b
a97434d
1afc965
86655f1
aaeae9b
a2a444b
8ac2dc3
9f8313c
db9d9cb
71b7e7a
2091557
7307be9
9b30284
ce1faf1
4fee48f
88ab046
c5a230f
7c88d88
c58a7b8
d9e9a8c
4a56150
23b5961
8bf2e4f
111eb32
a839d51
a19b39a
7f02e56
55af327
86b7af9
9b3c821
69eab91
971cc26
657b666
fc53d47
24f5125
9e775e1
387ac95
25508bf
2c8ce43
ccf9441
c6eab02
edd7806
8afa92e
4e0f2df
15ba943
81c5833
50c31d3
7e43322
2449ee6
e55e189
1d51022
eb8f150
415f62e
d38f3e9
fd66c1d
e9b6d97
52da3cc
a3be688
2ff7e7a
6e8080c
9266166
e92dcc5
2d8f1b7
f5a5457
6d7b2af
6ad69d4
af71e3a
bddbc66
ca2027f
2ed26ef
bf9ba73
0beed94
07771f1
9332ed3
17241c6
ca91bfd
368b0ac
7fcb098
723e4f0
803fa8f
ad87e78
9afd68c
86ac649
907853e
ba48a16
d1f5d1a
e450853
51dd954
58a7811
d3bca5f
50deb93
f1dc1a1
1f439a0
166d7b2
10b2c8d
9d6cf39
8021e00
a15141b
ca2785d
e902009
16475c3
6b5930d
be73180
25f1693
ecc343b
b32e6f0
864506d
6bc728a
2d38178
84f5a66
91b4c75
e1dbd1d
1ad4388
42258b8
481700a
e09762f
cbd55db
f12afb4
8d059cd
e05d9ff
bd6bf35
ef04db2
0f14cca
7db8222
65f2198
34e615b
bbe67a6
0acc450
7fea216
4a317e8
f5709ae
3ae8e5e
0ba0126
de32a57
ed024ab
eb3cbcd
5634d63
37dec7a
05cbcb1
96f4e06
3f74a7f
7f0b785
ccb3b88
576d194
f02c11d
0b2b440
78b2951
97bbeba
3a34220
888e9bb
fab8b36
49f2af8
69b985b
3b42c14
57f36c9
f780d9a
e7ca9c4
d4cef21
60bcfdb
2d3706e
286aff7
c43d203
d40cb32
dfc02f3
52f5cdc
3fe2993
a13dc49
0aa39e2
3b033ad
6d64f7f
099f257
f1e2557
abea250
dc972d4
ad54644
723445f
9308002
ae08fae
320b72d
c66842b
827e619
1fd92d9
26aab3b
bc1ee2f
4d7129e
9d82809
e84b48e
62cf3ee
e2044e6
7634b3e
2223d12
823dd27
a5ccc7b
8c2e873
568be83
60e6b5c
7c15666
a086581
a5a5842
0e0510e
ce4d90f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4553,17 +4553,20 @@ def barrier(self, *qargs: QubitSpecifier, label=None) -> InstructionSet: | |
|
|
||
| def delay( | ||
| self, | ||
| duration: ParameterValueType, | ||
| duration: Union[ParameterValueType, expr.Expr], | ||
| qarg: QubitSpecifier | None = None, | ||
| unit: str = "dt", | ||
| unit: str | None = None, | ||
| ) -> InstructionSet: | ||
| """Apply :class:`~.circuit.Delay`. If qarg is ``None``, applies to all qubits. | ||
| When applying to multiple qubits, delays with the same duration will be created. | ||
|
|
||
| Args: | ||
| duration (int or float or ParameterExpression): duration of the delay. | ||
| duration (Object): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. FWIW you can just drop |
||
| duration of the delay. If this is an :class:`~.expr.Expr`, it must be | ||
| a constant expression of type :class:`~.types.Duration`. | ||
| qarg (Object): qubit argument to apply this delay. | ||
| unit (str): unit of the duration. Supported units: ``'s'``, ``'ms'``, ``'us'``, | ||
| unit (str | None): unit of the duration, unless ``duration`` is an :class:`~.expr.Expr` | ||
| in which case it must not be specified. Supported units: ``'s'``, ``'ms'``, ``'us'``, | ||
| ``'ns'``, ``'ps'``, and ``'dt'``. Default is ``'dt'``, i.e. integer time unit | ||
| depending on the target backend. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,12 +13,15 @@ | |
| """Unify time unit in circuit for scheduling and following passes.""" | ||
| from typing import Set | ||
|
|
||
| from qiskit.circuit import Delay | ||
| from qiskit.circuit import Delay, Duration | ||
| from qiskit.circuit.classical import expr | ||
| from qiskit.circuit.duration import duration_in_dt | ||
| from qiskit.dagcircuit import DAGCircuit | ||
| from qiskit.transpiler.basepasses import TransformationPass | ||
| from qiskit.transpiler.exceptions import TranspilerError | ||
| from qiskit.transpiler.instruction_durations import InstructionDurations | ||
| from qiskit.transpiler.target import Target | ||
| from qiskit.utils import apply_prefix | ||
|
|
||
|
|
||
| class TimeUnitConversion(TransformationPass): | ||
|
|
@@ -71,52 +74,83 @@ def run(self, dag: DAGCircuit): | |
| if self._durations_provided: | ||
| inst_durations.update(self.inst_durations, getattr(self.inst_durations, "dt", None)) | ||
|
|
||
| # The float-value converted units for delay expressions, either all in 'dt' | ||
| # or all in seconds. | ||
| expression_durations = {} | ||
|
|
||
| # Choose unit | ||
| if inst_durations.dt is not None: | ||
| time_unit = "dt" | ||
| else: | ||
| # Check what units are used in delays and other instructions: dt or SI or mixed | ||
| units_delay = self._units_used_in_delays(dag) | ||
| if self._unified(units_delay) == "mixed": | ||
| has_dt = False | ||
| has_si = False | ||
|
|
||
| # We _always_ need to traverse duration expressions to convert them to | ||
| # a float. But we also use the opportunity to note if they intermix cycles | ||
| # and wall-time, in case we don't have a `dt` to use to unify all instruction | ||
| # durations. | ||
| for node in dag.op_nodes(op=Delay): | ||
| if isinstance(node.op.duration, expr.Expr): | ||
| if any( | ||
| isinstance(x, expr.Stretch) for x in expr.iter_identifiers(node.op.duration) | ||
| ): | ||
| # If any of the delays use a stretch expression, we can't run scheduling | ||
| # passes anyway, so we bail out. In theory, we _could_ still traverse | ||
| # through the stretch expression and replace any Duration value nodes it may | ||
| # contain with ones of the same units, but it'd be complex and probably unuseful. | ||
| self.property_set["time_unit"] = "stretch" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does anything use this property set value? If so how do they handle the string stretch here? I would expect our current scheduling passes to just skip or fail in the presence of stretches. Is this what raises the error when transpilation is called with a stretch duration?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, the scheduling passes look at this and raise if it's |
||
| return dag | ||
|
|
||
| visitor = _EvalDurationImpl(inst_durations.dt) | ||
| duration = node.op.duration.accept(visitor) | ||
| if visitor.in_cycles(): | ||
| has_dt = True | ||
| # We need to round in case the expression evaluated to a non-integral 'dt'. | ||
| duration = duration_in_dt(duration, 1.0) | ||
| else: | ||
| has_si = True | ||
| if duration < 0: | ||
| raise TranspilerError( | ||
| f"Expression '{node.op.duration}' resolves to a negative duration!" | ||
| ) | ||
|
kevinhartman marked this conversation as resolved.
|
||
| expression_durations[node._node_id] = duration | ||
|
mtreinish marked this conversation as resolved.
|
||
| else: | ||
| if node.op.unit == "dt": | ||
| has_dt = True | ||
| else: | ||
| has_si = True | ||
| if inst_durations.dt is None and has_dt and has_si: | ||
| raise TranspilerError( | ||
| "Fail to unify time units in delays. SI units " | ||
| "and dt unit must not be mixed when dt is not supplied." | ||
| ) | ||
| units_other = inst_durations.units_used() | ||
| if self._unified(units_other) == "mixed": | ||
| raise TranspilerError( | ||
| "Fail to unify time units in instruction_durations. SI units " | ||
| "and dt unit must not be mixed when dt is not supplied." | ||
| ) | ||
|
|
||
| unified_unit = self._unified(units_delay | units_other) | ||
| if unified_unit == "SI": | ||
| if inst_durations.dt is None: | ||
| # Check what units are used in other instructions: dt or SI or mixed | ||
| units_other = inst_durations.units_used() | ||
| unified_unit = self._unified(units_other) | ||
| if unified_unit == "SI" and not has_dt: | ||
| time_unit = "s" | ||
| elif unified_unit == "dt": | ||
| elif unified_unit == "dt" and not has_si: | ||
| time_unit = "dt" | ||
| else: | ||
| raise TranspilerError( | ||
| "Fail to unify time units. SI units " | ||
| "and dt unit must not be mixed when dt is not supplied." | ||
| ) | ||
| else: | ||
| time_unit = "dt" | ||
|
|
||
| # Make instructions with local durations consistent. | ||
| for node in dag.op_nodes(Delay): | ||
| op = node.op.to_mutable() | ||
| op.duration = inst_durations._convert_unit(op.duration, op.unit, time_unit) | ||
| if node._node_id in expression_durations: | ||
| op.duration = expression_durations[node._node_id] | ||
| else: | ||
| op.duration = inst_durations._convert_unit(op.duration, op.unit, time_unit) | ||
| op.unit = time_unit | ||
| dag.substitute_node(node, op) | ||
|
|
||
| self.property_set["time_unit"] = time_unit | ||
| return dag | ||
|
|
||
| @staticmethod | ||
| def _units_used_in_delays(dag: DAGCircuit) -> Set[str]: | ||
| units_used = set() | ||
| for node in dag.op_nodes(op=Delay): | ||
| units_used.add(node.op.unit) | ||
| return units_used | ||
|
|
||
| @staticmethod | ||
| def _unified(unit_set: Set[str]) -> str: | ||
| if not unit_set: | ||
|
|
@@ -135,3 +169,65 @@ def _unified(unit_set: Set[str]) -> str: | |
| return "SI" | ||
|
|
||
| return "mixed" | ||
|
|
||
|
|
||
| class _EvalDurationImpl(expr.ExprVisitor[float]): | ||
| """Evaluates the expression to a single float result. | ||
|
|
||
| If ``dt`` is provided or all durations are already in ``dt``, the result is in ``dt``. | ||
| Otherwise, the result will be in seconds, and all durations MUST be in wall-time (SI). | ||
| """ | ||
|
|
||
| __slots__ = ("dt", "has_dt", "has_si") | ||
|
Comment on lines
+174
to
+181
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Absolutely applaud the effort here haha. I'm not certain who's going to write an expression using only duration literals in a way that is valid through this pass via constant folding, but I love that it'll work if they do. |
||
|
|
||
| def __init__(self, dt=None): | ||
| self.dt = dt | ||
| self.has_dt = False | ||
| self.has_si = False | ||
|
|
||
| def in_cycles(self): | ||
| """Returns ``True`` if units are 'dt' after visit.""" | ||
| return self.has_dt or self.dt is not None | ||
|
|
||
| def visit_value(self, node, /) -> float: | ||
| if isinstance(node.value, float): | ||
| return node.value | ||
| if isinstance(node.value, Duration.dt): | ||
| if self.has_si and self.dt is None: | ||
| raise TranspilerError( | ||
| "Fail to unify time units in delays. SI units " | ||
| "and dt unit must not be mixed when dt is not supplied." | ||
| ) | ||
| self.has_dt = True | ||
| return node.value[0] | ||
| if isinstance(node.value, Duration): | ||
| if self.has_dt and self.dt is None: | ||
| raise TranspilerError( | ||
| "Fail to unify time units in delays. SI units " | ||
| "and dt unit must not be mixed when dt is not supplied." | ||
| ) | ||
| self.has_si = True | ||
| # Setting 'divisor' to 1 when there's no 'dt' is just to simplify | ||
| # the logic (we don't need to divide). | ||
| divisor = self.dt if self.dt is not None else 1 | ||
| if isinstance(node.value, Duration.s): | ||
| return node.value[0] / divisor | ||
| from_unit = node.value.unit() | ||
| return apply_prefix(node.value[0], from_unit) / divisor | ||
| raise TranspilerError(f"invalid duration expression: {node}") | ||
|
|
||
| def visit_binary(self, node, /) -> float: | ||
|
kevinhartman marked this conversation as resolved.
|
||
| left = node.left.accept(self) | ||
| right = node.right.accept(self) | ||
| if node.op == expr.Binary.Op.ADD: | ||
| return left + right | ||
| if node.op == expr.Binary.Op.SUB: | ||
| return left - right | ||
| if node.op == expr.Binary.Op.MUL: | ||
| return left * right | ||
| if node.op == expr.Binary.Op.DIV: | ||
| return left / right | ||
| raise TranspilerError(f"invalid duration expression: {node}") | ||
|
|
||
| def visit_cast(self, node, /) -> float: | ||
| return node.operand.accept(self) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It always surprises me that we allow this. Who is going to make their delay
acos(A) / pi + e^(pi*B)