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

Optimise QubitPermutationGate decomposition #6588

Conversation

migueltorrescosta
Copy link
Contributor

@migueltorrescosta migueltorrescosta commented May 7, 2024

Solves #5097 as per comment #5097 (comment) .

@migueltorrescosta migueltorrescosta requested review from vtomole, cduck and a team as code owners May 7, 2024 21:14
@CirqBot CirqBot added the size: S 10< lines changed <50 label May 7, 2024
@migueltorrescosta migueltorrescosta force-pushed the optimize-qubit-permutation-gate-decompose-method branch 2 times, most recently from 827a59c to 26e15fb Compare May 7, 2024 21:37
Copy link

codecov bot commented May 7, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.79%. Comparing base (9ddb8c8) to head (8b5a61f).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #6588   +/-   ##
=======================================
  Coverage   97.79%   97.79%           
=======================================
  Files        1126     1126           
  Lines       95784    95781    -3     
=======================================
+ Hits        93670    93671    +1     
+ Misses       2114     2110    -4     

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

reversed_permutation_map = {v: i for i, v in enumerate(self.permutation)}

while reversed_permutation_map:
a = list(reversed_permutation_map.keys())[0]
Copy link
Collaborator

@NoureldinYosri NoureldinYosri May 7, 2024

Choose a reason for hiding this comment

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

This code has time complexity O(num_qubits * num_cycles) but can be done in O(num_qubits), try

permutation = self.permutation.copy()

for i in range(len(permutation)):
   if permutation[i] == -1: continue
   cycle = [i]
   while permutation[cycle[-1]] != i:
      cycle.append(permutation[cylce[-1]])
   for j in cycle: permutation[j] = -1
   cycle.reverse() 
   # yield len(cycle) - 1 swap operations
   

Copy link
Contributor Author

@migueltorrescosta migueltorrescosta May 7, 2024

Choose a reason for hiding this comment

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

What should if permutation[i] = -1: continue be? It does not compile

Copy link
Contributor Author

@migueltorrescosta migueltorrescosta May 7, 2024

Choose a reason for hiding this comment

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

Letting

  • c being the number of cycles
  • q being the number of qubits

I believe the complexity is O(q) because the inner loop runs c/q times ( average over all loops ), and the outer loop runs c times ( once per cycle ). The total complexity then becomes O(c * (q/c) ) = O(q) . Did I miss something?

reversed_permutation_map = {v: i for i, v in enumerate(self.permutation)} # q

while reversed_permutation_map:  # This loop runs once per cycle
    a = list(reversed_permutation_map.keys())[0]
    b = reversed_permutation_map.pop(a)
    while b in reversed_permutation_map.keys():  # This loop runs once per qubit in a cycle
        yield swap_gates.SWAP(qubits[a], qubits[b])
        (a, b) = (b, reversed_permutation_map.pop(b))

Copy link
Collaborator

@NoureldinYosri NoureldinYosri May 7, 2024

Choose a reason for hiding this comment

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

list(reversed_permutation_map.keys())[0]

this line is O(n)

Copy link
Collaborator

@NoureldinYosri NoureldinYosri May 7, 2024

Choose a reason for hiding this comment

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

you can make your implementation O(q) by changing that line to

a = next(iter(reversed_permutation_map))

but even in that case the implementation would be a bit convoluted

Copy link
Collaborator

Choose a reason for hiding this comment

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

all the CI checks pass for current commits, where is this test failing?
image

Copy link
Contributor Author

@migueltorrescosta migueltorrescosta May 7, 2024

Choose a reason for hiding this comment

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

The tests fail when I attempted the suggested solution #6588 (comment) , even with the fixes mentioned in #6588 (comment). They do not fail with the currently committed code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@NoureldinYosri I believe this is ready to go as is. Do mention it if there is something I should change.

Copy link
Collaborator

Choose a reason for hiding this comment

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

please commit the code that fails

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

cirq-core/cirq/ops/permutation_gate.py Outdated Show resolved Hide resolved
cirq-core/cirq/ops/permutation_gate.py Outdated Show resolved Hide resolved
cirq-core/cirq/ops/permutation_gate.py Outdated Show resolved Hide resolved
@migueltorrescosta
Copy link
Contributor Author

Suggestions applied. The shorter code feels easier to follow:

remaining_permutations = {v: i for i, v in enumerate(self.permutation)}

while remaining_permutations:
    a = next(iter(remaining_permutations))
    b = remaining_permutations.pop(a)
    while b in remaining_permutations.keys():
        yield swap_gates.SWAP(qubits[a], qubits[b])
        (a, b) = (b, remaining_permutations.pop(b))

vs

permutation = [p for p in self.permutation]

for i in range(len(permutation)):

    if permutation[i] == -1:
        continue
    cycle = [i]
    while permutation[cycle[-1]] != i:
        cycle.append(permutation[cycle[-1]])

    for j in cycle:
        permutation[j] = -1

    for idx in cycle[1:]:
        yield swap_gates.SWAP(qubits[cycle[0]], qubits[idx])

It might be a question of personal taste, so I am happy to have either merged

@migueltorrescosta migueltorrescosta force-pushed the optimize-qubit-permutation-gate-decompose-method branch from cd7501f to 8b5a61f Compare May 10, 2024 01:26
Copy link
Collaborator

@NoureldinYosri NoureldinYosri 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 your help with this task

fixes #5097

@NoureldinYosri NoureldinYosri linked an issue May 10, 2024 that may be closed by this pull request
@NoureldinYosri NoureldinYosri merged commit ee2b78f into quantumlib:main May 10, 2024
34 checks passed
jselig-rigetti pushed a commit to jselig-rigetti/Cirq that referenced this pull request May 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size: S 10< lines changed <50
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimize decomposition of cirq.QubitPermutationGate
3 participants