-
Notifications
You must be signed in to change notification settings - Fork 42
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
Use templates to simplify applying operations to tensors #52
Conversation
Codecov Report
@@ Coverage Diff @@
## master #52 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 3 3
Lines 52 52
=========================================
Hits 52 52
Continue to review full report at Codecov.
|
Thanks @ThomasLoke for this contribution! Someone from the team will get back to you shortly with a code review |
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.
Hi @ThomasLoke, this is a really exciting improvement, thank you so much! 😍
The code looks way neater! 🎊
Two things stood out as further changes needed:
- Updating the Python wrapper to allow
>16
qubits - Adjusting a couple of docstrings
The rest of the comments are minor suggestions/questions for my own understanding.
@@ -0,0 +1,26 @@ | |||
#pragma once |
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.
Might be worth adding the license and a description of the module (see pennylane_lightning/src/lightning_qubit.hpp
as an example).
#pragma once | |
// Copyright 2020 Xanadu Quantum Technologies Inc. | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
#pragma once |
pennylane_lightning/src/typedefs.hpp
Outdated
using pfunc_Xq_one_param = Gate_Xq<X>(*)(const double&); | ||
|
||
template<int X> | ||
using pfunc_Xq_three_params = Gate_Xq<X>(*)(const double&, const double&, const double&); |
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.
using pfunc_Xq_three_params = Gate_Xq<X>(*)(const double&, const double&, const double&); | |
using pfunc_Xq_three_params = Gate_Xq<X>(*)(const double&, const double&, const double&); | |
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.
I've never been a fan of an extra line of whitespace at the end of a file. I don't mind adding it, but I thought I'd ask why the preference?
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.
Apparently it's just a POSIX convention: https://stackoverflow.com/a/729795/10958457
But it's such a strong convention these days, that all linters will complain if it is missing (even github lol 😆 )
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.
Aha, fair enough! I guess I'll modify my preferences then :)
{ | ||
return QubitOperationsGenerator<Dim, Dim>::apply(state, ops, wires, params); | ||
} | ||
}; |
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.
}; | |
}; | |
case 48: return QubitOperations<48>::apply(state, ops, wires, params); | ||
case 49: return QubitOperations<49>::apply(state, ops, wires, params); | ||
case 50: return QubitOperations<50>::apply(state, ops, wires, params); | ||
default: throw std::invalid_argument("No support for > 50 qubits"); |
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.
💯
With the PennyLane UI this is checked in Python, see
_MAX_WIRES = 16 |
The max number of wires will have to be updated there too.
* Note that only up to 16 qubits are currently supported. This limitation is due to the Eigen | ||
* Tensor library not supporting dynamically ranked tensors. | ||
* |
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 keeping this but with 50?
const vector<string>& ops, | ||
const vector<vector<int>>& wires, | ||
const vector<vector<float>>& params, | ||
Values... values) |
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.
(Minor suggestion): just to conform to with the apply_ops
function signature. If accepted, further applies in the signatures below.
Values... values) | |
Shape... shape) |
|
||
return Map<VectorXcd> (shuffled_evolved_tensor.data(), shuffled_evolved_tensor.size(), 1); | ||
} | ||
|
||
// Template classes for a generic interface for qubit operations |
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.
It might be good to make a class docstring for the generic class (which can be referenced by specialized classes). The docstring could also include the description of template parameters (this is not used in other docstrings in the code base right now, but would actually be useful to be adapted).
const vector<vector<float>>& params, | ||
Values... values) | ||
{ | ||
return QubitOperationsGenerator<Dim, ValueIdx - 1>::apply(state, ops, wires, params, 2, values...); |
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.
Would we want to restrict the ranges of Dim
and ValueIdx
? E.g., what happens for ValueIdx==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.
The ValueIdx==0
case is handled by the specialisation:
// Generic multi-qubit case
template<int Dim>
class QubitOperationsGenerator<Dim, 0>
which is how the recursive template terminates. I could add a runtime check here, but it seems unnecessary especially since it's going to be checked N
times if ValueIdx
starts at N
due to recursion. Unless there's a way of statically enforcing ValueIdx >= 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.
Right! Was curious about the ValueIdx==0
and return QubitOperationsGenerator<Dim, -1>::apply(state, ops, wires, params, 2, values...);
case (and further negative values as the second template argument).
it seems unnecessary especially since it's going to be checked N times if ValueIdx starts at N due to recursion
Yeah, the current way of using it wouldn't result in ValueIdx <1
. Was curious what would happen if such a ValueIdx
is inputted explicitly.
Unless there's a way of statically enforcing ValueIdx >= 0?
Hmm, think a runtime check would be applicable in such a case. 🤔
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.
Was curious what would happen if such a ValueIdx is inputted explicitly.
Because templates are all determined at compile-time, I believe it'll recurse up until the compiler's template recursion limit, at which point it'll fail to compile. So in theory, it shouldn't even compile.
Of course, we might at some point hit the template recursion limit for really large numbers of qubits. At least on MSVC, its larger than 50. If we do cross that bridge, a more scalable approach will be needed that doesn't involve Eigen tensors with an arbitrary rank.
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.
That's right, just checked it and it indeed runs into compile-time problems. Thanks for the clarification!
pennylane_lightning/src/typedefs.hpp
Outdated
template<int X> | ||
using State_Xq = Eigen::Tensor<std::complex<double>, X>; | ||
|
||
template<int X> | ||
using Gate_Xq = Eigen::Tensor<std::complex<double>, 2 * X>; | ||
|
||
using Pairs = Eigen::IndexPair<int>; | ||
template<int X> | ||
using Pairs_Xq = std::array<Eigen::IndexPair<int>, X>; | ||
|
||
// Creating aliases based on the function signatures of each operation | ||
|
||
template<int X> | ||
using pfunc_Xq = Gate_Xq<X>(*)(); | ||
|
||
template<int X> | ||
using pfunc_Xq_one_param = Gate_Xq<X>(*)(const double&); | ||
|
||
template<int X> | ||
using pfunc_Xq_three_params = Gate_Xq<X>(*)(const double&, const double&, const double&); |
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.
🥳
if (qubits <= 0) | ||
throw std::invalid_argument("Must specify one or more qubits"); | ||
|
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.
(Minor suggestion): could add a small unit test here and for qubits > 50
.
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.
Thought about this, but I didn't feel like it was worth the hassle of maintaining (especially trying to fake a 50+ qubit invocation of the method; I could just put invalid parameters in, but I wouldn't blame the test for failing for any other reason).
🤦 My mistake, I forgot to test the Python side of things. I'll address that and the other review changes. |
Failing test:
Which is unsurprising. Not sure what I should do with this--remove the test? Also I've not worked with Black before--what did I miss? |
It's reads like a poem 😆 |
After importing def test_warning(self, monkeypatch):
"""Test that a warning is produced when lightning.qubit is instantiated with more than
the supported number of qubits"""
class MockDefaultQubit:
"""Mock DefaultQubit class."""
def __init__(self, wires, *args, **kwargs):
"""Mock __init__ method."""
self.num_wires = wires
with monkeypatch.context() as m:
# Mock the DefaultQubit class to avoid an extensive memory allocation
m.setattr(pennylane_lightning.lightning_qubit.DefaultQubit, "__init__", MockDefaultQubit.__init__)
with pytest.warns(UserWarning, match="The number of wires exceeds"):
pennylane_lightning.lightning_qubit.LightningQubit(wires=LightningQubit._MAX_WIRES + 1)
The tool uses |
like an accidental haiku |
* Main recursive template to generate multi-qubit operations | ||
* @tparam Dim The number of qubits (i.e. tensor rank) | ||
* @tparam ValueIdx Index to be decremented recursively until 0 to generate the dimensions of the tensor |
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.
* Main recursive template to generate multi-qubit operations | |
* @tparam Dim The number of qubits (i.e. tensor rank) | |
* @tparam ValueIdx Index to be decremented recursively until 0 to generate the dimensions of the tensor | |
* Main recursive template to generate multi-qubit operations. | |
* | |
* @tparam Dim the number of qubits (i.e. tensor rank) | |
* @tparam ValueIdx index to be decremented recursively until 0 to generate the dimensions of the tensor |
* Terminal specialised template for general multi-qubit operations | ||
* @tparam Dim The number of qubits (i.e. tensor rank) |
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.
* Terminal specialised template for general multi-qubit operations | |
* @tparam Dim The number of qubits (i.e. tensor rank) | |
* Terminal specialised template for general multi-qubit operations. | |
* | |
* @tparam Dim the number of qubits (i.e. tensor rank) |
* Terminal specialised template for single qubit operations | ||
* @tparam ValueIdx Ignored, but required to specialised the main recursive template |
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.
* Terminal specialised template for single qubit operations | |
* @tparam ValueIdx Ignored, but required to specialised the main recursive template | |
* Terminal specialised template for single qubit operations. | |
* | |
* @tparam ValueIdx ignored but required to specialise the main recursive template |
* Terminal specialised template for two qubit operations | ||
* @tparam ValueIdx Ignored, but required to specialised the main recursive template |
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.
* Terminal specialised template for two qubit operations | |
* @tparam ValueIdx Ignored, but required to specialised the main recursive template | |
* Terminal specialised template for two qubit operations. | |
* | |
* @tparam ValueIdx ignored but required to specialise the main recursive template |
* Generic interface that invokes the generator to generate the desired multi-qubit operation | ||
* @tparam Dim The number of qubits (i.e. tensor rank) |
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.
* Generic interface that invokes the generator to generate the desired multi-qubit operation | |
* @tparam Dim The number of qubits (i.e. tensor rank) | |
* Generic interface that invokes the generator to generate the desired multi-qubit operation. | |
* | |
* @tparam Dim the number of qubits (i.e. tensor rank) |
pennylane_lightning/src/typedefs.hpp
Outdated
// Copyright 2020 Xanadu Quantum Technologies Inc. | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. |
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.
Worth adding the whitespace as for other files.
// Copyright 2020 Xanadu Quantum Technologies Inc. | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
// Copyright 2020 Xanadu Quantum Technologies Inc. | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. |
"The number of wires exceeds {}, reverting to NumPy-based evaluation.".format( | ||
self._MAX_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.
Could use an f string here
"The number of wires exceeds {}, reverting to NumPy-based evaluation.".format( | |
self._MAX_WIRES | |
), | |
f"The number of wires exceeds {self._MAX_WIRES}, reverting to NumPy-based evaluation." |
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! 💯 This is a great change @ThomasLoke, thank you! 🙂
Left some minor suggestions, once they are addressed this is merge-ready from my side.
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.
Thanks @ThomasLoke!
Thanks everyone ^_^ |
Also extends support from 16 qubits to 50 qubits (its unlikely anyone would have memory on the order of petabytes).