-
Notifications
You must be signed in to change notification settings - Fork 106
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
Multi-target gate circuit art improvement #2185
base: main
Are you sure you want to change the base?
Conversation
let next_qubit = qubit + 1; | ||
|
||
// Check if the next qubit is also in this operation. | ||
if operation.targets.iter().any(|t| t.q_id == next_qubit) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically, in terms of pure complexity, it would make more sense to make a hashsets out of these ids, inside the loop of line 336, and use it in this line (with contains(&next_qubit)
).
However, given the overhead of creating hashsets, and moreover, the relatively slow performance of hashsets in Rust (even FxHashSet, from my experience), I'm almost sure that this loop is faster for gates with a small number of target qubits.
But if it's actually common for these gates to have a large number of targets, I can easily replace this bit of code and use a FxHashSet here, just let me know.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with the reasoning. It would have to be a custom intrinsic in Q# that takes a very large number of qubits, and I just don't expect to see that in practice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the fix and additional test cases. Just a few requests for readability.
compiler/qsc_circuit/src/circuit.rs
Outdated
@@ -325,6 +330,26 @@ impl Display for Circuit { | |||
// to row in the diagram | |||
let mut register_to_row = FxHashMap::default(); | |||
|
|||
// Keep track of which qubits have the qubit after them in the same multi-qubit operation. | |||
let mut consequtive_qubits = FxHashSet::default(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor typo (consecutive).
But actually consider renaming this. I spent some time scratching my head over how this could possibly work, because I thought the set literally contained some consecutive qubit IDs (e.g. 2, 3, 4, 5) 😄
How about qubits_with_gap_row_below
? Not a pretty name, but at least it's less likely to send the reader on a wild goose chase.
compiler/qsc_circuit/src/circuit.rs
Outdated
end_column: usize, | ||
) -> std::fmt::Result { | ||
// Temporary string so we can trim whitespace at the end | ||
let default_renderer = ColumnRenderer::default(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment above this line got pushed up - it's supposed to refer to this line below this.
However I think you can just use unwrap_or_default
below instead of declaring a default here. It's not going to incur an extra allocation or anything like that (since the struct is just an int anyway)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unwrap_or_default
doesn't work, because column_renderers.get(&column).unwrap()
is a &ColumnRenderer, while the default part of unwrap_or_default
returns an actual ColumnRenderer.
And doing something like .unwrap_or(&ColumnRenderer::default())
also doesn't work because of temporary value dropped while borrowed
, because I can't reference something made inside this.
Rust is fighting against me here. Can you think of another way to do it that won't end up moving (or needing to clone) the renderer we get from column_renderers
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, interesting. I would just add #[derive(Clone, Copy)]
on the struct to stop "fighting" rust. It's really not a big deal cloning this object since it's simply a usize
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The solution to the ColumnWidthsByColumn
naming question (in the latest commit) ended up solving this, as well as simplifying this code quite a lot (especially with clippy's suggestion of using enumerate
rather than keep the for loop in this method).
Let me know what you think.
compiler/qsc_circuit/src/circuit.rs
Outdated
@@ -96,7 +96,7 @@ impl Config { | |||
} | |||
|
|||
type ObjectsByColumn = FxHashMap<usize, CircuitObject>; | |||
type ColumnWidthsByColumn = FxHashMap<usize, usize>; | |||
type ColumnWidthsByColumn = FxHashMap<usize, ColumnRenderer>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like how you organized this. I just think we can simplify the names.
type ColumnWidthsByColumn = FxHashMap<usize, ColumnRenderer>; | |
type FullColumns = FxHashMap<usize, Column>; |
That's what the new type is... it's just the column! No need to overthink it imo.
And now we can think of the hashmap as containing only the columns that have objects in them, so I suggested a name that reflects that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not 100% sure I agree with this:
My thinking is that the "ByColumn" postfix is consistent with ObjectsByColumn
in the row above, because both are a hashmap with the key being the column index.
And the ColumnRenderer
itself is a struct that takes circuit objects and turns them into ASCII art, so calling it a Column
sounds a bit confusing to me.
Not to mention how, if you agree with my point about ByColumn
but don't agree with this point, then we'll alias this hashmap as ColumnByColumn
, and that's just silly. 😄
Having said that, I will of course rename both of these if you still think your suggested names are better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I realize now that I can resolve most of this comment and the other open one pretty easily, it's an oversight on my side - this alias isn't necessary, because we don't actually need a hashmap for this. Let me try it out...
You'll still have to decide between Column
and ColumnRenderer
, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, short answer: Totally agree with you on the bit where you say it would be inconsistent with ObjectsByColumn
map right above this - I hadn't noticed that. Consistency trumps all, so let's stick with your version. Maybe consider ColumnWidth
instead of ColumnRenderer
.
Long answer: humor me for a second while I try to explain what I originally had in mind. Naming discussions tend to get pretty philosophical.
- "column index": it's just a number and that's what the original hashmap index
usize
refers to. - "column": that's the struct that describes the "physical" column in the diagram, so to speak. It contains attributes of the column, such as its width.
Personally I'm biased against struct names like "Something-Doer" because I prefer structs to have names that describe what the thing is, not what it does.
I guess then the natural conclusion would have been to rename these to ObjectsByColumnIndex
and ColumnsByColumnIndex
... and then rename all the other variables that refer to column
s below to column_index
... and I mean... that is getting into silly territory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that works - please look at the latest commit. Basically, since the column renderers are made by iterating over 0..end_column
, they can just be put in a vector. That means there's no need for this hashmap at all.
I can still rename the ColumnRenderer
to Column
, though then the enumeration in lines 192 and 200 will be over (column_index, column)
instead of the current (column, renderer)
. Not sure what's clearer in this case.
let next_qubit = qubit + 1; | ||
|
||
// Check if the next qubit is also in this operation. | ||
if operation.targets.iter().any(|t| t.q_id == next_qubit) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree with the reasoning. It would have to be a custom intrinsic in Q# that takes a very large number of qubits, and I just don't expect to see that in practice.
compiler/qsc_circuit/src/circuit.rs
Outdated
@@ -335,7 +360,15 @@ impl Display for Circuit { | |||
|
|||
register_to_row.insert((q.id, None), rows.len() - 1); | |||
|
|||
for i in 0..q.num_children { | |||
// If this qubit has no children, but it is in a multi-qubit operation with | |||
// the next qubit, we add one classical row for clarity. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// the next qubit, we add one classical row for clarity. | |
// the next qubit, we add an empty row to make room for the vertical connector. | |
// We can just use a classical wire type for this row since the wire won't actually be rendered. |
I now see how you're exploiting the "classical" wire type to create an empty row. I think that's fine (no need to overengineer it and add a 3rd wire type) but it's subtle enough that I think it warrants an explanatory comment.
Heads up to @ScottCarda-MS who I think has been working near this code. |
This PR has a proposed fix for issue #2184, but it mainly includes refactoring of the circuit-art code based on comments from PR #2126.
The only real functionality change in this code is that multi-target gates get extra lines that behave in the same way as measurement lines, in order to better convey the qubits involved.
For example, a circuit like this one:
instead of rendering as
will instead render as
This is very similar to what we get if we measure the first qubit of each of these two-qubit gate, i.e.
On top of this change in rendering, the first few commits in this PR are actually just code refactoring. As such, it's probably best to review this PR commit by commit, for two reason - a. it's going to be much easier to follow, and b. any of these commits can be reverted if you think they make the code more complicated instead of making it easier to read.
The commits are as follows:
new
method which is in charge of making sure that the column width is an odd number, and also aDefault
trait implementation for the case where we don't need a specific width.