Skip to content

Feature/Implement Qubit Register Consolidation #222

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

vinayswamik
Copy link
Collaborator

Summary of changes

Implement device qubit(__PYQASM_QUBITS__) support in QasmVisitor and QasmModule

  • Added a new parameter device_qubits to QasmVisitor and QasmModule to manage qubit consolidation.
  • Introduced _get_pyqasm_device_qubit_index method for qubit index mapping and _qubit_register_consolidation to ensure compatibility with device constraints.
  • Updated measurement, barrier, and gate operations to utilize the new device qubit logic.
  • Added unit tests to validate the functionality of qubit consolidation and error handling for exceeding device qubit limits.
    enhance a little bit wihtout changing anuthing

Closes #187

- Added a new parameter `device_qubits` to `QasmVisitor` and `QasmModule` to manage qubit consolidation.
- Introduced methods for qubit index mapping and register consolidation to ensure compatibility with device constraints.
- Updated measurement, barrier, and gate operations to utilize the new device qubit logic.
- Added unit tests to validate the functionality of qubit consolidation and error handling for exceeding device qubit limits.
@codecov-commenter
Copy link

codecov-commenter commented Jun 27, 2025

Codecov Report

Attention: Patch coverage is 98.96907% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/pyqasm/visitor.py 98.93% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@@ -57,6 +57,7 @@ def __init__(self, name: str, program: Program):
self._unrolled_ast = Program(statements=[])
self._external_gates: list[str] = []
self._decompose_native_gates: Optional[bool] = None
self._device_qubits: Optional[int] = 0
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
self._device_qubits: Optional[int] = 0
self._device_qubits: Optional[int] = None

Copy link
Member

@TheGupta2012 TheGupta2012 Jun 30, 2025

Choose a reason for hiding this comment

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

Also, let's make this a kwarg of the load(s) method, eg. -

module = pyqasm.loads(qasm, device_qubits = 23)

The idea is that a module will be defined using a specific number of device qubits but the users can "choose to consolidate" the qasm according to the device specification. Then, the corresponding argument in unroll becomes consolidate_qubits or equivalent.

NOTE: this will also help us implicitly validate the total qubit count according to the total num of device qubits. We can add this check inside the validate functionality

Copy link
Member

@TheGupta2012 TheGupta2012 left a comment

Choose a reason for hiding this comment

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

Hi @vinayswamik , the changes look good!

A point to consider for the design -

Consolidation of qubits inside the qasm operations should be done as a "finalize" step. Presently, you are updating the qubit identifiers directly during the unroll / validate phase and subsequently adding the __PYQASM_QUBITS__ declaration. While it works for the test cases, this transformation logic should be separated in a new step.

A cleaner approach would be to refactor the transformation of statements (which you have done inside each of reset, barrier, etc.) as part of the _qubit_register_consolidation method. It should read the unrolled statements and transform the qubits for each of them, possibly utilizing the Qasm3Transformer module. I only see self._get_pyqasm_device_qubit_index being utilized in your changes and can be decoupled from the visitor code.

@@ -86,6 +86,7 @@ def __init__( # pylint: disable=too-many-arguments
external_gates: list[str] | None = None,
unroll_barriers: bool = True,
max_loop_iters: int = int(1e9),
device_qubits: int = 0,
Copy link
Member

Choose a reason for hiding this comment

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

Derive from module, use something like a _consolidate_qubits flag to check and perform the consolidation

"""
_offsets: dict[str, int] = {}
_offset = 0
for name, n_qubits in self._global_qreg_size_map.items():
Copy link
Member

Choose a reason for hiding this comment

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

This should be done once as the offsets will not change after all the declarations are registered with the module.
I believe we should update the offsets whenever a new qubit declaration is encountered in the module, and save it as an attribute for future use.

f"Total qubits '({total_qubits})' exceed device qubits '({self._device_qubits})'."
)

if "__PYQASM_QUBITS__" in self._global_qreg_size_map:
Copy link
Member

Choose a reason for hiding this comment

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

also check the variable scope, no variables should be declared with this name

for stmt in unrolled_stmts:
if isinstance(stmt, qasm3_ast.QubitDeclaration):
removable_statements.append(stmt)
for r_stmt in removable_statements:
Copy link
Member

Choose a reason for hiding this comment

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

It is better to construct a new list of valid_statements which do not contain the original QubitDeclarations. You can just append any statement which is not of type QubitDeclaration.

The reason is that .remove(...) method works in O(n) time, n being the size of our list. For a large number of qubit declarations, say k, this will scale as O(n*k).

pyqasm_reg_id = qasm3_ast.Identifier("__PYQASM_QUBITS__")
pyqasm_reg_size = qasm3_ast.IntegerLiteral(self._device_qubits)
pyqasm_reg_stmt = qasm3_ast.QubitDeclaration(pyqasm_reg_id, pyqasm_reg_size)
unrolled_stmts.insert(1, pyqasm_reg_stmt)
Copy link
Member

Choose a reason for hiding this comment

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

How did you derive 1 to be the insert position?

Copy link
Collaborator Author

@vinayswamik vinayswamik Jun 30, 2025

Choose a reason for hiding this comment

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

for any valid qasm3 code first stmt is include(stdgates) in unrolled statements. but there might be other include stmts in future. one better way would be below approach, which costs only O(n).

pyqasm_reg_id = qasm3_ast.Identifier("__PYQASM_QUBITS__")
pyqasm_reg_size = qasm3_ast.IntegerLiteral(self._device_qubits)
pyqasm_reg_stmt = qasm3_ast.QubitDeclaration(pyqasm_reg_id, pyqasm_reg_size)

 valid_statements = []
_inserted_pyqasm_reg = False
for stmt in unrolled_stmts:
      if isinstance(stmt, qasm3_ast.QubitDeclaration):
          continue
      if not _inserted_pyqasm_reg and not isinstance(stmt, qasm3_ast.Include):
          valid_statements.append(pyqasm_reg_stmt)
          _inserted_pyqasm_reg = True
      valid_statements.append(stmt)

Copy link
Member

@TheGupta2012 TheGupta2012 Jul 1, 2025

Choose a reason for hiding this comment

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

On second thoughts, I believe declaring the __PYQASM_QUBITS__ as the first statement and ignoring all subsequent QubitDeclarations would be the simplest way forward.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

sounds good.. 👍.

@vinayswamik
Copy link
Collaborator Author

Hi @vinayswamik , the changes look good!

A point to consider for the design -

Consolidation of qubits inside the qasm operations should be done as a "finalize" step. Presently, you are updating the qubit identifiers directly during the unroll / validate phase and subsequently adding the __PYQASM_QUBITS__ declaration. While it works for the test cases, this transformation logic should be separated in a new step.

A cleaner approach would be to refactor the transformation of statements (which you have done inside each of reset, barrier, etc.) as part of the _qubit_register_consolidation method. It should read the unrolled statements and transform the qubits for each of them, possibly utilizing the Qasm3Transformer module. I only see self._get_pyqasm_device_qubit_index being utilized in your changes and can be decoupled from the visitor code.

Yeah,I thought about doing all the changes in finalize. But, the reason why i did changes inside the visit operations is, stmts like branching statements are still exists as branching statements even after unrolling.
If we want to do changes inside them, we might need to go again into _visit_branching_statement and get all possible statements that require changes. which costs additional code inside _visit_branching_statementand outside as well.

My suggestion would be move all the current code in (reset, barrier, measurement, gate) to a new function in Qasm3Transformer. then invoke that only when consolidate_qubits == True in visit operations.

And also we can do consolidation with/without providing device_qubits. Here we consider device_qubits = total qubits in the code.

@TheGupta2012
Copy link
Member

TheGupta2012 commented Jul 1, 2025

Yeah,I thought about doing all the changes in finalize. But, the reason why i did changes inside the visit operations is, stmts like branching statements are still exists as branching statements even after unrolling. If we want to do changes inside them, we might need to go again into _visit_branching_statement and get all possible statements that require changes. which costs additional code inside _visit_branching_statementand outside as well.

My suggestion would be move all the current code in (reset, barrier, measurement, gate) to a new function in Qasm3Transformer. then invoke that only when consolidate_qubits == True in visit operations.

And also we can do consolidation with/without providing device_qubits. Here we consider device_qubits = total qubits in the code.

Yea, that's a good point, the branches might exist in the code even after unroll. As you said, let's refactor the current code to the transformer and use the flag consolidate_qubits to trigger it.

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.

[FEATURE] Qubit Register Consolidation
3 participants