From 22f2f62bf975795c0e5d0254b05d5956443737f6 Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Mon, 6 Jul 2026 10:28:04 +0000 Subject: [PATCH 1/2] Enhance variable arity functions to accept optional parameters across multiple components - Updated `space`, `time`, `error_rate`, and related methods in Rust and Python to accept an optional `params` argument, e.g., concrete rotation angles from a rotation instruction in a trace. - Modified the `generic_function` to handle callables with an additional `params` argument. - Added tests to verify functionality with the new parameter handling in both Python and Rust. --- source/qdk_package/qdk/qre/_application.py | 8 +- source/qdk_package/qdk/qre/_qre.pyi | 25 ++- source/qdk_package/src/qre.rs | 194 ++++++++++++++------- source/qdk_package/tests/qre/test_isa.py | 48 +++++ source/qre/src/isa.rs | 58 +++--- source/qre/src/isa/provenance.rs | 6 +- source/qre/src/isa/tests.rs | 30 ++-- source/qre/src/trace.rs | 38 ++-- source/qre/src/trace/estimation.rs | 6 +- 9 files changed, 287 insertions(+), 126 deletions(-) diff --git a/source/qdk_package/qdk/qre/_application.py b/source/qdk_package/qdk/qre/_application.py index 05a53579772..cea87748204 100644 --- a/source/qdk_package/qdk/qre/_application.py +++ b/source/qdk_package/qdk/qre/_application.py @@ -6,6 +6,7 @@ import types from abc import ABC, abstractmethod from concurrent.futures import ThreadPoolExecutor +from inspect import signature from types import NoneType from typing import ( ClassVar, @@ -93,7 +94,12 @@ def enumerate_traces( Trace: A trace for each enumerated set of trace parameters. """ - param_type = get_type_hints(self.__class__.get_trace).get("parameters") + # Resolve the trace-parameter type from the first parameter of + # ``get_trace`` (after ``self``), rather than a hard-coded name, so + # subclasses are free to name the parameter however they like. + type_hints = get_type_hints(self.__class__.get_trace) + param_names = [name for name in signature(self.__class__.get_trace).parameters if name != "self"] + param_type = type_hints.get(param_names[0]) if param_names else NoneType if param_type is types.NoneType: yield self.get_trace(None) # type: ignore return diff --git a/source/qdk_package/qdk/qre/_qre.pyi b/source/qdk_package/qdk/qre/_qre.pyi index ed2c04d4704..31e144176c7 100644 --- a/source/qdk_package/qdk/qre/_qre.pyi +++ b/source/qdk_package/qdk/qre/_qre.pyi @@ -606,10 +606,10 @@ class Constraint: ... class _IntFunction: - def __call__(self, arity: int) -> int: ... + def __call__(self, arity: int, params: Optional[list[float]] = None) -> int: ... class _FloatFunction: - def __call__(self, arity: int) -> float: ... + def __call__(self, arity: int, params: Optional[list[float]] = None) -> float: ... @overload def constant_function(value: int) -> _IntFunction: ... @@ -679,12 +679,28 @@ def block_linear_function( def generic_function(func: Callable[[int], int]) -> _IntFunction: ... @overload def generic_function(func: Callable[[int], float]) -> _FloatFunction: ... +@overload +def generic_function( + func: Callable[[int, list[float]], int], +) -> _IntFunction: ... +@overload def generic_function( - func: Callable[[int], int | float], + func: Callable[[int, list[float]], float], +) -> _FloatFunction: ... +def generic_function( + func: ( + Callable[[int], int | float] + | Callable[[int, list[float]], int | float] + ), ) -> _IntFunction | _FloatFunction: """ Create a generic function from a Python callable. + The callable may accept either a single ``arity`` argument or an + ``arity`` plus a ``params`` list of floats. Whether the resulting + function is integer- or float-valued is determined by the callable's + return type annotation (defaulting to float when unannotated). + Note: Only use this function if the other function constructors (constant_function, linear_function, and block_linear_function) do not @@ -693,7 +709,8 @@ def generic_function( simple as possible to minimize overhead. Args: - func (Callable[[int], int | float]): The Python callable. + func (Callable[[int], int | float] | Callable[[int, list[float]], int | float]): + The Python callable. Returns: _IntFunction | _FloatFunction: The generic function. diff --git a/source/qdk_package/src/qre.rs b/source/qdk_package/src/qre.rs index 1cd33a3f3c2..df9047a1126 100644 --- a/source/qdk_package/src/qre.rs +++ b/source/qdk_package/src/qre.rs @@ -277,34 +277,42 @@ impl Instruction { self.0.arity() } - #[pyo3(signature = (arity=None))] - pub fn space(&self, arity: Option) -> Option { - self.0.space(arity) + #[allow(clippy::needless_pass_by_value)] + #[pyo3(signature = (arity=None, params=None))] + pub fn space(&self, arity: Option, params: Option>) -> Option { + self.0.space(arity, params.as_deref().unwrap_or(&[])) } - #[pyo3(signature = (arity=None))] - pub fn time(&self, arity: Option) -> Option { - self.0.time(arity) + #[allow(clippy::needless_pass_by_value)] + #[pyo3(signature = (arity=None, params=None))] + pub fn time(&self, arity: Option, params: Option>) -> Option { + self.0.time(arity, params.as_deref().unwrap_or(&[])) } - #[pyo3(signature = (arity=None))] - pub fn error_rate(&self, arity: Option) -> Option { - self.0.error_rate(arity) + #[allow(clippy::needless_pass_by_value)] + #[pyo3(signature = (arity=None, params=None))] + pub fn error_rate(&self, arity: Option, params: Option>) -> Option { + self.0.error_rate(arity, params.as_deref().unwrap_or(&[])) } - #[pyo3(signature = (arity=None))] - pub fn expect_space(&self, arity: Option) -> PyResult { - Ok(self.0.expect_space(arity)) + #[allow(clippy::needless_pass_by_value)] + #[pyo3(signature = (arity=None, params=None))] + pub fn expect_space(&self, arity: Option, params: Option>) -> PyResult { + Ok(self.0.expect_space(arity, params.as_deref().unwrap_or(&[]))) } - #[pyo3(signature = (arity=None))] - pub fn expect_time(&self, arity: Option) -> PyResult { - Ok(self.0.expect_time(arity)) + #[allow(clippy::needless_pass_by_value)] + #[pyo3(signature = (arity=None, params=None))] + pub fn expect_time(&self, arity: Option, params: Option>) -> PyResult { + Ok(self.0.expect_time(arity, params.as_deref().unwrap_or(&[]))) } - #[pyo3(signature = (arity=None))] - pub fn expect_error_rate(&self, arity: Option) -> PyResult { - Ok(self.0.expect_error_rate(arity)) + #[allow(clippy::needless_pass_by_value)] + #[pyo3(signature = (arity=None, params=None))] + pub fn expect_error_rate(&self, arity: Option, params: Option>) -> PyResult { + Ok(self + .0 + .expect_error_rate(arity, params.as_deref().unwrap_or(&[]))) } pub fn set_source(&mut self, index: usize) { @@ -353,14 +361,14 @@ impl qre::ParetoItem2D for Instruction { fn objective1(&self) -> Self::Objective1 { self.0 - .space(None) - .unwrap_or_else(|| self.0.expect_space(Some(1))) + .space(None, &[]) + .unwrap_or_else(|| self.0.expect_space(Some(1), &[])) } fn objective2(&self) -> Self::Objective2 { self.0 - .time(None) - .unwrap_or_else(|| self.0.expect_time(Some(1))) + .time(None, &[]) + .unwrap_or_else(|| self.0.expect_time(Some(1), &[])) } } @@ -371,20 +379,20 @@ impl qre::ParetoItem3D for Instruction { fn objective1(&self) -> Self::Objective1 { self.0 - .space(None) - .unwrap_or_else(|| self.0.expect_space(Some(1))) + .space(None, &[]) + .unwrap_or_else(|| self.0.expect_space(Some(1), &[])) } fn objective2(&self) -> Self::Objective2 { self.0 - .time(None) - .unwrap_or_else(|| self.0.expect_time(Some(1))) + .time(None, &[]) + .unwrap_or_else(|| self.0.expect_time(Some(1), &[])) } fn objective3(&self) -> Self::Objective3 { self.0 - .error_rate(None) - .unwrap_or_else(|| self.0.expect_error_rate(Some(1))) + .error_rate(None, &[]) + .unwrap_or_else(|| self.0.expect_error_rate(Some(1), &[])) } } @@ -764,15 +772,19 @@ pub struct FloatFunction(qre::VariableArityFunction); #[pymethods] impl IntFunction { - fn __call__(&self, arity: u64) -> u64 { - self.0.evaluate(arity) + #[pyo3(signature = (arity, params = None))] + #[allow(clippy::needless_pass_by_value)] + fn __call__(&self, arity: u64, params: Option>) -> u64 { + self.0.evaluate(arity, params.as_deref().unwrap_or(&[])) } } #[pymethods] impl FloatFunction { - fn __call__(&self, arity: u64) -> f64 { - self.0.evaluate(arity) + #[pyo3(signature = (arity, params = None))] + #[allow(clippy::needless_pass_by_value)] + fn __call__(&self, arity: u64, params: Option>) -> f64 { + self.0.evaluate(arity, params.as_deref().unwrap_or(&[])) } } @@ -829,17 +841,31 @@ pub fn block_linear_function<'py>( } } +/// Closure type used to wrap a Python callable that maps an arity and its +/// parameters to an integer value. +type ParamsIntClosure = Arc u64 + Send + Sync>; + +/// Closure type used to wrap a Python callable that maps an arity and its +/// parameters to a floating point value. +type ParamsFloatClosure = Arc f64 + Send + Sync>; + #[pyfunction] pub fn generic_function<'py>( py: Python<'py>, func: Bound<'py, PyAny>, ) -> PyResult> { - // Try to get return type annotation from the function + // Determine whether the callable is integer-valued by inspecting its + // return type annotation. The annotation may be either the actual `int` + // type object or, under `from __future__ import annotations` (PEP 563), + // the stringized name `"int"`; both are treated as integer-valued. let is_int = if let Ok(annotations) = func.getattr("__annotations__") { if let Ok(return_type) = annotations.get_item("return") { - // Check if return type is float - let float_type = py.get_type::(); - return_type.eq(float_type).unwrap_or(false) + let int_type = py.get_type::(); + return_type.eq(&int_type).unwrap_or(false) + || return_type + .extract::() + .map(|s| s.trim() == "int") + .unwrap_or(false) } else { false } @@ -849,32 +875,78 @@ pub fn generic_function<'py>( let func: Py = func.unbind(); - if is_int { - let closure = move |arity: u64| -> u64 { - Python::attach(|py| { - let result = func.call1(py, (arity,)); - match result { - Ok(value) => value.extract::(py).unwrap_or(0), - Err(_) => 0, - } - }) - }; - - let arc: Arc u64 + Send + Sync> = Arc::new(closure); - IntFunction(qre::VariableArityFunction::generic_from_arc(arc)).into_bound_py_any(py) - } else { - let closure = move |arity: u64| -> f64 { - Python::attach(|py| { - let result = func.call1(py, (arity,)); - match result { - Ok(value) => value.extract::(py).unwrap_or(0.0), - Err(_) => 0.0, - } - }) - }; + // Determine whether the callable accepts a second `params` argument by + // inspecting its argument count. Two-argument callables are evaluated with + // the instruction parameters. + let has_params = Python::attach(|py| { + func.getattr(py, "__code__") + .and_then(|code| code.getattr(py, "co_argcount")) + .and_then(|count| count.extract::(py)) + .map(|count| count >= 2) + .unwrap_or(false) + }); - let arc: Arc f64 + Send + Sync> = Arc::new(closure); - FloatFunction(qre::VariableArityFunction::generic_from_arc(arc)).into_bound_py_any(py) + match (is_int, has_params) { + (true, true) => { + let closure = move |arity: u64, params: &[f64]| -> u64 { + Python::attach(|py| { + let params = params.to_vec(); + let result = func.call1(py, (arity, params)); + match result { + Ok(value) => value.extract::(py).unwrap_or(0), + Err(_) => 0, + } + }) + }; + let arc: ParamsIntClosure = Arc::new(closure); + IntFunction(qre::VariableArityFunction::generic_with_params_from_arc( + arc, + )) + .into_bound_py_any(py) + } + (true, false) => { + let closure = move |arity: u64| -> u64 { + Python::attach(|py| { + let result = func.call1(py, (arity,)); + match result { + Ok(value) => value.extract::(py).unwrap_or(0), + Err(_) => 0, + } + }) + }; + let arc: Arc u64 + Send + Sync> = Arc::new(closure); + IntFunction(qre::VariableArityFunction::generic_from_arc(arc)).into_bound_py_any(py) + } + (false, true) => { + let closure = move |arity: u64, params: &[f64]| -> f64 { + Python::attach(|py| { + let params = params.to_vec(); + let result = func.call1(py, (arity, params)); + match result { + Ok(value) => value.extract::(py).unwrap_or(0.0), + Err(_) => 0.0, + } + }) + }; + let arc: ParamsFloatClosure = Arc::new(closure); + FloatFunction(qre::VariableArityFunction::generic_with_params_from_arc( + arc, + )) + .into_bound_py_any(py) + } + (false, false) => { + let closure = move |arity: u64| -> f64 { + Python::attach(|py| { + let result = func.call1(py, (arity,)); + match result { + Ok(value) => value.extract::(py).unwrap_or(0.0), + Err(_) => 0.0, + } + }) + }; + let arc: Arc f64 + Send + Sync> = Arc::new(closure); + FloatFunction(qre::VariableArityFunction::generic_from_arc(arc)).into_bound_py_any(py) + } } } diff --git a/source/qdk_package/tests/qre/test_isa.py b/source/qdk_package/tests/qre/test_isa.py index 7857529e6a9..e6feb353486 100644 --- a/source/qdk_package/tests/qre/test_isa.py +++ b/source/qdk_package/tests/qre/test_isa.py @@ -348,12 +348,60 @@ def error_rate(x: int) -> float: space_fn = generic_function(lambda x: 12) assert isinstance(space_fn, _FloatFunction) + # I can provide an optional second argument for an int-valued function + def time2(x: int, params: list[float]) -> int: + return x + int(sum(params)) + + time_fn2 = generic_function(time2) + assert isinstance(time_fn2, _IntFunction) + assert time_fn2(5, [1.0, 2.0]) == 8 + + # I can provide an optional second argument for a float-valued function + def error_rate2(x: int, params: list[float]) -> float: + return x + sum(params) + + error_rate_fn2 = generic_function(error_rate2) + assert isinstance(error_rate_fn2, _FloatFunction) + assert error_rate_fn2(5, [1.0, 2.0]) == 8.0 + i = _make_instruction(42, 0, None, time_fn, 12, None, error_rate_fn, {}) assert i.space(5) == 12 assert i.time(5) == 25 assert i.error_rate(5) == 2.5 +def test_generic_function_stringized_annotations(): + """Test that string return annotations (PEP 563) select int vs float.""" + from qdk.qre._qre import _IntFunction, _FloatFunction + + # Simulate `from __future__ import annotations`, where annotations are + # stored as strings rather than the actual type objects. + def int_fn(arity): + return arity * 2 + + int_fn.__annotations__ = {"arity": "int", "return": "int"} + assert isinstance(generic_function(int_fn), _IntFunction) + + def float_fn(arity): + return arity / 2.0 + + float_fn.__annotations__ = {"arity": "int", "return": "float"} + assert isinstance(generic_function(float_fn), _FloatFunction) + + # Stringized int annotation combined with a params argument. + def int_params_fn(arity, params): + return arity + int(sum(params)) + + int_params_fn.__annotations__ = { + "arity": "int", + "params": "list[float]", + "return": "int", + } + fn = generic_function(int_params_fn) + assert isinstance(fn, _IntFunction) + assert fn(5, [1.0, 2.0]) == 8 + + def test_isa_from_architecture(): """Test generating logical ISAs from an architecture and QEC code.""" arch = GateBased(gate_time=50, measurement_time=100) diff --git a/source/qre/src/isa.rs b/source/qre/src/isa.rs index cdeac805247..3d842990e23 100644 --- a/source/qre/src/isa.rs +++ b/source/qre/src/isa.rs @@ -352,60 +352,62 @@ impl Instruction { } #[must_use] - pub fn space(&self, arity: Option) -> Option { + pub fn space(&self, arity: Option, params: &[f64]) -> Option { match &self.metrics { Metrics::FixedArity { space, .. } => Some(*space), - Metrics::VariableArity { space_fn, .. } => arity.map(|a| space_fn.evaluate(a)), + Metrics::VariableArity { space_fn, .. } => arity.map(|a| space_fn.evaluate(a, params)), } } #[must_use] - pub fn length(&self, arity: Option) -> Option { + pub fn length(&self, arity: Option, params: &[f64]) -> Option { match &self.metrics { Metrics::FixedArity { length, .. } => Some(*length), - Metrics::VariableArity { length_fn, .. } => arity.map(|a| length_fn.evaluate(a)), + Metrics::VariableArity { length_fn, .. } => { + arity.map(|a| length_fn.evaluate(a, params)) + } } } #[must_use] - pub fn time(&self, arity: Option) -> Option { + pub fn time(&self, arity: Option, params: &[f64]) -> Option { match &self.metrics { Metrics::FixedArity { time, .. } => Some(*time), - Metrics::VariableArity { time_fn, .. } => arity.map(|a| time_fn.evaluate(a)), + Metrics::VariableArity { time_fn, .. } => arity.map(|a| time_fn.evaluate(a, params)), } } #[must_use] - pub fn error_rate(&self, arity: Option) -> Option { + pub fn error_rate(&self, arity: Option, params: &[f64]) -> Option { match &self.metrics { Metrics::FixedArity { error_rate, .. } => Some(*error_rate), Metrics::VariableArity { error_rate_fn, .. } => { - arity.map(|a| error_rate_fn.evaluate(a)) + arity.map(|a| error_rate_fn.evaluate(a, params)) } } } #[must_use] - pub fn expect_space(&self, arity: Option) -> u64 { - self.space(arity) + pub fn expect_space(&self, arity: Option, params: &[f64]) -> u64 { + self.space(arity, params) .expect("Instruction does not support variable arity") } #[must_use] - pub fn expect_length(&self, arity: Option) -> u64 { - self.length(arity) + pub fn expect_length(&self, arity: Option, params: &[f64]) -> u64 { + self.length(arity, params) .expect("Instruction does not support variable arity") } #[must_use] - pub fn expect_time(&self, arity: Option) -> u64 { - self.time(arity) + pub fn expect_time(&self, arity: Option, params: &[f64]) -> u64 { + self.time(arity, params) .expect("Instruction does not support variable arity") } #[must_use] - pub fn expect_error_rate(&self, arity: Option) -> f64 { - self.error_rate(arity) + pub fn expect_error_rate(&self, arity: Option, params: &[f64]) -> f64 { + self.error_rate(arity, params) .expect("Instruction does not support variable arity") } @@ -557,7 +559,7 @@ impl InstructionConstraint { } Metrics::VariableArity { error_rate_fn, .. } => { if let (Some(constraint_arity), Some(bound)) = (self.arity, &self.error_rate_fn) - && !bound.evaluate(&error_rate_fn.evaluate(constraint_arity)) + && !bound.evaluate(&error_rate_fn.evaluate(constraint_arity, &[])) { return false; } @@ -598,6 +600,13 @@ pub enum Metrics { }, } +/// A callback that maps an instruction arity to a value of type `T`. +type ArityFn = Arc T + Send + Sync>; + +/// A callback that maps an instruction arity and its parameters to a value of +/// type `T`. +type ArityWithParamsFn = Arc T + Send + Sync>; + #[derive(Clone, Serialize, Deserialize)] pub enum VariableArityFunction { Constant { @@ -613,7 +622,11 @@ pub enum VariableArityFunction { }, #[serde(skip)] Generic { - func: Arc T + Send + Sync>, + func: ArityFn, + }, + #[serde(skip)] + GenericWithParams { + func: ArityWithParamsFn, }, } @@ -642,11 +655,15 @@ impl + std::ops::Mul + Copy + FromPrimitive> } } - pub fn generic_from_arc(func: Arc T + Send + Sync>) -> Self { + pub fn generic_from_arc(func: ArityFn) -> Self { VariableArityFunction::Generic { func } } - pub fn evaluate(&self, arity: u64) -> T { + pub fn generic_with_params_from_arc(func: ArityWithParamsFn) -> Self { + VariableArityFunction::GenericWithParams { func } + } + + pub fn evaluate(&self, arity: u64, params: &[f64]) -> T { match self { VariableArityFunction::Constant { value } => *value, VariableArityFunction::Linear { slope } => { @@ -662,6 +679,7 @@ impl + std::ops::Mul + Copy + FromPrimitive> + *offset } VariableArityFunction::Generic { func } => func(arity), + VariableArityFunction::GenericWithParams { func } => func(arity, params), } } } diff --git a/source/qre/src/isa/provenance.rs b/source/qre/src/isa/provenance.rs index 5f68ca180e5..47062e84571 100644 --- a/source/qre/src/isa/provenance.rs +++ b/source/qre/src/isa/provenance.rs @@ -154,9 +154,9 @@ impl ProvenanceGraph { .iter() .filter_map(|&idx| { let instr = &self.nodes[idx].instruction; - let space = instr.space(Some(1))?; - let time = instr.time(Some(1))?; - let error = instr.error_rate(Some(1))?; + let space = instr.space(Some(1), &[])?; + let time = instr.time(Some(1), &[])?; + let error = instr.error_rate(Some(1), &[])?; Some(InstructionParetoItem { node_index: idx, space, diff --git a/source/qre/src/isa/tests.rs b/source/qre/src/isa/tests.rs index b847a6b0498..4b1ae943bfd 100644 --- a/source/qre/src/isa/tests.rs +++ b/source/qre/src/isa/tests.rs @@ -10,10 +10,10 @@ fn test_fixed_arity_instruction() { assert_eq!(instr.id(), 1); assert_eq!(instr.encoding(), Encoding::Physical); assert_eq!(instr.arity(), Some(2)); - assert_eq!(instr.time(None), Some(100)); - assert_eq!(instr.space(None), Some(10)); - assert_eq!(instr.length(None), Some(5)); - assert_eq!(instr.error_rate(None), Some(0.01)); + assert_eq!(instr.time(None, &[]), Some(100)); + assert_eq!(instr.space(None, &[]), Some(10)); + assert_eq!(instr.length(None, &[]), Some(5)); + assert_eq!(instr.error_rate(None, &[]), Some(0.01)); } #[test] @@ -30,13 +30,13 @@ fn test_variable_arity_instruction() { assert_eq!(instr.arity(), None); // Check evaluation at specific arity - assert_eq!(instr.time(Some(3)), Some(30)); // 3 * 10 - assert_eq!(instr.space(Some(3)), Some(5)); - assert_eq!(instr.length(Some(3)), Some(5)); // Defaulted to space_fn - assert_eq!(instr.error_rate(Some(3)), Some(0.001)); + assert_eq!(instr.time(Some(3), &[]), Some(30)); // 3 * 10 + assert_eq!(instr.space(Some(3), &[]), Some(5)); + assert_eq!(instr.length(Some(3), &[]), Some(5)); // Defaulted to space_fn + assert_eq!(instr.error_rate(Some(3), &[]), Some(0.001)); // Check None arity returns None for variable metrics - assert_eq!(instr.time(None), None); + assert_eq!(instr.time(None, &[]), None); } #[test] @@ -138,17 +138,17 @@ fn test_variable_arity_satisfies() { #[test] fn test_variable_arity_function() { let linear_fn = VariableArityFunction::linear(10); - assert_eq!(linear_fn.evaluate(3), 30); - assert_eq!(linear_fn.evaluate(0), 0); + assert_eq!(linear_fn.evaluate(3, &[]), 30); + assert_eq!(linear_fn.evaluate(0, &[]), 0); let constant_fn = VariableArityFunction::constant(5); - assert_eq!(constant_fn.evaluate(3), 5); - assert_eq!(constant_fn.evaluate(0), 5); + assert_eq!(constant_fn.evaluate(3, &[]), 5); + assert_eq!(constant_fn.evaluate(0, &[]), 5); // Test with a custom function let custom_fn = VariableArityFunction::generic(|arity| arity * arity); // Quadratic - assert_eq!(custom_fn.evaluate(3), 9); - assert_eq!(custom_fn.evaluate(4), 16); + assert_eq!(custom_fn.evaluate(3, &[]), 9); + assert_eq!(custom_fn.evaluate(4, &[]), 16); } #[test] diff --git a/source/qre/src/trace.rs b/source/qre/src/trace.rs index ab884e388ed..709559668c8 100644 --- a/source/qre/src/trace.rs +++ b/source/qre/src/trace.rs @@ -249,7 +249,7 @@ impl Trace { .block .depth_and_used(Some(&|op: &Gate| { let instr = get_instruction(locked, op.id)?; - Ok(instr.expect_time(Some(op.qubits.len() as u64))) + Ok(instr.expect_time(Some(op.qubits.len() as u64), &op.params)) }))? .0) } @@ -289,7 +289,7 @@ impl Trace { // ------------------------------------------------------------------ if let Some(resource_states) = &self.resource_states { for (state_id, count) in resource_states { - let rate = get_error_rate_by_id(&locked, *state_id)?; + let rate = get_error_rate_by_id(&locked, *state_id, &[])?; let actual_error = result.add_error(rate * (*count as f64)); if actual_error > max_error { return Err(Error::MaximumErrorExceeded { @@ -311,9 +311,9 @@ impl Trace { let arity = gate.qubits.len() as u64; - let rate = instr.expect_error_rate(Some(arity)); + let rate = instr.expect_error_rate(Some(arity), &gate.params); - let qubit_count = instr.expect_space(Some(arity)) as f64 / arity as f64; + let qubit_count = instr.expect_space(Some(arity), &gate.params) as f64 / arity as f64; if let Err(i) = qubit_counts.binary_search_by(|qc| qc.total_cmp(&qubit_count)) { qubit_counts.insert(i, qubit_count); @@ -346,9 +346,9 @@ impl Trace { let mut total_factory_qubits = 0; for (factory, count) in &factories { let instr = get_instruction(&locked, *factory)?; - let factory_time = get_time(instr)?; - let factory_space = get_space(instr)?; - let factory_error_rate = get_error_rate(instr)?; + let factory_time = get_time(instr, &[])?; + let factory_space = get_space(instr, &[])?; + let factory_error_rate = get_error_rate(instr, &[])?; let runs = result.runtime() / factory_time; if runs == 0 { @@ -380,7 +380,7 @@ impl Trace { .get(&instruction_ids::MEMORY) .ok_or(Error::InstructionNotFound(instruction_ids::MEMORY))?; - let memory_space = memory.expect_space(Some(memory_qubits)); + let memory_space = memory.expect_space(Some(memory_qubits), &[]); result.add_qubits(memory_space); result.set_property( PHYSICAL_MEMORY_QUBITS, @@ -391,10 +391,10 @@ impl Trace { // respect to the total runtime of the algorithm. let rounds = result .runtime() - .div_ceil(memory.expect_time(Some(memory_qubits))); + .div_ceil(memory.expect_time(Some(memory_qubits), &[])); - let actual_error = - result.add_error(rounds as f64 * memory.expect_error_rate(Some(memory_qubits))); + let actual_error = result + .add_error(rounds as f64 * memory.expect_error_rate(Some(memory_qubits), &[])); if actual_error > max_error { return Err(Error::MaximumErrorExceeded { actual_error, @@ -804,27 +804,27 @@ fn get_instruction<'a>(isa: &'a LockedISA<'_>, id: u64) -> Result<&'a Instructio isa.get(&id).ok_or(Error::InstructionNotFound(id)) } -fn get_space(instruction: &Instruction) -> Result { +fn get_space(instruction: &Instruction, params: &[f64]) -> Result { instruction - .space(None) + .space(None, params) .ok_or(Error::CannotExtractSpace(instruction.id())) } -fn get_time(instruction: &Instruction) -> Result { +fn get_time(instruction: &Instruction, params: &[f64]) -> Result { instruction - .time(None) + .time(None, params) .ok_or(Error::CannotExtractTime(instruction.id())) } -fn get_error_rate(instruction: &Instruction) -> Result { +fn get_error_rate(instruction: &Instruction, params: &[f64]) -> Result { instruction - .error_rate(None) + .error_rate(None, params) .ok_or(Error::CannotExtractErrorRate(instruction.id())) } -fn get_error_rate_by_id(isa: &LockedISA<'_>, id: u64) -> Result { +fn get_error_rate_by_id(isa: &LockedISA<'_>, id: u64, params: &[f64]) -> Result { let instr = get_instruction(isa, id)?; instr - .error_rate(None) + .error_rate(None, params) .ok_or(Error::CannotExtractErrorRate(id)) } diff --git a/source/qre/src/trace/estimation.rs b/source/qre/src/trace/estimation.rs index d23045c270c..050ae80928c 100644 --- a/source/qre/src/trace/estimation.rs +++ b/source/qre/src/trace/estimation.rs @@ -338,13 +338,13 @@ pub fn estimate_with_graph( // Filter out nodes that don't meet the constraint bounds. let instruction = graph_lock.instruction(node); constraint.error_rate().is_none_or(|c| { - c.evaluate(&instruction.error_rate(Some(1)).unwrap_or(0.0)) + c.evaluate(&instruction.error_rate(Some(1), &[]).unwrap_or(0.0)) }) }) .map(|&node| { let instruction = graph_lock.instruction(node); - let space = instruction.space(Some(1)).unwrap_or(0); - let time = instruction.time(Some(1)).unwrap_or(0); + let space = instruction.space(Some(1), &[]).unwrap_or(0); + let time = instruction.time(Some(1), &[]).unwrap_or(0); NodeProfile { node_index: node, space, From ff464a591de559c6699fdb1965725b75d26c8b6a Mon Sep 17 00:00:00 2001 From: Mathias Soeken Date: Mon, 6 Jul 2026 11:39:05 +0000 Subject: [PATCH 2/2] Fix clippy lints. --- source/qdk_package/src/qre.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/source/qdk_package/src/qre.rs b/source/qdk_package/src/qre.rs index df9047a1126..0ef260e92f6 100644 --- a/source/qdk_package/src/qre.rs +++ b/source/qdk_package/src/qre.rs @@ -864,8 +864,7 @@ pub fn generic_function<'py>( return_type.eq(&int_type).unwrap_or(false) || return_type .extract::() - .map(|s| s.trim() == "int") - .unwrap_or(false) + .is_ok_and(|s| s.trim() == "int") } else { false } @@ -882,8 +881,7 @@ pub fn generic_function<'py>( func.getattr(py, "__code__") .and_then(|code| code.getattr(py, "co_argcount")) .and_then(|count| count.extract::(py)) - .map(|count| count >= 2) - .unwrap_or(false) + .is_ok_and(|count| count >= 2) }); match (is_int, has_params) {