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 transform for quantum Monte Carlo #1316

Merged
merged 31 commits into from
Jun 4, 2021
Merged

Add transform for quantum Monte Carlo #1316

merged 31 commits into from
Jun 4, 2021

Conversation

trbromley
Copy link
Contributor

@trbromley trbromley commented May 14, 2021

Context:

Adds a quantum Monte Carlo transform. Given an input circuit, the output will be a transformed circuit to do the full QMC algorithm.

Description of the Change:

Adds the transform to the qml.transforms.qmc module, along with supporting functionality.

Benefits:

This transform can be used for resource estimation of the number of gates required to perform quantum Monte Carlo. It can also, in principle, be compatible with a hardware implementation (if a suitable low-noise, high-depth device were available).

Possible Drawbacks:

Potential confusion with the existing QuantumMonteCarlo template, which does the unitary-based version (not compatible with hardware or resource estimation).

The example uses pennylane.templates.state_preparations.mottonen._uniform_rotation_dagger for the R matrix. Should we consider pulling that out and giving a nicer name? Can we use an alternative in the example?

@codecov
Copy link

codecov bot commented May 17, 2021

Codecov Report

Merging #1316 (ce2e393) into master (7bf2d17) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1316   +/-   ##
=======================================
  Coverage   98.17%   98.18%           
=======================================
  Files         155      156    +1     
  Lines       11631    11685   +54     
=======================================
+ Hits        11419    11473   +54     
  Misses        212      212           
Impacted Files Coverage Δ
pennylane/__init__.py 98.57% <ø> (ø)
pennylane/templates/subroutines/qmc.py 100.00% <ø> (ø)
pennylane/transforms/__init__.py 100.00% <100.00%> (ø)
pennylane/transforms/qmc.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 7bf2d17...ce2e393. Read the comment docs.

@trbromley trbromley added this to the v0.16.0 milestone May 31, 2021
@trbromley trbromley changed the title [WIP] Add transform for quantum Monte Carlo Add transform for quantum Monte Carlo Jun 1, 2021
@trbromley trbromley marked this pull request as ready for review June 1, 2021 18:10
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.

Beautiful addition. I'm suggesting minor improvements to the docstrings, otherwise looks good to go from my side, I couldn't see any issues

.github/CHANGELOG.md Show resolved Hide resolved
conventional multi-controlled-Z gate with an additional bit flip on each qubit before and after.

This function performs the multi-controlled-Z gate via a multi-controlled-X gate by picking an
arbitrary target wire to perform the X and adding a Hadamard on that wire either side of the
Copy link
Contributor

Choose a reason for hiding this comment

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

So this works for any target wire? I suppose, but just checking

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, the target wire can be any wire in wires. Here is a sanity check:

import pennylane as qml
import numpy as np

wires = 3
dev = qml.device("default.qubit", wires=wires)
Z = np.zeros((2 ** wires, 2 ** wires))
np.fill_diagonal(Z, 1)
Z[-1, -1] = -1

target_wire = 0
control_wires = set(range(wires)) - set([target_wire])

@qml.qnode(dev)
def find_unitary(state):
    qml.QubitStateVector(state, wires=range(wires))
    
    qml.Hadamard(wires=target_wire)
    qml.MultiControlledX(wires=target_wire, control_wires=control_wires)
    qml.Hadamard(wires=target_wire)
    
    qml.QubitUnitary(Z, wires=range(wires))
    return qml.state()

I = np.eye(2 ** wires)
U = np.real_if_close(np.array([find_unitary(state) for state in I]).T)
assert np.allclose(U, I)

pennylane/transforms/qmc.py Outdated Show resolved Hide resolved
pennylane/transforms/qmc.py Outdated Show resolved Hide resolved
quantum phase estimation (see :class:`~.QuantumPhaseEstimation` for more details).

Args:
fn (Callable): a quantum function that applies quantum operations according to the
Copy link
Contributor

Choose a reason for hiding this comment

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

Any restrictions on this function? Can it have arbitrary inputs and outputs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can have any arguments and keyword arguments. The function itself should enact a quantum circuit, and it shouldn't return anything.

So far, we've been calling these things quantum functions, although I'm not sure how evolved or well-defined the concept is.

Copy link
Member

@josh146 josh146 Jun 7, 2021

Choose a reason for hiding this comment

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

'quantum function' was originally defined on the glossary as a function that applies quantum operations, and returns a measurement statistic. I think it has since been removed, but mention of it remains in the QNode entry:

A quantum node consists of a quantum function (such as a variational circuit), and a device on which it executes.

We've since started using the term a bit more loosely; the original definition of a quantum function was strictly

def qfunc(params):
    quantum_operation()
    quantum_operation()
    quantum_operation()
    return measurement_statistic()

but we have since broadened it (in usage, not strictly in definition) to also include

def qfunc(params):
    quantum_operation()
    quantum_operation()
    quantum_operation()

This second usage is attractive because:

  • It still tends to make sense to users; they still have a Python function that applies quantum operations
  • It is more general than 'ansatz'; you often have a small quantum function that you use but wouldn't want to denote an 'ansatz'.
  • With templates now being Operation objects, it tends to work quite nicely. In the above example, quantum_operation() itself is a quantum function.

pennylane/transforms/qmc.py Outdated Show resolved Hide resolved
algorithm

>>> qtape = qmc.qtape.expand(depth=1)
>>> qtape.get_resources()
Copy link
Contributor

Choose a reason for hiding this comment

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

Whaaat!?? 🤯 🤯 🤯 🤯
I had no idea we could do this. So cool!

Copy link
Member

@josh146 josh146 Jun 4, 2021

Choose a reason for hiding this comment

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

Yes!! But don't advertise this yet as we don't want this to be user facing. Instead, we will be going with the following approaches:

In particular, the ability to do

>>> qml.specs(qmc)(*params) # get all specs
>>> qml.depth(qml)(*params) # get a specific spec

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool! @josh146 do you recommend removing this part then? On the other hand, it's a shame to hide the gate count.

Copy link
Member

Choose a reason for hiding this comment

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

Good question. I would say - leave it for now, but we need to remember to update it when the preferred UI changes 🙂

pennylane/transforms/qmc.py Show resolved Hide resolved
@qml.qnode(dev)
def unitary_z(basis_state):
qml.BasisState(basis_state, wires=range(n_wires))
circ()
Copy link
Contributor

Choose a reason for hiding this comment

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

Wait, what does circ() do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

circ() is the function passed into get_unitary(). It'll be the usual "quantum function" style of applying some PL ops and not returning anything. Then get_unitary() returns the unitary described by circ().

In practice in the tests below, circ() will be, for example the qmc circuit or just the controlled Z.

Copy link
Contributor

Choose a reason for hiding this comment

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

Got it!

Got confused, I thought this was a python function; didn't realize it was an argument to get_unitary

A_wires = [0, "a", -1.1, -10, "bbb"]
target_wire = "Ancilla"
wires = A_wires + [target_wire]
estimation_wires = ["bob", -3, 42, "penny", "lane"]
Copy link
Contributor

Choose a reason for hiding this comment

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

🤣 Maybe this will be the way I remember to test unconventional wire naming: give them fun names!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would be nice to get creative since I've used these names before!

trbromley and others added 4 commits June 4, 2021 12:01
Copy link
Contributor Author

@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 for the review @ixfoduap!

conventional multi-controlled-Z gate with an additional bit flip on each qubit before and after.

This function performs the multi-controlled-Z gate via a multi-controlled-X gate by picking an
arbitrary target wire to perform the X and adding a Hadamard on that wire either side of the
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, the target wire can be any wire in wires. Here is a sanity check:

import pennylane as qml
import numpy as np

wires = 3
dev = qml.device("default.qubit", wires=wires)
Z = np.zeros((2 ** wires, 2 ** wires))
np.fill_diagonal(Z, 1)
Z[-1, -1] = -1

target_wire = 0
control_wires = set(range(wires)) - set([target_wire])

@qml.qnode(dev)
def find_unitary(state):
    qml.QubitStateVector(state, wires=range(wires))
    
    qml.Hadamard(wires=target_wire)
    qml.MultiControlledX(wires=target_wire, control_wires=control_wires)
    qml.Hadamard(wires=target_wire)
    
    qml.QubitUnitary(Z, wires=range(wires))
    return qml.state()

I = np.eye(2 ** wires)
U = np.real_if_close(np.array([find_unitary(state) for state in I]).T)
assert np.allclose(U, I)

quantum phase estimation (see :class:`~.QuantumPhaseEstimation` for more details).

Args:
fn (Callable): a quantum function that applies quantum operations according to the
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It can have any arguments and keyword arguments. The function itself should enact a quantum circuit, and it shouldn't return anything.

So far, we've been calling these things quantum functions, although I'm not sure how evolved or well-defined the concept is.

if not wires.contains_wires(target_wire):
raise ValueError("The target wire must be contained within wires")

@wraps(fn)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's defined here. The idea is to persist the name and docstring of the original function, rather than overwriting it with wrapper.

For example:

from functools import wraps

def fn():
    """Docstring"""
    ...

def wrapper1():
    """Wrong Docstring"""
    fn()
    return 
    
@wraps(fn)
def wrapper2():
    """Wrong Docstring"""
    fn()
    return

The output is:

>>> print("Name of wrapper1:     ", wrapper1.__name__)
Name of wrapper1:      wrapper1
>>> print("Docstring of wrapper1:", wrapper1.__doc__)
Docstring of wrapper1: Wrong Docstring
>>> print("Name of wrapper2:     ", wrapper2.__name__)
Name of wrapper2:      fn
>>> print("Docstring of wrapper2:", wrapper2.__doc__)
Docstring of wrapper2: Docstring

I'd never really used this before, so good chance for me to learn too!

@qml.qnode(dev)
def unitary_z(basis_state):
qml.BasisState(basis_state, wires=range(n_wires))
circ()
Copy link
Contributor Author

Choose a reason for hiding this comment

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

circ() is the function passed into get_unitary(). It'll be the usual "quantum function" style of applying some PL ops and not returning anything. Then get_unitary() returns the unitary described by circ().

In practice in the tests below, circ() will be, for example the qmc circuit or just the controlled Z.

A_wires = [0, "a", -1.1, -10, "bbb"]
target_wire = "Ancilla"
wires = A_wires + [target_wire]
estimation_wires = ["bob", -3, 42, "penny", "lane"]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would be nice to get creative since I've used these names before!

pennylane/transforms/qmc.py Outdated Show resolved Hide resolved
pennylane/transforms/qmc.py Show resolved Hide resolved
@trbromley trbromley requested a review from ixfoduap June 4, 2021 18:39
@qml.qnode(dev)
def unitary_z(basis_state):
qml.BasisState(basis_state, wires=range(n_wires))
circ()
Copy link
Contributor

Choose a reason for hiding this comment

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

Got it!

Got confused, I thought this was a python function; didn't realize it was an argument to get_unitary

@trbromley trbromley merged commit 0dcbed5 into master Jun 4, 2021
@trbromley trbromley deleted the add_qmc_transform branch June 4, 2021 21:10
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

3 participants