-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Oxidize parts of HLS #13369
Oxidize parts of HLS #13369
Conversation
One or more of the following people are relevant to this code:
|
Pull Request Test Coverage Report for Build 11681228503Details
💛 - Coveralls |
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 believe this is about ready to merge (I'll let @Cryoris have the last word). I just left some suggestions about ignored_qubits
only to test whether a HashSet
would be the better approach here. I left the transformed functions as suggestions here.
/// Used internally for keeping the computations in `O(n)` | ||
ignored: Vec<bool>, |
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.
Since this isn't used as much here, maybe we could remove it in favor of providing a HashSet<usize>
whenever needed? Performing a hash lookup for a usize shouldn't be very expensive, and since we're only using this as a way of checking a value should be ignored, a HashSet<usize>
as a function argument should suffice. We'd have to benchmark it to see if it affects performance in any way.
/// Returns the number of enabled clean qubits, ignoring the given qubits | ||
/// ToDo: check if it's faster to avoid using ignored | ||
fn num_clean(&mut self, ignored_qubits: Vec<usize>) -> usize { | ||
for q in &ignored_qubits { | ||
self.ignored[*q] = true; | ||
} | ||
|
||
let count = (0..self.num_qubits) | ||
.filter(|q| !self.ignored[*q] && self.enabled[*q] && self.state[*q]) | ||
.count(); | ||
|
||
for q in &ignored_qubits { | ||
self.ignored[*q] = false; | ||
} | ||
|
||
count | ||
} |
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.
Just so you're able to test quickly, see if using a hashset affects this in any way:
/// Returns the number of enabled clean qubits, ignoring the given qubits | |
/// ToDo: check if it's faster to avoid using ignored | |
fn num_clean(&mut self, ignored_qubits: Vec<usize>) -> usize { | |
for q in &ignored_qubits { | |
self.ignored[*q] = true; | |
} | |
let count = (0..self.num_qubits) | |
.filter(|q| !self.ignored[*q] && self.enabled[*q] && self.state[*q]) | |
.count(); | |
for q in &ignored_qubits { | |
self.ignored[*q] = false; | |
} | |
count | |
} | |
/// Returns the number of enabled clean qubits, ignoring the given qubits | |
/// ToDo: check if it's faster to avoid using ignored | |
fn num_clean(&mut self, ignored_qubits: HashSet<usize>) -> usize { | |
(0..self.num_qubits) | |
.filter(|q| !ignored_qubits.contains(q) && self.enabled[*q] && self.state[*q]) | |
.count() | |
} |
/// Returns the number of enabled dirty qubits, ignoring the given qubits | ||
/// ToDo: check if it's faster to avoid using ignored | ||
fn num_dirty(&mut self, ignored_qubits: Vec<usize>) -> usize { | ||
for q in &ignored_qubits { | ||
self.ignored[*q] = true; | ||
} | ||
|
||
let count = (0..self.num_qubits) | ||
.filter(|q| !self.ignored[*q] && self.enabled[*q] && !self.state[*q]) | ||
.count(); | ||
|
||
for q in &ignored_qubits { | ||
self.ignored[*q] = false; | ||
} | ||
|
||
count | ||
} |
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.
Same case here (for testing):
/// Returns the number of enabled dirty qubits, ignoring the given qubits | |
/// ToDo: check if it's faster to avoid using ignored | |
fn num_dirty(&mut self, ignored_qubits: Vec<usize>) -> usize { | |
for q in &ignored_qubits { | |
self.ignored[*q] = true; | |
} | |
let count = (0..self.num_qubits) | |
.filter(|q| !self.ignored[*q] && self.enabled[*q] && !self.state[*q]) | |
.count(); | |
for q in &ignored_qubits { | |
self.ignored[*q] = false; | |
} | |
count | |
} | |
/// Returns the number of enabled dirty qubits, ignoring the given qubits | |
/// ToDo: check if it's faster to avoid using ignored | |
fn num_dirty(&mut self, ignored_qubits: HashSet<usize>) -> usize { | |
(0..self.num_qubits) | |
.filter(|q| !ignored_qubits.contains(q) && self.enabled[*q] && !self.state[*q]) | |
.count() |
/// Get `num_qubits` enabled qubits, excluding `ignored_qubits`, and returning the | ||
/// clean qubits first. | ||
/// ToDo: check if it's faster to avoid using ignored | ||
fn borrow(&mut self, num_qubits: usize, ignored_qubits: Vec<usize>) -> Vec<usize> { | ||
for q in &ignored_qubits { | ||
self.ignored[*q] = true; | ||
} | ||
|
||
let clean_ancillas = (0..self.num_qubits) | ||
.filter(|q| !self.ignored[*q] && self.enabled[*q] && self.state[*q]); | ||
let dirty_ancillas = (0..self.num_qubits) | ||
.filter(|q| !self.ignored[*q] && self.enabled[*q] && !self.state[*q]); | ||
let out: Vec<usize> = clean_ancillas | ||
.chain(dirty_ancillas) | ||
.take(num_qubits) | ||
.collect(); | ||
|
||
for q in &ignored_qubits { | ||
self.ignored[*q] = false; | ||
} | ||
out | ||
} |
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.
Here's how it would look with the Hashset:
/// Get `num_qubits` enabled qubits, excluding `ignored_qubits`, and returning the | |
/// clean qubits first. | |
/// ToDo: check if it's faster to avoid using ignored | |
fn borrow(&mut self, num_qubits: usize, ignored_qubits: Vec<usize>) -> Vec<usize> { | |
for q in &ignored_qubits { | |
self.ignored[*q] = true; | |
} | |
let clean_ancillas = (0..self.num_qubits) | |
.filter(|q| !self.ignored[*q] && self.enabled[*q] && self.state[*q]); | |
let dirty_ancillas = (0..self.num_qubits) | |
.filter(|q| !self.ignored[*q] && self.enabled[*q] && !self.state[*q]); | |
let out: Vec<usize> = clean_ancillas | |
.chain(dirty_ancillas) | |
.take(num_qubits) | |
.collect(); | |
for q in &ignored_qubits { | |
self.ignored[*q] = false; | |
} | |
out | |
} | |
/// Get `num_qubits` enabled qubits, excluding `ignored_qubits`, and returning the | |
/// clean qubits first. | |
/// ToDo: check if it's faster to avoid using ignored | |
fn borrow(&mut self, num_qubits: usize, ignored_qubits: HashSet<usize>) -> Vec<usize> { | |
let clean_ancillas = (0..self.num_qubits) | |
.filter(|q| !ignored_qubits.contains(q) && self.enabled[*q] && self.state[*q]); | |
let dirty_ancillas = (0..self.num_qubits) | |
.filter(|q| !self.ignored[*q] && self.enabled[*q] && !self.state[*q]); | |
clean_ancillas | |
.chain(dirty_ancillas) | |
.take(num_qubits) | |
.collect() | |
} |
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.
After doing some testing with sets, the performance is almost the same. If anything I could suggest using the HashSet
route once we have the HighLevelSynthesis
entirely in Rust just to avoid doing the extra conversions (from list
to set
in Python and from PySet
to HashSet
in Rust).
This code looks great and is ready to merge as is, thank you for your contribution 🚀
Thanks @raynelfss! I have just run a few experiments (after you have already merged the code) to see what happens on circuits with many small gates:
and on circuits with many large gates that do not need to be synthesized:
In fact, the performance even becomes a tiny bit better when using So, indeed, I am 100% switching to the |
Summary
This PR oxidize small parts of
HighLevelSynthesis
:QubitTracker
andQubitContext
. It is implemented on top of #13240 and replaces #13180.Details and comments
This is a more or less mechanical porting of the python code in #13240. Ideally, I would also like to port the auxiliary function
_definitely_skip_node
in time for 1.3.0.