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

[WIP] - Maximum Independent Set with DQVA ansatz #386

Open
wants to merge 36 commits into
base: master
Choose a base branch
from

Conversation

pangara
Copy link

@pangara pangara commented Nov 20, 2021

Before submitting

Please complete the following checklist when submitting a PR:

  • Ensure that your tutorial executes correctly, and conforms to the
    guidelines specified in the README.

  • Add a thumbnail link to your tutorial in beginner.rst, or if a
    QML implementation, in implementations.rst.

  • All QML tutorials conform to
    PEP8 standards.
    To auto format files, simply pip install black, and then
    run black -l 100 path/to/file.py.

When all the above are checked, delete everything above the dashed
line and fill in the pull request template.


Title: Maximum Independent Set with DQVA ansatz

Summary:

Relevant references: Approaches to Constrained Quantum Approximate Optimization

Possible Drawbacks:

Related GitHub Issues:

@anthayes92 anthayes92 self-requested a review January 18, 2022 23:08
Copy link

@anthayes92 anthayes92 left a comment

Choose a reason for hiding this comment

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

Amazing work @pangara! ✨ I certainly felt like I understand DQVA better after reading this demo!

I've left some comments and suggestions throughout and admittedly will need to come back to go through the code in more detail

demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
Comment on lines 59 to 63
# infeasible solutions). However, the QAO-Ansatz requires more complex
# quantum circuitry, which in the case of the MIS, manifests itself as
# Multi-Controlled Toffoli gates that require high connectivity between
# qubits. This makes the QAO-Ansatz inadequate for large graphs on current
# near-term quantum computers.

Choose a reason for hiding this comment

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

Great insight! We have found that the large depth of these circuits results in far too much noise on current hardware to get any useful output

Comment on lines 44 to 46
# example, a mixing operator :math:`U_M (\beta)` is decomposed into a
# sequence of partial mixers :math:`U_{M, x}` which may not correspond to
# time evolution under a fixed mixer Hamiltonian :math:`H_M`.

Choose a reason for hiding this comment

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

So here is the idea that the partial mixers correspond to time evolution under components of H_M but not H_M itself? Feels like we could add a sentence to clarify how we do model time evolution for the mixer here

demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
# .. math::
#
#
# \tilde{b}_{v_j} = \frac{1+Z_{v_j}}{2}.

Choose a reason for hiding this comment

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

Again, should be Identity

Copy link
Author

Choose a reason for hiding this comment

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

Same here> The way I understood it was that Z__v_j is PauliZ applied to qubit v_j

demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
Copy link
Contributor

@angusjlowe angusjlowe left a comment

Choose a reason for hiding this comment

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

Awesome work @pangara :) I just finished a pass through the first half of the tutorial and will leave additional comments soon.

demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
@@ -0,0 +1,795 @@
r""".. _dqva_mis:
Copy link
Contributor

Choose a reason for hiding this comment

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

Putting this suggestion on the first line for convenience. This is a bit nitpicky, but the thumbnail for this tutorial does not convey too much information about what to expect. Just stating this in case you have an easy fix; otherwise it's not too important, as other tutorials suffer from this as well.

demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
# parameters. You can learn more about this in `PennyLane's tutorial on
# QAOA <https://pennylane.ai/qml/demos/tutorial_qaoa_intro.html>`__.
#
# For solving constrained combinatorial optimization problems such as maximal independent set (MIS)
Copy link
Contributor

Choose a reason for hiding this comment

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

This comment does not require a change, just food for thought. Unfortunately, the word "constraint" is a bit ambiguous here, since even Max-Cut has "constraints". These are the constraints whose violation decreases the objective function. This is distinct from "hard constraints" which translate to infeasible solutions in the input space (bitstrings). I think the original QAO-Ansatz paper (Hadfield et al.) has some discussion about this point.

demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
Copy link

@anthayes92 anthayes92 left a comment

Choose a reason for hiding this comment

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

Code is looking really well structured @pangara! Easy to follow and learn from 🎓

I've added some suggestions, mainly tidying up. One of the bigger suggestions would be to add dosctrings for some of the bigger functions. This will help users if they wanted to re-use some of your code (which I'm sure they will!). I've left an example to follow in the code.

There's a couple more functions toward the end of the demo I need to come back to review

demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
Copy link
Contributor

@angusjlowe angusjlowe left a comment

Choose a reason for hiding this comment

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

Second pass, still more comments to come!

demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
# .. math::
#
#
# \tilde{B} = \prod_{j=1}^{l} \tilde{b}_{v_j}.
Copy link
Contributor

Choose a reason for hiding this comment

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

Kind of weird that the dependence on the node label is is suppressed for this operator but I see that the paper also does this...

demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
Comment on lines +252 to +253
# where P is the permutation's function of labels from :math:`1` to
# :math:`N`.
Copy link
Contributor

Choose a reason for hiding this comment

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

I know this is inherited from the paper as well, but this notation doesn't really make sense to me. (I can infer the meaning, but not ideal to have to do that.)

Co-authored-by: Angus Lowe <[email protected]>
Co-authored-by: anthayes92 <[email protected]>
Copy link

@anthayes92 anthayes92 left a comment

Choose a reason for hiding this comment

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

Final few suggestion on the code have been added, the main suggestion being to break down some of the code that gets repeated into smaller helper functions that can be re-used.

The logic within the code is looking fantastic, amazing work @pangara 🙌

demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
Comment on lines 639 to 640
# 5. If no new Hamming weight is obtained, the partial mixers are randomized and steps 2 and 3 are repeated to check if
# a better Hamming weight is found. The number of randomizations is controlled via a hyperparameter.

Choose a reason for hiding this comment

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

Are there rendering issues with this line break?

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems so on my end as well

Comment on lines 644 to 744

# Select an ordering for the partial mixers
if mixer_order == None:
cur_permutation = np.random.permutation(list(graph.nodes)).tolist()
else:
cur_permutation = mixer_order

def f(params):

probs = probability_dqva(
P, params=params, init_state=cur_init_state, mixer_order=cur_permutation
)
avg_cost = 0
for sample in range(0, len(probs)):

x = [int(bit) for bit in list(np.binary_repr(sample, len(graph.nodes)))]
# Cost function is Hamming weight
avg_cost += probs[sample] * sum(x)

return -avg_cost

# Begin outer optimization loop
best_indset = init_state
best_init_state = init_state
cur_init_state = init_state
best_params = None
best_perm = copy.copy(cur_permutation)

# Randomly permute the order of mixer unitaries m times
for mixer_round in range(1, m + 1):

inner_round = 1
new_hamming_weight = hamming_weight(cur_init_state)

while True:
print(
"Start round {}.{}, Initial state = {}".format(
mixer_round, inner_round, cur_init_state
)
)

# Begin inner variational loop
num_params = P * (len(graph.nodes()) + 1)
print("\tNum params =", num_params)
init_params = np.random.uniform(low=0.0, high=2 * np.pi, size=num_params)
print("\tCurrent Mixer Order:", cur_permutation)

# Optimize parameters
optimizer = qml.GradientDescentOptimizer(stepsize=0.5)
cur_params = init_params.copy()

for i in range(70):
cur_params, opt_cost = optimizer.step_and_cost(f, cur_params)

opt_params = cur_params.copy()

print("\tOptimal cost:", opt_cost)

probs = probability_dqva(
P, params=opt_params, init_state=cur_init_state, mixer_order=cur_permutation
)

top_counts = list(
map(lambda x: np.binary_repr(x, len(graph.nodes)), np.argsort(probs))
)[::-1]

best_hamming_weight = hamming_weight(best_indset)
better_strs = []

for bitstr in top_counts[:1]:
this_hamming = hamming_weight(bitstr)
if is_indset(bitstr, graph) and this_hamming > best_hamming_weight:
better_strs.append((bitstr, this_hamming))
better_strs = sorted(better_strs, key=lambda t: t[1], reverse=True)

# If no improvement was made, break and go to next mixer round
if len(better_strs) == 0:
print(
"\tNone of the measured bitstrings had higher Hamming weight than:", best_indset
)
break

# Otherwise, save the new bitstring and repeat
best_indset, new_hamming_weight = better_strs[0]
best_init_state = cur_init_state
best_params = opt_params
best_perm = copy.copy(cur_permutation)
cur_init_state = best_indset
print(
"\tFound new independent set: {}, Hamming weight = {}".format(
best_indset, new_hamming_weight
)
)
inner_round += 1

# Choose a new permutation of the mixer unitaries
cur_permutation = np.random.permutation(list(graph.nodes)).tolist()

print("\tRETURNING, best hamming weight:", new_hamming_weight)
return best_indset, best_params, best_init_state, best_perm
Copy link

@anthayes92 anthayes92 Jan 24, 2022

Choose a reason for hiding this comment

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

This function uses a lot of the same code that solve_mis_qaoa uses on line 363. Again we could think about creating smaller functions to be reused here. The above examples of helper function suggestions can be used as a guide here 😃

This may also have the bonus of breaking up these MIS solver functions into smaller digestible pieces

Copy link
Author

Choose a reason for hiding this comment

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

I have made two helpers for this - hopefully it's better. This is a long function!

demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
Comment on lines 485 to 491
base_str = "0" * len(graph.nodes)
for i in range(len(graph.nodes)):
init_str = list(base_str)
init_str[i] = "1"
out = solve_mis_qaoa("".join(init_str), P=1, m=4, threshold=1e-5, cutoff=2)
print(f"Init string: {init_str}, Best MIS: {out[0]}")
print()

Choose a reason for hiding this comment

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

This could be made into a function since it is used again after using DQVA to solve MIS on line 754

demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
Comment on lines 639 to 640
# 5. If no new Hamming weight is obtained, the partial mixers are randomized and steps 2 and 3 are repeated to check if
# a better Hamming weight is found. The number of randomizations is controlled via a hyperparameter.
Copy link
Contributor

Choose a reason for hiding this comment

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

Seems so on my end as well

Copy link
Contributor

@glassnotes glassnotes left a comment

Choose a reason for hiding this comment

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

@pangara great to see this come together 🎉 I've just done a quick pass, I didn't go thoroughly through all the math or code examples.

demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
print()

######################################################################
# Starting with an all-zero initial string will give us an independent
Copy link
Contributor

Choose a reason for hiding this comment

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

Depending on how the randomness works out this may be confusing; in my local build, the all-zero start found an MIS of size 3, but the subsequent code block, not all of them did, and I had to scroll through a lot of output. Maybe for this case it would be better to streamline the output so that it only returns the final result of the loop (like only indicate which string was the starting string and the best MIS it found)

# is the same as the QAO-Ansatz (the Hamming weight operator).
#
# In the DQVA, the way mixers are defined is slightly different from the
# QAO-Ansatz and are allowed to be independent.
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
# QAO-Ansatz and are allowed to be independent.
# QAO-Ansatz and are allowed to be independent:

Independent of what?

demonstrations/tutorial_dqva_mis.py Outdated Show resolved Hide resolved
#


def solve_mis_dqva(init_state: Optional[str], P: Optional[int]=1, m: Optional[int]=1, mixer_order: Optional[List]=None, threshold: Optional[float]=1e-5, cutoff: Optional[int]=1) -> Tuple[str, np.ndarray, str, List]:
Copy link
Contributor

Choose a reason for hiding this comment

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

As a general comment, I had a hard time following the code blocks and outputs because there was just a lot of "stuff" in them (type hints, full docstrings, print statements, etc.) For a demo, there isn't usually full documentation unless there is something particularly unclear (e.g., parameters that weren't discussed in the text), since in a sense the prose is documenting what is being done.

Choose a reason for hiding this comment

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

Ah so I actually advocated for docstrings and typehints but perhaps I was too quick to assume that this is makes things clearer for everyone!

IMO typehints could be dropped but docstrings could be useful for re-usability (though not sure if this is the intention of demos). Happy to leave this to your best judgement.

Copy link
Member

Choose a reason for hiding this comment

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

As @glassnotes notes, we generally avoid docstrings and typehints in demos 🙂 They can be a distraction from the content and code for readers that are not familiar with Python software development, and can in some cases hinder from the flow of the text

# solve MIS, which involves classical partitioning of a large graph into
# sub-graphs, finding a solution using DQVA on a subgraph, and then
# preparing a set of states to be passed as an input, essentially
# stitching together solutions.
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice conclusion 👍

Co-authored-by: Olivia Di Matteo <[email protected]>
Copy link

@anthayes92 anthayes92 left a comment

Choose a reason for hiding this comment

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

Approved subject to adding the remaining suggestions ✔️ 🎉

Thanks for the consistent great work here @pangara. This demo come together really nicely!

demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
demonstrations/tutorial_dqva_mis.py Show resolved Hide resolved
#


def solve_mis_dqva(init_state: Optional[str], P: Optional[int]=1, m: Optional[int]=1, mixer_order: Optional[List]=None, threshold: Optional[float]=1e-5, cutoff: Optional[int]=1) -> Tuple[str, np.ndarray, str, List]:

Choose a reason for hiding this comment

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

Ah so I actually advocated for docstrings and typehints but perhaps I was too quick to assume that this is makes things clearer for everyone!

IMO typehints could be dropped but docstrings could be useful for re-usability (though not sure if this is the intention of demos). Happy to leave this to your best judgement.

@github-actions
Copy link

Thank you for opening this pull request.

You can find the built site at this link.

Deployment Info:

  • Pull Request ID: 386
  • Deployment SHA: a96ff251161c342bd08c0f95a020ff1cd2015bb5
    (The Deployment SHA refers to the latest commit hash the docs were built from)

Note: It may take several minutes for updates to this pull request to be reflected on the deployed site.

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.

6 participants