Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions source/qdk_package/qdk/qre/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
Constraint,
ConstraintBound,
EstimationResult,
ErrorComposition,
FactoryResult,
ISARequirements,
Block,
Expand Down Expand Up @@ -76,6 +77,7 @@
"EvictionStrategy",
"DynamicMemoryCompute",
"Encoding",
"ErrorComposition",
"EstimationResult",
"EstimationTable",
"EstimationTableColumn",
Expand Down
27 changes: 21 additions & 6 deletions source/qdk_package/qdk/qre/_estimation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
_estimate_parallel,
_estimate_with_graph,
_EstimationCollection,
ErrorComposition,
Trace,
)
from ._trace import TraceQuery, PSSPC, LatticeSurgery
Expand All @@ -28,6 +29,7 @@ def estimate(
max_error: float = 1.0,
post_process: bool = False,
use_graph: bool = True,
composition: ErrorComposition = ErrorComposition.UnionBound,
name: Optional[str] = None,
) -> EstimationTable:
"""
Expand Down Expand Up @@ -73,6 +75,11 @@ def estimate(
builds a graph of ISAs and prunes suboptimal ISAs during estimation.
If False, use the Rust estimation path that does not perform any
pruning and simply enumerates all ISAs for each trace.
composition (ErrorComposition): Controls how per-item error
contributions are composed into the total error.
``ErrorComposition.UnionBound`` (default) sums the contributions,
while ``ErrorComposition.Product`` composes them as
``1 - prod(1 - p_i)``.
name (Optional[str]): An optional name for the estimation. If given, this
will be added as a first column to the results table for all entries.

Expand Down Expand Up @@ -106,7 +113,11 @@ def estimate(
num_isas = arch_ctx._provenance.total_isa_count()

collection = _estimate_with_graph(
cast(list[Trace], traces_only), arch_ctx._provenance, max_error, True
cast(list[Trace], traces_only),
arch_ctx._provenance,
max_error,
True,
composition,
)
isas = collection.isas
else:
Expand All @@ -115,7 +126,7 @@ def estimate(
num_isas = len(isas)

collection = _estimate_parallel(
cast(list[Trace], traces_only), isas, max_error, True
cast(list[Trace], traces_only), isas, max_error, True, composition
)

total_jobs = collection.total_jobs
Expand All @@ -133,7 +144,7 @@ def estimate(
trace_sample_isa[t_idx] = isa_idx
for t_idx, isa_idx in trace_sample_isa.items():
params, trace = params_and_traces[t_idx]
sample = trace.estimate(isas[isa_idx], max_error)
sample = trace.estimate(isas[isa_idx], max_error, composition)
if sample is not None:
pre_q = sample.qubits
pre_r = sample.runtime
Expand Down Expand Up @@ -164,7 +175,7 @@ def estimate(
pp_collection = _EstimationCollection()
for t_idx, isa_idx, _q, _r in approx_pareto:
params, trace = params_and_traces[t_idx]
result = trace.estimate(isas[isa_idx], max_error)
result = trace.estimate(isas[isa_idx], max_error, composition)
if result is not None:
pp_result = app_ctx.application.post_process(params, result)
if pp_result is not None:
Expand All @@ -181,7 +192,11 @@ def estimate(
num_isas = arch_ctx._provenance.total_isa_count()

collection = _estimate_with_graph(
cast(list[Trace], traces), arch_ctx._provenance, max_error, False
cast(list[Trace], traces),
arch_ctx._provenance,
max_error,
False,
composition,
)
else:
isas = list(isa_query.enumerate(arch_ctx))
Expand All @@ -190,7 +205,7 @@ def estimate(

# Use the Rust parallel estimation path
collection = _estimate_parallel(
cast(list[Trace], traces), isas, max_error, False
cast(list[Trace], traces), isas, max_error, False, composition
)

total_jobs = collection.total_jobs
Expand Down
1 change: 1 addition & 0 deletions source/qdk_package/qdk/qre/_qre.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
_estimate_parallel,
_estimate_with_graph,
_EstimationCollection,
ErrorComposition,
EstimationResult,
FactoryResult,
_FloatFunction,
Expand Down
88 changes: 83 additions & 5 deletions source/qdk_package/qdk/qre/_qre.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -688,10 +688,7 @@ def generic_function(
func: Callable[[int, list[float]], float],
) -> _FloatFunction: ...
def generic_function(
func: (
Callable[[int], int | float]
| Callable[[int, list[float]], int | float]
),
func: Callable[[int], int | float] | Callable[[int, list[float]], int | float],
) -> _IntFunction | _FloatFunction:
"""
Create a generic function from a Python callable.
Expand Down Expand Up @@ -935,6 +932,49 @@ class _ProvenanceGraph:
"""
...

class ErrorComposition:
"""
Controls how per-item error contributions are composed into the total
error reported by an estimation.

Both modes estimate the probability that at least one error occurs across
many fault-prone operations, each with failure probability ``p_i``. They
differ in the assumptions they make:

- ``UnionBound`` computes ``sum(p_i)`` (Boole's inequality). It is a strict
upper bound that holds for any events, whether or not they are
independent.

Advantages: always conservative (never underestimates); requires no
independence assumption; cheap; additive, so contributions from
subsystems simply add together.

Disadvantages: can exceed 1.0, which is meaningless as a probability; it
grows increasingly loose as the individual errors grow, because it
overcounts the overlap between simultaneous failures.

- ``Product`` computes ``1 - prod(1 - p_i)``, i.e. one minus the
probability that no operation fails, assuming the failures are
independent.

Advantages: always stays in ``[0, 1)``; it is tight (in fact exact) when
the errors are independent.

Disadvantages: it relies on independence, so it can underestimate when
errors are positively correlated; it is slightly more work to compute;
and for small ``p_i`` it barely differs from the union-bound sum, so the
two only diverge noticeably in the high-error regime.

Attributes:
UnionBound (ErrorComposition): Union bound; contributions are summed
(``sum(p_i)``). This is the default and can exceed 1.0.
Product (ErrorComposition): Product composition; contributions combine
as ``1 - prod(1 - p_i)``.
"""

UnionBound: ErrorComposition
Product: ErrorComposition

class EstimationResult:
"""
Represents the result of a resource estimation.
Expand Down Expand Up @@ -1016,6 +1056,26 @@ class EstimationResult:
"""
...

@property
def error_composition(self) -> ErrorComposition:
"""
The composition mode used when accumulating error contributions.

Returns:
ErrorComposition: The current composition mode.
"""
...

@error_composition.setter
def error_composition(self, composition: ErrorComposition) -> None:
"""
Set the composition mode used when accumulating error contributions.

Args:
composition (ErrorComposition): The composition mode to set.
"""
...

@property
def factories(self) -> dict[int, FactoryResult]:
"""
Expand Down Expand Up @@ -1425,7 +1485,10 @@ class Trace:
...

def estimate(
self, isa: ISA, max_error: Optional[float] = None
self,
isa: ISA,
max_error: Optional[float] = None,
composition: ErrorComposition = ErrorComposition.UnionBound,
) -> Optional[EstimationResult]:
"""
Estimate resources for the trace given a logical ISA.
Expand All @@ -1434,6 +1497,11 @@ class Trace:
isa (ISA): The logical ISA.
max_error (Optional[float]): The maximum allowed error. If None,
Pareto points are computed.
composition (ErrorComposition): Controls how per-item error
contributions are composed. ``ErrorComposition.UnionBound``
(default) sums the contributions, while
``ErrorComposition.Product`` composes them as
``1 - prod(1 - p_i)``.

Returns:
Optional[EstimationResult]: The estimation result if max_error is
Expand Down Expand Up @@ -1720,6 +1788,7 @@ def _estimate_parallel(
isas: list[ISA],
max_error: float = 1.0,
post_process: bool = False,
composition: ErrorComposition = ErrorComposition.UnionBound,
) -> _EstimationCollection:
"""
Estimate resources for multiple traces and ISAs in parallel.
Expand All @@ -1730,6 +1799,10 @@ def _estimate_parallel(
max_error (float): The maximum allowed error. The default is 1.0.
post_process (bool): If True, computes auxiliary data such as result
summaries needed for post-processing after estimation.
composition (ErrorComposition): Controls how per-item error
contributions are composed. ``ErrorComposition.UnionBound``
(default) sums the contributions, while
``ErrorComposition.Product`` composes them as ``1 - prod(1 - p_i)``.

Returns:
_EstimationCollection: The estimation collection.
Expand All @@ -1741,6 +1814,7 @@ def _estimate_with_graph(
graph: _ProvenanceGraph,
max_error: float = 1.0,
post_process: bool = False,
composition: ErrorComposition = ErrorComposition.UnionBound,
) -> _EstimationCollection:
"""
Estimate resources using a Pareto-filtered provenance graph.
Expand All @@ -1755,6 +1829,10 @@ def _estimate_with_graph(
max_error (float): The maximum allowed error. The default is 1.0.
post_process (bool): If True, computes auxiliary data such as result
summaries and ISAs needed for post-processing after estimation.
composition (ErrorComposition): Controls how per-item error
contributions are composed. ``ErrorComposition.UnionBound``
(default) sums the contributions, while
``ErrorComposition.Product`` composes them as ``1 - prod(1 - p_i)``.

Returns:
_EstimationCollection: The estimation collection.
Expand Down
Loading
Loading