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

Wires as Sequence with new indexing #983

Merged
merged 26 commits into from
Jan 18, 2021
Merged

Wires as Sequence with new indexing #983

merged 26 commits into from
Jan 18, 2021

Conversation

dwierichs
Copy link
Contributor

@dwierichs dwierichs commented Jan 5, 2021

Context
All details can be found in the previous PR attempt via subclassing Wires from orderedset.OrderedSet. #979
Here we keep the subclassing from collections.abc.Sequence but change the indexing behaviour to standard python
iterable behaviour like list or tuple.

@codecov
Copy link

codecov bot commented Jan 5, 2021

Codecov Report

Merging #983 (f9b52b0) into master (a2fa39c) will increase coverage by 1.10%.
The diff coverage is 98.63%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #983      +/-   ##
==========================================
+ Coverage   96.79%   97.89%   +1.10%     
==========================================
  Files         148      151       +3     
  Lines       10507    11059     +552     
==========================================
+ Hits        10170    10826     +656     
+ Misses        337      233     -104     
Impacted Files Coverage Δ
pennylane/qnodes/base.py 98.67% <ø> (-0.66%) ⬇️
pennylane/wires.py 98.51% <97.56%> (-1.49%) ⬇️
pennylane/_device.py 96.11% <100.00%> (-3.38%) ⬇️
pennylane/beta/devices/default_tensor.py 95.23% <100.00%> (+0.08%) ⬆️
pennylane/circuit_drawer/grid.py 88.70% <100.00%> (-7.59%) ⬇️
pennylane/circuit_graph.py 98.59% <100.00%> (ø)
pennylane/devices/default_gaussian.py 98.94% <100.00%> (-0.35%) ⬇️
pennylane/devices/default_mixed.py 100.00% <100.00%> (ø)
pennylane/devices/default_qubit_autograd.py 98.21% <100.00%> (-1.79%) ⬇️
pennylane/devices/default_qubit_tf.py 91.04% <100.00%> (+1.21%) ⬆️
... and 55 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a2fa39c...f9b52b0. Read the comment docs.

@dwierichs dwierichs changed the title Wires as sequence with new indexing Wires as Sequence with new indexing Jan 5, 2021
@dwierichs
Copy link
Contributor Author

@mariaschuld Maybe you can give some input on this version of the Wires class?
The error log of the sphinx test is rather cryptic to me, it would be great if you could help me fix this.

@josh146
Copy link
Member

josh146 commented Jan 13, 2021

Hey @dwierichs, the reason for the error is the following:

WARNING: Tried to skip objects {'Sequence'} in module pennylane.operation, but they were not present.  Ignoring.

(I had to search the logs for WARNING, and there were quite a few benign ones 😆)

This is likely because one of the imports in operation.py changed.

It can be fixed by navigating to doc/code/qml_operation.py (where the documentation for the operation class is generated), and removing Sequence from the following:

.. automodapi:: pennylane.operation
    :no-heading:
    :include-all-objects:
    :skip: Sequence, Enum, IntEnum, Variable, ClassPropertyDescriptor, multi_dot, pauli_eigs, Wires

The skip statement essentially tells Sphinx "these objects are imported into this file, but are not defined here, so do not include them within the documentation for the pennylane.operation module".

@mariaschuld
Copy link
Contributor

Thanks @dwierichs! Is the PR ready for review? Did you have a chance to see if the speed improvement still holds?

I don't mind reviewing even if the docs do not build yet error-free, and maybe I can help sorting out the issues and also do some profiling this side.

@dwierichs
Copy link
Contributor Author

Yes, this PR is ready for review. I think there would be more things to do, but maybe it is best to single out this rather basic change, what do you think?
The speedup is not as nicely measurable now, because the method of Wires in which most time is spent is a different one between PL 0.13 and this PR, but the runtime behaviour for the small benchmark I used so far, on 10 qubits, is

version overall runtime (20 executions on 10 qubits) in sec.
PL 0.13 4.88
PL 0.14.0.dev0 (current master) 4.4
this PR 3.18

I think this benchmark is somewhat imprecise, but given the properly different runtimes, I think one can claim a speedup :-)

@dwierichs
Copy link
Contributor Author

The skip statement essentially tells Sphinx "these objects are imported into this file, but are not defined here, so do not include them within the documentation for the pennylane.operation module".

Great, thank you for the explanation and help! Is fixed.

Copy link
Contributor

@mariaschuld mariaschuld left a comment

Choose a reason for hiding this comment

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

On it.

The speedup is not as nicely measurable now, because the method of Wires in which most time is spent is a different one between PL 0.13 and this PR, but the runtime behaviour for the small benchmark I used so far, on 10 qubits, is

Just to understand, do you mean there is less time spent in _process? Wouldn't that be good?

pennylane/wires.py Outdated Show resolved Hide resolved
Co-authored-by: Maria Schuld <mariaschuld@gmail.com>
@dwierichs
Copy link
Contributor Author

Just to understand, do you mean there is less time spent in _process? Wouldn't that be good?

Sorry, that was unclear. In the previous PR the cumtime spent in Wires.__init__ was a good measure for the partial cost induced by the class. Now the __init__ cost are way way smaller, because indexing does not call it any longer. This means that "the cost of the Wires class" is not dominated by the __init__ cumtime and therefore less well-defined. In short, I just wanted to point out, that this comparison is not very rigorous. :)

@mariaschuld
Copy link
Contributor

Perfect!

I am still stunned by how few changes the new indexing requires, since indexing should change all sorts of things, like for w in wires statements etc... I need to play around a bit to understand this!

By the way, one could search for all occurrences of .labels[ and change the ones that refer to wires to [. Shall I make a PR into your branch doing that?

@dwierichs
Copy link
Contributor Author

I fully agree, this change caused way less headaches than I anticipated!

Yes, one could replace indexing of .labels by indexing the Wires instance itself. However, this will call the labels eventually, so I guess it actually would be a tiny bit slower (but cleaner, so maybe it's better to do it).
Regarding the indexing with a slice there is a difference, if I see this correctly: For a Wires instance wires and a slice instance s, wires.labels[s] will return a tuple and wires[s] will return a Wires object.

@dwierichs
Copy link
Contributor Author

The modifications of _process are WIP.

@dwierichs
Copy link
Contributor Author

dwierichs commented Jan 15, 2021

Current design choice:

  1. an iterable passed to Wires.__init__ will be interpreted as flat, i.e. as long as the elements in the iterable are hashable, they become the labels in the Wires instance. The behaviour is thus consistent across all input types but one (see below), in particular including 0-d qml.numpy arrays, which are interpreted as single-element iterable, and Wires instances, of which the labels simply are inherited.
  2. An exception of 1. is made for iterables containing Wires instances. Those are passed to Wires.all_wires , which is the recommended way of creating a Wires object from an iterable of Wires instances.
  3. __contains__ does not test for containment sequentially any longer, i.e. [3,4] in Wires([1,2,3,4]) = False. This is consistent with 1. above, allowing for labels that are iterable themselves. Again, checking for containment of a Wires instance is the (only!) exception, resulting in Wires([3,4]) in Wires([1,2,3,4]) = True. Therefore, __contains__ is completely aligned with 1. and 2. and except for the Wires exception it behaves like "normal" python iterables like list or tuple (Note that [0,1] in [0,1,2] = False).

… performant. TF and Autograd DefaultQubit devices have very weird `wire_map`s, making test_compare_default_qubit fail currently.
pennylane/devices/default_gaussian.py Show resolved Hide resolved
Comment on lines +163 to +176

def map_wires(self, wires):
"""Map the wire labels of wires using this device's wire map.

Args:
wires (Wires): wires whose labels we want to map to the device's internal labelling scheme

Returns:
Wires: wires with new labels
"""
wires = Wires([w.item() if isinstance(w, tensor) else w for w in wires])
mapped_wires = super().map_wires(wires)

return mapped_wires
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is the same code with default_qubit_tf, would it make sense to move this code to DeviceQubit?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This would make all QubitDevices, including the default qubits and default mixed state qubit, check for tensor instances, which maybe is not necessary?

Comment on lines +46 to +49
except TypeError as e:
# Make sure it really was a hashability issue
if str(e).startswith("unhashable"):
raise WireError("Wires must be hashable; got {}.".format(wires)) from e
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should reraise e if it wasn't this error.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, there was a dedicated error raised previously. But then codecov complained that that was not tested and I tried to find a test case raising this. Did not find one, maybe you can add it if you have an idea how to break this.

@dwierichs
Copy link
Contributor Author

The latest design choice update removes the exception from _process for lists of Wires instances (point 2 of the list above) and also from __contains__.
This means that a Wires instance in an iterable will be treated like any other label and no merging/flattening is performed (For list-of-Wires -> Wires there is Wires.all_wires)
Due to this, the __contains__ method will not serialize the containment check when invoked with a second Wires instance:

Wires([2, 3]) in Wires(range(5)) # False

As it is useful to be able to check for all elements of a Wires instance to be contained in another Wires instance, we introduced contains_wires:

a = Wires(range(10))
b = Wires(range(3))
a.contains_wires(b) # True
b.contains_wires(a) # False

Note that contains_wires is currently very specific in the sense that it will only ever return True if the input is a Wires instance.

pennylane/wires.py Outdated Show resolved Hide resolved
pennylane/wires.py Outdated Show resolved Hide resolved
dwierichs and others added 2 commits January 18, 2021 17:33
Co-authored-by: Chase Roberts <keeper6928@gmail.com>
Copy link
Contributor

@chaserileyroberts chaserileyroberts left a comment

Choose a reason for hiding this comment

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

Looks good to me!

Copy link
Contributor

@chaserileyroberts chaserileyroberts left a comment

Choose a reason for hiding this comment

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

Looks good to me!

@chaserileyroberts chaserileyroberts merged commit 1fa2bc4 into PennyLaneAI:master Jan 18, 2021
@chaserileyroberts
Copy link
Contributor

chaserileyroberts commented Jan 18, 2021

This PR had awesome benchmark improvements. Amazing work!


       before           after         ratio
     [a2fa39cd]       [1fa2bc44]
     <pull/988/merge~1>       <pull/1008/merge~2>
      8.40±0.06ms       6.02±0.2ms    ~0.72  asv.core_suite.CircuitEvaluation.time_circuit(10, 3)
-      15.8±0.2ms       11.1±0.4ms     0.70  asv.core_suite.CircuitEvaluation.time_circuit(10, 6)
-      22.5±0.2ms       15.7±0.6ms     0.70  asv.core_suite.CircuitEvaluation.time_circuit(10, 9)
      2.12±0.08ms      1.84±0.09ms    ~0.87  asv.core_suite.CircuitEvaluation.time_circuit(2, 3)
      3.16±0.04ms       2.68±0.1ms    ~0.85  asv.core_suite.CircuitEvaluation.time_circuit(2, 6)
       4.09±0.2ms       3.39±0.2ms    ~0.83  asv.core_suite.CircuitEvaluation.time_circuit(2, 9)
       4.02±0.1ms       3.12±0.1ms    ~0.78  asv.core_suite.CircuitEvaluation.time_circuit(5, 3)
-      6.80±0.1ms       4.90±0.3ms     0.72  asv.core_suite.CircuitEvaluation.time_circuit(5, 6)
      9.56±0.08ms       7.34±0.3ms    ~0.77  asv.core_suite.CircuitEvaluation.time_circuit(5, 9)
       10.0±0.6ms       7.86±0.4ms    ~0.78  asv.core_suite.GradientComputation.time_gradient_autograd(2, 3)
       28.8±0.3ms         21.3±1ms    ~0.74  asv.core_suite.GradientComputation.time_gradient_autograd(2, 6)
-      55.5±0.8ms         38.6±2ms     0.70  asv.core_suite.GradientComputation.time_gradient_autograd(5, 3)
-         195±1ms          139±2ms     0.71  asv.core_suite.GradientComputation.time_gradient_autograd(5, 6)
-      14.1±0.6ms       10.5±0.2ms     0.75  asv.core_suite.GradientComputation.time_gradient_tf(2, 3)
-      34.7±0.6ms       26.7±0.1ms     0.77  asv.core_suite.GradientComputation.time_gradient_tf(2, 6)
-        61.0±2ms       45.5±0.4ms     0.75  asv.core_suite.GradientComputation.time_gradient_tf(5, 3)
-         210±1ms          149±2ms     0.71  asv.core_suite.GradientComputation.time_gradient_tf(5, 6)
       10.0±0.1ms       7.86±0.3ms    ~0.78  asv.core_suite.GradientComputation.time_gradient_torch(2, 3)
-      28.2±0.2ms       21.0±0.1ms     0.74  asv.core_suite.GradientComputation.time_gradient_torch(2, 6)
-        57.7±2ms         38.2±1ms     0.66  asv.core_suite.GradientComputation.time_gradient_torch(5, 3)
-         204±5ms          138±3ms     0.68  asv.core_suite.GradientComputation.time_gradient_torch(5, 6)

@dwierichs
Copy link
Contributor Author

Very happy to see that it systematically improved the performance! Thank you for reviewing and benchmarking!

@dwierichs dwierichs deleted the Wires_as_Sequence_with_new_indexing branch January 18, 2021 20:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants