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

Add Carry and Sum Operators #1169

Merged
merged 56 commits into from
Apr 20, 2021
Merged

Add Carry and Sum Operators #1169

merged 56 commits into from
Apr 20, 2021

Conversation

DSGuala
Copy link
Contributor

@DSGuala DSGuala commented Mar 25, 2021

Context:
Carry and sum operators are used to build quantum adders. These can be useful in various applications e.g. Shor's algorithm.

Description of the Change:
Added the basic QubitCarry and QubitSum operators to qubit.py

Benefits:
Will enable basic summation operations.

Possible Drawbacks:
The user still needs to organize the carry and sum operators to perform multi-bit additions.
There may be more optimal summation methods depending on the application (e.g. by using the quantum fourier transform)

@DSGuala DSGuala added the WIP 🚧 Work-in-progress label Mar 25, 2021
@codecov
Copy link

codecov bot commented Mar 25, 2021

Codecov Report

Merging #1169 (a902d99) into master (cb4d2cc) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1169   +/-   ##
=======================================
  Coverage   98.13%   98.13%           
=======================================
  Files         145      145           
  Lines       10984    11010   +26     
=======================================
+ Hits        10779    10805   +26     
  Misses        205      205           
Impacted Files Coverage Δ
pennylane/devices/default_mixed.py 100.00% <ø> (ø)
pennylane/devices/default_qubit.py 100.00% <ø> (ø)
pennylane/ops/qubit.py 98.34% <100.00%> (+0.04%) ⬆️

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 cb4d2cc...a902d99. Read the comment docs.

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 @DSGuala, this PR is progressing nicely!

.github/CHANGELOG.md Outdated Show resolved Hide resolved
.github/CHANGELOG.md Outdated Show resolved Hide resolved
.github/CHANGELOG.md Outdated Show resolved Hide resolved
.github/CHANGELOG.md Outdated Show resolved Hide resolved
.github/CHANGELOG.md Outdated Show resolved Hide resolved
pennylane/ops/qubit.py Outdated Show resolved Hide resolved
* Gradient recipe: TODO

Args:
wires (Union[Wires or int]): the wires the operation acts on
Copy link
Contributor

Choose a reason for hiding this comment

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

There are two ways we could think about this:

  • Either we just let the user pass in wires (must be 4 of them)
  • We have 4 separate arguments, e.g., input carry, output carry, input A, input B

I'm in favour of the first option, but as long as we make it clear which wire does what. This is similar to CNOT, where we have a single wires argument.
image

However, in going for the first option, we'll have to be more careful when we make an adder between two registers, since this Carry operation will work on a combination of those registers.

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 also prefer the first option. I think it should be okay as long as we are clear about which wires do what.

pennylane/ops/qubit.py Outdated Show resolved Hide resolved
pennylane/ops/qubit.py Outdated Show resolved Hide resolved
pennylane/ops/qubit.py Outdated Show resolved Hide resolved
DSGuala and others added 8 commits March 26, 2021 08:06
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Update default.mixed
Add expand()
.github/CHANGELOG.md Outdated Show resolved Hide resolved
.github/CHANGELOG.md Outdated Show resolved Hide resolved
.github/CHANGELOG.md Outdated Show resolved Hide resolved
DSGuala and others added 8 commits April 5, 2021 11:07
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Updated docstrings
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 @DSGuala! I have requested some changes.

pennylane/ops/qubit.py Outdated Show resolved Hide resolved
@@ -0,0 +1,451 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Contributor

Choose a reason for hiding this comment

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

On second thoughts, maybe doc/_static/templates/carry/ and doc/_static/templates/sum might be better spots for this content.

Although they are ops, they are more familiar conceptually as a template 👍

Copy link
Member

Choose a reason for hiding this comment

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

Question: if carry is thought more as a template than an op, should it be in the templates module? In addition, would we add it to the templates quickstart gallery?

Going even further, where do we draw the line between an op and a template? Are ops what we think of when we imagine low-level device gate sets (X, Y, RX, CNOT, CSWAP), whereas a template is a larger operation designed with a specific purpose in mind?

Copy link
Member

Choose a reason for hiding this comment

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

🤔 Thinking over my last comment:

  • Operations are general purpose unitaries, often relating to the underlying device architecture, that must be combined to build up problem-specific unitaries.

  • Templates are larger unitaries designed for specific algorithms or applications

Is this the distinction we want to draw?

I think this is important to consider, as it determines whether the source code lives in pennylane/templates or pennylane/ops/qubit.py (which is getting very crowded!)

@mariaschuld might also have some thoughts here.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a very good question 🤔 and the line can indeed be blurred.

Could we define a template to be a unitary where only the expand() method is known? At least for Carry and Sum, we know the matrix representation too.

But yeah, it's not fully clear where the line should be. The original motivation to put these as ops was:

  • We'll be using them as primitives within something that is definitely a template: the Adder circuit
  • Similar precedents are already ops, e.g., CRot has a matrix and decomposition, but is unlikely to be used directly on hardware (?)
  • Doesn't seem to fit what I expect from a template, e.g., is not "scalable" (i.e., can have variable depth/width) and doesn't have input parameters.

Copy link
Member

Choose a reason for hiding this comment

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

Could we define a template to be a unitary where only the expand() method is known? At least for Carry and Sum, we know the matrix representation too.

I'm not sure this makes sense either - there is nothing stopping a device from declaring that it supports an Op directly, causing the expand to be avoided. And, the matrix property is just for convenience, most devices ignore it.

I think the main difference will essentially come down to a conceptual difference, and will likely be flexible.

@@ -0,0 +1,451 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="531.153pt" height="66.041pt" viewBox="0 0 531.153 66.041" version="1.1">
Copy link
Contributor

Choose a reason for hiding this comment

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

image
Are we sure about the bottom-right? I don't have a reason to think it's wrong, but just want to double check.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ohhh so that's the carried value?

Copy link
Contributor Author

@DSGuala DSGuala Apr 12, 2021

Choose a reason for hiding this comment

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

The bottom right comes from this paper and is indeed the carried value. Perhaps there is a clearer way to write it. Just |C> does not seem clear enough

@@ -0,0 +1,451 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="531.153pt" height="66.041pt" viewBox="0 0 531.153 66.041" version="1.1">
<defs>
Copy link
Contributor

Choose a reason for hiding this comment

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

image
For me, just the RHS one is sufficient

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perfect, will change this.

pennylane/ops/qubit.py Outdated Show resolved Hide resolved
pennylane/devices/default_qubit.py Outdated Show resolved Hide resolved
tests/ops/test_qubit_ops.py Show resolved Hide resolved
Comment on lines 2092 to 2096
([0, 1, 2, 3], "0000", "0000"),
([0, 1, 2, 3], "0010", "0010"),
([0, 1, 2, 3], "0100", "0110"),
([0, 1, 2, 3], "0110", "0101"),
([0, 1, 2, 3], "1010", "1011"),
Copy link
Contributor

Choose a reason for hiding this comment

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

I like these! Ideally it would be good to cover all 16 eventualities, and confirm by hand that the second bitstring is indeed the carry.

Copy link
Contributor

Choose a reason for hiding this comment

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

Going over all 16 eventualities + changing to the tape mode with and without expansion should be a comprehensive enough set of tests (and same for Sum)

Copy link
Contributor

Choose a reason for hiding this comment

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

I like these! Ideally it would be good to cover all 16 eventualities, and confirm by hand that the second bitstring is indeed the carry.

Also that the sum is the correct one, right?

Comment on lines 2097 to 2098
([3, 1, 2, 0], "0110", "1100"),
([3, 2, 0, 1], "1010", "0110"),
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice to permute!

([3, 2, 0, 1], "1010", "0110"),
],
)
def test_carry(self, wires, input_string, output_string):
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be good to test the expand() method. To do this, I suggest here having a parametrize over expand = True/False.

Then, below, instead of using

@qml.qnode(dev)
def circuit():
   ...
   return qml.probs()

You can do

with qml.tape.QuantumTape() as tape:
    ...
    qml.probs()

This is using the lower level tape functionality, which gives us control over expansion.

To execute, we'd then do

dev.execute(tape)

So, when it comes to expanding, before executing we'd do tape = tape.expand(), which will then allow us to test if the expansion is correct.

DSGuala and others added 4 commits April 9, 2021 17:14
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Co-authored-by: Tom Bromley <49409390+trbromley@users.noreply.github.com>
Copy link
Contributor

@ixfoduap ixfoduap 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 so far!

My main comments are regarding docstrings, which I believe could be improved for further clarity

.github/CHANGELOG.md Outdated Show resolved Hide resolved

class Carry(Operation):
r"""Carry(wires)
Apply the ``Carry`` operation to the input wires.
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm left confused after reading the docstring. Can we provide a short intuitive explanation of what this operation does? Maybe with a simple but explicit example?

Copy link
Contributor

Choose a reason for hiding this comment

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

Is it correct to say that this operation performs addition of two bit values and stores the value that is carried over in a separate qubit? So for example it would map |0>|1>|1>|0>-->|1>|1>|0>|1>? I'm still confused about the role of the fourth qubit... 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes! I will be improving the docstrings. The carry operation would map |0>|1>|1>|0> --> |0>|1>|0>|1>. The fourth qubit takes the carried value. I think the confusion here comes from the fact that Carry is a primitive operation for an Adder template. The fourth qubit actually takes the carry value of the first three qubits.

pennylane/ops/qubit.py Outdated Show resolved Hide resolved
pennylane/ops/qubit.py Outdated Show resolved Hide resolved
pennylane/ops/qubit.py Outdated Show resolved Hide resolved
wires (Union[Wires or int]): the wires the operation acts on

**Example**
TODO
Copy link
Contributor

Choose a reason for hiding this comment

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

Should add extra text explaining that qubit 3 is the carry qubit, otherwise it may be confusing why we apply Sum to four qubits only to sum three of them


class Sum(Operation):
r"""Sum()
Apply a ``Sum`` operation on the input wires.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not really accurate, is it? The operation is only summing up two of the wires, not all of them, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hopefully this is clearer in the new version. For sum, it is indeed the modulo 2 sum of all input wires

wires (Union[Wires or int]): the wires the operation acts on

**Example**
TODO
Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, the operation only acts on three wires, right? So this should be wires=[0,1,2] and explain that one of the wires is a carry qubit

wires (Union[Wires or int]): the wires the operation acts on

**Example**
TODO
Copy link
Contributor

Choose a reason for hiding this comment

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

Finally, these docstrings could also benefit from the example actually performing a specific sum, e.g. 1+1 = 0. And it would be good to explain everything is done modulo 2

Comment on lines 2092 to 2096
([0, 1, 2, 3], "0000", "0000"),
([0, 1, 2, 3], "0010", "0010"),
([0, 1, 2, 3], "0100", "0110"),
([0, 1, 2, 3], "0110", "0101"),
([0, 1, 2, 3], "1010", "1011"),
Copy link
Contributor

Choose a reason for hiding this comment

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

I like these! Ideally it would be good to cover all 16 eventualities, and confirm by hand that the second bitstring is indeed the carry.

Also that the sum is the correct one, right?

DSGuala and others added 3 commits April 15, 2021 10:11
* Update imgs

* Add to changelog

* Add to docs

* Update docs

* Update expand

* Add to docs

* Update example

* Typo

* Add subtlety

* Add

* Update docstring

* Update docs

* Respond to suggestion
pennylane/ops/qubit.py Outdated Show resolved Hide resolved
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 @DSGuala!

@trbromley trbromley added do not merge ⚠️ Do not merge the pull request until this label is removed merge-ready ✔️ All tests pass and the PR is ready to be merged. labels Apr 15, 2021
@josh146 josh146 removed the do not merge ⚠️ Do not merge the pull request until this label is removed label Apr 20, 2021
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 @DSGuala! Ready to merge - would be good to do one quick last check of the docs to see if everything is still rendering nicely.

@@ -401,7 +401,13 @@ def test_adjoint_unitaries(self, op, tol):
np.testing.assert_allclose(res2, np.eye(2 ** len(op.wires)), atol=tol)
assert op.wires == op_d.wires
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add QubitSum to this test aswell?

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.

Just add the test I mentioned earlier

@DSGuala DSGuala merged commit 4c6722d into master Apr 20, 2021
@DSGuala DSGuala deleted the carry_sum branch April 20, 2021 19:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
merge-ready ✔️ All tests pass and the PR is ready to be merged.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants