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

Use templates to simplify applying operations to tensors #52

Merged
merged 11 commits into from
Jan 12, 2021

Conversation

ThomasLoke
Copy link
Contributor

@ThomasLoke ThomasLoke commented Jan 2, 2021

Also extends support from 16 qubits to 50 qubits (its unlikely anyone would have memory on the order of petabytes).

@codecov
Copy link

codecov bot commented Jan 2, 2021

Codecov Report

Merging #52 (3171839) into master (2eac157) will not change coverage.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff            @@
##            master       #52   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            3         3           
  Lines           52        52           
=========================================
  Hits            52        52           
Impacted Files Coverage Δ
pennylane_lightning/lightning_qubit.py 100.00% <100.00%> (ø)

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 2eac157...3171839. Read the comment docs.

@josh146
Copy link
Member

josh146 commented Jan 4, 2021

Thanks @ThomasLoke for this contribution! Someone from the team will get back to you shortly with a code review

Copy link
Contributor

@antalszava antalszava left a 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:

  1. Updating the Python wrapper to allow >16 qubits
  2. Adjusting a couple of docstrings

The rest of the comments are minor suggestions/questions for my own understanding.

@@ -0,0 +1,26 @@
#pragma once
Copy link
Contributor

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).

Suggested change
#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

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&);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
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&);

Copy link
Contributor Author

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?

Copy link
Member

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 😆 )

image

Copy link
Contributor Author

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);
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
};
};

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");
Copy link
Contributor

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

The max number of wires will have to be updated there too.

Comment on lines 47 to 49
* Note that only up to 16 qubits are currently supported. This limitation is due to the Eigen
* Tensor library not supporting dynamically ranked tensors.
*
Copy link
Contributor

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)
Copy link
Contributor

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.

Suggested change
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
Copy link
Contributor

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...);
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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. 🤔

Copy link
Contributor Author

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.

Copy link
Contributor

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!

Comment on lines 7 to 26
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&);
Copy link
Contributor

Choose a reason for hiding this comment

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

🥳

Comment on lines +41 to +43
if (qubits <= 0)
throw std::invalid_argument("Must specify one or more qubits");

Copy link
Contributor

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.

Copy link
Contributor Author

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).

@ThomasLoke
Copy link
Contributor Author

Updating the Python wrapper to allow >16 qubits

🤦 My mistake, I forgot to test the Python side of things. I'll address that and the other review changes.

@ThomasLoke
Copy link
Contributor Author

ThomasLoke commented Jan 6, 2021

Failing test:

self = <LightningQubit device (wires=51, shots=1000) at 0x7fdfd4480350>
...
numpy.core._exceptions._ArrayMemoryError: Unable to allocate 32.0 PiB for an array with shape (2251799813685248,) and data type complex128

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?

@chaserileyroberts
Copy link

Failing test:

LightningQubit device wires=51

Unable to allocate 32.0 PiB

for an array with shape 2251799813685248

Which is unsurprising.

It's reads like a poem 😆

@antalszava
Copy link
Contributor

antalszava commented Jan 7, 2021

Which is unsurprising. Not sure what I should do with this--remove the test?

After importing import pennylane_lightning in the test file, this should do it:

    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)

Also I've not worked with Black before--what did I miss?

The tool uses black-20.8b1, which version do you have locally? Running black -l 100 pennylane_lightning/lightning_qubit.py would do the reformatting.

@josh146
Copy link
Member

josh146 commented Jan 7, 2021

It's reads like a poem 😆

like an accidental haiku

Comment on lines 351 to 353
* 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
Copy link
Contributor

@antalszava antalszava Jan 11, 2021

Choose a reason for hiding this comment

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

Suggested change
* 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

Comment on lines 372 to 373
* Terminal specialised template for general multi-qubit operations
* @tparam Dim The number of qubits (i.e. tensor rank)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* 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)

Comment on lines 392 to 393
* Terminal specialised template for single qubit operations
* @tparam ValueIdx Ignored, but required to specialised the main recursive template
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* 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

Comment on lines 412 to 413
* Terminal specialised template for two qubit operations
* @tparam ValueIdx Ignored, but required to specialised the main recursive template
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* 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

Comment on lines 432 to 433
* Generic interface that invokes the generator to generate the desired multi-qubit operation
* @tparam Dim The number of qubits (i.e. tensor rank)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* 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)

Comment on lines 1 to 10
// 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.
Copy link
Contributor

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.

Suggested change
// 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.

Comment on lines 96 to 98
"The number of wires exceeds {}, reverting to NumPy-based evaluation.".format(
self._MAX_WIRES
),
Copy link
Contributor

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

Suggested change
"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."

Copy link
Contributor

@antalszava antalszava 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! 💯 This is a great change @ThomasLoke, thank you! 🙂

Left some minor suggestions, once they are addressed this is merge-ready from my side.

Copy link
Contributor

@trbromley trbromley left a comment

Choose a reason for hiding this comment

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

Thanks @ThomasLoke!

@antalszava antalszava merged commit 14b50f9 into PennyLaneAI:master Jan 12, 2021
@ThomasLoke
Copy link
Contributor Author

Thanks everyone ^_^

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.

5 participants