Skip to content

Conversation

ewinston
Copy link
Contributor

Summary

This PR attempts to detect when the unitary matrix has a block diagonal representation with respect to one of it's qubits at each step of the recursion in the QSD. If this occurs, one may skip the cosine-sine decomposition and therefore reduce the CX gate count. In addition this may help avoid numerical errors the underlying scipy.linalg.cossin function seems to have for block-diagonal input matrices.

Details and comments

@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 Jan 18, 2025

Pull Request Test Coverage Report for Build 15163180521

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

  • 40 of 42 (95.24%) changed or added relevant lines in 3 files are covered.
  • 953 unchanged lines in 22 files lost coverage.
  • Overall coverage decreased (-0.4%) to 88.325%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/circuit/library/generalized_gates/unitary.py 6 8 75.0%
Files with Coverage Reduction New Missed Lines %
qiskit/transpiler/passes/optimization/template_matching/template_substitution.py 1 93.54%
crates/qasm2/src/lex.rs 2 92.23%
qiskit/circuit/parameter.py 2 96.77%
crates/circuit/src/packed_instruction.rs 4 93.18%
qiskit/primitives/containers/observables_array.py 4 96.53%
qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py 4 94.9%
qiskit/transpiler/passes/routing/star_prerouting.py 4 97.77%
crates/circuit/src/lib.rs 6 94.44%
qiskit/transpiler/passes/layout/dense_layout.py 6 93.26%
qiskit/transpiler/passes/layout/sabre_layout.py 8 93.94%
Totals Coverage Status
Change from base Build 14929222382: -0.4%
Covered Lines: 78396
Relevant Lines: 88759

💛 - Coveralls

@ShellyGarion ShellyGarion added this to the 2.0.0 milestone Jan 21, 2025
@ShellyGarion
Copy link
Member

ShellyGarion commented Jan 28, 2025

should hopefully close #10313 and replace #12779 (#10320)

@jakelishman
Copy link
Member

Glancing quickly, I think this PR is implementing a special case of a question something like "is there a subset of target qubits for which the partial trace commutes with $Z^{(i)}$?". That feels like something we could tackle by Hilbert-space re-ordering at the top level of the decomposition, so we can get them out of the way earlier and reduce the difficulty at all levels.

I want to stress how little I've thought this through, so I might just be chatting nonsense.

@ShellyGarion
Copy link
Member

Glancing quickly, I think this PR is implementing a special case of a question something like "is there a subset of target qubits for which the partial trace commutes with Z ( i ) ?". That feels like something we could tackle by Hilbert-space re-ordering at the top level of the decomposition, so we can get them out of the way earlier and reduce the difficulty at all levels.

I want to stress how little I've thought this through, so I might just be chatting nonsense.

@jakelishman - We have tried to extend this suggestion to the case of multi-controlled gates, but it seems that the algorithm of Shende et. al. decomposes the unitary gate using uniformly-controlled Rz or Ry gates, and this structure is not preserved in the induction step. Any concrete suggestions would be helpful.

@ShellyGarion
Copy link
Member

Comparing QSD to multi-controlled synthesis for 1-qubit gates (see: test_mc_1qubit_opt):
Usually it's better to use the multi-controlled synthesis directly, except of a few cases (with 3,4,5 qubits and p or u basis gate).

num_qubits= 3 base_gate= x cx count using mc: OrderedDict({'u': 8, 'cx': 6}) cx count using qsd: OrderedDict({'u': 15, 'cx': 8})
num_qubits= 3 base_gate= p cx count using mc: OrderedDict({'u': 7, 'cx': 6}) cx count using qsd: OrderedDict({'u': 10, 'cx': 6})
num_qubits= 3 base_gate= u cx count using mc: OrderedDict({'u': 25, 'cx': 22}) cx count using qsd: OrderedDict({'u': 20, 'cx': 10})
num_qubits= 4 base_gate= x cx count using mc: OrderedDict({'u': 16, 'cx': 14}) cx count using qsd: OrderedDict({'u': 77, 'cx': 47})
num_qubits= 4 base_gate= p cx count using mc: OrderedDict({'u': 24, 'cx': 20}) cx count using qsd: OrderedDict({'u': 18, 'cx': 14})
num_qubits= 4 base_gate= u cx count using mc: OrderedDict({'u': 56, 'cx': 51}) cx count using qsd: OrderedDict({'u': 87, 'cx': 51})
num_qubits= 5 base_gate= x cx count using mc: OrderedDict({'u': 41, 'cx': 36}) cx count using qsd: OrderedDict({'u': 396, 'cx': 238})
num_qubits= 5 base_gate= p cx count using mc: OrderedDict({'u': 53, 'cx': 44}) cx count using qsd: OrderedDict({'u': 34, 'cx': 30})
num_qubits= 5 base_gate= u cx count using mc: OrderedDict({'u': 109, 'cx': 92}) cx count using qsd: OrderedDict({'u': 388, 'cx': 236})
num_qubits= 6 base_gate= x cx count using mc: OrderedDict({'u': 97, 'cx': 84}) cx count using qsd: OrderedDict({'u': 1642, 'cx': 1012})
num_qubits= 6 base_gate= p cx count using mc: OrderedDict({'u': 97, 'cx': 84}) cx count using qsd: OrderedDict({'u': 1686, 'cx': 1046})
num_qubits= 6 base_gate= u cx count using mc: OrderedDict({'u': 181, 'cx': 164}) cx count using qsd: OrderedDict({'u': 1668, 'cx': 1030})
num_qubits= 7 base_gate= x cx count using mc: OrderedDict({'u': 155, 'cx': 140}) cx count using qsd: OrderedDict({'u': 6794, 'cx': 4244})
num_qubits= 7 base_gate= p cx count using mc: OrderedDict({'u': 155, 'cx': 140}) cx count using qsd: OrderedDict({'u': 2364, 'cx': 1488})
num_qubits= 7 base_gate= u cx count using mc: OrderedDict({'u': 267, 'cx': 252}) cx count using qsd: OrderedDict({'u': 3436, 'cx': 2156})
num_qubits= 8 base_gate= x cx count using mc: OrderedDict({'u': 242, 'cx': 220}) cx count using qsd: OrderedDict({'u': 27734, 'cx': 17494})

@ShellyGarion
Copy link
Member

Comparing QSD to multi-controlled synthesis for 2-qubit gates (see: test_mc_2qubit_opt):
Usually it's better to use the multi-controlled synthesis directly, except when the number of qubits is 4.

num_qubits= 3 cx count using mc: OrderedDict({'u': 20, 'cx': 10}) cx count using qsd: OrderedDict({'u': 20, 'cx': 10})
num_qubits= 4 cx count using mc: OrderedDict({'u': 82, 'cx': 48}) cx count using qsd: OrderedDict({'u': 78, 'cx': 46})
num_qubits= 5 cx count using mc: OrderedDict({'u': 396, 'cx': 238}) cx count using qsd: OrderedDict({'u': 406, 'cx': 246})
num_qubits= 6 cx count using mc: OrderedDict({'u': 1668, 'cx': 1030}) cx count using qsd: OrderedDict({'u': 1686, 'cx': 1046})
num_qubits= 7 cx count using mc: OrderedDict({'u': 6836, 'cx': 4278}) cx count using qsd: OrderedDict({'u': 6870, 'cx': 4310})

Co-authoed by: Alexander Ivrii <alexi@il.ibm.com>
@alexanderivrii
Copy link
Member

Thanks for providing the table (the second last column should read "opt_a2 = True", correct?

@ShellyGarion
Copy link
Member

Thanks for providing the table (the second last column should read "opt_a2 = True", correct?

yes, fixed. thanks!

if not (
matrix_equal(Operator(self.definition).to_matrix(), self.to_matrix(), atol=1e-7)
):
self.definition = Isometry(self.matrix, 0, 0).definition
Copy link
Member

@ShellyGarion ShellyGarion May 18, 2025

Choose a reason for hiding this comment

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

code updated to use Isometry the calculated Operator is different than the original matrix.

Copy link
Member

Choose a reason for hiding this comment

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

This is not ideal as this adds additional overhead and does not guarantee that we always get the better definition based on QSD. However, this is the best we can do for now and this does significantly improve the gate counts when this does work. Thus I am in favor of merging this in the current form, but we should open an issue to investigate and improve numerical decomposition errors in cossin.


cmat_def = qs_decomposition(cmat, opt_a1=True, opt_a2=False)
if not matrix_equal(Operator(cmat_def).to_matrix(), cmat, atol=1e-7):
self.definition = Isometry(cmat, 0, 0).definition
Copy link
Member

Choose a reason for hiding this comment

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

code updated to use Isometry the calculated Operator is different than the original matrix.

Copy link
Member

Choose a reason for hiding this comment

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

Do you know what are the typical numerical errors when running the Isometry code?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think there are special numerical errors. QSD is done recursively and this is the reason that we get numeric errors. However, Isometry is not iterative. I can add similar tests to Isometry.

Copy link
Member

@alexanderivrii alexanderivrii 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 doing this, this looks good. I have left just one nitpick about the argument _ctrl_index. Can you also open an issue to investigate/fix numerical errors in the cossin decomposition?

if not (
matrix_equal(Operator(self.definition).to_matrix(), self.to_matrix(), atol=1e-7)
):
self.definition = Isometry(self.matrix, 0, 0).definition
Copy link
Member

Choose a reason for hiding this comment

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

This is not ideal as this adds additional overhead and does not guarantee that we always get the better definition based on QSD. However, this is the best we can do for now and this does significantly improve the gate counts when this does work. Thus I am in favor of merging this in the current form, but we should open an issue to investigate and improve numerical decomposition errors in cossin.


cmat_def = qs_decomposition(cmat, opt_a1=True, opt_a2=False)
if not matrix_equal(Operator(cmat_def).to_matrix(), cmat, atol=1e-7):
self.definition = Isometry(cmat, 0, 0).definition
Copy link
Member

Choose a reason for hiding this comment

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

Do you know what are the typical numerical errors when running the Isometry code?

@ShellyGarion
Copy link
Member

See: #14422

Copy link
Member

@alexanderivrii alexanderivrii left a comment

Choose a reason for hiding this comment

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

Thanks @ShellyGarion, LGTM!

@alexanderivrii alexanderivrii added this pull request to the merge queue May 21, 2025
Merged via the queue into Qiskit:main with commit 83d85a0 May 21, 2025
24 checks passed
@eliarbel eliarbel added the Changelog: New Feature Include in the "Added" section of the changelog label Jun 3, 2025
rahaman-quantum pushed a commit to rahaman-quantum/qiskit that referenced this pull request Jun 20, 2025
* add test of block extration wrt qubit

* passing tests. Seems mcx synthesis could be more optimal.

* replace isometry by qsd in unitary control method

* remove qsd tests from test_synthesis

* move qsd synthesis tests

* fix random seed. update tolerance in matrices differences

* formatting, clean tests

* extend tollerance in test

* add multi-controlled tests

* extend tollerance

* extend tollerance

* reduce number of controls in tests

* update docs

* add release notes

* formtting

* update tollerance

* update qsd following review

* update release notes following review

* update tests following review

* change tollerance

* improve layout following review

* fix svd,
Co-authoed by: Alexander Ivrii <alexi@il.ibm.com>

* raise atol

* update atol

* remove assertion

* remove assertion

* update cos_sin_decomp tolerance

* update tollerance

* linting

* if the Operaotr is not the same as the original matrix, we use Isometery instead of QSD

* update atol

* move import line

* update the tests to allow using Isometry

* disable cyclic imports

* handle ctrl_index following review

---------

Co-authored-by: ShellyGarion <shelly@il.ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: New Feature Include in the "Added" section of the changelog synthesis
Projects
None yet
Development

Successfully merging this pull request may close these issues.

UnitaryGate.control implementation high gate count
7 participants