Skip to content

Conversation

phalakbh
Copy link
Contributor

@phalakbh phalakbh commented Jul 7, 2025

Summary

This PR ports the InstructionDurationCheck analysis pass from Python to Rust for improved performance and integration with the Qiskit transpiler. No changes to pass logic or user-facing behavior are introduced.

Details and comments

@phalakbh phalakbh requested a review from a team as a code owner July 7, 2025 17:54
@qiskit-bot qiskit-bot added the Community PR PRs from contributors that are not 'members' of the Qiskit repo label Jul 7, 2025
@qiskit-bot
Copy link
Collaborator

Thank you for opening a new pull request.

Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient.

While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone.

One or more of the following people are relevant to this code:

  • @Qiskit/terra-core

@raynelfss raynelfss added Changelog: None Do not include in changelog Rust This PR or issue is related to Rust code in the repository Intern PR PR submitted by IBM Quantum interns mod: transpiler Issues and PRs related to Transpiler and removed Community PR PRs from contributors that are not 'members' of the Qiskit repo labels Jul 7, 2025
@raynelfss raynelfss added this to the 2.2.0 milestone Jul 7, 2025
@raynelfss raynelfss self-assigned this Jul 7, 2025
@coveralls
Copy link

coveralls commented Jul 7, 2025

Pull Request Test Coverage Report for Build 16472913654

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 18 of 42 (42.86%) changed or added relevant lines in 4 files are covered.
  • 1482 unchanged lines in 33 files lost coverage.
  • Overall coverage decreased (-0.03%) to 87.752%

Changes Missing Coverage Covered Lines Changed/Added Lines %
crates/transpiler/src/passes/instruction_duration_check.rs 14 38 36.84%
Files with Coverage Reduction New Missed Lines %
crates/synthesis/src/linear_phase/mod.rs 1 90.91%
crates/circuit/src/converters.rs 2 97.33%
crates/synthesis/src/multi_controlled/mcmt.rs 2 89.19%
crates/transpiler/src/passes/basis_translator/compose_transforms.rs 2 96.61%
qiskit/transpiler/passes/optimization/commutative_inverse_cancellation.py 2 97.26%
crates/circuit/src/circuit_instruction.rs 3 88.22%
crates/circuit/src/dag_node.rs 3 70.0%
crates/transpiler/src/passes/disjoint_layout.rs 3 89.04%
qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py 4 94.62%
qiskit/transpiler/passes/routing/sabre_swap.py 5 92.31%
Totals Coverage Status
Change from base Build 16123284064: -0.03%
Covered Lines: 81491
Relevant Lines: 92865

💛 - Coveralls

Copy link
Member

@mtreinish mtreinish left a comment

Choose a reason for hiding this comment

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

Thanks for getting this started, this has the potential to speed up the pass quite a bit. I left a few inline comments on how to improve it a bit, I also think there is a bug in the pass that predates this PR, but we should fix it while we're porting this, it should be simple to do.


//Check delay durations
for (_, packed_op) in dag.op_nodes(false) {
if packed_op.op.name() == "delay" {
Copy link
Member

Choose a reason for hiding this comment

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

It would be more efficient to do this check using the rust types directly rather than a string comparison. This should be something like:

Suggested change
if packed_op.op.name() == "delay" {
if let OperationRef::StandardInstruction(StandardInstruction::Delay(_unit)) = packed_op.op.view() {

I haven't tested this code exactly so it's worth validating the exact syntax locally. But this is the basic idea, is use the rust type instead of the string, as this ends up be 2 integer comparisons vs a string comparison and will be faster, especially since this is being run for ever node in the dag.

for (_, packed_op) in dag.op_nodes(false) {
if packed_op.op.name() == "delay" {
let params = packed_op.params_view();
if let Some(param) = params.first() {
Copy link
Member

Choose a reason for hiding this comment

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

Can this ever be None? If not I think we should probably error because the DAGCircuit is invalid.

_ => continue,
};

if !(duration % acquire_align == 0 || duration % pulse_align == 0) {
Copy link
Member

Choose a reason for hiding this comment

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

FWIW % in rust isn't the same as % in python. In this case I don't think it matters but whenever I see this I always want to double check whether % is correct or whether I should be using rem_euclid instead. It's probably worth double checking.

Copy link
Contributor Author

@phalakbh phalakbh Jul 8, 2025

Choose a reason for hiding this comment

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

Thanks for letting me know. I did check, both acquire_align and pulse_align seem to accept negative integer values (though I don't know if negative alignment values make sense here) so it is probably best to use rem_euclid instead of %.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Update: Unlike what I said earlier, I think we can stick to using u32 types for the alignments and the % operator for alignment checks. While Python technically allows passing negative values for acquire_align and pulse_align, it won't be meaningful. Target's implementation too always returns unsigned integers for both acquire_align and pulse_align, so using u32 in Rust will continue to enforce this constraint.

Comment on lines 49 to 52
let duration = match param {
Param::Float(val) => *val as u32,
_ => continue,
};
Copy link
Member

Choose a reason for hiding this comment

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

Is this correct? I would expect Param::Obj when the unit is dt because we're storing an integer. This will skip that case and we won't validate the alignment.

Also, shouldn't we be validating the unit here, like if the delay is in units of ns the alignment in terms of dt being checked below doesn't work. I realize you faithfully ported this from Python, but it doesn't seem like the python logic was correct. If the unit is not in terms of dt we can't check the alignment and this pass won't be able to handle that delay.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see, I will make the changes regarding Param::Obj. Just to clarify, should we skip on checking the alignment if the delay unit is anything other than dt, or should we convert the unit from seconds to dt first and then check the alignment?

Copy link
Member

Choose a reason for hiding this comment

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

This pass doesn't do unit conversion. There is a TimeUnitConversion transpiler pass that runs which is supposed to handle that: https://quantum.cloud.ibm.com/docs/en/api/qiskit/1.2/qiskit.transpiler.passes.TimeUnitConversion if it's not in units of dt either skipping or erroring is a appropriate. I might lean toward returning an error because the assumptions this pass works under aren't valid. But there might be backwards compatibility concerns with that, it's worth figuring out how the current python implementation behaves and work against that.

Copy link
Contributor

@raynelfss raynelfss left a comment

Choose a reason for hiding this comment

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

Good progress! I just have one comment (the rest are nitpicks)

) -> PyResult<bool> {
let num_stretches = dag.num_stretches();

//Rescheduling is not necessary
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor nitpick: Add a whitespace after the // in the comment.

Suggested change
//Rescheduling is not necessary
// Rescheduling is not necessary

return Ok(false);
}

//Check delay durations
Copy link
Contributor

Choose a reason for hiding this comment

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

See https://github.com/Qiskit/qiskit/pull/14700/files#r2193260197

Suggested change
//Check delay durations
// Check delay durations

Comment on lines 50 to 51
Param::Float(val) => *val as u32,
_ => continue,
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this the correct way of handling this? IIRC a duration can either be a number or an Expr. So we know that brings the question of whether we can keep this rust native? I know Expr already exists in Rust but we don't yet use it within instructions.

@phalakbh
Copy link
Contributor Author

Thank you @mtreinish and @raynelfss for your comments! I have made the suggested changes. I think it is ready for another review, please let me know if there is anything else that I could improve upon.

@phalakbh phalakbh requested review from mtreinish and raynelfss July 11, 2025 19:51
Copy link
Contributor

@raynelfss raynelfss left a comment

Choose a reason for hiding this comment

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

Most of it looks good but I have some concerns about the dt check.

return Err(TranspilerError::new_err(
"Delay duration must have dt unit for checking alignment.",
));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this correct? I know Matthew mentioned that throwing an error here might be the best option given how the pass works. Since the error didn't exist in Python, does it seem to break any of the existing pipelines? (Judging from the CI runs everything seems to run as expected, but just out of curiosity).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So far it doesn't break any of the pipelines. The TimeUnitConversion pass takes care of unifying the units before sending it to this pass. Since the alignment constraints are only meaningful when defined in dt and checking the alignment would only make sense if the unit of durations is also dt. If we are getting unit as seconds, I believe it is safe to return the error.

Comment on lines 62 to 66
let duration = match param {
Param::Float(val) => Some(*val as u32),
Param::Obj(val) | Param::ParameterExpression(val) => {
val.bind(py).extract::<u32>().ok()
}
Copy link
Contributor

Choose a reason for hiding this comment

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

There are two things you can do about this match arm here depending on what you do about the comment above:

  • If what was mentioned above is true, and we don't operate on anything that's not on terms of dt, we should drop the Float and ParameterExpression arms since dt will always use int as a type.
  • If we will be removing the DT check and will continue either way instead, we should drop only the ParameterExpression arm.
    If any of the dropped cases is found, either erroring or skipping should be expected. I'm leaning more towards erroring so the user knows something is wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in f364f35!

Copy link
Contributor

@raynelfss raynelfss left a comment

Choose a reason for hiding this comment

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

This is about ready to merge. Just one comment about the new error message.

let duration = match param {
Param::Obj(val) => val.bind(py).extract::<u32>(),
_ => Err(TranspilerError::new_err(
"Delay instruction parameter is not an object.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
"Delay instruction parameter is not an object.",
"The provided Delay duration is not in terms of dt.",

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done!

@raynelfss raynelfss added this pull request to the merge queue Jul 23, 2025
Merged via the queue into Qiskit:main with commit 2877bf0 Jul 23, 2025
26 checks passed
raynelfss pushed a commit to raynelfss/qiskit that referenced this pull request Aug 1, 2025
* Add rust-native instruction duration check in transpiler

* Make num_stretches method public in DAGCircuit implementation

* Improve error handling and parameter validation

* Enforce dt unit for delay durations and update alignment parameters to accept unsigned integers

* Modify match arm to return error when instruction paramter is not an object

* Correct the error message for when duration is not in dt
raynelfss pushed a commit to raynelfss/qiskit that referenced this pull request Aug 11, 2025
* Add rust-native instruction duration check in transpiler

* Make num_stretches method public in DAGCircuit implementation

* Improve error handling and parameter validation

* Enforce dt unit for delay durations and update alignment parameters to accept unsigned integers

* Modify match arm to return error when instruction paramter is not an object

* Correct the error message for when duration is not in dt
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: None Do not include in changelog Intern PR PR submitted by IBM Quantum interns mod: transpiler Issues and PRs related to Transpiler Rust This PR or issue is related to Rust code in the repository
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Port InstructionDurationCheck to Rust
5 participants