Skip to content
Draft
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
7 changes: 7 additions & 0 deletions source/pip/qsharp/estimator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
EstimatorParams,
)

from ._layout_psspc import PSSPCEstimator
from ._qec_surface_code import SurfaceCode
from ._factory_round_based import RoundBasedFactory

__all__ = [
"EstimatorError",
"LogicalCounts",
Expand All @@ -33,4 +37,7 @@
"EstimatorConstraints",
"EstimatorInputParamsItem",
"EstimatorParams",
"PSSPCEstimator",
"SurfaceCode",
"RoundBasedFactory",
]
188 changes: 188 additions & 0 deletions source/pip/qsharp/estimator/_factory_round_based.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
from typing import Any, Dict
from ._utils import extract_qubit_metric


class RoundBasedFactory:
"""
Factory for generating magic states using round-based distillation protocols.

This class implements magic state distillation using round-based protocols.
It generates distillation units that can operate at both physical and logical levels.
"""

def __init__(
self,
*,
with_physical: bool = True,
gate_time: str = "gate_time",
gate_error: str = "gate_error",
clifford_error: str = "clifford_error",
use_max_qubits_per_round: bool = False,
max_rounds: int = 3,
max_extra_rounds: int = 5
):
"""
Initialize the round-based magic state factory.

:param with_physical: Whether to include physical-level distillation units
(default: True)
:param gate_time: Key name (or list of key names) for extracting gate time
from qubit metrics. If a list is provided, the sum of all corresponding
times is used (default: "gate_time")
:param gate_error: Key name (or list of key names) for extracting gate
error rate from qubit metrics. If a list is provided, the maximum of
all corresponding error rates is used (default: "gate_error")
:param clifford_error: Key name (or list of key names) for extracting
Clifford gate error rate from qubit metrics. If a list is provided,
the maximum of all corresponding error rates is used
(default: "clifford_error")
:param use_max_qubits_per_round: Whether to maximize qubits used per round
(default: False)
:param max_rounds: Maximum number of distillation rounds (default: 3)
:param max_extra_rounds: Maximum number of additional rounds beyond
max_rounds (default: 5)
"""
self.with_physical = with_physical
self.gate_time = gate_time
self.gate_error = gate_error
self.clifford_error = clifford_error
self.use_max_qubits_per_round = use_max_qubits_per_round
self.max_rounds = max_rounds
self.max_extra_rounds = max_extra_rounds

def distillation_units(
self, code: Any, qubit: Dict[str, Any], max_code_parameter: int
):
"""
Generate a list of distillation units for magic state production.

Creates distillation units using 15-to-1 protocols (RM prep and space
efficient variants) at both physical level (if enabled) and across
all valid code parameters up to the maximum.

:param code: QEC code object that provides code parameters and metrics
:param qubit: Dictionary containing physical qubit characteristics
:param max_code_parameter: Maximum code parameter (distance) to consider
:return: List of distillation unit dictionaries, each containing
configuration and callable functions for resource calculations
"""
units = []

gate_time = extract_qubit_metric(qubit, self.gate_time)
clifford_error = extract_qubit_metric(qubit, self.clifford_error)

if self.with_physical:
units.append(
_create_unit(
"15-to-1 RM prep",
1,
24,
gate_time,
1,
31,
clifford_error,
)
)
units.append(
_create_unit(
"15-to-1 space efficient",
1,
45,
gate_time,
1,
12,
clifford_error,
)
)

for code_parameter in code.code_parameter_range():
if code.code_parameter_cmp(qubit, code_parameter, max_code_parameter) == 1:
break

units.append(
_create_unit(
"15-to-1 RM prep",
code_parameter,
11,
code.logical_cycle_time(qubit, code_parameter),
code.physical_qubits(code_parameter),
31,
code.logical_error_rate(qubit, code_parameter),
)
)
units.append(
_create_unit(
"15-to-1 space efficient",
code_parameter,
13,
code.logical_cycle_time(qubit, code_parameter),
code.physical_qubits(code_parameter),
20,
code.logical_error_rate(qubit, code_parameter),
)
)

return units

def trivial_distillation_unit(
self, code: Any, qubit: Dict[str, Any], code_parameter: Any
):
"""
Creates this 1-to-1 distillation unit in the case where the target error
rate is already met by the physical qubit.

:param code: QEC code object that provides code parameters and metrics
:param qubit: Dictionary containing physical qubit characteristics
:param code_parameter: Code parameter chosen to run the algorithm
"""

return {
"name": "trivial 1-to-1",
"code_parameter": code_parameter,
"num_input_states": 1,
"num_output_states": 1,
"physical_qubits": lambda _: code.physical_qubits(code_parameter),
"duration": lambda _: code.logical_cycle_time(qubit, code_parameter),
"output_error_rate": lambda input_error_rate: input_error_rate,
"failure_probability": lambda _: 0.0,
}


def _create_unit(
name: str,
code_parameter: Any,
num_cycles: int,
cycle_time: int,
physical_qubits_factor: int,
physical_qubits: int,
clifford_error_rate: float,
):
"""
Create a distillation unit configuration dictionary.

:param name: Name of the distillation protocol
:param code_parameter: Code parameter (distance) for this unit
:param num_cycles: Number of cycles required for distillation
:param cycle_time: Time per cycle
:param physical_qubits_factor: Multiplier for physical qubit count
:param physical_qubits: Base number of physical qubits
:param clifford_error_rate: Error rate for Clifford operations
:return: Dictionary containing unit configuration and callable functions
for calculating physical qubits, duration, output error rate, and
failure probability
"""

return {
"name": name,
"code_parameter": code_parameter,
"num_input_states": 15,
"num_output_states": 1,
"physical_qubits": lambda _: physical_qubits * physical_qubits_factor,
"duration": lambda _: num_cycles * cycle_time,
"output_error_rate": lambda input_error_rate: 35 * input_error_rate**3
+ 7.1 * clifford_error_rate,
"failure_probability": lambda input_error_rate: 15 * input_error_rate
+ 356 * clifford_error_rate,
}
142 changes: 142 additions & 0 deletions source/pip/qsharp/estimator/_layout_psspc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import math
from typing import Union, List, Optional

from ._estimator import LogicalCounts


NUM_MEASUREMENTS_PER_R = 1
NUM_MEASUREMENTS_PER_TOF = 3


class PSSPCEstimator:
"""
Computes post-layout logical resources based on the Parallel Synthesis
Sequential Pauli Computation (PSSPC) layout method.
"""

def __init__(
self,
source: Union[List[str], str, LogicalCounts],
expression: Optional[str] = None,
):
"""
Constructor for PSSPC layout method. The source can be a list of Q#
source files (use a list even if there is only one file), a path to a Q#
project (directory with a qsharp.json file), or a LogicalCounts object.

:param source: The Q# source files, project path, or LogicalCounts
object.
:param expression: An entry point expression that must only be used when
the source is a list of Q# files or a project path.
"""

if isinstance(source, LogicalCounts):
if expression is not None:
raise ValueError(
"Cannot specify entry point expression when source is LogicalCounts"
)
self._counts = source
else:
if expression is None:
raise ValueError(
"Must specify entry point expression when source is not LogicalCounts"
)
self._counts = self._compute_counts(source, expression)

def logical_qubits(self):
"""
Calculates the number of logical qubits required for the PSSPC layout
according to Eq. (D1) in [arXiv:2211.07629](https://arxiv.org/pdf/2211.07629)
"""

num_qubits = self._counts["numQubits"]

qubit_padding = math.ceil(math.sqrt(8 * num_qubits)) + 1
return 2 * num_qubits + qubit_padding

def logical_depth(self, budget):
"""
Calculates the number of multi-qubit Pauli measurements executed in
sequence according to Eq. (D3) in
[arXiv:2211.07629](https://arxiv.org/pdf/2211.07629)
"""

budget_rotations = budget["rotations"]
tof_count = self._counts.get("cczCount", 0) + self._counts.get("ccixCount", 0)
num_ts_per_rotation = self._num_ts_per_rotation(budget_rotations)

return (
(
self._counts.get("measurementCount", 0)
+ self._counts.get("rotationCount", 0)
+ self._counts.get("tCount", 0)
)
* NUM_MEASUREMENTS_PER_R
+ tof_count * NUM_MEASUREMENTS_PER_TOF
+ (
num_ts_per_rotation
* self._counts.get("rotationDepth", 0)
* NUM_MEASUREMENTS_PER_R
)
)

def num_magic_states(self, budget, index):
"""
Calculates the number of T magic states that are consumbed by
multi-qubit Pauli measurements executed by PSSPC according to Eq. (D4)
in [arXiv:2211.07629](https://arxiv.org/pdf/2211.07629)
"""

# Only works for one kind of magic states, which is assumed to be T
# magic states
assert index == 0

budget_rotations = budget["rotations"]
tof_count = self._counts.get("cczCount", 0) + self._counts.get("ccixCount", 0)
num_ts_per_rotation = self._num_ts_per_rotation(budget_rotations)

return (
4 * tof_count
+ self._counts.get("tCount", 0)
+ num_ts_per_rotation * self._counts.get("rotationCount", 0)
)

def algorithm_overhead(self, budget):
"""
Returns the pre-layout logical resources as algorithm overhead, which
can be accessed from the estimation result.
"""
return self._counts

def prune_error_budget(self, budget, strategy):
if self._counts.get("rotationCount", 0) == 0:
budget_rotations = budget.get("rotations", 0)
budget["rotations"] = 0
budget["logical"] += budget_rotations / 2
budget["magic_states"] += budget_rotations / 2

def _compute_counts(self, source: Union[List[str], str], expression):
# NOTE: Importing qsharp here to avoid circular dependency
import qsharp

if isinstance(source, list):
qsharp.init()
for file in source:
qsharp.eval(qsharp._fs.read_file(file)[1])

Check notice

Code scanning / devskim

If untrusted data (data from HTTP requests, user submitted files, etc.) is included in an eval statement it can allow an attacker to inject their own code. Note

Review eval for untrusted data
elif isinstance(source, str):
qsharp.init(project_root=source)
else:
raise ValueError("Invalid source type for PSSPCEstimator")

return qsharp.logical_counts(expression)

def _num_ts_per_rotation(self, rotation_budget):
rotation_count = self._counts.get("rotationCount", 0)

if rotation_count > 0:
return math.ceil(0.53 * math.log2(rotation_count / rotation_budget) + 4.86)

else:
return 0
Loading