Fix #880: return simplified QubitOperator from symmetry_conserving_bravyi_kitaev - #1440
Fix #880: return simplified QubitOperator from symmetry_conserving_bravyi_kitaev#1440stark256-spec wants to merge 2 commits into
Conversation
…serving_bravyi_kitaev symmetry_conserving_bravyi_kitaev could return QubitOperators with un-simplified terms: remove_indices() shifts qubit indices and can map two qubits onto the same index, producing a term with multiple Paulis acting on one qubit (e.g. ((0, 'X'), (1, 'Y'), (1, 'X'))). Because those terms are written straight into the operator's .terms dict, they bypass the simplification normally done on construction. get_sparse_operator (via qubit_operator_sparse) assumes each qubit appears at most once per term, so it raised 'ValueError: axis 0 index 7 exceeds matrix dimension 4' on such operators. Rebuild the operator after remove_indices so every term is routed back through QubitOperator's simplification, restoring canonical one-Pauli-per-qubit form. Add a regression test using the reporter's reproducer that checks the terms are canonical and that the resulting sparse operator matches the explicitly-simplified operator. Closes quantumlib#880
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request addresses issue #880 by ensuring that symmetry_conserving_bravyi_kitaev returns a simplified QubitOperator in canonical form (with at most one Pauli operator per qubit). This is achieved by rebuilding the operator term-by-term, which triggers QubitOperator's internal simplification and prevents errors when calling get_sparse_operator. A regression test has also been added to verify this behavior. There are no review comments, so I have no additional feedback to provide.
mhucka
left a comment
There was a problem hiding this comment.
Thank you for this work. This is almost ready; I just have one trivial change and a question about one of the tests.
| @@ -99,7 +99,17 @@ def symmetry_conserving_bravyi_kitaev(fermion_hamiltonian, active_orbitals, acti | |||
| ) | |||
| qubit_hamiltonian = remove_indices(qubit_hamiltonian, (active_orbitals / 2, active_orbitals)) | |||
There was a problem hiding this comment.
Should this use integer division instead?
| qubit_hamiltonian = remove_indices(qubit_hamiltonian, (active_orbitals / 2, active_orbitals)) | |
| qubit_hamiltonian = remove_indices(qubit_hamiltonian, (active_orbitals // 2, active_orbitals)) |
| sparse_op = get_sparse_operator(trafo_op) | ||
|
|
||
| # ... and must match the explicitly-simplified operator. | ||
| simplified = QubitOperator() | ||
| for term, coefficient in trafo_op.terms.items(): | ||
| simplified += QubitOperator(term, coefficient) | ||
| expected = get_sparse_operator(simplified) | ||
|
|
||
| self.assertTrue(numpy.allclose(sparse_op.toarray(), expected.toarray())) |
There was a problem hiding this comment.
I'm not 100% sure, but I think this does not achieve the objective of the test. The problem is that trafo_op is already simplified, if I understand what symmetry_conserving_bravyi_kitaev is doing. So, the expected here is being constructed from an already-simplified thing. which means the comparison on line 179 of sparse_op to expected essentially compares trafo_op against itself.
|
Also, @stark256-spec, the testing process described in this PR's description are not what this project does. Please see |
- Use // for the active_orbitals/2 index computations passed to edit_hamiltonian_for_spin and remove_indices (integer indices). - The regression test previously built its 'expected' operator by re-simplifying the (already simplified) output, so it compared the result against itself. Compare against an independently specified, hand-verified QubitOperator instead.
|
Thanks @mhucka — both addressed in a585261. 1. Integer division. Done. I switched both 2. The test — you're right, thank you. Now that the fix returns an already-simplified operator, re-summing its terms is a no-op, so the old All 6 tests pass; |
|
Good point, thanks — I've updated the PR description's testing section to follow CONTRIBUTING.md. It now describes the regression test and references the repo's own check scripts ( |
Closes #880.
Problem
get_sparse_operatorraises on the operator produced bysymmetry_conserving_bravyi_kitaev:Root cause
symmetry_conserving_bravyi_kitaevfinishes by callingremove_indices, which shifts qubit indices. When two qubits are mapped onto the same new index, a term ends up with multiple Paulis acting on one qubit, e.g.:remove_indiceswrites these terms straight into the operator’s.termsdict, so they bypass the simplificationQubitOperatornormally performs on construction.qubit_operator_sparseassumes each qubit appears at most once per term (it grows the tensor product one factor per Pauli), so a repeated qubit makes the per-term matrix larger than then_qubitsHilbert space and the assembly raises.Fix
Rebuild the operator after
remove_indicesso every term is routed back throughQubitOperator’s simplification, restoring canonical one-Pauli-per-qubit form (e.g.((0, X), (1, Y), (1, X)) → ((0, X), (1, Z))). This keeps the operator mathematically identical — the existing eigenspectrum-based tests still pass — while satisfying the invariant thatQubitOperators are simplified, whichget_sparse_operatorand other consumers rely on.Tests
Added
test_output_is_simplified_qubit_operator, a regression test built from the reporter's reproducer. It now asserts the transform equals an independently worked-out, fully-simplifiedQubitOperatorfor that input (rather than re-deriving the expectation from the output), checks every term is canonical (one Pauli per qubit), and checksget_sparse_operatorno longer raises. The existing eigenspectrum-based tests continue to pass, confirming the operator is unchanged mathematically.Verified with the repo's check scripts:
check/pytest -m "not slow" src/openfermion/transforms/opconversions/remove_symmetry_qubits_test.pypasses, andcheck/format-incrementalandcheck/pylint-changed-filesare clean.check/mypyreports no issues in the changed module.