-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Brand operation indices with OperationIndex
#13696
base: main
Are you sure you want to change the base?
Conversation
Operations on the `DAGCircuit` that return `NodeIndex`s that are known to refer to `Operation` instances now return a transparent wrapper type, `OperationIndex`. `DAGCircuit` can be indexed with an `OperationIndex`, which returns the `&PackedInstruction` directly, rather than requiring the caller to indirect through the `OperationType` enum, which 99% of the time they don't care about. `DAGCircuit` methods that are required to act on operations now take `OperationIndex` arguments, shifting the typing validation left; the compiler now effectively tracks provenance of indices in the type system. You can convert a `NodeIndex` into an `OperationIndex` as part of a DAG lookup by using `DAGCircuit::get_operation`, which returns the `&PackedInstruction` (if available), and upgrades the `NodeIndex` into an `OperationIndex` at the same time. Most of the time this is not necessary, as the methods that can only return operation nodes now return `OperationIndex`. Most of the changes in this commit are simplifying relevant call sites, and removing `if let NodeType::Operation(inst) = &dag[node_index] { }` constructs, since they were automatically flattened into `let inst = &dag[op_index]`.
One or more of the following people are relevant to this code:
|
Pull Request Test Coverage Report for Build 12873983166Details
💛 - 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.
This is a neat idea and makes working with the op node API surface of DAGCircuit
much cleaner.
Just a few minor comments.
/// | ||
/// The safety guarantees of [OperationIndex::new] must be upheld for each of the values in `val`. | ||
#[inline] | ||
unsafe fn cast_node_vec_to_operation_vec(nodes: Vec<NodeIndex>) -> Vec<OperationIndex> { |
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.
Out of curiosity, is there any chance Rust is smart enough that we don't actually need this? I.e., if we do nodes.into_iter().map(|n| OperationIndex(n)).collect::<Vec<_>>()
, does that in effect do this anyway?
It looks like Vec
's FromIterator
does something really similar: https://doc.rust-lang.org/beta/src/alloc/vec/spec_from_iter.rs.html#49-58
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 don't know, honestly. Even if it does on one compiler, I'm not sure if it'd feel comfortable relying on the behaviour, though equally, I don't love this unsafe
function at all either.
This function is one of my biggest problems with this PR - I think overall it's decent, but the end result isn't fully as nice as I'd hoped. Not sure what exactly I have a problem with, but it definitely didn't feel like a slam-dunk when I looked at the final diff.
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 think overall it's decent, but the end result isn't fully as nice as I'd hoped.
I mean the entire PR, btw.
#[repr(transparent)] | ||
pub struct OperationIndex(NodeIndex); | ||
impl OperationIndex { | ||
/// Brand a [NodeIndex] as referring to an operation. |
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 word "brand" feels a little weird IMO (I think of cattle). Can we pick something else? Perhaps "designate", "mark", or even "downcast"?
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, whatever you like, really. "Mark" is nice and short, "downcast" is consistent with type theory and PyO3.
} | ||
} | ||
|
||
#[derive(Hash, Eq, PartialEq, Clone, Debug)] | ||
#[derive(Hash, Eq, PartialEq, Clone, Copy, Debug)] |
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.
Nice! I think I forgot to add that when doing the Var
stuff.
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.
Honestly, that bit probably didn't need to be in this same PR - I think I did it because one of the code collapses became a lot neater if Wire
was copy, but really I could have done that separately.
#[inline] | ||
pub fn get_operation(&self, index: NodeIndex) -> Option<(OperationIndex, &PackedInstruction)> { | ||
match self.dag.node_weight(index) { | ||
Some(NodeType::Operation(inst)) => Some((OperationIndex(index), inst)), |
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.
Should we put OperationIndex
into its own inner module to force DAGCircuit
implementors to also reason about using the unsafe OperationIndex::new
?
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 yeah, I think that's a really good idea.
Let's hold this PR for a sec, then, and I'll push up another one that makes qiskit_circuit::dag
and moves the relevant existing files into that, since this change would mean we'll be up to four files that are specific to the DAG (dag_node.rs
, dag_circuit.rs
, rustworkx_core_vnext.rs
and the new index file), and we can start making the imports in qiskit_accelerate
a little cleaner too.
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.
Ok, that PR is #13721, so this PR can wait for that one.
Summary
Operations on the
DAGCircuit
that returnNodeIndex
s that are known to refer toOperation
instances now return a transparent wrapper type,OperationIndex
.DAGCircuit
can be indexed with anOperationIndex
, which returns the&PackedInstruction
directly, rather than requiring the caller to indirect through theOperationType
enum, which 99% of the time they don't care about.DAGCircuit
methods that are required to act on operations now takeOperationIndex
arguments, shifting the typing validation left; the compiler now effectively tracks provenance of indices in the type system. You can convert aNodeIndex
into anOperationIndex
as part of a DAG lookup by usingDAGCircuit::get_operation
, which returns the&PackedInstruction
(if available), and upgrades theNodeIndex
into anOperationIndex
at the same time. Most of the time this is not necessary, as the methods that can only return operation nodes now returnOperationIndex
.Most of the changes in this commit are simplifying relevant call sites, and removing
if let NodeType::Operation(inst) = &dag[node_index] { }
constructs, since they were automatically flattened intolet inst = &dag[op_index]
.Details and comments
This was the goal I had in mind at the end of my little sequence of patches late last week (#13680, #13681, #13682, #13683). I don't love the
unsafe
casting of theVec
forcollect_1q_runs
etc - perhaps we can extend the rustworkx API to allow a mapping function, so we can do it all in safe no-copy Rust.A little over one third of the diff of this PR is whitespace-only changes where the level of indent is reduced. For example,
elide_permutations.rs
claims a diffstat of +49,-52, but withgit diff -b
it reduces to +2,-5.