From 08705646fd6cd8a4c8adbe0895d8a57d81c85cb5 Mon Sep 17 00:00:00 2001 From: Jessica Jeng Date: Tue, 24 Feb 2026 23:25:33 -0800 Subject: [PATCH 1/4] feat: move Instruction::get_qubits into InstructionHandler --- quil-rs/src/instruction/circuit.rs | 60 ++-- quil-rs/src/instruction/gate.rs | 5 +- quil-rs/src/instruction/mod.rs | 271 ++++++++++++++---- quil-rs/src/parser/command.rs | 14 +- quil-rs/src/program/analysis/qubit_graph.rs | 4 +- quil-rs/src/program/mod.rs | 62 ++-- ...l_rs__program__tests__to_instructions.snap | 10 +- 7 files changed, 300 insertions(+), 126 deletions(-) diff --git a/quil-rs/src/instruction/circuit.rs b/quil-rs/src/instruction/circuit.rs index c59ff0dbc..435f707df 100644 --- a/quil-rs/src/instruction/circuit.rs +++ b/quil-rs/src/instruction/circuit.rs @@ -1,8 +1,11 @@ #[cfg(feature = "stubs")] use pyo3_stub_gen::derive::gen_stub_pyclass; +#[cfg(not(feature = "python"))] +use optipy::strip_pyo3; + use crate::{ - pickleable_new, + instruction::Qubit, quil::{Quil, INDENT}, }; @@ -12,24 +15,42 @@ use super::Instruction; #[cfg_attr(feature = "stubs", gen_stub_pyclass)] #[cfg_attr( feature = "python", - pyo3::pyclass(module = "quil.instructions", eq, get_all, set_all, subclass) + pyo3::pyclass(module = "quil.instructions", eq, subclass) )] +#[cfg_attr(not(feature = "python"), strip_pyo3)] pub struct CircuitDefinition { + #[pyo3(get, set)] pub name: String, + + #[pyo3(get, set)] pub parameters: Vec, - // These cannot be fixed qubits and thus are not typed as `Qubit` - pub qubit_variables: Vec, + + // These cannot be fixed qubits and thus cannot be accessed directly + // outside of this crate + #[pyo3(get, set)] + pub(crate) qubits: Vec, + + #[pyo3(get, set)] pub instructions: Vec, } -pickleable_new! { - impl CircuitDefinition { - pub fn new( - name: String, - parameters: Vec, - qubit_variables: Vec, - instructions: Vec, - ); +#[cfg_attr(feature = "stubs", gen_stub_pymethods)] +#[cfg_attr(feature = "python", pyo3::pymethods)] +#[cfg_attr(not(feature = "python"), strip_pyo3)] +impl CircuitDefinition { + #[new] + pub fn new( + name: String, + parameters: Vec, + qubit_variables: Vec, + instructions: Vec, + ) -> Self { + Self { + name, + parameters, + qubits: qubit_variables.into_iter().map(Qubit::Variable).collect(), + instructions, + } } } @@ -51,8 +72,13 @@ impl Quil for CircuitDefinition { } write!(writer, ")")?; } - for qubit_variable in &self.qubit_variables { - write!(writer, " {qubit_variable}")?; + for qubit in &self.qubits { + let q = if fall_back_to_debug { + qubit.to_quil_or_debug() + } else { + qubit.to_quil()? + }; + write!(writer, " {q}")?; } writeln!(writer, ":")?; for instruction in &self.instructions { @@ -86,7 +112,7 @@ mod test_circuit_definition { CircuitDefinition { name: "BELL".to_owned(), parameters: vec![], - qubit_variables: vec!["a".to_owned(), "b".to_owned()], + qubits: vec![Qubit::Variable("a".to_owned()), Qubit::Variable("b".to_owned())], instructions: vec![ Instruction::Gate(Gate { name: "H".to_owned(), @@ -111,7 +137,7 @@ mod test_circuit_definition { CircuitDefinition { name: "BELL".to_owned(), parameters: vec!["a".to_owned(), "b".to_owned()], - qubit_variables: vec!["a".to_owned(), "b".to_owned()], + qubits: vec![Qubit::Variable("a".to_owned()), Qubit::Variable("b".to_owned())], instructions: vec![ Instruction::Gate(Gate { name: "RZ".to_owned(), @@ -154,7 +180,7 @@ mod test_circuit_definition { CircuitDefinition { name: "BELL".to_owned(), parameters: vec!["a".to_owned()], - qubit_variables: vec!["a".to_owned(), "b".to_owned()], + qubits: vec![Qubit::Variable("a".to_owned()), Qubit::Variable("b".to_owned())], instructions: vec![ Instruction::Gate(Gate { name: "RZ".to_owned(), diff --git a/quil-rs/src/instruction/gate.rs b/quil-rs/src/instruction/gate.rs index cbf0853c4..e6d84aef8 100644 --- a/quil-rs/src/instruction/gate.rs +++ b/quil-rs/src/instruction/gate.rs @@ -1056,8 +1056,8 @@ impl<'a, T: AsRef> GateSignature<'a, T> { } } -#[cfg(feature = "python")] impl<'a, T: AsRef> GateSignature<'a, T> { + #[cfg(any(feature = "python", test))] pub(crate) fn try_new( name: &'a T, gate_parameters: &'a [T], @@ -1073,14 +1073,17 @@ impl<'a, T: AsRef> GateSignature<'a, T> { }) } + #[cfg(feature = "python")] pub(crate) fn gate_parameters(&self) -> &[T] { self.gate_parameters } + #[cfg(feature = "python")] pub(crate) fn qubit_parameters(&self) -> &[T] { self.qubit_parameters } + #[cfg(feature = "python")] pub(crate) fn gate_type(&self) -> GateType { self.gate_type } diff --git a/quil-rs/src/instruction/mod.rs b/quil-rs/src/instruction/mod.rs index c64ba7439..63f97c5c4 100644 --- a/quil-rs/src/instruction/mod.rs +++ b/quil-rs/src/instruction/mod.rs @@ -685,81 +685,220 @@ impl Instruction { } /// 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 + where + T: FromIterator<&'a Qubit> + IntoIterator, + { match self { - Instruction::Gate(gate) => gate.qubits.iter().collect(), - Instruction::CalibrationDefinition(calibration) => calibration - .identifier + Instruction::CircuitDefinition(CircuitDefinition { + qubits, + instructions, + name: _, + parameters: _, + }) => qubits + .iter() + .chain( + instructions + .iter() + .flat_map(|inst| inst.default_qubits::()), + ) + .collect(), + + Instruction::CalibrationDefinition(CalibrationDefinition { + identifier, + instructions, + }) => identifier .qubits .iter() .chain( - calibration - .instructions + instructions + .iter() + .flat_map(|inst| inst.default_qubits::()), + ) + .collect(), + + Instruction::MeasureCalibrationDefinition(MeasureCalibrationDefinition { + identifier, + instructions, + }) => iter::once(&identifier.qubit) + .chain( + instructions .iter() - .flat_map(|inst| inst.get_qubits()), + .flat_map(|inst| inst.default_qubits::()), ) .collect(), - Instruction::MeasureCalibrationDefinition(measurement) => { - iter::once(&measurement.identifier.qubit) - .chain( - measurement - .instructions - .iter() - .flat_map(|inst| inst.get_qubits()), - ) - .collect() + + Instruction::Measurement(Measurement { + qubit, + name: _, + target: _, + }) => iter::once(qubit).collect(), + + Instruction::Reset(Reset { qubit }) => qubit.iter().collect(), + + Instruction::Gate(Gate { + qubits, + name: _, + parameters: _, + modifiers: _, + }) + | Instruction::Fence(Fence { qubits }) + | Instruction::Delay(Delay { + qubits, + duration: _, + frame_names: _, + }) => qubits.iter().collect(), + + Instruction::FrameDefinition(FrameDefinition { + identifier: frame, + attributes: _, + }) + | Instruction::Capture(Capture { + frame, + blocking: _, + memory_reference: _, + waveform: _, + }) + | Instruction::Pulse(Pulse { + frame, + blocking: _, + waveform: _, + }) + | Instruction::RawCapture(RawCapture { + frame, + blocking: _, + duration: _, + memory_reference: _, + }) + | Instruction::SetFrequency(SetFrequency { + frame, + frequency: _, + }) + | Instruction::SetPhase(SetPhase { frame, phase: _ }) + | Instruction::SetScale(SetScale { frame, scale: _ }) + | Instruction::ShiftFrequency(ShiftFrequency { + frame, + frequency: _, + }) + | Instruction::ShiftPhase(ShiftPhase { frame, phase: _ }) => { + frame.qubits.iter().collect() } - Instruction::Measurement(measurement) => vec![&measurement.qubit], - Instruction::Reset(reset) => match &reset.qubit { - Some(qubit) => vec![qubit], - None => vec![], - }, - Instruction::Delay(delay) => delay.qubits.iter().collect(), - Instruction::Fence(fence) => fence.qubits.iter().collect(), - Instruction::Capture(capture) => capture.frame.qubits.iter().collect(), - Instruction::Pulse(pulse) => pulse.frame.qubits.iter().collect(), - Instruction::RawCapture(raw_capture) => raw_capture.frame.qubits.iter().collect(), - _ => vec![], + + Instruction::SwapPhases(SwapPhases { frame_1, frame_2 }) => { + frame_1.qubits.iter().chain(frame_2.qubits.iter()).collect() + } + + _ => iter::empty().collect(), } } /// Return mutable references to the [`Qubit`]s contained within an instruction - pub fn get_qubits_mut(&mut self) -> Vec<&mut Qubit> { + pub(crate) fn default_qubits_mut<'a, T>(&'a mut self) -> T + where + T: FromIterator<&'a mut Qubit> + IntoIterator, + { match self { - Instruction::Gate(gate) => gate.qubits.iter_mut().collect(), - Instruction::CalibrationDefinition(calibration) => calibration - .identifier + Instruction::CircuitDefinition(CircuitDefinition { + qubits, + instructions, + name: _, + parameters: _, + }) => qubits + .iter_mut() + .chain( + instructions + .iter_mut() + .flat_map(|inst| inst.default_qubits_mut::()), + ) + .collect(), + + Instruction::CalibrationDefinition(CalibrationDefinition { + identifier, + instructions, + }) => identifier .qubits .iter_mut() .chain( - calibration - .instructions + instructions + .iter_mut() + .flat_map(|inst| inst.default_qubits_mut::()), + ) + .collect(), + + Instruction::MeasureCalibrationDefinition(MeasureCalibrationDefinition { + identifier, + instructions, + }) => iter::once(&mut identifier.qubit) + .chain( + instructions .iter_mut() - .flat_map(|inst| inst.get_qubits_mut()), + .flat_map(|inst| inst.default_qubits_mut::()), ) .collect(), - Instruction::MeasureCalibrationDefinition(measurement) => { - iter::once(&mut measurement.identifier.qubit) - .chain( - measurement - .instructions - .iter_mut() - .flat_map(|inst| inst.get_qubits_mut()), - ) - .collect() + + Instruction::Measurement(Measurement { + qubit, + name: _, + target: _, + }) => iter::once(qubit).collect(), + + Instruction::Reset(Reset { qubit }) => qubit.iter_mut().collect(), + + Instruction::Gate(Gate { + qubits, + name: _, + parameters: _, + modifiers: _, + }) + | Instruction::Fence(Fence { qubits }) + | Instruction::Delay(Delay { + qubits, + duration: _, + frame_names: _, + }) => qubits.iter_mut().collect(), + + Instruction::FrameDefinition(FrameDefinition { + identifier: frame, + attributes: _, + }) + | Instruction::Capture(Capture { + frame, + blocking: _, + memory_reference: _, + waveform: _, + }) + | Instruction::Pulse(Pulse { + frame, + blocking: _, + waveform: _, + }) + | Instruction::RawCapture(RawCapture { + frame, + blocking: _, + duration: _, + memory_reference: _, + }) + | Instruction::SetFrequency(SetFrequency { + frame, + frequency: _, + }) + | Instruction::SetPhase(SetPhase { frame, phase: _ }) + | Instruction::SetScale(SetScale { frame, scale: _ }) + | Instruction::ShiftFrequency(ShiftFrequency { + frame, + frequency: _, + }) + | Instruction::ShiftPhase(ShiftPhase { frame, phase: _ }) => { + frame.qubits.iter_mut().collect() } - Instruction::Measurement(measurement) => vec![&mut measurement.qubit], - Instruction::Reset(reset) => match &mut reset.qubit { - Some(qubit) => vec![qubit], - None => vec![], - }, - Instruction::Delay(delay) => delay.qubits.iter_mut().collect(), - Instruction::Fence(fence) => fence.qubits.iter_mut().collect(), - Instruction::Capture(capture) => capture.frame.qubits.iter_mut().collect(), - Instruction::Pulse(pulse) => pulse.frame.qubits.iter_mut().collect(), - Instruction::RawCapture(raw_capture) => raw_capture.frame.qubits.iter_mut().collect(), - _ => vec![], + + Instruction::SwapPhases(SwapPhases { frame_1, frame_2 }) => frame_1 + .qubits + .iter_mut() + .chain(frame_2.qubits.iter_mut()) + .collect(), + + _ => iter::empty().collect(), } } @@ -807,7 +946,7 @@ impl Instruction { jump_unless.target.resolve_placeholder(target_resolver); } other => { - for qubit in other.get_qubits_mut() { + for qubit in other.default_qubits_mut::>() { qubit.resolve_placeholder(&qubit_resolver); } } @@ -904,6 +1043,16 @@ pub trait InstructionHandler { ) -> Result { DefaultHandler.memory_accesses(extern_signature_map, instruction) } + + #[inline] + fn get_qubits<'a>(&self, instruction: &'a Instruction) -> HashSet<&'a Qubit> { + DefaultHandler.get_qubits(instruction) + } + + #[inline] + fn get_qubits_mut<'a>(&self, instruction: &'a mut Instruction) -> HashSet<&'a mut Qubit> { + DefaultHandler.get_qubits_mut(instruction) + } } /// The default instruction-handling behavior. @@ -983,6 +1132,14 @@ impl InstructionHandler for DefaultHandler { .map(|condition| program.frames.filter(condition)) } + fn get_qubits<'a>(&self, instruction: &'a Instruction) -> HashSet<&'a Qubit> { + instruction.default_qubits() + } + + fn get_qubits_mut<'a>(&self, instruction: &'a mut Instruction) -> HashSet<&'a mut Qubit> { + instruction.default_qubits_mut() + } + fn memory_accesses( &self, extern_signature_map: &ExternSignatureMap, @@ -1248,7 +1405,7 @@ impl InstructionHandler for DefaultHandler { instructions, name: _, parameters: _, - qubit_variables: _, + .. }) | Instruction::MeasureCalibrationDefinition(MeasureCalibrationDefinition { instructions, diff --git a/quil-rs/src/parser/command.rs b/quil-rs/src/parser/command.rs index cdee35354..b3ae0d588 100644 --- a/quil-rs/src/parser/command.rs +++ b/quil-rs/src/parser/command.rs @@ -362,12 +362,12 @@ pub(crate) fn parse_defcircuit<'a>( Ok(( input, - Instruction::CircuitDefinition(CircuitDefinition { + Instruction::CircuitDefinition(CircuitDefinition::new( name, parameters, qubit_variables, instructions, - }), + )), )) } @@ -904,7 +904,10 @@ mod tests { Instruction::CircuitDefinition(CircuitDefinition { name: "BELL".to_owned(), parameters: vec![], - qubit_variables: vec!["a".to_owned(), "b".to_owned()], + qubits: vec![ + Qubit::Variable("a".to_owned()), + Qubit::Variable("b".to_owned()) + ], instructions: vec![ Instruction::Gate(Gate { name: "H".to_owned(), @@ -936,7 +939,10 @@ mod tests { Instruction::CircuitDefinition(CircuitDefinition { name: "BELL".to_owned(), parameters: vec!["a".to_owned()], - qubit_variables: vec!["a".to_owned(), "b".to_owned()], + qubits: vec![ + Qubit::Variable("a".to_owned()), + Qubit::Variable("b".to_owned()) + ], instructions: vec![ Instruction::Gate(Gate { name: "RZ".to_owned(), diff --git a/quil-rs/src/program/analysis/qubit_graph.rs b/quil-rs/src/program/analysis/qubit_graph.rs index b990f6bbe..e136e0737 100644 --- a/quil-rs/src/program/analysis/qubit_graph.rs +++ b/quil-rs/src/program/analysis/qubit_graph.rs @@ -68,11 +68,9 @@ impl<'a> QubitGraph<'a> { } } - let qubits: Vec<_> = instruction.get_qubits().into_iter().collect(); - let node = graph.add_node(instruction); - for qubit in qubits { + for qubit in handler.get_qubits(instruction) { if let Some(last_instruction) = last_instruction_for_qubit.insert(qubit, node) { graph.add_edge(last_instruction, node, ()); } diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index ad1f62dd9..3ac63574e 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -228,9 +228,6 @@ impl Program { /// instructions are still added to the [`Program::extern_pragma_map`]; /// duplicate `PRAGMA EXTERN` names are overwritten. pub fn add_instruction(&mut self, instruction: Instruction) { - self.used_qubits - .extend(instruction.get_qubits().into_iter().cloned()); - match instruction { Instruction::CalibrationDefinition(calibration) => { self.calibrations.insert_calibration(calibration); @@ -263,35 +260,14 @@ impl Program { Instruction::WaveformDefinition(WaveformDefinition { name, definition }) => { self.waveforms.insert(name, definition); } - Instruction::Gate(gate) => { - self.instructions.push(Instruction::Gate(gate)); - } - Instruction::Measurement(measurement) => { - self.instructions - .push(Instruction::Measurement(measurement)); - } - Instruction::Reset(reset) => { - self.instructions.push(Instruction::Reset(reset)); - } - Instruction::Delay(delay) => { - self.instructions.push(Instruction::Delay(delay)); - } - Instruction::Fence(fence) => { - self.instructions.push(Instruction::Fence(fence)); - } - Instruction::Capture(capture) => { - self.instructions.push(Instruction::Capture(capture)); - } - Instruction::Pulse(pulse) => { - self.instructions.push(Instruction::Pulse(pulse)); - } Instruction::Pragma(pragma) if pragma.name == RESERVED_PRAGMA_EXTERN => { self.extern_pragma_map.insert(pragma); } - Instruction::RawCapture(raw_capture) => { - self.instructions.push(Instruction::RawCapture(raw_capture)); + other => { + self.used_qubits + .extend(other.default_qubits::>().into_iter().cloned()); + self.instructions.push(other) } - other => self.instructions.push(other), } } @@ -371,7 +347,7 @@ impl Program { }), ] .into_iter() - .chain(self.body_instructions().cloned()) + .chain(self.body_instructions().iter().cloned()) .chain(vec![ Instruction::Arithmetic(Arithmetic { operator: ArithmeticOperator::Subtract, @@ -446,18 +422,17 @@ impl Program { } impl Program { - /// Returns an iterator over immutable references to the instructions that make up the body of the program. - pub fn body_instructions(&self) -> impl Iterator { - self.instructions.iter() + /// Returns a slice of immutable references to the instructions that make up the body of the program. + pub fn body_instructions(&self) -> &[Instruction] { + &self.instructions } - pub fn into_body_instructions(self) -> impl Iterator { - self.instructions.into_iter() + pub fn into_body_instructions(self) -> Vec { + self.instructions } /// Returns an iterator over mutable references to the instructions that make up the body of the program. - #[cfg(test)] - pub(crate) fn for_each_body_instruction(&mut self, closure: F) + pub fn for_each_body_instruction(&mut self, closure: F) where F: Fn(&mut Instruction), { @@ -790,7 +765,12 @@ impl Program { self.used_qubits = self .to_instructions() .iter() - .flat_map(|instruction| instruction.get_qubits().into_iter().cloned()) + .flat_map(|instruction| { + instruction + .default_qubits::>() + .into_iter() + .cloned() + }) .collect() } @@ -943,7 +923,7 @@ impl Program { // Stable iteration order makes placeholder resolution deterministic for instruction in &self.instructions { - let qubits = instruction.get_qubits(); + let qubits = instruction.default_qubits::>(); for qubit in qubits { match qubit { @@ -2377,7 +2357,7 @@ CALL foo octets[1] reals let mut program = Program::new(); program.add_instruction(Instruction::GateDefinition(gate_definition.clone())); assert_eq!(program.gate_definitions.len(), 1); - assert_eq!(program.body_instructions().count(), 0); + assert_eq!(program.body_instructions().len(), 0); let invocation = Gate::new( "PMW3", @@ -2412,7 +2392,7 @@ CALL foo octets[1] reals ) .expect("must be a valid gate"); program.add_instruction(Instruction::Gate(invocation)); - assert_eq!(program.body_instructions().count(), 1); + assert_eq!(program.body_instructions().len(), 1); let program_copy = program.clone_without_body_instructions(); assert_eq!(program_copy.gate_definitions.len(), 1); @@ -2423,7 +2403,7 @@ CALL foo octets[1] reals .expect("must exist"), &gate_definition ); - assert_eq!(program_copy.body_instructions().count(), 0); + assert_eq!(program_copy.body_instructions().len(), 0); } /// Test that we can expand a gate sequence definition in a program. Note, for more diff --git a/quil-rs/src/program/snapshots/quil_rs__program__tests__to_instructions.snap b/quil-rs/src/program/snapshots/quil_rs__program__tests__to_instructions.snap index 942f927b4..71f85f759 100644 --- a/quil-rs/src/program/snapshots/quil_rs__program__tests__to_instructions.snap +++ b/quil-rs/src/program/snapshots/quil_rs__program__tests__to_instructions.snap @@ -126,9 +126,13 @@ expression: program.to_instructions() CircuitDefinition { name: "BELL", parameters: [], - qubit_variables: [ - "q0", - "q1", + qubits: [ + Variable( + "q0", + ), + Variable( + "q1", + ), ], instructions: [ Gate( From 39b5579dba141eb51fa58c19b9086bd53a1eb83a Mon Sep 17 00:00:00 2001 From: Jessica Jeng Date: Wed, 25 Feb 2026 08:57:10 -0800 Subject: [PATCH 2/4] chore: make program use DefaultHandler.get_qubits --- quil-rs/src/program/mod.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index 3ac63574e..bed3a6d85 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -30,11 +30,11 @@ use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; use crate::instruction::{ Arithmetic, ArithmeticOperand, ArithmeticOperator, Call, CircuitDefinition, Declaration, - DefGateSequenceExpansionError, ExternError, ExternPragmaMap, ExternSignatureMap, - FrameDefinition, FrameIdentifier, GateDefinition, GateError, GateSpecification, Instruction, - InstructionHandler, Jump, JumpUnless, Label, Matrix, MemoryReference, Move, Pragma, Qubit, - QubitPlaceholder, ScalarType, Target, TargetPlaceholder, Vector, Waveform, WaveformDefinition, - RESERVED_PRAGMA_EXTERN, + DefGateSequenceExpansionError, DefaultHandler, ExternError, ExternPragmaMap, + ExternSignatureMap, FrameDefinition, FrameIdentifier, GateDefinition, GateError, + GateSpecification, Instruction, InstructionHandler, Jump, JumpUnless, Label, Matrix, + MemoryReference, Move, Pragma, Qubit, QubitPlaceholder, ScalarType, Target, TargetPlaceholder, + Vector, Waveform, WaveformDefinition, RESERVED_PRAGMA_EXTERN, }; use crate::parser::{lex, parse_instructions, ParseError}; use crate::program::defgate_sequence_expansion::{ @@ -265,7 +265,7 @@ impl Program { } other => { self.used_qubits - .extend(other.default_qubits::>().into_iter().cloned()); + .extend(DefaultHandler.get_qubits(&other).into_iter().cloned()); self.instructions.push(other) } } @@ -765,12 +765,7 @@ impl Program { self.used_qubits = self .to_instructions() .iter() - .flat_map(|instruction| { - instruction - .default_qubits::>() - .into_iter() - .cloned() - }) + .flat_map(|instruction| DefaultHandler.get_qubits(instruction).into_iter().cloned()) .collect() } @@ -923,7 +918,7 @@ impl Program { // Stable iteration order makes placeholder resolution deterministic for instruction in &self.instructions { - let qubits = instruction.default_qubits::>(); + let qubits = DefaultHandler.get_qubits(instruction); for qubit in qubits { match qubit { From eae9eddb802e394144a4e79580b59b598f930f56 Mon Sep 17 00:00:00 2001 From: Jessica Jeng Date: Tue, 3 Mar 2026 14:48:50 -0800 Subject: [PATCH 3/4] chore: review comments --- quil-rs/src/instruction/circuit.rs | 9 ++++----- quil-rs/src/instruction/gate.rs | 32 +++++++++++++++--------------- quil-rs/src/instruction/quilpy.rs | 14 +++++++++++++ quil-rs/src/program/mod.rs | 12 +++++++++++ 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/quil-rs/src/instruction/circuit.rs b/quil-rs/src/instruction/circuit.rs index 435f707df..984023343 100644 --- a/quil-rs/src/instruction/circuit.rs +++ b/quil-rs/src/instruction/circuit.rs @@ -1,5 +1,5 @@ #[cfg(feature = "stubs")] -use pyo3_stub_gen::derive::gen_stub_pyclass; +use pyo3_stub_gen::derive::{gen_stub_pyclass, gen_stub_pymethods}; #[cfg(not(feature = "python"))] use optipy::strip_pyo3; @@ -12,7 +12,7 @@ use crate::{ use super::Instruction; #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "stubs", gen_stub_pyclass)] +#[cfg_attr(feature = "stubs", gen_stub_pyclass, gen_stub_pymethods)] #[cfg_attr( feature = "python", pyo3::pyclass(module = "quil.instructions", eq, subclass) @@ -25,9 +25,8 @@ pub struct CircuitDefinition { #[pyo3(get, set)] pub parameters: Vec, - // These cannot be fixed qubits and thus cannot be accessed directly - // outside of this crate - #[pyo3(get, set)] + // These should always be Qubit::Variable + #[pyo3(get)] pub(crate) qubits: Vec, #[pyo3(get, set)] diff --git a/quil-rs/src/instruction/gate.rs b/quil-rs/src/instruction/gate.rs index e6d84aef8..9304b99a2 100644 --- a/quil-rs/src/instruction/gate.rs +++ b/quil-rs/src/instruction/gate.rs @@ -1056,8 +1056,23 @@ impl<'a, T: AsRef> GateSignature<'a, T> { } } +#[cfg(feature = "python")] +impl<'a, T: AsRef> GateSignature<'a, T> { + pub(crate) fn gate_parameters(&self) -> &[T] { + self.gate_parameters + } + + pub(crate) fn qubit_parameters(&self) -> &[T] { + self.qubit_parameters + } + + pub(crate) fn gate_type(&self) -> GateType { + self.gate_type + } +} + +#[cfg(any(feature = "python", test))] impl<'a, T: AsRef> GateSignature<'a, T> { - #[cfg(any(feature = "python", test))] pub(crate) fn try_new( name: &'a T, gate_parameters: &'a [T], @@ -1072,21 +1087,6 @@ impl<'a, T: AsRef> GateSignature<'a, T> { gate_type, }) } - - #[cfg(feature = "python")] - pub(crate) fn gate_parameters(&self) -> &[T] { - self.gate_parameters - } - - #[cfg(feature = "python")] - pub(crate) fn qubit_parameters(&self) -> &[T] { - self.qubit_parameters - } - - #[cfg(feature = "python")] - pub(crate) fn gate_type(&self) -> GateType { - self.gate_type - } } impl Quil for GateSignature<'_> { diff --git a/quil-rs/src/instruction/quilpy.rs b/quil-rs/src/instruction/quilpy.rs index 81233761a..a7a4d8c6b 100644 --- a/quil-rs/src/instruction/quilpy.rs +++ b/quil-rs/src/instruction/quilpy.rs @@ -456,6 +456,20 @@ pickleable_new! { } } +#[cfg_attr(feature = "stubs", gen_stub_pymethods)] +#[pymethods] +impl CircuitDefinition { + fn __getnewargs__<'py>(&self, py: pyo3::Python<'py>) -> PyResult> { + ( + self.name.clone(), + self.parameters.clone(), + self.qubits.clone(), + self.instructions.clone(), + ) + .into_pyobject(py) + } +} + #[cfg(feature = "stubs")] impl pyo3_stub_gen::PyStubType for ExternPragmaMap { fn type_output() -> pyo3_stub_gen::TypeInfo { diff --git a/quil-rs/src/program/mod.rs b/quil-rs/src/program/mod.rs index bed3a6d85..0c9630453 100644 --- a/quil-rs/src/program/mod.rs +++ b/quil-rs/src/program/mod.rs @@ -765,6 +765,18 @@ impl Program { self.used_qubits = self .to_instructions() .iter() + // This ensures parity with the behavior of `Self::add_instruction` + .filter(|instruction| match instruction { + Instruction::CalibrationDefinition(_) + | Instruction::CircuitDefinition(_) + | Instruction::FrameDefinition(_) + | Instruction::Declaration(_) + | Instruction::GateDefinition(_) + | Instruction::MeasureCalibrationDefinition(_) + | Instruction::WaveformDefinition(_) => false, + Instruction::Pragma(pragma) if pragma.name == RESERVED_PRAGMA_EXTERN => false, + _ => true, + }) .flat_map(|instruction| DefaultHandler.get_qubits(instruction).into_iter().cloned()) .collect() } From e180e09bd38b3dc8b3dc9401a83f1f937571a03e Mon Sep 17 00:00:00 2001 From: Jessica Jeng Date: Wed, 4 Mar 2026 12:10:21 -0600 Subject: [PATCH 4/4] chore: stubs --- quil-rs/python/quil/instructions.pyi | 6 ++---- quil-rs/src/instruction/circuit.rs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/quil-rs/python/quil/instructions.pyi b/quil-rs/python/quil/instructions.pyi index d9dd44520..c568b9738 100644 --- a/quil-rs/python/quil/instructions.pyi +++ b/quil-rs/python/quil/instructions.pyi @@ -384,11 +384,9 @@ class CircuitDefinition: @parameters.setter def parameters(self, value: builtins.list[builtins.str]) -> None: ... @property - def qubit_variables(self) -> builtins.list[builtins.str]: ... - @qubit_variables.setter - def qubit_variables(self, value: builtins.list[builtins.str]) -> None: ... + def qubits(self) -> builtins.list[Qubit]: ... def __eq__(self, other: builtins.object) -> builtins.bool: ... - def __getnewargs__(self) -> tuple[builtins.str, builtins.list[builtins.str], builtins.list[builtins.str], builtins.list[Instruction]]: ... + def __getnewargs__(self) -> tuple: ... def __new__(cls, name: builtins.str, parameters: typing.Sequence[builtins.str], qubit_variables: typing.Sequence[builtins.str], instructions: typing.Sequence[Instruction]) -> CircuitDefinition: ... def __repr__(self) -> builtins.str: r""" diff --git a/quil-rs/src/instruction/circuit.rs b/quil-rs/src/instruction/circuit.rs index 984023343..4f491745c 100644 --- a/quil-rs/src/instruction/circuit.rs +++ b/quil-rs/src/instruction/circuit.rs @@ -12,7 +12,7 @@ use crate::{ use super::Instruction; #[derive(Clone, Debug, PartialEq)] -#[cfg_attr(feature = "stubs", gen_stub_pyclass, gen_stub_pymethods)] +#[cfg_attr(feature = "stubs", gen_stub_pyclass)] #[cfg_attr( feature = "python", pyo3::pyclass(module = "quil.instructions", eq, subclass)