# Copyright 2019-2024 Cambridge Quantum Computing
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
""" BackendInfo class: additional information on Backends """
from dataclasses import asdict, dataclass, field
from typing import Any
from pytket.architecture import Architecture, FullyConnected
from pytket.circuit import Node, OpType
_OpTypeErrs = dict[OpType, float]
_Edge = tuple[Node, Node]
def _serialize_all_node_gate_errors(
d: dict[Node, _OpTypeErrs] | None
) -> list[list] | None:
if d is None:
return None
return [
[n.to_list(), {ot.name: err for ot, err in errs.items()}]
for n, errs in d.items()
]
def _deserialize_all_node_gate_errors(
l: list[list] | None,
) -> dict[Node, _OpTypeErrs] | None:
if l is None:
return None
return {
Node.from_list(n): {OpType.from_name(ot): err for ot, err in errs.items()}
for n, errs in l
}
def _serialize_all_edge_gate_errors(d: dict[_Edge, _OpTypeErrs] | None) -> list | None:
if d is None:
return None
return [
[[n0.to_list(), n1.to_list()], {ot.name: err for ot, err in errs.items()}]
for (n0, n1), errs in d.items()
]
def _deserialize_all_edge_gate_errors(
l: list | None,
) -> dict[_Edge, _OpTypeErrs] | None:
if l is None:
return None
return {
(Node.from_list(n0), Node.from_list(n1)): {
OpType.from_name(ot): err for ot, err in errs.items()
}
for (n0, n1), errs in l
}
def _serialize_all_readout_errors(
d: dict[Node, list[list[float]]] | None
) -> list[list] | None:
if d is None:
return None
return [[n.to_list(), errs] for n, errs in d.items()]
def _deserialize_all_readout_errors(
l: list[list] | None,
) -> dict[Node, list[list[float]]] | None:
if l is None:
return None
return {Node.from_list(n): errs for n, errs in l}
def _serialize_averaged_node_gate_errors(
d: dict[Node, float] | None
) -> list[list] | None:
if d is None:
return None
return [[n.to_list(), err] for n, err in d.items()]
def _deserialize_averaged_node_gate_errors(
l: list[list] | None,
) -> dict[Node, float] | None:
if l is None:
return None
return {Node.from_list(n): err for n, err in l}
def _serialize_averaged_edge_gate_errors(
d: dict[_Edge, float] | None
) -> list[list] | None:
if d is None:
return None
return [[[n0.to_list(), n1.to_list()], err] for (n0, n1), err in d.items()]
def _deserialize_averaged_edge_gate_errors(
l: list[list] | None,
) -> dict[tuple, float] | None:
if l is None:
return None
return {(Node.from_list(n0), Node.from_list(n1)): err for (n0, n1), err in l}
def _serialize_averaged_readout_errors(
d: dict[Node, float] | None
) -> list[list] | None:
if d is None:
return None
return [[n.to_list(), err] for n, err in d.items()]
def _deserialize_averaged_readout_errors(
l: list[list] | None,
) -> dict[Node, float] | None:
if l is None:
return None
return {Node.from_list(n): err for n, err in l}
[docs]
@dataclass
class BackendInfo:
"""
Stores various properties of a Backend.
This provides all device information useful for compilation.
:param name: Class name of the backend.
:param device_name: Name of the device.
:param version: Pytket-extension version installed when creating object.
:param architecture: Optional device connectivity.
:param gate_set: Set of supported gate types.
:param n_cl_reg: number of classical registers supported.
:param supports_fast_feedforward: Flag for hardware support of fast feedforward.
:param supports_reset: Flag for hardware support of reset operation
:param supports_midcircuit_meas: Flag for hardware support of midcircuit
measurement.
:param all_node_gate_errors: Dictionary between architecture Node and error rate
for different single qubit operations.
:param all_edge_gate_errors: Dictionary between architecture couplings and error
rate for different two-qubit operations.
:param all_readout_errors: Dictionary between architecture Node and uncorrelated
single qubit readout errors (2x2 readout probability matrix).
:param averaged_node_gate_errors: Dictionary between architecture Node and averaged
error rate for all single qubit operations.
:param averaged_edge_gate_errors: Dictionary between architecture couplings and
averaged error rate for all two-qubit operations.
:param averaged_readout_errors: Dictionary between architecture Node and averaged
readout errors.
:param misc: key-value map with further provider-specific information (must be
JSON-serializable)
"""
# identifying information
name: str
device_name: str | None
version: str
# hardware constraints
architecture: Architecture | FullyConnected | None
gate_set: set[OpType]
n_cl_reg: int | None = None
# additional feature support
supports_fast_feedforward: bool = False
supports_reset: bool = False
supports_midcircuit_measurement: bool = False
# additional basic device characterisation information
all_node_gate_errors: dict[Node, dict[OpType, float]] | None = None
all_edge_gate_errors: dict[tuple[Node, Node], dict[OpType, float]] | None = None
all_readout_errors: dict[Node, list[list[float]]] | None = None
averaged_node_gate_errors: dict[Node, float] | None = None
averaged_edge_gate_errors: dict[tuple[Node, Node], float] | None = None
averaged_readout_errors: dict[Node, float] | None = None
# miscellaneous, eg additional noise characterisation and provider-supplied
# information
misc: dict[str, Any] = field(default_factory=dict)
@property
def nodes(self) -> list[Node]:
"""
List of device nodes of the backend. Returns empty list
if the `architecture` field is not provided.
:return: List of nodes.
:rtype: List[Node]
"""
if self.architecture is None:
return []
return self.architecture.nodes
@property
def n_nodes(self) -> int:
"""
Number of nodes in the architecture of the device. Returns 0
if the `architecture` field is not provided.
:return: Number of nodes.
:rtype: int
"""
return len(self.nodes)
[docs]
def add_misc(self, key: str, val: Any) -> None:
"""
Add a new entry in BackendInfo's dictionary of additional information.
:param key: Key to store and retrieve value.
:type key: str
:param val: Value to be stored.
"""
if key in self.misc:
raise KeyError("Attempting to add an already existing entry to misc dict")
self.misc[key] = val
[docs]
def get_misc(self, key: str) -> Any:
"""
Retrieve information stored in Backend's additional information store
:param key: Key to retrieve value.
:type key: str
:raises KeyError: There is no value stored with the given key.
:return: The value stored at the given key.
"""
return self.misc[key]
[docs]
def to_dict(self) -> dict[str, Any]:
"""
Generate a dictionary serialized representation of BackendInfo,
suitable for writing to JSON.
:return: JSON serializable dictionary.
:rtype: Dict[str, Any]
"""
self_dict = asdict(self)
if self_dict["architecture"] is not None:
self_dict["architecture"] = self_dict["architecture"].to_dict()
self_dict["gate_set"] = [op.value for op in self_dict["gate_set"]]
self_dict["all_node_gate_errors"] = _serialize_all_node_gate_errors(
self_dict["all_node_gate_errors"]
)
self_dict["all_edge_gate_errors"] = _serialize_all_edge_gate_errors(
self_dict["all_edge_gate_errors"]
)
self_dict["all_readout_errors"] = _serialize_all_readout_errors(
self_dict["all_readout_errors"]
)
self_dict["averaged_node_gate_errors"] = _serialize_averaged_node_gate_errors(
self_dict["averaged_node_gate_errors"]
)
self_dict["averaged_edge_gate_errors"] = _serialize_averaged_edge_gate_errors(
self_dict["averaged_edge_gate_errors"]
)
self_dict["averaged_readout_errors"] = _serialize_averaged_readout_errors(
self_dict["averaged_readout_errors"]
)
return self_dict
[docs]
@classmethod
def from_dict(cls, d: dict[str, Any]) -> "BackendInfo":
"""
Construct BackendInfo object from JSON serializable dictionary
representation, as generated by BackendInfo.to_dict.
:return: Instance of BackendInfo constructed from dictionary.
:rtype: BackendInfo
"""
args = dict(**d)
arch = args["architecture"]
if arch is not None:
if "links" in arch:
args["architecture"] = Architecture.from_dict(args["architecture"])
else:
args["architecture"] = FullyConnected.from_dict(args["architecture"])
args["gate_set"] = {OpType(op) for op in args["gate_set"]}
args["all_node_gate_errors"] = _deserialize_all_node_gate_errors(
args["all_node_gate_errors"]
)
args["all_edge_gate_errors"] = _deserialize_all_edge_gate_errors(
args["all_edge_gate_errors"]
)
args["all_readout_errors"] = _deserialize_all_readout_errors(
args["all_readout_errors"]
)
args["averaged_node_gate_errors"] = _deserialize_averaged_node_gate_errors(
args["averaged_node_gate_errors"]
)
args["averaged_edge_gate_errors"] = _deserialize_averaged_edge_gate_errors(
args["averaged_edge_gate_errors"]
)
args["averaged_readout_errors"] = _deserialize_averaged_readout_errors(
args["averaged_readout_errors"]
)
return cls(**args)
def fully_connected_backendinfo( # type: ignore
name: str,
device_name: str | None,
version: str,
n_nodes: int,
gate_set: set[OpType],
**kwargs
) -> BackendInfo:
"""
Construct a BackendInfo with a FullyConnected architecture.
:param name: Class name of the backend.
:param device_name: Name of the device.
:param version: Version of the pytket Backend.
:param n_nodes: Number of nodes of the device.
:param gate_set: Set of supported gate types.
:param \\**kwargs: All further arguments are passed to the BackendInfo constructor.
"""
return BackendInfo(
name, device_name, version, FullyConnected(n_nodes), gate_set, **kwargs
)