Skip to content

Conversation

@debasmita2102
Copy link
Contributor

  • Implement a new UnrollBoxes transpiler pass to replace BoxOp “scaffolding” with the gates from the boxed circuit body.

  • Support nested boxes via @trivial_recurse, so boxes inside boxes are unrolled correctly.

  • Add an annotation-aware API (known_annotations: Callable[[dict], bool]) so callers can control which annotations are safe to ignore during unrolling.

  • Fixes Transpiler pass to replace boxes by their contents? #14468

@debasmita2102 debasmita2102 requested a review from a team as a code owner December 1, 2025 10:05
@qiskit-bot
Copy link
Collaborator

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

  • @Qiskit/terra-core

@coveralls
Copy link

coveralls commented Dec 1, 2025

Pull Request Test Coverage Report for Build 20022336002

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

  • 11 of 24 (45.83%) changed or added relevant lines in 2 files are covered.
  • 1321 unchanged lines in 26 files lost coverage.
  • Overall coverage decreased (-0.05%) to 88.337%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/transpiler/passes/unroll_boxes.py 10 23 43.48%
Files with Coverage Reduction New Missed Lines %
crates/circuit/src/parameter/symbol_expr.rs 1 73.31%
crates/transpiler/src/passes/unitary_synthesis.rs 1 93.32%
qiskit/circuit/commutation_checker.py 1 94.74%
crates/cext/src/circuit.rs 3 88.39%
qiskit/circuit/controlflow/if_else.py 3 97.33%
qiskit/circuit/library/generalized_gates/pauli.py 3 91.67%
qiskit/primitives/statevector_estimator.py 3 94.23%
crates/qasm2/src/lex.rs 5 91.77%
crates/qasm2/src/parse.rs 6 96.62%
crates/transpiler/src/passes/litinski_transformation.rs 6 96.72%
Totals Coverage Status
Change from base Build 19837335950: -0.05%
Covered Lines: 96127
Relevant Lines: 108819

💛 - Coveralls

@Shobhit21287
Copy link

Hi Debasmita, thanks for doing this! The code is very clean and mostly LGTM. I had a question though.

self.known_annotations = known_annotations or (lambda ann: True)
self.max_depth = max_depth

def _validate_annotations(self, box_op: BoxOp, depth: int = 0) -> bool:

Choose a reason for hiding this comment

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

What's the use of the depth argument exactly? I assume that it's to not unroll BoxOps that exceed a certain depth. Will this work fine for the recursive case with multiple nested Boxes? Also I don't see it being passed in the .run(self,dag) function. Is this supposed to be called by the user as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank @Shobhit21287 . The depth argument was intended for a future extension where UnrollBoxes would stop unrolling once a configurable nesting depth (max_depth) is reached. Right now _validate_annotations is always called without a depth, so the parameter is effectively unused and recursive calls always see depth == 0

Copy link
Contributor Author

Choose a reason for hiding this comment

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

anyways I removed it thanks @Shobhit21287


def __init__(
self,
recursive: bool = True,
Copy link
Contributor

@aaryav-3 aaryav-3 Dec 4, 2025

Choose a reason for hiding this comment

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

Hi, thank you for your contribution. I had a couple of clarifications regarding this PR. There is a recursive flag being used over here, and the value is being set. However, I don't see it being reused anywhere else in code. Has it been set for future use? If yes, would you say it makes more sense to keep this addition in a future PR rather than this one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep that recursive wasn’t used anywhere. It was left over from an earlier prototype, I have removed the recursive and max_depth parameters and now only expose known_annotations; if we decide we need a configurable recursion limit later, I can add that in a follow‑up PR with tests


for ann_dict in getattr(box_op, "annotations", []):
if not self.known_annotations(ann_dict):
return False
Copy link
Contributor

Choose a reason for hiding this comment

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

can we add a test case here to test behavior on passing unknown annotations? To ensure that this callable returns False, but the :class:BoxOp remains in the circuit?

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’ve added test_unknown_annotation_keeps_box, which sets an unknown annotation and a known_annotations predicate that only accepts ‘safe’; the test asserts that the BoxOp is preserved in this case.

class UnrollBoxes(TransformationPass):
"""Unroll BoxOp operations by replacing them with their internal circuit body."""

def __init__(
Copy link
Contributor

Choose a reason for hiding this comment

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

I think there is merit in writing a short doctstring here, defining the input arguments, their types and short descriptions for the :meth:__init__. Since this is a user-facing functionality.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, that makes sense. I havee added a short constructor docstring for UnrollBoxes.init describing the known_annotations argument and how it controls which boxes are safe to unroll.

Copy link
Contributor

@aaryav-3 aaryav-3 left a comment

Choose a reason for hiding this comment

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

Thank you so much for your contribution, this PR is minimally and effectively written to introduce the new transpilar pass, I just had a couple of doubts and clarifications before it is good to go

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Transpiler pass to replace boxes by their contents?

5 participants