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

Named Topologies #4370

Merged
merged 32 commits into from Aug 23, 2021
Merged

Named Topologies #4370

merged 32 commits into from Aug 23, 2021

Conversation

mpharrigan
Copy link
Collaborator

@mpharrigan mpharrigan commented Jul 30, 2021

"Named topologies" provide a mapping from a simple dataclass to a unique graph. This PR also includes basic utilities for matching problem topologies to devices

image
image
image
image

@review-notebook-app
Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@google-cla google-cla bot added the cla: yes Makes googlebot stop complaining. label Jul 30, 2021
@mpharrigan mpharrigan marked this pull request as ready for review August 2, 2021 23:09
@mpharrigan mpharrigan requested review from cduck, vtomole, wcourtney and a team as code owners August 2, 2021 23:09
@mpharrigan mpharrigan requested a review from maffoo August 2, 2021 23:09
Copy link
Collaborator

@viathor viathor left a comment

Choose a reason for hiding this comment

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

I only had a quick look for now, will have another later. Other than a few minor comments about type annotations, I also wonder why put this under cirq.google? It doesn't seem to be specific to Google's quantum hardware.

cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-core/cirq/protocols/json_serialization_test.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies_test.py Outdated Show resolved Hide resolved
@mpharrigan
Copy link
Collaborator Author

@dstrain115 ptal

I will move the files to wherever. It might be easier to read the diff from your last review if it doesn't include a move. Let me know

@CirqBot CirqBot added size: XL lines changed >1000 size: L 250< lines changed <1000 and removed size: XL lines changed >1000 labels Aug 12, 2021
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
cirq-google/cirq_google/named_topologies.py Outdated Show resolved Hide resolved
@viathor
Copy link
Collaborator

viathor commented Aug 16, 2021

One more comment from earlier which was missed: There are platforms other than google's that I think use the two topologies defined in this PR. Also, this implementation is generic enough that we could move it from cirq-google to cirq. Otherwise, we might end up with duplication or an awkward dependency of cirq-x on cirq-google.

In fact the two topologies are generic enough that one could use them to play around with small repetition or surface codes independent of any hardware concerns.

Copy link
Collaborator

@MichaelBroughton MichaelBroughton left a comment

Choose a reason for hiding this comment

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

Looking over this I have a few higher level questions/comments:

  1. It looks to me like the gist of this code functionality is to enable a user to have some device/connectivity graph and identify all the different ways they could generate a line/slantedrectangle subgraph on it. get_placements works on two networkx graphs, mentions that the intended application is device placement but makes no use of the cirq.Device class, would it be possible to also have: get_placements(my_device: cirq.Device, my_desired_subgraph: nx.Graph) ? Building on this, devices has this:
    def qid_pairs(self) -> Optional[FrozenSet['cirq.SymmetricalQidPair']]:
        """Returns a set of qubit edges on the device, if possible."""

Why can't we just add a method to the device class that returns a generator for these desired subgraph types (with the added benefit of directing our users towards devices instead of having two very similar things that both mention topologies + placements etc. and seperating the functionality when we don't need to) ?

def all_subgraphs(self, my_desired_subgraph):
    """Returns a generator to all possible placements with my_desired_subgraph topology."""

Looking at the tutorial for the intended usage, a SYC23_GRAPH graph is created, but we already have that implicitly defined as a cirq.Device. To me this functionality seems far more intuitive to add to the Device class and we don't loss any generality/functionality in supporting our own hardware here by doing this. Does anyone else see any downsides to this ?

  1. Given the size and amount of contention on this PR, I think it might be better if we first got the library implementation agreed on and then moved to do a notebook/tutorial in a second PR since this tutorial does look pretty short and is a little lacking in terms of structure when you compare it to other notebooks we offer like: https://quantumai.google/cirq/tutorials/variational_algorithm or https://www.tensorflow.org/quantum/tutorials/gradients

@mpharrigan
Copy link
Collaborator Author

It looks to me like the gist of this code functionality is to enable a user to have some device/connectivity graph and identify all the different ways they could generate a line/slantedrectangle subgraph on it

That's one part, yes. The first is to assign names to common graphs we use in applications.

get_placements works on two networkx graphs, mentions that the intended application is device placement but makes no use of the cirq.Device class, would it be possible to also have: get_placements(my_device: cirq.Device, my_desired_subgraph: nx.Graph) ? Building on this, devices has this:

Pending #3245, we should be able to more easily go from device to graph. This functionality works for any two graphs, irrespective of whether they correspond to a cirq.Device.

Why can't we just add a method to the device class that returns a generator for these desired subgraph types (with the added benefit of directing our users towards devices instead of having two very similar things that both mention topologies + placements etc. and seperating the functionality when we don't need to) ?

I think this hinges on the "this is tightly coupled to Devices" premise. All things being equal, I prefer free functions to methods. This makes code re-use easier.

a SYC23_GRAPH graph is created, but we already have that implicitly defined as a cirq.Device. To me this functionality seems far more intuitive to add to the Device class and we don't loss any generality/functionality in supporting our own hardware here by doing this. Does anyone else see any downsides to this ?

Yes, this is a long-standing feature request. The implicit definition based on GridQubits is very dangerous is you have two qubits that "seem" adjacent by their indices but aren't actually coupled (either by design or because of a fault). The device roadmap item is out of scope for this PR.

Given the size and amount of contention on this PR, I think it might be better if we first got the library implementation agreed on and then moved to do a notebook/tutorial in a second PR since this tutorial does look pretty short and is a little lacking in terms of structure when you compare it to other notebooks we offer like: https://quantumai.google/cirq/tutorials/variational_algorithm or https://www.tensorflow.org/quantum/tutorials/gradients

I'm definitely in the camp of "something is better than nothing". I think these concepts are inherently visual and you can get a lot by just plotting a bunch of example graphs.

This is annoying to do with all the __init__.pys
and the json stuff and you can't import cirq
from inside cirq.
@mpharrigan
Copy link
Collaborator Author

Moved to cirq-core ptal

@mpharrigan
Copy link
Collaborator Author

How do we deal with the notebook failure? The new notebook won't work against Cirq stable because the functionality isn't in cirq stable

Copy link
Collaborator

@MichaelBroughton MichaelBroughton left a comment

Choose a reason for hiding this comment

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

Things are looking good! Just a few remaining questions on the TiltedSquareLattice and the get_placements function then we should be good to merge.

I'm still not a huge fan of keeping the tutorial in this PR since it does seem pretty slim compared to our other tutorials and will need to be constantly iterated on as more components of testbed, devices etc. land in the repo in order to bring it up to standard with the other tutorials we have. This seems like it'd be a lot more work than just doing a completely seperate PR with only this tutorial in it so we could have a more in depth review on it. Do you have strong feelings here ? I would prefer to heir on the side of caution with this and stick to go/smaller-cls .

Comment on lines 180 to 227
g = nx.Graph()

def _add_edge(unit_row: int, unit_col: int, *, which: int):
"""Helper function to add edges in 'unit cell coordinates'."""
y = unit_col + unit_row
x = unit_col - unit_row

if which == 0:
# Either in the bulk or on a ragged boundary, we need this edge
g.add_edge((x, y), (x, y - 1))
elif which == 1:
# This is added in the bulk and for a "top" (extra height) ragged boundary
g.add_edge((x, y), (x + 1, y))
elif which == 2:
# This is added in the bulk and for a "side" (extra width) ragged boundary
g.add_edge((x, y), (x - 1, y))
elif which == 3:
# This is only added in the bulk.
g.add_edge((x, y), (x, y + 1))
else:
raise ValueError() # coverage: ignore

# Iterate over unit cells, which are in units of 2*width, 2*height.
# Add all all four edges when we're in the bulk.
unit_cell_height = self.height // 2
unit_cell_width = self.width // 2
for unit_row in range(unit_cell_height):
for unit_col in range(unit_cell_width):
for i in range(4):
_add_edge(unit_row, unit_col, which=i)

extra_h = self.height % 2
if extra_h:
# Add extra height to the final half-row.
for unit_col in range(unit_cell_width):
_add_edge(unit_cell_height, unit_col, which=0)
_add_edge(unit_cell_height, unit_col, which=1)

extra_w = self.width % 2
if extra_w:
# Add extra width to the final half-column
for unit_row in range(unit_cell_height):
_add_edge(unit_row, unit_cell_width, which=0)
_add_edge(unit_row, unit_cell_width, which=2)

if extra_w and extra_h:
# Add the final corner node when we have both ragged boundaries
_add_edge(unit_cell_height, unit_cell_width, which=0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we might be able to tighten this up and just do something like:

rect1 = set((i + j, i - j) for i in range(w//2 + 1) for j in range(h//2 + 1))
rect2 = set(((i + j) // 2, (i - j) // 2) for i in range(1, w + 1, 2) for j in range(1, h + 1, 2))
all_nodes = rect1 | rect2
g = nx.Graph()
for u in all_nodes:
  for dx, dy in [(1,0), (-1, 0), (0,1), (0,-1)]:
      v = (u[0] + dx, u[1] + dy) 
      if v in all_nodes:
          g.add_edge(u, v)

here w=self.width and h=self.height.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

Comment on lines +237 to +239
n_nodes = (self.width // 2 + 1) * (self.height // 2 + 1)
n_nodes += ((self.width + 1) // 2) * ((self.height + 1) // 2)
object.__setattr__(self, 'n_nodes', n_nodes)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we just use len(g.nodes) or g.number_of_nodes here ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We could. I wanted a formula for the number of nodes in terms of width and height; the unit test verifies that the two methods agree. The potential benefit of a contributor being able to read off a formula by looking at the code offsets the downside of not using g.number_of_nodes()

Comment on lines 263 to 276
"""Get 'placements' mapping small_graph nodes onto those of `big_graph`.

We often consider the case where `big_graph` is a nx.Graph representation of a Device
whose nodes are `cirq.Qid`s like `GridQubit`s and `small_graph` is a NamedTopology graph.
In this case, this function returns a list of placement dictionaries. Each dictionary
maps the nodes in `small_graph` to nodes in `big_graph` with a monomorphic relationship.
That's to say: if an edge exists in `small_graph` between two nodes, it will exist in
`big_graph` between the mapped nodes.

We restrict only to unique set of `big_graph` qubits. Some monomorphisms may be basically
the same mapping just rotated/flipped which we purposefully exclude. This could
exclude meaningful differences like using the same qubits but having the edges assigned
differently, but it prevents the number of placements from blowing up.
"""
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we tighthen up this docstring a bit ? it's looking a little on the longer side and doesn't list the args: or returns:. It won't render too well on the devsite without it. Also might want to mention that there is some ordering imposed on the returned subgraphs thanks to the sorted lower down in the function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Refactored into Args, Returns. Tightened up some of the wording. Kept most of the content. I think it's all pretty important.

This is in a class of functions that doesn't correspond to the super pure general everything function: instead, it encodes general ideas I usually need when doing the particular form of graph matching for putting a circuit on a device. It might not be for everyone. The pure, general version would just be networkx's sugraph_monomorphism_iter(). The intricacies in this version need to be documented and motivated

Copy link
Collaborator

@dstrain115 dstrain115 left a comment

Choose a reason for hiding this comment

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

Just a few minor nits. The PR is much improved after all the hard work and edits. Thanks for putting in the effort to really address all the difficult comments so thoroughly! I like the new name and conventions.

object.__setattr__(self, 'graph', graph)

def draw(self, ax=None, tilted: bool = True, **kwargs) -> Dict[Any, Tuple[int, int]]:
"""Draw this graph.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I would add a small bit of detail here, such as Draw this graph using matplotlib. so that the docstring is not redundant with the function name.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

added matplotlib notes here and everywhere

object.__setattr__(self, 'n_nodes', n_nodes)

def draw(self, ax=None, tilted=True, **kwargs):
"""Draw this graph
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: same comment here, plus missing period.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

done

@mpharrigan
Copy link
Collaborator Author

I'm still not a huge fan of keeping the tutorial in this PR since it does seem pretty slim compared to our other tutorials and will need to be constantly iterated on [...] in order to bring it up to standard with the other tutorials we have.

With the move to cirq-core, I've moved this out of the "tutorials" directory. As you point out, it's not really a tutorial. It's documentation. [I guess originally when I put it in cirq-google, I put it with the other notebooks which are all tutorials, but cirq regular-doc-pages is a mix of markdown and notebooks so we gucci]. I'm going to strongly advocate for including visual diagrams with this PR. A picture is worth a thousand lines of docstring.

and will need to be constantly iterated on as more components of testbed, devices etc. land in the repo in order to bring it up to standard with the other tutorials we have.

Agile / small CLs / small PRs / whatever you want to call it are really a call for exactly this: MVP plus iteration trumps waterfall.

@mpharrigan
Copy link
Collaborator Author

@MichaelBroughton pushed changes for latest round of review; PTAL

Copy link
Collaborator

@MichaelBroughton MichaelBroughton left a comment

Choose a reason for hiding this comment

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

LGTM, looks like the notebook is still failing on from cirq import TiltedSquareLattice.

@mpharrigan mpharrigan added the automerge Tells CirqBot to sync and merge this PR. (If it's running.) label Aug 23, 2021
@CirqBot CirqBot added the front_of_queue_automerge CirqBot uses this label to indicate (and remember) what's being merged next. label Aug 23, 2021
@CirqBot CirqBot merged commit 6ecaf07 into quantumlib:master Aug 23, 2021
@CirqBot CirqBot removed automerge Tells CirqBot to sync and merge this PR. (If it's running.) front_of_queue_automerge CirqBot uses this label to indicate (and remember) what's being merged next. labels Aug 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla: yes Makes googlebot stop complaining. size: L 250< lines changed <1000
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants