feat: normalize behavior and usage of Instruction::get_qubits#497
feat: normalize behavior and usage of Instruction::get_qubits#497mingyoungjeng wants to merge 4 commits intomainfrom
Instruction::get_qubits#497Conversation
There was a problem hiding this comment.
Other comments (7)
-
quil-rs/src/instruction/mod.rs (824-824)
Same syntax error with double colon instead of single colon for generic type parameter.
.flat_map(|inst| inst.default_qubits_mut::<T>()), -
quil-rs/src/instruction/mod.rs (727-727)
Same syntax error with double colon instead of single colon for generic type parameter.
.flat_map(|inst| inst.default_qubits::<T>()), -
quil-rs/src/instruction/mod.rs (835-835)
Same syntax error with double colon instead of single colon for generic type parameter.
.flat_map(|inst| inst.default_qubits_mut::<T>()), -
quil-rs/src/instruction/mod.rs (949-949)
Same syntax error with double colon instead of single colon for generic type parameter.
for qubit in other.default_qubits_mut::<Vec<_>>() { - quil-rs/src/parser/command.rs (904-911) There's an inconsistency between the implementation and test expectations. The PR changes `CircuitDefinition` to use a constructor method and replaces `qubit_variables` with `qubits`, but the test cases still expect the old field name `qubit_variables`. This will likely cause test failures.
- quil-rs/src/program/mod.rs (435-435) The `for_each_body_instruction` method was previously marked as `#[cfg(test)]` but is now public. Was this intentional? If so, consider adding documentation to explain its purpose and usage for public API consumers.
- quil-rs/src/program/mod.rs (266-270) This change modifies when qubits are added to `used_qubits`. Previously, all instructions would have their qubits tracked, but now only body instructions do. This is a behavior change that might affect users who rely on the previous behavior. Consider documenting this change in the PR description or adding a comment explaining the rationale.
💡 To request another review, post a new comment with "/windsurf-review".
asaites
left a comment
There was a problem hiding this comment.
Overall I think this is on the right track, but needs a little adjustment. I think there's an inconsistency in the qubit caching when adding an instruction vs rebuilding the cache directly.
| @@ -1,8 +1,11 @@ | |||
| #[cfg(feature = "stubs")] | |||
| use pyo3_stub_gen::derive::gen_stub_pyclass; | |||
There was a problem hiding this comment.
You've got a compilation error because this needs to be:
| use pyo3_stub_gen::derive::gen_stub_pyclass; | |
| use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; |
You can test these ahead of time by running
cargo make test-allAlong the same lines, you need to regenerate the type stubs and check them, since you've made changes to the Python API. You can do that with:
cargo make generate-stubsSince that requires the stubs feature, in this instance, it would have caught the issue.
| #[cfg_attr(not(feature = "python"), strip_pyo3)] | ||
| impl CircuitDefinition { | ||
| #[new] | ||
| pub fn new( |
There was a problem hiding this comment.
Since you're not able to use pickleable_new here1, you should implement __getnewargs__ manually so that CircuitDefinitions (and its subclasses) remain pickleable.
Since doing so is purely for Python users, our convention is to put it in a quilpy module. In this case, the appropriate place is src/instructions/quilpy.rs, likely around line 460.
Here's one possible implementation (the important thing is that its arguments are compatible with the method marked #[new]):
#[cfg_attr(feature = "stubs", gen_stub_pymethods)]
#[pymethods]
impl CircuitDefinition {
fn __getnewargs__<'py>(&self, py: pyo3::Python<'py>) -> PyResult<Bound<'py, PyTuple>> {
(
self.name.clone(),
self.parameters.clone(),
self.qubits.clone(),
self.instructions.clone(),
).into_pyobject(py)
}
}Footnotes
-
I'd still like to look into improving this macro to make it useful across a wider range of cases, and likely move it to
rigetti-pyo3. ↩
|
|
||
| // These cannot be fixed qubits and thus cannot be accessed directly | ||
| // outside of this crate | ||
| #[pyo3(get, set)] |
There was a problem hiding this comment.
You probably don't want set, if these must be Qubit::Variable, as otherwise Python users can set the list to other kinds of Qubits.
To that end, I think you should at least document that this must be Qubit::Variable explicitly. You could enforce that with a newtype that wraps String and serves as the type of both Qubit::Variable and the type of this Vec, but its not clear to me that's any better than just using String itself, which you're trying to change. That leads me to the obvious question:
Why should this type be Vec<Qubit> if it really is restricted to only the Variable variant?
There was a problem hiding this comment.
The main reason I had to make it Vec<Qubit> instead of the prior Vec<String> is because I needed to extract &Qubits from the circuit definition.
I could try to make something like this work?
enum QubitRef<'a> {
Fixed(u64),
Placeholder(QubitPlaceholder),
Variable(&str),
}| } | ||
|
|
||
| #[cfg(feature = "python")] | ||
| impl<'a, T: AsRef<str>> GateSignature<'a, T> { |
There was a problem hiding this comment.
Nit: I think you should just pull the try_new method into a separate impl block instead of repeating the #[cfg(feature = "python")]s.
| /// Return immutable references to the [`Qubit`]s contained within an instruction | ||
| #[allow(dead_code)] | ||
| pub fn get_qubits(&self) -> Vec<&Qubit> { | ||
| pub(crate) fn default_qubits<'a, T>(&'a self) -> T |
There was a problem hiding this comment.
As I look at this implementation, I am starting to think we should have an actual QubitIterator that can encapsulate this logic without all the collects. I'd like to see some performance details on this, as perhaps it wouldn't matter anyway, but at the very least, the implementation gets pretty messy here. I could imagine it being more of a performance/usability improvement for the Python API, but again, we'd need some profiling data to really say.
There was a problem hiding this comment.
Alternatively, I could just force it to be HashSet or Vec. It was Vec previously, but it makes more sense (imo) to be HashSet -- all the consumers either didn't care or would have preferred HashSet.
| .to_instructions() | ||
| .iter() | ||
| .flat_map(|instruction| instruction.get_qubits().into_iter().cloned()) | ||
| .flat_map(|instruction| DefaultHandler.get_qubits(instruction).into_iter().cloned()) |
There was a problem hiding this comment.
This can result in the same used_qubits as the old add_instruction implementation: if you call resolve_placeholders_with_custom_resolvers() (which calls this method) on a Program with DEFCAL instructions, the Variables end up in the cache. That was also true before this PR, but it represents an inconsistency between the way add_instruction and this method work within this PR.
I'll also note that DEFCIRCUIT qubits will also make their way into the cache after calling this method, but that wasn't true before the PR.
|
get_qubitsandget_qubits_mutnow properly returns the qubits used for previously-unaccounted-for InstructionsProgram::add_instructiononly caches body instructions inProgram::used_qubits.Instruction::get_qubitsandInstruction::get_qubits_mutfunctionality into theInstructionHandlertraitCloses #498