"""Builder for public configuration of the Iceberg QEC architecture."""
from copy import deepcopy
from dataclasses import asdict, dataclass, field, replace
from enum import StrEnum, auto
from typing import Any, Self
from hugr import Hugr
from hugr.package import Package
from hugr.utils import JsonType
from guppyft.encode import AnnotatingArchitectureInstance, annotate_encoding
[docs]
@dataclass(frozen=True)
class RUSStateFactoryConf:
"""Iceberg RUS state factory configuration.
Attributes:
size: Maximum number of states to be produced in parallel.
max_attempts: Maximum number of repeat-until-success attempts.
"""
size: int
max_attempts: int
[docs]
def to_json(self) -> JsonType:
"""Serialize the RUS state factory config to JSON."""
return asdict(self)
[docs]
class DynamicQubitReuse(StrEnum):
"""Configuration for how dynamic qubits are allocated or reused. Fresh qubits are
from block allocations and have not yet been allocated. When dynamic qubits are
freed, depending on the configuration, future dynamic qubit allocations can reset
and reuse these qubits.
When all dynamic qubits from a logical block have been freed, the block itself is
freed and becomes available to be reallocated."""
disabled = auto()
"""Only allocate fresh qubits from logical block allocations. Do not reset and reuse
dynamic qubits."""
if_necessary = auto()
"""Prefer using fresh logical qubits. Only reuse logical qubits if no more blocks
are available to be allocated."""
enabled = auto()
"""Use available fresh qubits, then freed qubits, before preparing another block."""
[docs]
@dataclass
class IcebergQEDPolicy:
"""Policy to determine when QED cycles are injected.
Each logical block accumulates a cost based on `costs`. Once a block's accumulated
cost reaches `threshold`, a QED cycle is performed on that block and its counter is
reset.
Attributes:
threshold: Threshold at which a QED cycle is triggered.
costs: See `IcebergQEDPolicy.OperationCosts`.
"""
[docs]
@dataclass
class OperationCosts:
"""Configuration for operation costs that are used to determined when QED cycles
should be performed during runtime."""
idle: float = 0.0
alloc_dynq: float = 0.0
x: float = 0.0
y: float = 0.0
z: float = 0.0
x_all: float = 0.0
y_all: float = 0.0
z_all: float = 0.0
x_all_except_at: float = 0.0
z_all_except_at: float = 0.0
x_all_except_z_at: float = 0.0
z_all_except_x_at: float = 0.0
rx: float = 0.0
rz: float = 0.0
pauli_exp_x_all: float = 0.0
pauli_exp_y_all: float = 0.0
pauli_exp_z_all: float = 0.0
pauli_exp_x_all_except_at: float = 0.0
pauli_exp_z_all_except_at: float = 0.0
pauli_exp_x_all_except_z_at: float = 0.0
pauli_exp_z_all_except_x_at: float = 0.0
h_all: float = 0.0
cx_all: float = 0.0
cx_intrablock: float = 0.0
cx_interblock: float = 0.0
xx_intrablock: float = 0.0
yy_intrablock: float = 0.0
zz_intrablock: float = 0.0
xx_phase_intrablock: float = 0.0
xx_phase_interblock: float = 0.0
yy_phase_intrablock: float = 0.0
yy_phase_interblock: float = 0.0
zz_phase_intrablock: float = 0.0
zz_phase_interblock: float = 0.0
fan_in: float = 0.0
fan_out: float = 0.0
swap_intrablock: float = 0.0
def __setattr__(self, key: str, value: Any) -> None:
"""Set a non-negative cost for a known logical operation."""
if not hasattr(self, key):
raise KeyError(f"Unknown cost key: {key}")
if value < 0:
raise ValueError(f"Op cost cannot be negative: received {value}")
super().__setattr__(key, value)
threshold: int = 1
costs: OperationCosts = field(default_factory=OperationCosts)
[docs]
def to_json(self) -> JsonType:
"""Serialise the QED policy to JSON."""
return asdict(self)
@dataclass(frozen=True, kw_only=True)
class _IcebergOptions:
qed_policy: IcebergQEDPolicy
syndrome_during_measurement: bool
state_factory_conf: RUSStateFactoryConf
dynamic_qubit_reuse: DynamicQubitReuse
[docs]
@dataclass(frozen=True)
class IcebergInstance(AnnotatingArchitectureInstance):
"""An Iceberg architecture instance built by `IcebergBuilder.build`."""
_n_blocks: int
_block_size: int
_options: _IcebergOptions
[docs]
def annotate(self, hugr: Package | Hugr[Any]) -> None:
"""Annotate the current parameters of the Iceberg code architecture on the given
HUGR's metadata. See `guppyft.encode.annotate_encoding` for more information."""
annotate_encoding(
hugr,
"iceberg",
{
"schema_version": 1,
"n_blocks": self._n_blocks,
"block_size": self._block_size,
"qed_policy": self._options.qed_policy.to_json(),
"syndrome_during_measurement": self._options.syndrome_during_measurement, # noqa: E501 ruff format keeps changing this line back
"state_factory": self._options.state_factory_conf.to_json(),
"dynamic_qubit_reuse": self._options.dynamic_qubit_reuse,
},
)
[docs]
@dataclass(frozen=True, kw_only=True)
class IcebergBuilder:
"""Iceberg architecture builder class for creating `IcebergInstance` objects."""
_options: _IcebergOptions = field(
default=_IcebergOptions(
qed_policy=IcebergQEDPolicy(),
syndrome_during_measurement=False,
state_factory_conf=RUSStateFactoryConf(
size=1,
max_attempts=3,
),
dynamic_qubit_reuse=DynamicQubitReuse.enabled,
)
)
[docs]
def with_qed_policy(self, qed_policy: IcebergQEDPolicy) -> Self:
"""Set the QED policy."""
return replace(self, _options=replace(self._options, qed_policy=qed_policy))
[docs]
def with_state_factory_conf(self, conf: RUSStateFactoryConf) -> Self:
"""Set the configuration for the state factory producing Iceberg blocks."""
if conf.size < 1:
raise ValueError(
f"Cannot set the size of the RUS state factory lower than 1,"
f" {conf.size} given"
)
if conf.max_attempts < 1:
raise ValueError(
f"Cannot set the max attempts of the RUS state factory lower than 1,"
f" {conf.max_attempts} given"
)
return replace(self, _options=replace(self._options, state_factory_conf=conf))
[docs]
def with_dynamic_qubit_reuse(self, conf: DynamicQubitReuse) -> Self:
"""Set whether dynamic qubits should be reset and reused."""
return replace(self, _options=replace(self._options, dynamic_qubit_reuse=conf))
[docs]
def build(self, *, n_blocks: int, block_size: int) -> IcebergInstance:
"""Build an `IcebergInstance` configured for `n_blocks` logical blocks each
with `block_size` logical qubits."""
if n_blocks < 0:
raise ValueError(
f"Cannot set the number of blocks lower than 0, {n_blocks} given"
)
if block_size < 2 or block_size % 2 != 0:
raise ValueError(
f"Block size has to be even and at least 2, {block_size} given"
)
return IcebergInstance(
_n_blocks=n_blocks, _block_size=block_size, _options=deepcopy(self._options)
)