Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
203 commits
Select commit Hold shift + click to select a range
af4ba52
WIP
kevinhartman Feb 7, 2025
e423bf9
Add try_const to lift.
kevinhartman Feb 7, 2025
0a5917b
Try multiple singletons, new one for const.
kevinhartman Feb 7, 2025
a97434d
Revert "Try multiple singletons, new one for const."
kevinhartman Feb 7, 2025
1afc965
Remove Bool singleton test.
kevinhartman Feb 7, 2025
86655f1
Add const handling for stores, fix test bugs.
kevinhartman Feb 7, 2025
aaeae9b
Fix formatting.
kevinhartman Feb 7, 2025
a2a444b
Remove Duration and Stretch for now.
kevinhartman Feb 7, 2025
8ac2dc3
Cleanup, fix const bug in index.
kevinhartman Feb 7, 2025
9f8313c
Fix ordering issue for types with differing const-ness.
kevinhartman Feb 9, 2025
db9d9cb
Fix QPY serialization.
kevinhartman Feb 10, 2025
71b7e7a
Make expr.Lift default to non-const.
kevinhartman Feb 11, 2025
2091557
Revert to old test_expr_constructors.py.
kevinhartman Feb 11, 2025
7307be9
Make binary_logical lift independent again.
kevinhartman Feb 11, 2025
9b30284
Update tests, handle a few edge cases.
kevinhartman Feb 12, 2025
ce1faf1
Fix docstring.
kevinhartman Feb 12, 2025
4fee48f
Remove now redundant arg from tests.
kevinhartman Feb 12, 2025
88ab046
Add const testing for ordering.
kevinhartman Feb 12, 2025
c5a230f
Add const tests for shifts.
kevinhartman Feb 12, 2025
7c88d88
Add release note.
kevinhartman Feb 12, 2025
c58a7b8
Add const store tests.
kevinhartman Feb 12, 2025
d9e9a8c
Address lint, minor cleanup.
kevinhartman Feb 13, 2025
4a56150
Add Float type to classical expressions.
kevinhartman Feb 12, 2025
23b5961
Allow DANGEROUS conversion from Float to Bool.
kevinhartman Feb 12, 2025
8bf2e4f
Test Float ordering.
kevinhartman Feb 12, 2025
111eb32
Improve error messages for using Float with logical operators.
kevinhartman Feb 12, 2025
a839d51
Float tests for constructors.
kevinhartman Feb 12, 2025
a19b39a
Add release note.
kevinhartman Feb 12, 2025
7f02e56
Add Duration and Stretch classical types.
kevinhartman Feb 13, 2025
55af327
Add Duration type to qiskit.circuit.
kevinhartman Feb 13, 2025
86b7af9
Block Stretch from use in binary relations.
kevinhartman Feb 13, 2025
9b3c821
Add type ordering tests for Duration and Stretch.
kevinhartman Feb 13, 2025
69eab91
Test expr constructors for Duration and Stretch.
kevinhartman Feb 13, 2025
971cc26
Fix lint.
kevinhartman Feb 13, 2025
657b666
Implement operators +, -, *, /.
kevinhartman Feb 14, 2025
fc53d47
Add expression tests for arithmetic ops.
kevinhartman Feb 15, 2025
24f5125
Implement stretch support for circuit.
kevinhartman Feb 16, 2025
9e775e1
Initial support for writing QASM3.
kevinhartman Feb 16, 2025
387ac95
Support duration and stretch expressions in Delay.
kevinhartman Feb 17, 2025
25508bf
Reject const vars in add_var and add_input.
kevinhartman Feb 17, 2025
2c8ce43
Merge branch 'main' of github.com:Qiskit/qiskit into const-expr
kevinhartman Feb 17, 2025
ccf9441
Implement QPY support for const-typed expressions.
kevinhartman Feb 18, 2025
c6eab02
Remove invalid test.
kevinhartman Feb 18, 2025
edd7806
Update QPY version 14 desc.
kevinhartman Feb 18, 2025
8afa92e
Fix lint.
kevinhartman Feb 18, 2025
4e0f2df
Add serialization testing.
kevinhartman Feb 18, 2025
15ba943
Merge branch 'main' of github.com:Qiskit/qiskit into const-expr
kevinhartman Feb 18, 2025
81c5833
Merge branch 'const-expr' into expr-float
kevinhartman Feb 18, 2025
50c31d3
Test pre-v14 QPY rejects const-typed exprs.
kevinhartman Feb 18, 2025
7e43322
Merge branch 'main' of github.com:Qiskit/qiskit into const-expr
kevinhartman Feb 18, 2025
2449ee6
Merge branch 'const-expr' into expr-float
kevinhartman Feb 18, 2025
e55e189
QASM export for floats.
kevinhartman Feb 18, 2025
1d51022
QPY support for floats.
kevinhartman Feb 18, 2025
eb8f150
Fix lint.
kevinhartman Feb 18, 2025
415f62e
Merge branch 'expr-float' into expr-timing
kevinhartman Feb 18, 2025
d38f3e9
Settle on Duration circuit core type.
kevinhartman Feb 19, 2025
fd66c1d
QPY serialization for durations and stretches.
kevinhartman Feb 19, 2025
e9b6d97
Add QPY testing.
kevinhartman Feb 19, 2025
52da3cc
QASM support for stretch and duration.
kevinhartman Feb 19, 2025
a3be688
Fix lint.
kevinhartman Feb 19, 2025
2ff7e7a
Merge branch 'expr-timing' into math-expr
kevinhartman Feb 19, 2025
6e8080c
Add arithmetic operators to QASM.
kevinhartman Feb 19, 2025
9266166
QPY testing for arithmetic operations.
kevinhartman Feb 19, 2025
e92dcc5
QASM testing for arithmetic operations.
kevinhartman Feb 19, 2025
2d8f1b7
Merge branch 'math-expr' into circuit-stretch
kevinhartman Feb 19, 2025
f5a5457
Update tests for blocked const vars.
kevinhartman Feb 19, 2025
6d7b2af
Only test declare stretch QASM; fix lint.
kevinhartman Feb 19, 2025
6ad69d4
Special case for stretch in add_uninitialized_var.
kevinhartman Feb 19, 2025
af71e3a
Remove outdated docstring comment.
kevinhartman Feb 19, 2025
bddbc66
Remove outdated comment.
kevinhartman Feb 19, 2025
ca2027f
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Feb 19, 2025
2ed26ef
Support Delay duration expression in transpiler.
kevinhartman Feb 20, 2025
bf9ba73
Improve docstring for Delay.
kevinhartman Feb 20, 2025
0beed94
Error when stretch is used in a circuit during scheduling.
kevinhartman Feb 20, 2025
07771f1
Merge branch 'main' of github.com:Qiskit/qiskit into const-expr
kevinhartman Feb 20, 2025
9332ed3
Merge branch 'const-expr' into expr-float
kevinhartman Feb 20, 2025
17241c6
Merge branch 'expr-float' into expr-timing
kevinhartman Feb 20, 2025
ca91bfd
Don't use match since we still support Python 3.9.
kevinhartman Feb 20, 2025
368b0ac
Merge branch 'expr-timing' into math-expr
kevinhartman Feb 20, 2025
7fcb098
Merge branch 'math-expr' into circuit-stretch
kevinhartman Feb 20, 2025
723e4f0
Block const stores.
kevinhartman Feb 20, 2025
803fa8f
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Feb 20, 2025
ad87e78
Fix enum match.
kevinhartman Feb 20, 2025
9afd68c
Merge branch 'expr-timing' into math-expr
kevinhartman Feb 20, 2025
86ac649
Merge branch 'math-expr' into circuit-stretch
kevinhartman Feb 20, 2025
907853e
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Feb 20, 2025
ba48a16
Fix bad merge.
kevinhartman Feb 20, 2025
d1f5d1a
Add a few more serialization tests.
kevinhartman Feb 20, 2025
e450853
Fix lint.
kevinhartman Feb 20, 2025
51dd954
Add additional testing for Duration expression eval.
kevinhartman Feb 21, 2025
58a7811
Fix missing round when converting expr to 'dt'.
kevinhartman Feb 24, 2025
d3bca5f
Merge branch 'main' of github.com:Qiskit/qiskit into const-expr
kevinhartman Feb 24, 2025
50deb93
Revert visitors.py.
kevinhartman Feb 24, 2025
f1dc1a1
Address review comments.
kevinhartman Feb 24, 2025
1f439a0
Merge branch 'main' of github.com:Qiskit/qiskit into const-expr
kevinhartman Feb 25, 2025
166d7b2
Improve type docs.
kevinhartman Feb 26, 2025
10b2c8d
Merge branch 'main' of github.com:Qiskit/qiskit into const-expr
kevinhartman Feb 26, 2025
9d6cf39
Revert QPY, since the old format can support constexprs.
kevinhartman Feb 26, 2025
8021e00
Move const-ness from Type to Expr.
kevinhartman Feb 26, 2025
a15141b
Revert QPY testing, no longer needed.
kevinhartman Feb 26, 2025
ca2785d
Add explicit validation of const expr.
kevinhartman Feb 26, 2025
e902009
Revert stuff I didn't need to touch.
kevinhartman Feb 26, 2025
16475c3
Update release note.
kevinhartman Feb 26, 2025
6b5930d
A few finishing touches.
kevinhartman Feb 27, 2025
be73180
Merge branch 'const-expr-expr' into expr-float-expr
kevinhartman Feb 27, 2025
25f1693
Fix-up after merge.
kevinhartman Feb 27, 2025
ecc343b
Merge branch 'expr-float-expr' into expr-timing-expr
kevinhartman Feb 27, 2025
b32e6f0
Fix-ups after merge.
kevinhartman Feb 27, 2025
864506d
Fix lint.
kevinhartman Feb 27, 2025
6bc728a
Fix comment and release note.
kevinhartman Feb 27, 2025
2d38178
Merge branch 'expr-float' into expr-timing
kevinhartman Feb 27, 2025
84f5a66
Merge branch 'expr-timing' into math-expr
kevinhartman Feb 27, 2025
91b4c75
Fixes after merge.
kevinhartman Feb 27, 2025
e1dbd1d
Fix test.
kevinhartman Feb 28, 2025
1ad4388
Fix lint.
kevinhartman Feb 28, 2025
42258b8
Merge branch 'main' of github.com:Qiskit/qiskit into expr-float
kevinhartman Feb 28, 2025
481700a
Merge branch 'expr-float' into expr-timing
kevinhartman Feb 28, 2025
e09762f
Special-case Var const-ness for Stretch type.
kevinhartman Feb 28, 2025
cbd55db
Address review comments.
kevinhartman Feb 28, 2025
f12afb4
Update release note.
kevinhartman Feb 28, 2025
8d059cd
Merge branch 'expr-float' into expr-timing
kevinhartman Feb 28, 2025
e05d9ff
Update docs.
kevinhartman Feb 28, 2025
bd6bf35
Merge branch 'expr-timing' into math-expr
kevinhartman Feb 28, 2025
ef04db2
Add release notes and doc link.
kevinhartman Feb 28, 2025
0f14cca
Address review comments.
kevinhartman Mar 1, 2025
7db8222
Merge branch 'expr-float' into expr-timing
kevinhartman Mar 1, 2025
65f2198
Merge branch 'expr-timing' into math-expr
kevinhartman Mar 1, 2025
34e615b
Merge branch 'main' of github.com:Qiskit/qiskit into expr-timing
kevinhartman Mar 1, 2025
bbe67a6
Remove Stretch type.
kevinhartman Mar 2, 2025
0acc450
Remove a few more mentions of the binned stretch type.
kevinhartman Mar 2, 2025
7fea216
Add docstring for Duration.
kevinhartman Mar 2, 2025
4a317e8
Merge branch 'expr-timing' into math-expr
kevinhartman Mar 2, 2025
f5709ae
Remove Stretch type stuff.
kevinhartman Mar 2, 2025
3ae8e5e
Merge branch 'math-expr' into circuit-stretch
kevinhartman Mar 2, 2025
0ba0126
WIP
kevinhartman Mar 2, 2025
de32a57
Track stretch variables throughout circuits.
kevinhartman Mar 2, 2025
ed024ab
Update QASM exporter.
kevinhartman Mar 2, 2025
eb3cbcd
Fix existing tests and found bugs.
kevinhartman Mar 2, 2025
5634d63
Fix format.
kevinhartman Mar 2, 2025
37dec7a
Simplify structural eq visitor.
kevinhartman Mar 2, 2025
05cbcb1
Add num_identifiers.
kevinhartman Mar 2, 2025
96f4e06
Support QPY.
kevinhartman Mar 2, 2025
3f74a7f
Implement QASM visit for stretch expr.
kevinhartman Mar 3, 2025
7f0b785
Track stretches through DAGCircuit.
kevinhartman Mar 3, 2025
ccb3b88
Add visit_stretch to classical resource map.
kevinhartman Mar 3, 2025
576d194
Fix lint.
kevinhartman Mar 3, 2025
f02c11d
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Mar 3, 2025
0b2b440
Some merge fixes.
kevinhartman Mar 3, 2025
78b2951
Address review comments.
kevinhartman Mar 3, 2025
97bbeba
Remove unused import.
kevinhartman Mar 3, 2025
3a34220
Merge branch 'expr-timing' into math-expr
kevinhartman Mar 3, 2025
888e9bb
Merge branch 'math-expr' into circuit-stretch
kevinhartman Mar 3, 2025
fab8b36
Represent stretch with StretchDeclaration in QASM AST.
kevinhartman Mar 3, 2025
49f2af8
Remove visitor short circuit.
kevinhartman Mar 3, 2025
69b985b
Merge branch 'expr-timing' into math-expr
kevinhartman Mar 3, 2025
3b42c14
Merge branch 'math-expr' into circuit-stretch
kevinhartman Mar 3, 2025
57f36c9
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Mar 3, 2025
f780d9a
Address review comments.
kevinhartman Mar 3, 2025
e7ca9c4
Address review comments.
kevinhartman Mar 3, 2025
d4cef21
Merge branch 'main' of github.com:Qiskit/qiskit into math-expr
kevinhartman Mar 3, 2025
60bcfdb
Support division by Uint.
kevinhartman Mar 3, 2025
2d3706e
Merge branch 'math-expr' into circuit-stretch
kevinhartman Mar 3, 2025
286aff7
Add release note.
kevinhartman Mar 3, 2025
c43d203
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Mar 3, 2025
d40cb32
Fix lint.
kevinhartman Mar 3, 2025
dfc02f3
Merge branch 'math-expr' into circuit-stretch
kevinhartman Mar 3, 2025
52f5cdc
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Mar 3, 2025
3fe2993
Add test for QASM example.
kevinhartman Mar 3, 2025
a13dc49
Add missing DAG stretch plumbing.
kevinhartman Mar 4, 2025
0aa39e2
Fix test.
kevinhartman Mar 4, 2025
3b033ad
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Mar 4, 2025
6d64f7f
Fix QPY serialization bug.
kevinhartman Mar 4, 2025
099f257
Fix lint.
kevinhartman Mar 4, 2025
f1e2557
Add qpy test.
kevinhartman Mar 4, 2025
abea250
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Mar 4, 2025
dc972d4
Support remapping for stretch variables in compose.
kevinhartman Mar 4, 2025
ad54644
Merge branch 'main' of github.com:Qiskit/qiskit into math-expr
kevinhartman Mar 4, 2025
723445f
Merge branch 'math-expr' into circuit-stretch
kevinhartman Mar 4, 2025
9308002
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Mar 4, 2025
ae08fae
Fix qpy compat test.
kevinhartman Mar 4, 2025
320b72d
Add negative test for QPY stretch expr.
kevinhartman Mar 4, 2025
c66842b
Merge branch 'main' of github.com:Qiskit/qiskit into circuit-stretch
kevinhartman Mar 5, 2025
827e619
Add more circuit testing.
kevinhartman Mar 5, 2025
1fd92d9
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Mar 5, 2025
26aab3b
Fix tests after merge.
kevinhartman Mar 5, 2025
bc1ee2f
Add control flow builder tests.
kevinhartman Mar 5, 2025
4d7129e
Add circuit equality testing for stretch.
kevinhartman Mar 5, 2025
9d82809
Refer to 'stretches' instead of 'stretch variables'.
kevinhartman Mar 5, 2025
e84b48e
Remove | None for Stretch.name
kevinhartman Mar 5, 2025
62cf3ee
Address review comments.
kevinhartman Mar 6, 2025
e2044e6
Merge branch 'main' of github.com:Qiskit/qiskit into circuit-stretch
kevinhartman Mar 6, 2025
7634b3e
Update QPY desc.
kevinhartman Mar 6, 2025
2223d12
Fix num_identifiers.
kevinhartman Mar 6, 2025
823dd27
Merge branch 'main' of github.com:Qiskit/qiskit into circuit-stretch
kevinhartman Mar 6, 2025
a5ccc7b
Fix merge for control flow tests.
kevinhartman Mar 6, 2025
8c2e873
Merge branch 'circuit-stretch' into stretchy-delay
kevinhartman Mar 6, 2025
568be83
Merge branch 'main' of github.com:Qiskit/qiskit into stretchy-delay
kevinhartman Mar 6, 2025
60e6b5c
Address review comments.
kevinhartman Mar 6, 2025
7c15666
Fix docstrings.
kevinhartman Mar 6, 2025
a086581
Use Union instead of | for QC delay.
kevinhartman Mar 6, 2025
a5a5842
Update tests for deprecation (sorry!)
kevinhartman Mar 6, 2025
0e0510e
Merge branch 'main'of github.com:Qiskit/qiskit into stretchy-delay
kevinhartman Mar 6, 2025
ce4d90f
Trigger CI.
kevinhartman Mar 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion crates/circuit/src/operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,13 +297,14 @@ pub enum DelayUnit {
MS,
S,
DT,
EXPR,
}

unsafe impl ::bytemuck::CheckedBitPattern for DelayUnit {
type Bits = u8;

fn is_valid_bit_pattern(bits: &Self::Bits) -> bool {
*bits < 6
*bits < 7
}
}
unsafe impl ::bytemuck::NoUninit for DelayUnit {}
Expand All @@ -320,6 +321,7 @@ impl fmt::Display for DelayUnit {
DelayUnit::MS => "ms",
DelayUnit::S => "s",
DelayUnit::DT => "dt",
DelayUnit::EXPR => "expr",
}
)
}
Expand All @@ -335,6 +337,7 @@ impl<'py> FromPyObject<'py> for DelayUnit {
"ms" => DelayUnit::MS,
"s" => DelayUnit::S,
"dt" => DelayUnit::DT,
"expr" => DelayUnit::EXPR,
unknown_unit => {
return Err(PyValueError::new_err(format!(
"Unit '{}' is invalid.",
Expand Down
4 changes: 4 additions & 0 deletions qiskit/circuit/controlflow/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,11 +540,15 @@ def remove_stretch(self, stretch: expr.Stretch):
self._stretches_local.pop(stretch.name)

def get_var(self, name: str):
if name in self._stretches_local:
return None
if (out := self._vars_local.get(name)) is not None:
return out
return self._parent.get_var(name)

def get_stretch(self, name: str):
if name in self._vars_local:
return None
if (out := self._stretches_local.get(name)) is not None:
return out
return self._parent.get_stretch(name)
Expand Down
41 changes: 36 additions & 5 deletions qiskit/circuit/delay.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
Delay instruction (for circuit module).
"""
import numpy as np

from qiskit.circuit.classical import expr, types
from qiskit.circuit.exceptions import CircuitError
from qiskit.circuit.instruction import Instruction
from qiskit.circuit.gate import Gate
Expand All @@ -28,13 +30,35 @@ class Delay(Instruction):

_standard_instruction_type = StandardInstructionType.Delay

def __init__(self, duration, unit="dt"):
def __init__(self, duration, unit=None):
"""
Args:
duration: the length of time of the duration. Given in units of ``unit``.
unit: the unit of the duration. Must be ``"dt"`` or an SI-prefixed seconds unit.
duration: the length of time of the duration. If this is an
:class:`~.expr.Expr`, it must be a constant expression of type
:class:`~.types.Duration` and the ``unit`` parameter should be
omitted (or MUST be "expr" if it is specified).
unit: the unit of the duration, if ``duration`` is a numeric
value. Must be ``"dt"``, an SI-prefixed seconds unit, or "expr".

Raises:
CircuitError: A ``duration`` expression was specified with a resolved
type that is not timing-based, or the ``unit`` was improperly specified.
"""
if unit not in {"s", "ms", "us", "ns", "ps", "dt"}:
if isinstance(duration, expr.Expr):
if unit is not None and unit != "expr":
raise CircuitError(
"Argument 'unit' must not be specified for a duration expression."
)
if duration.type.kind is not types.Duration:
raise CircuitError(
f"Expression of type '{duration.type}' is not valid for 'duration'."
)
if not duration.const:
raise CircuitError("Duration expressions must be constant.")
unit = "expr"
elif unit is None:
unit = "dt"
elif unit not in {"s", "ms", "us", "ns", "ps", "dt"}:
raise CircuitError(f"Unknown unit {unit} is specified.")
# Double underscore to differentiate from the private attribute in
# `Instruction`. This can be changed to `_unit` in 2.0 after we
Expand Down Expand Up @@ -88,8 +112,9 @@ def __repr__(self):
"""Return the official string representing the delay."""
return f"{self.__class__.__name__}(duration={self.params[0]}[unit={self.unit}])"

# pylint: disable=too-many-return-statements
def validate_parameter(self, parameter):
"""Delay parameter (i.e. duration) must be int, float or ParameterExpression."""
"""Delay parameter (i.e. duration) must be Expr, int, float or ParameterExpression."""
if isinstance(parameter, int):
if parameter < 0:
raise CircuitError(
Expand All @@ -107,6 +132,12 @@ def validate_parameter(self, parameter):
raise CircuitError("Integer duration is expected for 'dt' unit.")
return parameter_int
return parameter
elif isinstance(parameter, expr.Expr):
if parameter.type.kind is not types.Duration:
raise CircuitError(f"Expression duration of type '{parameter.type}' is not valid.")
if not parameter.const:
raise CircuitError("Duration expressions must be constant.")
return parameter
elif isinstance(parameter, ParameterExpression):
Copy link
Copy Markdown
Member

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)

if len(parameter.parameters) > 0:
return parameter # expression has free parameters, we cannot validate it
Expand Down
11 changes: 7 additions & 4 deletions qiskit/circuit/quantumcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4555,17 +4555,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):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW you can just drop (Object) here it's all superseded by the type hinting in the docs.

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.

Expand Down
2 changes: 2 additions & 0 deletions qiskit/qasm3/exporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,8 @@ def build_duration(self, duration, unit) -> ast.Expression | None:
"""Build the expression of a given duration (if not ``None``)."""
if duration is None:
return None
if unit == "expr":
return self.build_expression(duration)
if unit == "ps":
return ast.DurationLiteral(1000 * duration, ast.DurationUnit.NANOSECOND)
unit_map = {
Expand Down
3 changes: 3 additions & 0 deletions qiskit/transpiler/passes/scheduling/padding/base_padding.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ def _pre_runhook(self, dag: DAGCircuit):
f"The input circuit {dag.name} is not scheduled. Call one of scheduling passes "
f"before running the {self.__class__.__name__} pass."
)
if self.property_set["time_unit"] == "stretch":
# This should have already been raised during scheduling, but just in case.
raise TranspilerError("Scheduling cannot run on circuits with stretch durations.")
for qarg, _ in enumerate(dag.qubits):
if not self.__delay_supported(qarg):
logger.debug(
Expand Down
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/scheduling/scheduling/alap.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def run(self, dag):
"""
if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None:
raise TranspilerError("ALAP schedule runs on physical circuits only")
if self.property_set["time_unit"] == "stretch":
raise TranspilerError("Scheduling cannot run on circuits with stretch durations.")

clbit_write_latency = self.property_set.get("clbit_write_latency", 0)

Expand Down
2 changes: 2 additions & 0 deletions qiskit/transpiler/passes/scheduling/scheduling/asap.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ def run(self, dag):
"""
if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None:
raise TranspilerError("ASAP schedule runs on physical circuits only")
if self.property_set["time_unit"] == "stretch":
raise TranspilerError("Scheduling cannot run on circuits with stretch durations.")

clbit_write_latency = self.property_set.get("clbit_write_latency", 0)

Expand Down
144 changes: 120 additions & 24 deletions qiskit/transpiler/passes/scheduling/time_unit_conversion.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the scheduling passes look at this and raise if it's "stretch". I think some of the other uses of time_unit in the property set have gone away since I started this work, but I think we still need something like this to let the scheduling passes know they shouldn't run, unless we want to examine all of the delays again.

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!"
)
Comment thread
kevinhartman marked this conversation as resolved.
expression_durations[node._node_id] = duration
Comment thread
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:
Expand All @@ -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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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:
Comment thread
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)
Loading