diff --git a/samples/qre/dollar_cost.ipynb b/samples/qre/dollar_cost.ipynb
new file mode 100644
index 00000000000..62b8cb7e360
--- /dev/null
+++ b/samples/qre/dollar_cost.ipynb
@@ -0,0 +1,99 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "89fb2f00",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " qubits | \n",
+ " runtime | \n",
+ " error | \n",
+ " USD cost | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 32110 | \n",
+ " 0 days 00:00:00.122500 | \n",
+ " 0.003948 | \n",
+ " 122.5 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " qubits runtime error USD cost\n",
+ "0 32110 0 days 00:00:00.122500 0.003948 122.5"
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from qdk.estimator import LogicalCounts\n",
+ "from qdk.qre import estimate\n",
+ "from qdk.qre.application import QSharpApplication\n",
+ "from qdk.qre.models import GateBased, RoundBasedFactory, SurfaceCode\n",
+ "\n",
+ "app = QSharpApplication(LogicalCounts({\"numQubits\": 100, \"tCount\": 10000, \"cczCount\": 1000, \"measurementCount\": 1000}))\n",
+ "architecture = GateBased(gate_time=50, measurement_time=1000)\n",
+ "architecture.usd_cost_per_hour = 3_600.0 * 1000\n",
+ "\n",
+ "results = estimate(\n",
+ " app,\n",
+ " architecture,\n",
+ " SurfaceCode.q() * RoundBasedFactory.q(),\n",
+ " max_error=0.01,\n",
+ ")\n",
+ "results.as_frame()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/source/qdk_package/qdk/qre/_architecture.py b/source/qdk_package/qdk/qre/_architecture.py
index 8b98bc7b18e..f598c49d6e3 100644
--- a/source/qdk_package/qdk/qre/_architecture.py
+++ b/source/qdk_package/qdk/qre/_architecture.py
@@ -40,6 +40,11 @@ class Architecture(ABC):
family: TechnologyFamily = TechnologyFamily.UNKNOWN
+ # Cost of running application, in USD per hour.
+ # If defined, it's assumed that the cost of running application is proportional to
+ # the runtime.
+ usd_cost_per_hour: float | None = None
+
@abstractmethod
def provided_isa(self, ctx: ISAContext) -> ISA:
"""
@@ -63,6 +68,26 @@ def context(self) -> ISAContext:
"""
return ISAContext(self)
+ def cost_usd(self, qubits: int, runtime_nanos: int) -> float | None:
+ """Estimates cost, in US dollars, of running an application.
+
+ Subclasses need to define `usd_cost_per_hour` (if USD cost is proportional to
+ runtime) or override `cost_usd`.
+
+ Args:
+ qubits: estimated number of qubits.
+ runtime_nanos: estimated runtime, in nanoseconds.
+
+ Returns:
+ If there is not enough information to estimate cost, returns None.
+ If application cannot be run on this architecture, returns Infinity.
+ Otherwise, returns estimated cost of running an application, in dollars.
+ """
+ if self.usd_cost_per_hour is not None:
+ runtime_hours = runtime_nanos / (3600 * 1e9)
+ return runtime_hours * self.usd_cost_per_hour
+ return None
+
@property
def assumptions(self) -> list[str]:
"""
@@ -233,6 +258,7 @@ def __init__(self, arch: Architecture):
self._bindings: dict[str, ISA] = {}
self._transforms: dict[int, Architecture | ISATransform] = {0: arch}
+ self.arch = arch
def _with_binding(self, name: str, isa: ISA) -> ISAContext:
"""Return a new context with an additional binding (internal use)."""
diff --git a/source/qdk_package/qdk/qre/_estimation.py b/source/qdk_package/qdk/qre/_estimation.py
index ec28847baa4..3611ff2a06f 100644
--- a/source/qdk_package/qdk/qre/_estimation.py
+++ b/source/qdk_package/qdk/qre/_estimation.py
@@ -300,6 +300,13 @@ def run_estimation(traces: list[Trace]):
EstimationTableEntry.from_result(result, arch_ctx) for result in collection
)
+ if any(e.cost_usd is not None for e in table):
+ table.add_column(
+ "USD cost",
+ lambda entry: entry.cost_usd,
+ formatter=lambda x: round(x, 2) if x is not None else None,
+ )
+
# Fill in the stats for this estimation run
table.stats.num_traces = num_traces
table.stats.num_isas = num_isas
diff --git a/source/qdk_package/qdk/qre/_results.py b/source/qdk_package/qdk/qre/_results.py
index 589a59ef9a1..8eea89bb242 100644
--- a/source/qdk_package/qdk/qre/_results.py
+++ b/source/qdk_package/qdk/qre/_results.py
@@ -213,6 +213,7 @@ class EstimationTableEntry:
and the number of copies required.
properties: Additional key-value properties attached to the
estimation result.
+ cost_usd: Cost of running application, in US dollars.
"""
qubits: int
@@ -221,6 +222,7 @@ class EstimationTableEntry:
source: InstructionSource
factories: dict[int, FactoryResult] = field(default_factory=dict)
properties: dict[int, int | float | bool | str] = field(default_factory=dict)
+ cost_usd: float | None = None
@classmethod
def from_result(
@@ -242,6 +244,7 @@ def from_result(
source=InstructionSource.from_isa(ctx, result.isa),
factories=result.factories.copy(),
properties=result.properties.copy(),
+ cost_usd=ctx.arch.cost_usd(result.qubits, result.runtime),
)
diff --git a/source/qdk_package/tests/qre/test_estimation.py b/source/qdk_package/tests/qre/test_estimation.py
index 43e2ad5a4d6..d81a64a5065 100644
--- a/source/qdk_package/tests/qre/test_estimation.py
+++ b/source/qdk_package/tests/qre/test_estimation.py
@@ -91,6 +91,24 @@ def test_estimation_max_error():
assert next(iter(results)).error <= max_error
+def test_estimation_includes_usd_cost():
+ app = QSharpApplication(LogicalCounts({"numQubits": 1, "measurementCount": 1}))
+ arch = GateBased(gate_time=50, measurement_time=100)
+ arch.usd_cost_per_hour = 2 * 3600 * 1e9 # 2 USD per nanosecond.
+
+ results = estimate(
+ app,
+ arch,
+ SurfaceCode.q() * ExampleFactory.q(),
+ PSSPC.q() * LatticeSurgery.q(),
+ )
+ assert results[0].runtime == 1050
+
+ frame = results.as_frame()
+ assert "USD cost" in frame.columns
+ assert frame["USD cost"].tolist() == [2100.0]
+
+
@pytest.mark.skipif(
"SLOW_TESTS" not in os.environ,
reason="turn on slow tests by setting SLOW_TESTS=1 in the environment",