-
Notifications
You must be signed in to change notification settings - Fork 41
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
Replace old backend with new #67
Conversation
Codecov Report
@@ Coverage Diff @@
## master #67 +/- ##
===========================================
+ Coverage 62.29% 98.03% +35.74%
===========================================
Files 5 3 -2
Lines 122 51 -71
===========================================
- Hits 76 50 -26
+ Misses 46 1 -45
Continue to review full report at Codecov.
|
if any(isinstance(r, QubitUnitary)for r in rotations): | ||
super().apply(operations=[], rotations=rotations) | ||
else: | ||
self._state = self.apply_lightning(np.copy(self._pre_rotated_state), rotations) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ThomasLoke - this is an example of a situation where we'd like to pass an arbitrary matrix to the C++ version of apply()
.
Here, we suppose someone wants to make an arbitrary Hermitian
measurement. In PL, this is converted to a diagonalizing unitary and computational basis measurement. The diagonalizing unitary is passed as rotations
at the end of the circuit, which in this case requires QubitUnitary
.
We don't currently support QubitUnitary
because we get an error when serializing op_param
, which is a list containing a NumPy array rather than a list of floats.
Being able to pass arbitrary unitaries to the backend would also allow us to directly support QubitUnitary
and also new gates like QFT.
Wanted to get your thoughts on how feasible it would be to extend support? Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Being able to pass arbitrary unitaries to the backend would also allow us to directly support QubitUnitary
For gates that need to be defined by a unitary that can't be known ahead of time, passing in the unitary directly is fine.
also new gates like QFT.
AFAIK, the QFT matrix is just parameterised by its dimension, so I'm not sure I see the need to pass in the matrix directly.
In general, if we can get away with an optimised kernel for gate application, we should. But that's only easily possible for gates that we know ahead of time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For gates that need to be defined by a unitary that can't be known ahead of time, passing in the unitary directly is fine.
Nice! To clarify, this isn't currently supported right? At least, I get an error if I try:
TypeError: apply(): incompatible function arguments. The following argument types are supported:
E 1. (arg0: numpy.ndarray[complex128], arg1: List[str], arg2: List[List[int]], arg3: List[List[float]], arg4: int) -> None
Since we require params
to be composed of lists of floats.
AFAIK, the QFT matrix is just parameterised by its dimension, so I'm not sure I see the need to pass in the matrix directly.
In general, if we can get away with an optimised kernel for gate application, we should. But that's only easily possible for gates that we know ahead of time.
Agreed! I guess what I'm getting at is that we just added QFT
to PennyLane, and there will inevitably be a gap between gates supported in PL and gates with a defined matrix in the backend. It would be nice for us to have a pipeline to just support any gate that has a matrix method, and we can optionally add in the kernel to the backend if we think it's a popular gate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, this isn't currently supported right
Yes, that is correct. In theory, it could be made to work with the current pybind bindings, by encoding the matrix in the vector<double> params
as a flattened array of doubles, but there's little benefit of doing so; modifying the bindings is a trivial thing to do.
It would be nice for us to have a pipeline to just support any gate that has a matrix method, and we can optionally add in the kernel to the backend if we think it's a popular gate.
Agreed. Conceivably, we could just delegate any unrecognised gate type to the supplied matrix (and error if that's not provided). However, I am wary about always needing to pay the cost of generating the matrix and passing it to the backend, if we don't have to. I don't expect it to be a large penalty (unless we end up supporting controlled unitary operations with arbitrarily many controls), but I'd rather we do without it if possible.
So I think that at the end of the day, what we really want is not to pass the numpy array of the matrix to the backend directly, but rather to pass a functional that when invoked, generates the numpy array for the matrix. This will allow us to lazily generate the matrix if required, but otherwise delegate to the optimised kernel.
.github/workflows/wheels.yml
Outdated
@@ -9,7 +9,7 @@ env: | |||
CIBW_SKIP: "cp27-* cp34-* cp35-* *i686 pp* *win32" | |||
|
|||
# Linux specific build settings | |||
|
|||
# TODO - remove eigen |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe worth merging lines 46 and 47 to check that this PR would indeed fix #60. That separation of tests execution was the hotfix for the issue.
@@ -65,15 +65,13 @@ Note that subsequent calls to ``pip install -e .`` will use cached binaries stor | |||
The following dependencies are required to install PennyLane-Lightning: | |||
|
|||
* A C++ compiler, such as ``g++``, ``clang``, or ``MSVC``. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we mention we want version 2014 and above now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So far the C++14 features that are used by the new backend could be replaced by a custom function. So we could stay with C++11 for now (unless another need arises).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good so far! 💯 Will do another round after tidying. :)
pennylane_lightning/src/Apply.cpp
Outdated
#include "Apply.hpp" | ||
#include "Gates.hpp" | ||
#include "StateVector.hpp" | ||
#include "Util.hpp" | ||
#include "Util.cpp" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#include "Util.cpp" |
CC = g++ | ||
CFLAGS = -std=c++11 -g -Wall -fopenmp -fPIC -I/usr/include \ | ||
-I../include -I$(EIGEN_INCLUDE_DIR) -I$(GOOGLETEST_DIR)/include \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🥳
@@ -1212,42 +1212,3 @@ def test_pauliz_hadamard(self, theta, phi, varphi, monkeypatch, qubit_device_3_w | |||
- 2 * np.cos(theta) * np.sin(phi) * np.sin(2 * varphi) | |||
) / 4 | |||
assert np.allclose(var, expected, atol=tol, rtol=0) | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🥳
@@ -6,27 +6,11 @@ on: | |||
env: | |||
CIBW_SKIP: "cp27-* cp34-* cp35-* *i686 pp* *win32" | |||
|
|||
# Linux specific build settings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feedback on the changes here is much appreciated!
@@ -138,7 +116,10 @@ def apply(self, operations, rotations=None, **kwargs): | |||
self._pre_rotated_state = self._state | |||
|
|||
if rotations: | |||
self._state = self.apply_lightning(self._pre_rotated_state, rotations) | |||
if any(isinstance(r, QubitUnitary)for r in rotations): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of the changes in this file are just replacing the old backend with the new, but these lines are also new and separate from the new backend. The idea is to quickly hack-in support for returning Hermitian
. This can be done more properly later based on the discussion here.
# State preparation is currently done in Python | ||
if operations: # make sure operations[0] exists | ||
if isinstance(operations[0], QubitStateVector): | ||
self._apply_state_vector(operations[0].parameters[0], operations[0].wires) | ||
self._apply_state_vector(operations[0].parameters[0].copy(), operations[0].wires) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is due to #81
if any(isinstance(r, QubitUnitary) for r in rotations): | ||
super().apply(operations=[], rotations=rotations) | ||
else: | ||
self._state = self.apply_lightning(np.copy(self._pre_rotated_state), rotations) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is getting me wondering: each time we call self.apply_lightning
, we are doing conversion between Python and C++. If we combined operations
and rotations
, then we could get away with only a single conversion. In exchange, it would not be so simple to obtain the _pre_rotated_state
as before.
Not quite something for this PR, just worth keeping in mind. 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed! Could we just return the pre-rotated state and the post-rotated state? Also I recall some discussion on the rotations API changing, but can't remember the decision?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we just return the pre-rotated state and the post-rotated state?
We should be able to, though it might require some thought to 1. still minimize the number of copies made of the state vector 2. keep the code readable.
A naive way would be to make the bound apply
C++ function return the pre-rotated state and change the state vector in place to yield the post-rotated state. However, wondering if such a function would lead to much confusion because we'd basically return an intermediate result of the computation.
Also unsure at this moment on how much overhead we're actually incurring from calling on bound C++ functions twice.
Also I recall some discussion on the rotations API changing, but can't remember the decision?
Not quite sure about this one. 🤔
if any(isinstance(r, QubitUnitary) for r in rotations): | ||
super().apply(operations=[], rotations=rotations) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So basically this allowed supporting Hermitian
, correct?
There was a problem hiding this 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! 💯 🍾
🎉 |
Fixes #60