From abbaa018c5c38a5fa472ccd6c8204c6937e6fc2b Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Tue, 28 Apr 2026 07:07:06 +0000 Subject: [PATCH 1/4] Runtime of a trace. --- source/pip/qsharp/qre/_qre.pyi | 12 ++++++++++++ source/pip/src/qre.rs | 5 +++++ source/qre/src/trace.rs | 19 +++++++++++-------- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/source/pip/qsharp/qre/_qre.pyi b/source/pip/qsharp/qre/_qre.pyi index 370bd2c886..13331879a7 100644 --- a/source/pip/qsharp/qre/_qre.pyi +++ b/source/pip/qsharp/qre/_qre.pyi @@ -1360,6 +1360,18 @@ class Trace: """ ... + def runtime(self, isa: ISA) -> Optional[int]: + """ + The trace runtime in nanoseconds for a given ISA. + + Args: + isa (ISA): The ISA to compute the runtime for. + + Returns: + Optional[int]: The trace runtime in nanoseconds, or None if it + cannot be computed. + """ + @property def num_gates(self) -> int: """ diff --git a/source/pip/src/qre.rs b/source/pip/src/qre.rs index 0b7b1ef60f..ad598606e1 100644 --- a/source/pip/src/qre.rs +++ b/source/pip/src/qre.rs @@ -1165,6 +1165,11 @@ impl Trace { self.0.depth() } + pub fn runtime(&self, isa: &ISA) -> Option { + let locked = isa.0.lock(); + self.0.runtime(&locked).ok() + } + #[getter] pub fn num_gates(&self) -> u64 { self.0.num_gates() diff --git a/source/qre/src/trace.rs b/source/qre/src/trace.rs index 0e2d1ef106..6ee39b6367 100644 --- a/source/qre/src/trace.rs +++ b/source/qre/src/trace.rs @@ -227,6 +227,16 @@ impl Trace { self.deep_iter().map(|(_, m)| m).sum() } + pub fn runtime(&self, locked: &LockedISA) -> Result { + Ok(self + .block + .depth_and_used(Some(&|op: &Gate| { + let instr = get_instruction(locked, op.id)?; + Ok(instr.expect_time(Some(op.qubits.len() as u64))) + }))? + .0) + } + #[allow( clippy::cast_precision_loss, clippy::cast_possible_truncation, @@ -310,14 +320,7 @@ impl Trace { Property::Int(total_compute_qubits.cast_signed()), ); - result.add_runtime( - self.block - .depth_and_used(Some(&|op: &Gate| { - let instr = get_instruction(&locked, op.id)?; - Ok(instr.expect_time(Some(op.qubits.len() as u64))) - }))? - .0, - ); + result.add_runtime(self.runtime(&locked)?); // ------------------------------------------------------------------ // Factory overhead estimation. Each factory produces states at From 1ee0642b5d27b00843efd07b5af80152d67d1784 Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Wed, 29 Apr 2026 11:26:04 +0000 Subject: [PATCH 2/4] Add tests for runtime method. --- source/qre/src/trace/tests.rs | 204 +++++++++++++++++++++++++++++----- 1 file changed, 176 insertions(+), 28 deletions(-) diff --git a/source/qre/src/trace/tests.rs b/source/qre/src/trace/tests.rs index 8b31717969..b0a9673df5 100644 --- a/source/qre/src/trace/tests.rs +++ b/source/qre/src/trace/tests.rs @@ -1,10 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +use crate::isa::{Encoding, ISA, Instruction}; +use crate::trace::{Trace, instruction_ids::*}; + #[test] fn test_trace_iteration() { - use crate::trace::Trace; - let mut trace = Trace::new(2); trace.add_operation(1, vec![0], vec![]); trace.add_operation(2, vec![1], vec![]); @@ -14,8 +15,6 @@ fn test_trace_iteration() { #[test] fn test_nested_blocks() { - use crate::trace::Trace; - let mut trace = Trace::new(3); trace.add_operation(1, vec![0], vec![]); let block = trace.add_block(2); @@ -31,8 +30,6 @@ fn test_nested_blocks() { #[test] fn test_depth_simple() { - use crate::trace::Trace; - let mut trace = Trace::new(2); trace.add_operation(1, vec![0], vec![]); trace.add_operation(2, vec![1], vec![]); @@ -47,8 +44,6 @@ fn test_depth_simple() { #[test] fn test_depth_with_blocks() { - use crate::trace::Trace; - let mut trace = Trace::new(2); trace.add_operation(1, vec![0], vec![]); // Depth 1 on q0 @@ -68,8 +63,6 @@ fn test_depth_with_blocks() { #[test] fn test_depth_parallel_blocks() { - use crate::trace::Trace; - let mut trace = Trace::new(4); let block1 = trace.add_block(1); @@ -89,8 +82,6 @@ fn test_depth_parallel_blocks() { #[test] fn test_depth_entangled() { - use crate::trace::Trace; - let mut trace = Trace::new(2); trace.add_operation(1, vec![0], vec![]); // q0: 1 trace.add_operation(2, vec![1], vec![]); // q1: 1 @@ -105,7 +96,7 @@ fn test_depth_entangled() { #[test] fn test_psspc_transform() { - use crate::trace::{PSSPC, Trace, TraceTransform, instruction_ids::*}; + use crate::trace::{PSSPC, TraceTransform}; let mut trace = Trace::new(3); @@ -134,7 +125,7 @@ fn test_psspc_transform() { #[test] fn test_lattice_surgery_transform() { - use crate::trace::{LatticeSurgery, Trace, TraceTransform, instruction_ids::*}; + use crate::trace::{LatticeSurgery, TraceTransform}; let mut trace = Trace::new(3); @@ -166,9 +157,6 @@ fn test_lattice_surgery_transform() { #[test] fn test_estimate_simple() { - use crate::isa::{Encoding, ISA, Instruction}; - use crate::trace::{Trace, instruction_ids::*}; - let mut trace = Trace::new(1); trace.add_operation(T, vec![0], vec![]); @@ -193,9 +181,6 @@ fn test_estimate_simple() { #[test] fn test_estimate_with_factory() { - use crate::isa::{Encoding, ISA, Instruction}; - use crate::trace::{Trace, instruction_ids::*}; - let mut trace = Trace::new(1); // Algorithm needs 1000 T states trace.increment_resource_state(T, 1000); @@ -241,9 +226,6 @@ fn test_estimate_with_factory() { #[test] fn test_trace_display_uses_instruction_names() { - use crate::trace::Trace; - use crate::trace::instruction_ids::{CNOT, H, MEAS_Z}; - let mut trace = Trace::new(2); trace.add_operation(H, vec![0], vec![]); trace.add_operation(CNOT, vec![0, 1], vec![]); @@ -267,8 +249,6 @@ fn test_trace_display_uses_instruction_names() { #[test] fn test_trace_display_unknown_instruction() { - use crate::trace::Trace; - let mut trace = Trace::new(1); trace.add_operation(0x9999, vec![0], vec![]); @@ -282,9 +262,6 @@ fn test_trace_display_unknown_instruction() { #[test] fn test_block_display_with_repetitions() { - use crate::trace::Trace; - use crate::trace::instruction_ids::H; - let mut trace = Trace::new(1); let block = trace.add_block(10); block.add_operation(H, vec![0], vec![]); @@ -297,3 +274,174 @@ fn test_block_display_with_repetitions() { ); assert!(display.contains('H'), "Expected 'H' in block: {display}"); } + +/// Helper to create an ISA with instructions that have known time values. +/// Each entry is (id, arity, time). +fn isa_with_times(entries: &[(u64, u64, u64)]) -> ISA { + let mut isa = ISA::new(); + for &(id, arity, time) in entries { + isa.add_instruction(Instruction::fixed_arity( + id, + Encoding::Logical, + arity, + time, + Some(1), + None, + 0.0, + )); + } + isa +} + +#[test] +fn test_runtime_single_operation() { + let mut trace = Trace::new(1); + trace.add_operation(T, vec![0], vec![]); + + let isa = isa_with_times(&[(T, 1, 100)]); + let locked = isa.lock(); + + assert_eq!(trace.runtime(&locked).unwrap(), 100); +} + +#[test] +fn test_runtime_parallel_operations() { + let mut trace = Trace::new(2); + trace.add_operation(T, vec![0], vec![]); + trace.add_operation(H, vec![1], vec![]); + + let isa = isa_with_times(&[(T, 1, 100), (H, 1, 50)]); + let locked = isa.lock(); + + // Parallel: runtime is the max of the two = 100 + assert_eq!(trace.runtime(&locked).unwrap(), 100); +} + +#[test] +fn test_runtime_sequential_operations() { + let mut trace = Trace::new(1); + trace.add_operation(T, vec![0], vec![]); + trace.add_operation(H, vec![0], vec![]); + + let isa = isa_with_times(&[(T, 1, 100), (H, 1, 50)]); + let locked = isa.lock(); + + // Sequential on qubit 0: 100 + 50 = 150 + assert_eq!(trace.runtime(&locked).unwrap(), 150); +} + +#[test] +fn test_runtime_with_repeated_block() { + let mut trace = Trace::new(1); + let block = trace.add_block(5); + block.add_operation(T, vec![0], vec![]); + + let isa = isa_with_times(&[(T, 1, 100)]); + let locked = isa.lock(); + + // Block depth = 100, repeated 5 times = 500 + assert_eq!(trace.runtime(&locked).unwrap(), 500); +} + +#[test] +fn test_runtime_nested_blocks() { + let mut trace = Trace::new(1); + let outer = trace.add_block(3); + let inner = outer.add_block(2); + inner.add_operation(H, vec![0], vec![]); + + let isa = isa_with_times(&[(H, 1, 10)]); + let locked = isa.lock(); + + // Inner: 10 * 2 = 20, outer: 20 * 3 = 60 + assert_eq!(trace.runtime(&locked).unwrap(), 60); +} + +#[test] +fn test_runtime_multi_qubit_gate() { + let mut trace = Trace::new(2); + trace.add_operation(CX, vec![0, 1], vec![]); + + let isa = isa_with_times(&[(CX, 2, 200)]); + let locked = isa.lock(); + + assert_eq!(trace.runtime(&locked).unwrap(), 200); +} + +#[test] +fn test_runtime_sequential_after_multi_qubit() { + let mut trace = Trace::new(2); + trace.add_operation(CX, vec![0, 1], vec![]); + trace.add_operation(T, vec![0], vec![]); + trace.add_operation(H, vec![1], vec![]); + + let isa = isa_with_times(&[(CX, 2, 200), (T, 1, 100), (H, 1, 50)]); + let locked = isa.lock(); + + // CX occupies both qubits to time 200 + // T on q0: 200 + 100 = 300 + // H on q1: 200 + 50 = 250 + // max = 300 + assert_eq!(trace.runtime(&locked).unwrap(), 300); +} + +#[test] +fn test_runtime_empty_trace() { + let trace = Trace::new(1); + + let isa = ISA::new(); + let locked = isa.lock(); + + assert_eq!(trace.runtime(&locked).unwrap(), 0); +} + +#[test] +fn test_runtime_block_parallel_to_operation() { + let mut trace = Trace::new(2); + // Block on q0 + let block = trace.add_block(4); + block.add_operation(T, vec![0], vec![]); + // Operation on q1 (parallel to block) + trace.add_operation(H, vec![1], vec![]); + + let isa = isa_with_times(&[(T, 1, 10), (H, 1, 50)]); + let locked = isa.lock(); + + // Block: 10 * 4 = 40 on q0 + // H: 50 on q1 + // max = 50 + assert_eq!(trace.runtime(&locked).unwrap(), 50); +} + +#[test] +fn test_runtime_missing_instruction_returns_error() { + let mut trace = Trace::new(1); + trace.add_operation(T, vec![0], vec![]); + + // ISA has no T instruction + let isa = ISA::new(); + let locked = isa.lock(); + + assert!(trace.runtime(&locked).is_err()); +} + +#[test] +fn test_runtime_mixed_sequential_and_parallel() { + let mut trace = Trace::new(3); + // q0: T(100) -> CX(200) on q0,q1 + // q1: H(50) -> CX(200) on q0,q1 + // q2: T(100) + trace.add_operation(T, vec![0], vec![]); + trace.add_operation(H, vec![1], vec![]); + trace.add_operation(T, vec![2], vec![]); + trace.add_operation(CX, vec![0, 1], vec![]); + + let isa = isa_with_times(&[(T, 1, 100), (H, 1, 50), (CX, 2, 200)]); + let locked = isa.lock(); + + // q0: T ends at 100, CX starts at max(100, 50)=100, ends at 300 + // q1: H ends at 50, CX starts at 100, ends at 300 + // q2: T ends at 100 + // max = 300 + assert_eq!(trace.runtime(&locked).unwrap(), 300); +} From 68ae8e1072fcc612cf412baa5b019c1d09147cdb Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Wed, 29 Apr 2026 11:57:58 +0000 Subject: [PATCH 3/4] Use expect instead of unwrap. --- source/qre/src/trace/tests.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/source/qre/src/trace/tests.rs b/source/qre/src/trace/tests.rs index b0a9673df5..9ac2ec8538 100644 --- a/source/qre/src/trace/tests.rs +++ b/source/qre/src/trace/tests.rs @@ -301,7 +301,7 @@ fn test_runtime_single_operation() { let isa = isa_with_times(&[(T, 1, 100)]); let locked = isa.lock(); - assert_eq!(trace.runtime(&locked).unwrap(), 100); + assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 100); } #[test] @@ -314,7 +314,7 @@ fn test_runtime_parallel_operations() { let locked = isa.lock(); // Parallel: runtime is the max of the two = 100 - assert_eq!(trace.runtime(&locked).unwrap(), 100); + assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 100); } #[test] @@ -327,7 +327,7 @@ fn test_runtime_sequential_operations() { let locked = isa.lock(); // Sequential on qubit 0: 100 + 50 = 150 - assert_eq!(trace.runtime(&locked).unwrap(), 150); + assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 150); } #[test] @@ -340,7 +340,7 @@ fn test_runtime_with_repeated_block() { let locked = isa.lock(); // Block depth = 100, repeated 5 times = 500 - assert_eq!(trace.runtime(&locked).unwrap(), 500); + assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 500); } #[test] @@ -354,7 +354,7 @@ fn test_runtime_nested_blocks() { let locked = isa.lock(); // Inner: 10 * 2 = 20, outer: 20 * 3 = 60 - assert_eq!(trace.runtime(&locked).unwrap(), 60); + assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 60); } #[test] @@ -365,7 +365,7 @@ fn test_runtime_multi_qubit_gate() { let isa = isa_with_times(&[(CX, 2, 200)]); let locked = isa.lock(); - assert_eq!(trace.runtime(&locked).unwrap(), 200); + assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 200); } #[test] @@ -382,7 +382,7 @@ fn test_runtime_sequential_after_multi_qubit() { // T on q0: 200 + 100 = 300 // H on q1: 200 + 50 = 250 // max = 300 - assert_eq!(trace.runtime(&locked).unwrap(), 300); + assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 300); } #[test] @@ -392,7 +392,7 @@ fn test_runtime_empty_trace() { let isa = ISA::new(); let locked = isa.lock(); - assert_eq!(trace.runtime(&locked).unwrap(), 0); + assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 0); } #[test] @@ -410,7 +410,7 @@ fn test_runtime_block_parallel_to_operation() { // Block: 10 * 4 = 40 on q0 // H: 50 on q1 // max = 50 - assert_eq!(trace.runtime(&locked).unwrap(), 50); + assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 50); } #[test] @@ -443,5 +443,5 @@ fn test_runtime_mixed_sequential_and_parallel() { // q1: H ends at 50, CX starts at 100, ends at 300 // q2: T ends at 100 // max = 300 - assert_eq!(trace.runtime(&locked).unwrap(), 300); + assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 300); } From 62ccca982b5342e61105768d0d9520bf40785baf Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Wed, 29 Apr 2026 12:00:51 +0000 Subject: [PATCH 4/4] Fix formatting. --- source/qre/src/trace/tests.rs | 50 ++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/source/qre/src/trace/tests.rs b/source/qre/src/trace/tests.rs index 9ac2ec8538..3d083b290d 100644 --- a/source/qre/src/trace/tests.rs +++ b/source/qre/src/trace/tests.rs @@ -301,7 +301,10 @@ fn test_runtime_single_operation() { let isa = isa_with_times(&[(T, 1, 100)]); let locked = isa.lock(); - assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 100); + assert_eq!( + trace.runtime(&locked).expect("runtime computation failed"), + 100 + ); } #[test] @@ -314,7 +317,10 @@ fn test_runtime_parallel_operations() { let locked = isa.lock(); // Parallel: runtime is the max of the two = 100 - assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 100); + assert_eq!( + trace.runtime(&locked).expect("runtime computation failed"), + 100 + ); } #[test] @@ -327,7 +333,10 @@ fn test_runtime_sequential_operations() { let locked = isa.lock(); // Sequential on qubit 0: 100 + 50 = 150 - assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 150); + assert_eq!( + trace.runtime(&locked).expect("runtime computation failed"), + 150 + ); } #[test] @@ -340,7 +349,10 @@ fn test_runtime_with_repeated_block() { let locked = isa.lock(); // Block depth = 100, repeated 5 times = 500 - assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 500); + assert_eq!( + trace.runtime(&locked).expect("runtime computation failed"), + 500 + ); } #[test] @@ -354,7 +366,10 @@ fn test_runtime_nested_blocks() { let locked = isa.lock(); // Inner: 10 * 2 = 20, outer: 20 * 3 = 60 - assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 60); + assert_eq!( + trace.runtime(&locked).expect("runtime computation failed"), + 60 + ); } #[test] @@ -365,7 +380,10 @@ fn test_runtime_multi_qubit_gate() { let isa = isa_with_times(&[(CX, 2, 200)]); let locked = isa.lock(); - assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 200); + assert_eq!( + trace.runtime(&locked).expect("runtime computation failed"), + 200 + ); } #[test] @@ -382,7 +400,10 @@ fn test_runtime_sequential_after_multi_qubit() { // T on q0: 200 + 100 = 300 // H on q1: 200 + 50 = 250 // max = 300 - assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 300); + assert_eq!( + trace.runtime(&locked).expect("runtime computation failed"), + 300 + ); } #[test] @@ -392,7 +413,10 @@ fn test_runtime_empty_trace() { let isa = ISA::new(); let locked = isa.lock(); - assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 0); + assert_eq!( + trace.runtime(&locked).expect("runtime computation failed"), + 0 + ); } #[test] @@ -410,7 +434,10 @@ fn test_runtime_block_parallel_to_operation() { // Block: 10 * 4 = 40 on q0 // H: 50 on q1 // max = 50 - assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 50); + assert_eq!( + trace.runtime(&locked).expect("runtime computation failed"), + 50 + ); } #[test] @@ -443,5 +470,8 @@ fn test_runtime_mixed_sequential_and_parallel() { // q1: H ends at 50, CX starts at 100, ends at 300 // q2: T ends at 100 // max = 300 - assert_eq!(trace.runtime(&locked).expect("runtime computation failed"), 300); + assert_eq!( + trace.runtime(&locked).expect("runtime computation failed"), + 300 + ); }