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

Change data type of QuantumCircuit.data elements #8093

Merged
merged 17 commits into from
Jun 21, 2022

Conversation

jakelishman
Copy link
Member

@jakelishman jakelishman commented May 20, 2022

Summary

These two commits change the data type of the elements of QuantumCircuit.data from an ad-hoc 3-tuple (Instruction, List[Qubit], List[Clbit]) to an encapsulating namedtuple-like class called CircuitInstruction with three attributes:

  • operation: currently an Instruction, but will presumably be Operation soon.
  • qubits: Tuple[Qubit, ...]
  • clbits: Tuple[Clbit, ...]

The first commit, b554a71, is the minimal set of changes needed to convert the data type, including backwards-compatibility shims. I suggest reviewers look at this first. The test suite passes (without modification) after this commit, which is a test of backwards compatibility.

The second commit, fb0fb32, changes every single usage of this tuple in Terra and its test suite over to using the new attribute-access pattern. This is almost entirely mechanical, but there is a slight backwards-compatibility break here. Previously, the data types of qargs and cargs in DAGOpNode and DAGDepNode were not specified, but in practice were lists most (but not all) of the time. After this commit, they are now tuples in 100% of Terra code. This should have little-to-no effect on downstream code, because everything would have broken if anybody actually attempted to mutate one of these lists. The only likely possibilities are if people are trying to add the values to a list (Terra very occasionally tried this), or if they are comparing for equality with an exact list.

Details and comments

See #7624 for a lot of the justification for why we want to change from an ad-hoc tuple to an extensible class as the backing scalar type. See also #7020 for a previous attempt at this. #7020 did not make the second commit in this PR, which is why this PR does not show the same performance regressions as that one.

I ran the test suites from Experiments and Nature against this, with no failures, suggesting the backwards compatibility shims in CircuitInstruction work pretty well, and the changes to DAGOpNode don't have an effect on downstream code.

The reason to use tuples not lists in the new instruction is to reduce memory usage on copy (such as from circuit to DAG); tuples can be bound directly, whereas lists need to be reallocated. They are also directly usable as dictionary keys (which we sometimes do), and we have no need to mutate them.

The new CircuitInstruction class is not immutable itself, although I don't propose we use that in any current usages. I anticipate we may want to use mutability when more items are in it (such as simple binding of parameters), especially if the collection underlying QuantumCircuit changes from a list to a DAG at any point. This may not be necessary, though.

For all the end-to-end benchmarks, this appears to have no effect on the timings, after the second commit (there are regressions after only the first). I rebased these commits to tweak some bits after I ran the benchmark suite, but comparing this PR to main, the slowdowns I found were:

+      1.20±0.01s        1.40±0.1s     1.17  assembler.AssemblerBenchmarks.time_assemble_circuit(2, 2048, 100)
+         413±1ms          461±5ms     1.12  circuit_construction.CircuitConstructionBench.time_circuit_copy(1, 131072)
+     5.64±0.09ms       7.00±0.4ms     1.24  circuit_construction.CircuitConstructionBench.time_circuit_copy(1, 2048)
+       102±0.5ms          115±3ms     1.13  circuit_construction.CircuitConstructionBench.time_circuit_copy(1, 32768)
+         540±2ms         603±10ms     1.12  circuit_construction.CircuitConstructionBench.time_circuit_copy(2, 131072)
+         584±2ms         644±10ms     1.10  circuit_construction.CircuitConstructionBench.time_circuit_copy(8, 131072)
+        695±10μs          917±4μs     1.32  converters.ConverterBenchmarks.time_circuit_to_instruction(1, 128)
+      10.1±0.1ms      13.2±0.07ms     1.30  converters.ConverterBenchmarks.time_circuit_to_instruction(1, 2048)
+      41.2±0.2ms       53.1±0.4ms     1.29  converters.ConverterBenchmarks.time_circuit_to_instruction(1, 8192)
+      6.63±0.1ms      8.13±0.07ms     1.23  converters.ConverterBenchmarks.time_circuit_to_instruction(14, 128)
+       110±0.2ms          132±1ms     1.20  converters.ConverterBenchmarks.time_circuit_to_instruction(14, 2048)
+      18.7±0.1ms       23.2±0.2ms     1.24  converters.ConverterBenchmarks.time_circuit_to_instruction(2, 2048)
+       169±0.3μs          203±2μs     1.20  converters.ConverterBenchmarks.time_circuit_to_instruction(2, 8)
+      75.0±0.6ms       92.0±0.5ms     1.23  converters.ConverterBenchmarks.time_circuit_to_instruction(2, 8192)
+      9.38±0.1ms       11.8±0.2ms     1.26  converters.ConverterBenchmarks.time_circuit_to_instruction(20, 128)
+         791±6μs      1.00±0.02ms     1.27  converters.ConverterBenchmarks.time_circuit_to_instruction(20, 8)
+     2.51±0.01ms      3.17±0.06ms     1.27  converters.ConverterBenchmarks.time_circuit_to_instruction(5, 128)
+      41.1±0.4ms       49.1±0.2ms     1.19  converters.ConverterBenchmarks.time_circuit_to_instruction(5, 2048)
+         290±5μs          342±3μs     1.18  converters.ConverterBenchmarks.time_circuit_to_instruction(5, 8)
+         166±1ms        198±0.7ms     1.20  converters.ConverterBenchmarks.time_circuit_to_instruction(5, 8192)
+      63.7±0.5ms       75.8±0.5ms     1.19  converters.ConverterBenchmarks.time_circuit_to_instruction(8, 2048)
+         383±8μs          470±5μs     1.23  converters.ConverterBenchmarks.time_circuit_to_instruction(8, 8)
+       258±0.6ms          308±3ms     1.20  converters.ConverterBenchmarks.time_circuit_to_instruction(8, 8192)

There is a ~20% performance regression on the few microbenchmarks where the construction time for the CircuitInstruction operations is the bottleneck (e.g. copying very narrow, very deep circuits), but actual transpilation workloads don't suffer. I feel like this is something we may need to accept, given the arguments in #7624.

@qiskit-bot
Copy link
Collaborator

Thank you for opening a new pull request.

Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient.

While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone.

One or more of the the following people are requested to review this:

This introduces a new, lightweight class `CircuitInstruction` the
encapsulates the previous 3-tuple that made up the items of
`QuantumCircuit.data`.  It includes a legacy API that makes the object
appear to be the old 3-tuple when used as if it were that tuple, and the
relatively small changeset for this commit is an approximate indication
that these should mostly be suitable.  There will be significant
performance penalties for accessing the legacy interface, however, due
to some internal type conversions, so it is a matter of priority to
convert internal usage to the new dotted-attribute access form.

The only places necessary to change across Terra were those where the
data elements were compared by referential equality to expected values
(such as in the control-flow builders), and places that were invalidly
accessing the private attribute `QuantumCircuit._data`.  All these are
updated.

`ParameterTable` should in the future be changed to operate over this
entire context, but that was beyond the scope of the initial commit.
@jakelishman
Copy link
Member Author

jakelishman commented May 20, 2022

For completeness, I also ran the Shor's algorithms tests that were the main problem in #7020. Here is the slowest of those on main:

Test id                                                                                             Runtime (s)
--------------------------------------------------------------------------------------------------  -----------
test.python.algorithms.test_shor.TestShor.test_shor_exponentiation_result_1__15__4___1__4__         6.800
test.python.algorithms.test_shor.TestShor.test_shor_exponentiation_result_2__15__7___1__4__7__13__  6.484
test.python.algorithms.test_shor.TestShor.test_shor_quantum_result_1__15__4__2_                     6.463
test.python.algorithms.test_shor.TestShor.test_shor_factoring_1__15___qasm_simulator____3__5__      6.434
test.python.algorithms.test_shor.TestShor.test_shor_quantum_result_2__15__7__4_                     6.321
test.python.algorithms.test_shor.TestShor.test_shor_no_factors_1_5                                  2.762
test.python.algorithms.test_shor.TestShor.test_shor_no_factors_2_7                                  2.692
test.python.algorithms.test_shor.TestShor.test_shor_bad_input_01___1__2_                            0.282
test.python.algorithms.test_shor.TestShor.test_shor_quantum_result_for_5_bit_number_2__21__13__2_   0.001
test.python.algorithms.test_shor.TestShor.test_shor_quantum_result_for_5_bit_number_1__17__8__8_    0.001

and now with this PR:

Test id                                                                                                                      Runtime (s)
---------------------------------------------------------------------------------------------------------------------------  -----------
test.python.algorithms.test_shor.TestShor.test_shor_exponentiation_result_2__15__7___1__4__7__13__                           7.419
test.python.algorithms.test_shor.TestShor.test_shor_factoring_1__15___qasm_simulator____3__5__                               7.300
test.python.algorithms.test_shor.TestShor.test_shor_quantum_result_2__15__7__4_                                              7.160
test.python.algorithms.test_shor.TestShor.test_shor_exponentiation_result_1__15__4___1__4__                                  7.125
test.python.algorithms.test_shor.TestShor.test_shor_quantum_result_1__15__4__2_                                              7.107
test.python.algorithms.test_shor.TestShor.test_shor_no_factors_1_5                                                           3.093
test.python.algorithms.test_shor.TestShor.test_shor_no_factors_2_7                                                           3.050
test.python.algorithms.test_shor.TestShor.test_shor_bad_input_01___1__2_                                                     0.254
test.python.algorithms.test_shor.TestShor.test_shor_modinv_1__2__15__8_                                                      0.001
test.python.algorithms.test_shor.TestShor.test_shor_exponentiation_result_for_5_bit_number_1__21__5___1__4__5__16__17__20__  0.001

That's about a 10% regression on those, as opposed to the 10-20x regression seen before.

@jakelishman jakelishman added priority: high Changelog: API Change Include in the "Changed" section of the changelog labels May 20, 2022
@jakelishman jakelishman added this to the 0.21 milestone May 20, 2022
This previous commit is the minimal set of changes needed for the entire
test suite to pass, but large parts of Terra and the test suite were
still using the backwards-compatibility shims when dealing with
`QuantumCircuit.data`.  This changes over every single usage within
Terra to use the new dotted-access form, rather than the tuple indexing
or iteration that previously existed.

The internal types of the `qargs` and `cargs` in `DAGDepNode` and
`DAGOpNode` are also changed to be `tuple` now; previously they were
semi-undefined, and in some cases were not consistent, but not caught in
the test suite.  Since changing the type of the `qubits` parameter in
`CircuitInstruction` to be tuple (unless using the
backwards-compatibility shims), this caused a lot of failures where the
value was being assigned directly.  Normalising to `tuple` has the same
benefit as it does in the circuit instruction - less copying overhead,
and no worrying about multiple writable references to the same object.

By far the largest set of changes are in the test suite; we do a lot of
comparing things for exact equality, and since the type is now changed
from `list` to `tuple`, all the tests had to be updated.  This isn't
expected to have much impact on user code, but changing the type to an
immutable one has large benefits for us when copying these data
structures.
@jakelishman
Copy link
Member Author

Given the lint failure, I'm guessing I've left a dodgy type hint in the QuantumCircuit structure somewhere. I'll have a look and see if I can find it in a bit - it doesn't affect the substance of this PR.

@coveralls
Copy link

coveralls commented May 20, 2022

Pull Request Test Coverage Report for Build 2538665497

  • 590 of 643 (91.76%) changed or added relevant lines in 57 files are covered.
  • 17 unchanged lines in 13 files lost coverage.
  • Overall coverage decreased (-0.001%) to 84.282%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/dagcircuit/dagdepnode.py 2 3 66.67%
qiskit/quantum_info/operators/channel/superop.py 4 5 80.0%
qiskit/quantum_info/operators/dihedral/dihedral_circuits.py 17 18 94.44%
qiskit/quantum_info/operators/symplectic/clifford_circuits.py 4 5 80.0%
qiskit/transpiler/passes/optimization/hoare_opt.py 0 1 0.0%
qiskit/opflow/converters/circuit_sampler.py 0 2 0.0%
qiskit/primitives/utils.py 3 5 60.0%
qiskit/qpy/binary_io/circuits.py 58 60 96.67%
qiskit/opflow/gradients/circuit_gradients/lin_comb.py 24 27 88.89%
qiskit/quantum_info/operators/symplectic/base_pauli.py 0 4 0.0%
Files with Coverage Reduction New Missed Lines %
qiskit/circuit/duration.py 1 65.63%
qiskit/circuit/quantumcircuitdata.py 1 85.58%
qiskit/qasm3/exporter.py 1 96.37%
qiskit/quantum_info/operators/dihedral/dihedral_circuits.py 1 82.18%
qiskit/quantum_info/operators/symplectic/base_pauli.py 1 87.43%
qiskit/quantum_info/operators/symplectic/pauli.py 1 86.56%
qiskit/quantum_info/states/densitymatrix.py 1 87.45%
qiskit/quantum_info/states/statevector.py 1 89.0%
qiskit/scheduler/lowering.py 1 96.47%
qiskit/scheduler/sequence.py 1 95.56%
Totals Coverage Status
Change from base Build 2538663499: -0.001%
Covered Lines: 54967
Relevant Lines: 65218

💛 - Coveralls

qiskit/circuit/instructionset.py Show resolved Hide resolved
qiskit/circuit/instructionset.py Outdated Show resolved Hide resolved
qiskit/circuit/quantumcircuit.py Outdated Show resolved Hide resolved
qiskit/circuit/quantumcircuitdata.py Show resolved Hide resolved
qiskit/circuit/quantumcircuitdata.py Show resolved Hide resolved
qiskit/circuit/quantumcircuitdata.py Show resolved Hide resolved
qiskit/circuit/controlflow/if_else.py Show resolved Hide resolved
Some uses of the legacy interface related to directly setting
`QuantumCircuit.data` had slipped through the cracks.  A bug in
`astroid` caused its inference to fail, and think that these settings
made the scalar type of `QuantumCircuit.data` the old 3-tuple after it
saw these settings.  Since there's a property setter for `data`, that's
a bug in `astroid`, but it still turned up some useful additional cases
that were useful to correct.
@jakelishman
Copy link
Member Author

I've updated this with some of Kevin's comments, and should have fixed all the linter failures. The linter was actually failing due to an inference bug in astroid (it seems to unreliably distinguish properties from attributes, and this can cause the same object to be inferred as two different types), but it turned up some useful additional cases that were still using the legacy interface.

@jakelishman jakelishman dismissed stale reviews from mtreinish and kevinhartman via 4252e68 June 21, 2022 17:34
@jakelishman
Copy link
Member Author

@kevinhartman, @mtreinish: fixed merge conflicts, and addressed actionable comments.

mtreinish
mtreinish previously approved these changes Jun 21, 2022
pylint isn't actually correct in this case.  `tuple(<list comp>)` is
more efficient than `tuple(<generator>)` (certainly as of Python 3.10),
because Python still needs to use a temporary vector-like expanding
collection while consuming the iterable so despite appearances there's
no memory benefit to the generator, and list comprehensions are
translated into faster bytecode than general generators.
@mergify mergify bot merged commit ea02667 into Qiskit:main Jun 21, 2022
@jakelishman jakelishman deleted the instruction-data branch June 22, 2022 21:33
rathishcholarajan added a commit to rathishcholarajan/qiskit-ibm-runtime that referenced this pull request Jul 5, 2022
Co-authored-by: Jake Lishman <jake@binhbar.com>
Co-authored-by: Jake Lishman <jake.lishman@ibm.com>
rathishcholarajan added a commit to Qiskit/qiskit-ibm-runtime that referenced this pull request Jul 5, 2022
* Copy Qiskit/qiskit#8055

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>

* Copy qpy changes from Qiskit/qiskit#8093

Co-authored-by: Jake Lishman <jake@binhbar.com>
Co-authored-by: Jake Lishman <jake.lishman@ibm.com>

* Copy Qiskit/qiskit#8200

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>

* Copy of Qiskit/qiskit#7300

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
Co-authored-by: Naoki Kanazawa <nkanazawa1989@gmail.com>

* Copy of Qiskit/qiskit#8235

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>

Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
Co-authored-by: Jake Lishman <jake@binhbar.com>
Co-authored-by: Jake Lishman <jake.lishman@ibm.com>
Co-authored-by: Naoki Kanazawa <nkanazawa1989@gmail.com>
jakelishman added a commit to jakelishman/qiskit-terra that referenced this pull request Jul 12, 2023
This removes (as best as our test suite touches) all uses of the
implicit legacy tuple form of `CircuitInstruction` that have crept in
since ea02667 (Qiskitgh-8093).  The changes are largely just mechanical over
to the new form.
github-merge-queue bot pushed a commit that referenced this pull request Jul 12, 2023
This removes (as best as our test suite touches) all uses of the
implicit legacy tuple form of `CircuitInstruction` that have crept in
since ea02667 (gh-8093).  The changes are largely just mechanical over
to the new form.
ElePT pushed a commit to ElePT/qiskit-algorithms that referenced this pull request Jul 27, 2023
* Convert QuantumCircuit.data items to CircuitInstruction

This introduces a new, lightweight class `CircuitInstruction` the
encapsulates the previous 3-tuple that made up the items of
`QuantumCircuit.data`.  It includes a legacy API that makes the object
appear to be the old 3-tuple when used as if it were that tuple, and the
relatively small changeset for this commit is an approximate indication
that these should mostly be suitable.  There will be significant
performance penalties for accessing the legacy interface, however, due
to some internal type conversions, so it is a matter of priority to
convert internal usage to the new dotted-attribute access form.

The only places necessary to change across Terra were those where the
data elements were compared by referential equality to expected values
(such as in the control-flow builders), and places that were invalidly
accessing the private attribute `QuantumCircuit._data`.  All these are
updated.

`ParameterTable` should in the future be changed to operate over this
entire context, but that was beyond the scope of the initial commit.

* Convert all usage of data tuple to CircuitInstruction

This previous commit is the minimal set of changes needed for the entire
test suite to pass, but large parts of Terra and the test suite were
still using the backwards-compatibility shims when dealing with
`QuantumCircuit.data`.  This changes over every single usage within
Terra to use the new dotted-access form, rather than the tuple indexing
or iteration that previously existed.

The internal types of the `qargs` and `cargs` in `DAGDepNode` and
`DAGOpNode` are also changed to be `tuple` now; previously they were
semi-undefined, and in some cases were not consistent, but not caught in
the test suite.  Since changing the type of the `qubits` parameter in
`CircuitInstruction` to be tuple (unless using the
backwards-compatibility shims), this caused a lot of failures where the
value was being assigned directly.  Normalising to `tuple` has the same
benefit as it does in the circuit instruction - less copying overhead,
and no worrying about multiple writable references to the same object.

By far the largest set of changes are in the test suite; we do a lot of
comparing things for exact equality, and since the type is now changed
from `list` to `tuple`, all the tests had to be updated.  This isn't
expected to have much impact on user code, but changing the type to an
immutable one has large benefits for us when copying these data
structures.

* Correct bad version in comment

* Simplify unnecessary if/else

* Update remnant legacy interface uses

Some uses of the legacy interface related to directly setting
`QuantumCircuit.data` had slipped through the cracks.  A bug in
`astroid` caused its inference to fail, and think that these settings
made the scalar type of `QuantumCircuit.data` the old 3-tuple after it
saw these settings.  Since there's a property setter for `data`, that's
a bug in `astroid`, but it still turned up some useful additional cases
that were useful to correct.

* Remove accidental blank line

* Use QuantumCircuit.copy_empty_like in Instruction constructors

* Correct global phase in Instruction.inverse

* Shut pylint up about consider-using-generator

pylint isn't actually correct in this case.  `tuple(<list comp>)` is
more efficient than `tuple(<generator>)` (certainly as of Python 3.10),
because Python still needs to use a temporary vector-like expanding
collection while consuming the iterable so despite appearances there's
no memory benefit to the generator, and list comprehensions are
translated into faster bytecode than general generators.

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
jakelishman added a commit to jakelishman/qiskit-terra that referenced this pull request Jun 21, 2024
This has been the legacy path since `CircuitInstruction` was added in
Qiskitgh-8093.  It's more performant to use the attribute-access patterns, and
with more of the internals moving to Rust and potentially needing more
use of additional class methods and attributes, we need to start
shifting people away from the old form.
jakelishman added a commit to jakelishman/qiskit-terra that referenced this pull request Jun 21, 2024
This has been the legacy path since `CircuitInstruction` was added in
Qiskitgh-8093.  It's more performant to use the attribute-access patterns, and
with more of the internals moving to Rust and potentially needing more
use of additional class methods and attributes, we need to start
shifting people away from the old form.
jakelishman added a commit to jakelishman/qiskit-terra that referenced this pull request Jun 21, 2024
This has been the legacy path since `CircuitInstruction` was added in
Qiskitgh-8093.  It's more performant to use the attribute-access patterns, and
with more of the internals moving to Rust and potentially needing more
use of additional class methods and attributes, we need to start
shifting people away from the old form.
github-merge-queue bot pushed a commit that referenced this pull request Jun 24, 2024
This has been the legacy path since `CircuitInstruction` was added in
gh-8093.  It's more performant to use the attribute-access patterns, and
with more of the internals moving to Rust and potentially needing more
use of additional class methods and attributes, we need to start
shifting people away from the old form.
Procatv pushed a commit to Procatv/qiskit-terra-catherines that referenced this pull request Aug 1, 2024
This has been the legacy path since `CircuitInstruction` was added in
Qiskitgh-8093.  It's more performant to use the attribute-access patterns, and
with more of the internals moving to Rust and potentially needing more
use of additional class methods and attributes, we need to start
shifting people away from the old form.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: API Change Include in the "Changed" section of the changelog priority: high
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants