Qualtran icon indicating copy to clipboard operation
Qualtran copied to clipboard

How bloqs choose how to display themselves

Open mpharrigan opened this issue 1 year ago • 6 comments

You may want to print out a string representation of a bloq or control how it is displayed in a diagram. This issue lays out the current state of how a bloq can inform the library how it wishes to be displayed and future improvements.

__repr__

The __repr__ method on a Python class gives a string representation of the object. We strongly advocate defining bloqs as attrs frozen classes, which will give you a good repr automatically. In fact: you have to jump through a minor hoop to override the attrs repr. Custom classes should define a repr that matches the style of the attrs repr. The repr should be exceedingly close to valid Python code you can paste into a terminal and get a convincing facsimile of the object in question.

__str__

The __str__ method on a Python class also gives a string representation of an object; but it can be more tailored to human readers (whereas the repr should be pretty close to executable Python code). Historically, we've relied on the Python/attrs default where we use the repr for __str__.

I think __str__ and pretty_name have significant overlap in how/where they're used, and we should probably rely on the __str__ method for "a reasonably concise, reasonably informative description of the bloq", see below.

CompositeBloq diagram info

graphviz

The graphviz drawer for composite bloqs uses Bloq.short_name() for the bloq title and Soquet.pretty() for each soquet. This uses Register.name. Following #728, this should probably hook in to the wire_symbols

musical score

The musical score drawer uses Bloq.short_name() to float a title of the bloq over its wire symbols and Bloq.wire_symbols to control the text or symbols used for each soquet.

Bloq.short_name()

As you can see: Bloq.short_name is primarily used as the title of bloqs in diagrams where space is indeed at a premium. By default it is the (potentially truncated) name of the bloq class and doesn't contain any information about bloq attribute values (by default).

  • We should introduce the assumption that this name will always be adjacent to soquet labels/wire symbols. This means it can omit information that is obvious from the soquet labels. We can make this optional if a title for the bloq is completely redundant #728
  • Note: short_name is also used to tag the quimb tensors. We can probably just remove this
  • I would like to rename this method to make it more obvious how it is used rather than the length of the string it produces. Maybe diagram_title or something. Its name should be harmonious with wire_symbols or whatever we potentially rename that to.

Call Graph diagram info

The call graph code currently uses Bloq.pretty_name() as a title and then some "bespoke" logic for adding a list of attribute name,value pairs.

  • "pretty_name" is a purposefully bad method name so that we have to replace it with something more meaningful.
  • In the call graph we don't have the added context of the wire symbols and indeed we may have two nodes for the same bloq class that differ only in attribute values.
  • The default pretty_name is the name of the bloq class.
  • The drawing code currently uses a different font for the name of the bloq (class) and the attribute details. Keeping this information more structured (than e.g. one string) could permit even more intelligent formatting in the future.

I propose using the __str__ for the title and adding a new method that returns notable attribute key/value pairs for the bloq so this information can be included in e.g. call graph diagrams. By default, this can try to use attrs introspection to return all of them. A notable attribute would be one a) not reflected in __str__ b) not the default value.

mpharrigan avatar Mar 13 '24 22:03 mpharrigan

Given #728, is short_name even necessary? If the wire symbols are built appropriately we should arrive at something resembling what I imagine we want:

Screenshot 2024-03-13 at 3 30 36 PM

It's not clear if we ever really want a title over the bloq?

If anything, perhaps the registers need a string to represent $|0\rangle^{\otimes L}$ or whatever.

fdmalone avatar Mar 13 '24 22:03 fdmalone

It's a good question. Your example works because each box goes with one line. Remember that in our automatic diagrams we can't e.g. make one big box spanning multiple registers and label it "U" or whatever. We could always try making it optional and see if there are any spots where we can't get rid of it.

mpharrigan avatar Mar 13 '24 23:03 mpharrigan

I was going to ask again about the can't part (having not really looked at the code). Without this it might be hard to get rid of the label like you say. For more opaque bloqs it's probably ok to just repeat U in your example, but the big box would be nice...

fdmalone avatar Mar 13 '24 23:03 fdmalone

you're not guaranteed that the lines will be next to each other. If you wanted to replace the alt/keep qrom with a big "U" box ... you couldn't because theres the second mu-sized wire in the way. If you want to weave the lines around...now you have the graphviz-style drawer

mpharrigan avatar Mar 13 '24 23:03 mpharrigan

But say we knew they were next to each other, so we just wanted to merge alt and keep with a big box (not that we would). Maybe group the Uniform and Hadamard into a big SubPrepare bloq for example.

fdmalone avatar Mar 14 '24 05:03 fdmalone

For flame graphs in #732 I ran into the issue where I wanted "a reasonably concise, reasonably informative description of the bloq". The options I had were either

  1. bloq.pretty() - which only gives the bloq class name by default and is not "reasonably informative" since for most bloqs the arguments (like bitsize) are easy to describe and very informative
  2. str(bloq) - Which defaults to the __repr__ generated by attrs and is not "reasonably concise" for cases when the bloq expects a sequence as input; eg: controlled bloqs expecting cvs like MultiControlPauli or bloqs expecting classical data like QROM.

I had to write a hacky function which

  1. Assumes bloqs are attrs frozen classes and iterates over the fields and tries to construct a representation of the form f"{bloq.__class_name__}[{arg1}][{arg2}]...[{argn}]"
  2. If the field is a sequence / numpy array, we use len(val) instead of val in the "[{arg}]"
  3. If the field is a subbloq, it delegates to _pretty_name(subbloq)
  4. if the field is a float, it uses f'{val:0.2g}'

This is a hack, but in general I agree it'd be great to have a consistent way to get "a reasonably concise, reasonably informative description of the bloq". +1 if we use __str__ for it and provide sensible defaults (better than just the class name, maybe like a better version of the hacky function I wrote above) for bloqs where people don't explicitly override the __str__

tanujkhattar avatar Apr 03 '24 21:04 tanujkhattar

Remaining TODO's:

  • remove pretty_name. Some of these should move to __str__ and some of them (that used to be short_name) should be moved to the wire_symbol title label
  • remove the old-style call graph drawer and switch usages to the new-style call graph drawer.

mpharrigan avatar Aug 23 '24 23:08 mpharrigan

@mpharrigan Can you explain what this means? "remove the old-style call graph drawer and switch usages to the new-style call graph drawer."

dstrain115 avatar Aug 27 '24 11:08 dstrain115

in the qualtran.drawing.bloq_counts module there are two classes: GraphvizCounts and GraphvizCallGraph. Their commonalities have been mostly factored out into a helper-base-class. One difference is the latter can optionally include a table of cost information (qubit & gate counts). The former uses some long-time logic for choosing how to display the bloqs: it uses pretty_name for a title and then tries to hack into the attrs field list for the bloq to find a succinct-ish list of attributes that are important to describe the bloq beyond just the pretty_name, which is usually the class name. This logic is a little too fancy and bloq authors should just craft a __str__ that is sufficiently short and sufficiently specific to be used as a node label in the call graph, which is what GraphvizCallGraph assumes

GraphvizCounts is used by the documentation infrastructure through show_call_graph.

mpharrigan avatar Aug 27 '24 16:08 mpharrigan