Skip to content

Commit a3bdbd0

Browse files
committed
Updating pyo3 version + adding reset() to byte message builder
1 parent b2097a9 commit a3bdbd0

File tree

5 files changed

+193
-20
lines changed

5 files changed

+193
-20
lines changed

Cargo.lock

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ categories = ["science", "simulation"]
3434
rand = "0.9"
3535
rand_chacha = "0.9"
3636
rand_xoshiro = "0.7"
37-
pyo3 = { version = "0.23", features = ["extension-module"] }
37+
pyo3 = { version = "0.24", features = ["extension-module"] }
3838
rayon = "1"
3939
clap = { version = "4", features = ["derive"] }
4040
log = "0.4"

crates/pecos-engines/src/channels/byte/builder.rs

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,11 +433,64 @@ impl ByteMessageBuilder {
433433
}
434434

435435
/// Clear the builder and start fresh
436+
///
437+
/// This method completely replaces the builder with a new instance,
438+
/// releasing any allocated memory. Use this when memory usage is a concern
439+
/// or when you want absolute certainty of a fresh state.
440+
///
441+
/// For performance-critical code or when creating many messages in sequence,
442+
/// consider using `reset()` instead, which preserves memory allocation.
443+
///
444+
/// After clearing, you'll need to configure the builder for the desired message type
445+
/// by calling `for_quantum_operations()` or `for_measurement_results()`.
436446
pub fn clear(&mut self) -> &mut Self {
437447
*self = Self::new();
438448
self
439449
}
440450

451+
/// Reset the builder state while preserving allocated memory
452+
///
453+
/// Unlike `clear()`, this method preserves the allocated memory buffer
454+
/// for better performance when reusing the same builder multiple times.
455+
/// This is the recommended method for performance-critical code,
456+
/// especially when creating many messages in sequence.
457+
///
458+
/// After resetting, you'll need to configure the builder for the desired message type
459+
/// by calling `for_quantum_operations()` or `for_measurement_results()`:
460+
///
461+
/// ```
462+
/// # use pecos_engines::channels::byte::builder::ByteMessageBuilder;
463+
/// let mut builder = ByteMessageBuilder::new();
464+
///
465+
/// // Create first message
466+
/// let _ = builder.for_quantum_operations();
467+
/// builder.add_h(&[0]);
468+
/// let message1 = builder.build();
469+
///
470+
/// // Reset and configure for next message
471+
/// builder.reset();
472+
/// let _ = builder.for_quantum_operations();
473+
/// builder.add_h(&[1]);
474+
/// let message2 = builder.build();
475+
/// ```
476+
///
477+
/// If memory usage is a concern or you want to ensure a completely fresh state,
478+
/// consider using `clear()` instead.
479+
pub fn reset(&mut self) -> &mut Self {
480+
// Truncate the buffer to just the batch header size
481+
self.buffer.truncate(size_of::<BatchHeader>());
482+
483+
// Zero out the batch header area more efficiently
484+
// Using slice fill is more efficient than a loop for small fixed-size areas
485+
self.buffer.fill(0);
486+
487+
// Reset message count and mode
488+
self.msg_count = 0;
489+
self.mode = BuilderMode::Empty;
490+
491+
self
492+
}
493+
441494
/// Build the final message batch without type checking
442495
pub fn build_unchecked(&mut self) -> ByteMessage {
443496
// Calculate total size and update batch header
@@ -703,4 +756,124 @@ mod tests {
703756
// Check the message count again
704757
assert_eq!(builder.message_count(), 1);
705758
}
759+
760+
#[test]
761+
fn test_reset() {
762+
// Create a builder
763+
let mut builder = ByteMessageBuilder::new();
764+
let _ = builder.for_quantum_operations();
765+
766+
// Add some gates
767+
builder.add_h(&[0]);
768+
builder.add_cx(&[0], &[1]);
769+
770+
// Check the message count
771+
assert_eq!(builder.message_count(), 3);
772+
773+
// Get the buffer capacity before reset
774+
let capacity_before = builder.buffer.capacity();
775+
776+
// Reset the builder
777+
builder.reset();
778+
779+
// Check the message count after reset
780+
assert_eq!(builder.message_count(), 0);
781+
assert_eq!(builder.mode(), BuilderMode::Empty);
782+
783+
// Verify the buffer capacity is preserved
784+
assert_eq!(builder.buffer.capacity(), capacity_before);
785+
786+
// Configure for quantum operations again
787+
let _ = builder.for_quantum_operations();
788+
789+
// Add a new gate
790+
builder.add_h(&[0]);
791+
792+
// Check the message count again
793+
assert_eq!(builder.message_count(), 2);
794+
795+
// Build the message and verify it's valid
796+
let message = builder.build();
797+
let commands = message.parse_quantum_operations().unwrap();
798+
assert_eq!(commands.len(), 1);
799+
assert_eq!(commands[0].gate_type, GateTypeId::H);
800+
}
801+
802+
#[test]
803+
fn compare_clear_vs_reset_performance() {
804+
const ITERATIONS: usize = 5000;
805+
const TRIALS: usize = 5;
806+
807+
let mut clear_durations = Vec::with_capacity(TRIALS);
808+
let mut reset_durations = Vec::with_capacity(TRIALS);
809+
810+
for _ in 0..TRIALS {
811+
// Test with clear()
812+
let start_clear = std::time::Instant::now();
813+
{
814+
let mut builder = ByteMessageBuilder::new();
815+
816+
for i in 0..ITERATIONS {
817+
if i > 0 {
818+
builder.clear();
819+
}
820+
821+
// Configure for quantum operations
822+
let _ = builder.for_quantum_operations();
823+
824+
// Add a gate
825+
builder.add_h(&[0]);
826+
827+
// Build the message
828+
let _message = builder.build();
829+
}
830+
}
831+
clear_durations.push(start_clear.elapsed());
832+
833+
// Test with reset()
834+
let start_reset = std::time::Instant::now();
835+
{
836+
let mut builder = ByteMessageBuilder::new();
837+
838+
for i in 0..ITERATIONS {
839+
if i > 0 {
840+
builder.reset();
841+
}
842+
843+
// Configure for quantum operations
844+
let _ = builder.for_quantum_operations();
845+
846+
// Add a gate
847+
builder.add_h(&[0]);
848+
849+
// Build the message
850+
let _message = builder.build();
851+
}
852+
}
853+
reset_durations.push(start_reset.elapsed());
854+
}
855+
856+
// Calculate averages
857+
#[allow(clippy::cast_precision_loss)]
858+
let avg_clear = clear_durations
859+
.iter()
860+
.map(std::time::Duration::as_secs_f64)
861+
.sum::<f64>()
862+
/ (TRIALS as f64);
863+
#[allow(clippy::cast_precision_loss)]
864+
let avg_reset = reset_durations
865+
.iter()
866+
.map(std::time::Duration::as_secs_f64)
867+
.sum::<f64>()
868+
/ (TRIALS as f64);
869+
870+
// Print results
871+
println!("Performance comparison ({TRIALS} trials of {ITERATIONS} iterations each):");
872+
println!(" clear() + for_quantum_operations(): {avg_clear:.6}s (average)");
873+
println!(" reset() + for_quantum_operations(): {avg_reset:.6}s (average)");
874+
println!(" reset() approach is {:.2}x faster", avg_clear / avg_reset);
875+
876+
// We don't assert anything here as performance can vary by environment,
877+
// but reset() should generally be faster
878+
}
706879
}

crates/pecos-engines/src/engines/noise/depolarizing.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -292,18 +292,18 @@ mod tests {
292292
let mut noise = DepolarizingNoise::new(0.01);
293293

294294
// Check initial probability
295-
assert_eq!(noise.probability(), 0.01);
295+
assert!((noise.probability() - 0.01).abs() < f64::EPSILON);
296296

297297
// Update probability and check it was updated
298298
noise.set_probability(0.05);
299-
assert_eq!(noise.probability(), 0.05);
299+
assert!((noise.probability() - 0.05).abs() < f64::EPSILON);
300300

301301
// Update to boundary values
302302
noise.set_probability(0.0);
303-
assert_eq!(noise.probability(), 0.0);
303+
assert!((noise.probability() - 0.0).abs() < f64::EPSILON);
304304

305305
noise.set_probability(1.0);
306-
assert_eq!(noise.probability(), 1.0);
306+
assert!((noise.probability() - 1.0).abs() < f64::EPSILON);
307307
}
308308

309309
#[test]
@@ -351,7 +351,7 @@ mod tests {
351351
.downcast_mut::<DepolarizingNoise>()
352352
.unwrap();
353353
downcast_noise.set_probability(0.05);
354-
assert_eq!(noise.probability(), 0.05);
354+
assert!((noise.probability() - 0.05).abs() < f64::EPSILON);
355355

356356
// Test with boxed trait object
357357
let mut boxed_noise: Box<dyn NoiseModel> = Box::new(DepolarizingNoise::new(0.01));

python/proto_bytemessage_py/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ name = "proto_bytemessage_py"
1717
crate-type = ["cdylib", "rlib"]
1818

1919
[dependencies]
20-
pyo3 = { version = "0.23", features = ["extension-module", "abi3-py312", "experimental-inspect", "auto-initialize"] }
21-
bytemuck = { version = "1.14", features = ["derive"] }
20+
pyo3 = { workspace = true, features = ["extension-module", "abi3-py312", "experimental-inspect", "auto-initialize"] }
21+
bytemuck = { workspace = true, features = ["derive"] }
2222
proto_bytemessage = { path = "../../crates/proto_bytemessage" }
2323

2424
[lints]

0 commit comments

Comments
 (0)