Skip to content
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

New transpiler techniques (gate optmization): U3GateFusion, RotationGateFusion and InverseCancellation #1606

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

carlos-luque
Copy link
Contributor

@carlos-luque carlos-luque commented Mar 14, 2025

We have worked on issue #1588 and added new techniques in transpiler optimization:

  • U3GateFusion: Merges pairs of U3 gates.
  • RotationGateFusion: Merges pairs of rotation gates along the same axis.
  • InverseCancellation: Eliminates pairs of adjacent gates (gates.H, gates.Y, gates.Z, gates.X, gates.CNOT, gates.CZ, gates.SWAP).

Checklist:

  • Reviewers confirm new code works as expected.
  • Tests are passing.
  • Coverage does not decrease.
  • Documentation is updated.

@alecandido alecandido requested a review from renatomello March 14, 2025 15:38
@renatomello renatomello added enhancement New feature or request transpiler labels Mar 15, 2025
@renatomello renatomello linked an issue Mar 15, 2025 that may be closed by this pull request
@renatomello renatomello added this to the Qibo 0.2.17 milestone Mar 15, 2025
@renatomello
Copy link
Contributor

@alecandido is there a way for external PRs to trigger the CI and run the tests?

Comment on lines +206 to +207
if not gate.qubits:
continue
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this necessary?

Comment on lines +109 to +117
def update_previous_gates(qubits, idx, gate):
"""Helper function to update previous gate tracking."""
for q in qubits:
previous_gates[q] = (idx, gate)

def clear_previous_gates(qubits):
"""Helper to clear tracking for given qubits."""
for q in qubits:
previous_gates[q] = None
Copy link
Contributor

@renatomello renatomello Mar 17, 2025

Choose a reason for hiding this comment

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

I advise to not define functions inside other functions unless it is strictly necessary

Comment on lines +121 to +122
if not gate.init_args:
continue
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, is this necessary? Why would a gate in the queue of a circuit not have init_args defined?

Comment on lines +98 to +105
def __find_pos_rm(self, newCircuit: Circuit):
"""
Identifies and marks pairs of inverse gates that can be removed from the circuit.

Args:
newCircuit: The Circuit.

"""
Copy link
Contributor

@renatomello renatomello Mar 17, 2025

Choose a reason for hiding this comment

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

The python convention is that names like newCircuit are reserved for classes, while simple variables should be all lowercase and separated by _, e.g. new_circuit.
Moreover, docstrings should be more descriptive and also follow the same typing patterns as the rest of qibo. Example below.

Suggested change
def __find_pos_rm(self, newCircuit: Circuit):
"""
Identifies and marks pairs of inverse gates that can be removed from the circuit.
Args:
newCircuit: The Circuit.
"""
def __find_pos_rm(self, new_circuit: Circuit):
"""Identifies and marks pairs of inverse gates that can be removed from the circuit.
Args:
new_circuit (:class:`qibo.models.circuit.Circuit`): Circuit to have gates identified.
"""

)
self.pos_rm_inv_gate = []

def __call__(self, circuit: Circuit) -> Circuit:
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing docstring

Comment on lines +132 to +140
secondary_match = (
all(
previous_gates[q] is not None
and self.__sameGates(gate, previous_gates[q][1])
for q in other_qubits
)
if other_qubits
else True
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you break this into multiple lines to improve readability? Thanks

Comment on lines +126 to +129
if previous_gates[primary_qubit] is None:
update_previous_gates(gate.init_args, i, gate)
else:
if isinstance(gate, self.inverse_gates):
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
if previous_gates[primary_qubit] is None:
update_previous_gates(gate.init_args, i, gate)
else:
if isinstance(gate, self.inverse_gates):
if previous_gates[primary_qubit] is None:
update_previous_gates(gate.init_args, i, gate)
elif isinstance(gate, self.inverse_gates):

Comment on lines +149 to +150
else:
update_previous_gates(gate.init_args, i, gate)
Copy link
Contributor

@renatomello renatomello Mar 17, 2025

Choose a reason for hiding this comment

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

Suggested change
else:
update_previous_gates(gate.init_args, i, gate)
else:
update_previous_gates(gate.init_args, i, gate)

Comment on lines +167 to +171
return (gate1.name, gate1.init_args, gate1.init_kwargs) == (
gate2.name,
gate2.init_args,
gate2.init_kwargs,
)
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
return (gate1.name, gate1.init_args, gate1.init_kwargs) == (
gate2.name,
gate2.init_args,
gate2.init_kwargs,
)
tuple_1 = (gate1.name, gate1.init_args, gate1.init_kwargs)
tuple_2 = (gate2.name, gate2.init_args, gate2.init_kwargs)
return bool(tuple_1 == tuple_2)

self.rotated_gates = gates.RX | gates.RY | gates.RZ
self.gates = []

def __call__(self, circuit: Circuit) -> Circuit:
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing docstring

Comment on lines +213 to +214
if isinstance(prev_gate, type(gate)):
tmp_gate = type(gate)(
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
if isinstance(prev_gate, type(gate)):
tmp_gate = type(gate)(
if isinstance(prev_gate, gate.__class__):
tmp_gate = gate.__class__(

self.gates.extend(g for g in previous_gates if isinstance(g, gates.U3))

@staticmethod
def __extract_u3_params(U):
Copy link
Contributor

Choose a reason for hiding this comment

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

It is recommended that variable names should be at least 3 letters. Ideally, more descriptive.

Comment on lines +72 to +81
class InverseCancellation(Optimizer):
"""
Identifies and removes pairs of adjacent gates from a quantum circuit.
"""

def __init__(self):
self.inverse_gates = (
gates.H | gates.Y | gates.Z | gates.X | gates.CNOT | gates.CZ | gates.SWAP
)
self.pos_rm_inv_gate = []
Copy link
Contributor

Choose a reason for hiding this comment

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

Move the class docstrings to the __init__ method and add any Args when necessary. This and the other classes.

@alecandido alecandido added run-workflow Forces github workflow execution and removed run-on-sim labels Mar 17, 2025
@alecandido
Copy link
Member

@alecandido is there a way for external PRs to trigger the CI and run the tests?

Yes, there is: it is similar to what you imagined. It is not the run-on-sim label, but rather the run-workflow.

Copy link

codecov bot commented Mar 17, 2025

Codecov Report

Attention: Patch coverage is 91.26984% with 11 lines in your changes missing coverage. Please review.

Project coverage is 98.97%. Comparing base (5c93898) to head (fa6c783).
Report is 145 commits behind head on master.

Files with missing lines Patch % Lines
src/qibo/transpiler/optimizer.py 91.20% 11 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1606      +/-   ##
==========================================
- Coverage   99.58%   98.97%   -0.61%     
==========================================
  Files          76       76              
  Lines       11322    11447     +125     
==========================================
+ Hits        11275    11330      +55     
- Misses         47      117      +70     
Flag Coverage Δ
unittests 98.97% <91.26%> (-0.61%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@carlos-luque
Copy link
Contributor Author

Thanks so much for your reviews, @renatomello. I am working on the code to add them

@renatomello renatomello modified the milestones: Qibo 0.2.17, Qibo 0.2.18 Mar 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request run-workflow Forces github workflow execution transpiler
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add some gate optimization techniques
3 participants