How bloqs choose how to display themselves
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_nameis 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_titleor something. Its name should be harmonious withwire_symbolsor 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_nameis 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.
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:
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.
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.
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...
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
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.
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
bloq.pretty()- which only gives the bloq class name by default and is not "reasonably informative" since for most bloqs the arguments (likebitsize) are easy to describe and very informativestr(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 expectingcvslikeMultiControlPaulior bloqs expecting classical data likeQROM.
I had to write a hacky function which
- 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}]" - If the field is a sequence / numpy array, we use
len(val)instead ofvalin the"[{arg}]" - If the field is a subbloq, it delegates to
_pretty_name(subbloq) - 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__
Remaining TODO's:
- remove
pretty_name. Some of these should move to__str__and some of them (that used to beshort_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 Can you explain what this means? "remove the old-style call graph drawer and switch usages to the new-style call graph drawer."
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.