import numpy as np
from kosmos.quantum_logic.entanglement_link import EntanglementLink
from kosmos.quantum_logic.quantum_state import QuantumState
from kosmos.quantum_logic.qubit import Qubit, QubitId, QubitType
from kosmos.quantum_logic.typing import QubitReference
from kosmos.topology.net import Network
from kosmos.topology.node import QuantumNode
from kosmos.topology.typing import NodeReference
BELL_PAIR_QUBITS = 2
MIN_FIDELITY_THRESHOLD = 0.25
[docs]
class QuantumRegisterManager:
"""Manager for quantum logic in the network.
This manager handles all qubits, entanglements and quantum states in the system.
Attributes:
qubits (dict[QubitId, Qubit]): Mapping from qubit IDs to their corresponding Qubit object.
states (dict[frozenset[QubitId], QuantumState]): Mapping from qubit IDs to their
corresponding quantum state.
"""
def __init__(self, network: Network) -> None:
"""Initialize quantum register manager.
Args:
network (Network): The network topology.
"""
self._network = network
self.qubits: dict[QubitId, Qubit] = {}
self._com_entanglements: dict[frozenset[QubitId], EntanglementLink] = {}
self.states: dict[frozenset[QubitId], QuantumState] = {}
self._qubit_to_state_key: dict[QubitId, frozenset[QubitId]] = {}
def _validate_quantum_node(self, node: NodeReference) -> QuantumNode:
"""Get the QuantumNode for a node reference if the node is valid, else raise.
Args:
node (NodeReference): Reference to the node to validate.
Returns:
QuantumNode: Valid quantum node instance from the network.
"""
node = self._network.validate_node(node)
if not isinstance(node, QuantumNode):
msg = "Node is not a quantum node."
raise TypeError(msg)
return node
def _validate_qubit(self, qubit: QubitReference, *, needs_registered: bool = True) -> QubitId:
"""Get the QubitId for a qubit reference if the qubit is allocated, else raise.
Args:
qubit (QubitReference): Qubit reference to validate.
needs_registered (bool): Whether the qubit needs to be registered. Defaults to True.
Returns:
QubitId: The qubit id.
"""
if isinstance(qubit, Qubit) and qubit not in self.qubits.values():
msg = f"Qubit {qubit} with id '{qubit.qid}' is not registered in the manager."
raise ValueError(msg)
qubit_id = (
qubit.qid
if isinstance(qubit, Qubit)
else QubitId(qubit)
if isinstance(qubit, str)
else qubit
)
if needs_registered and qubit_id not in self.qubits:
msg = f"Qubit with id '{qubit_id}' is not registered in the manager."
raise ValueError(msg)
return qubit_id
[docs]
def allocate_qubit(
self,
*,
node: NodeReference,
qubit_id: QubitId | str,
qubit_type: QubitType = QubitType.COMMUNICATION,
) -> Qubit:
"""Allocate a new qubit in the system for a given node and type.
Args:
node (NodeReference): Reference to the node where the qubit resides.
qubit_id (QubitId | str): Identifier for qubit.
qubit_type (QubitType): Type of qubit to allocate. Defaults to Communication qubit.
Returns:
Qubit: The newly created and registered qubit.
"""
node = self._validate_quantum_node(node)
qubit_id = self._validate_qubit(qubit_id, needs_registered=False)
qubit = Qubit(qubit_id, node, qubit_type)
self._add_qubit(qubit)
return qubit
def _add_qubit(self, qubit: Qubit) -> None:
"""Add qubit to state system manager.
Args:
qubit (Qubit): The qubit we want to add.
"""
if qubit.qid in self.qubits:
msg = f"Qubit with ID {qubit.qid} already exists."
raise ValueError(msg)
self.qubits[qubit.qid] = qubit
[docs]
def move_qubit(self, qubit: QubitReference, new_node: NodeReference) -> None:
"""Allocate qubit to a new node.
Args:
qubit (QubitReference): Qubit reference of the qubit to be moved.
new_node (NodeReference): Reference to the node to which the qubit is to be moved.
"""
qubit_id = self._validate_qubit(qubit)
self.qubits[qubit_id].node = self._validate_quantum_node(new_node)
[docs]
def remove_qubit(self, qubit: QubitReference) -> None:
"""Remove qubit and corresponding state.
Args:
qubit (QubitReference): Reference of the qubit to be removed.
"""
qubit_id = self._validate_qubit(qubit)
if qubit_id in self._qubit_to_state_key:
state_key = self._qubit_to_state_key[qubit_id]
if state_key in self._com_entanglements:
self.remove_com_entanglement(list(state_key))
else:
self.states.pop(state_key, None)
for qid in state_key:
self._qubit_to_state_key.pop(qid, None)
self.qubits.pop(qubit_id)
[docs]
def get_qubits_by_node(self, node: NodeReference) -> list[QubitId]:
"""Get all qubits located at a specific node.
Args:
node (NodeReference): Reference to the node.
Returns:
list[QubitId]: QubitIds allocated at certain node.
"""
node = self._validate_quantum_node(node)
return [qubit.qid for qubit in self.qubits.values() if qubit.node == node]
[docs]
def add_com_entanglement(self, entanglement: EntanglementLink, state: QuantumState) -> None:
"""Add communication entanglement to quantum register manager.
If the qubits are part of single-qubit states, remove them.
If the qubits are part of multi-qubit states, raise an error.
Args:
entanglement (EntanglementLink): The entanglement we want to add to the system.
state (QuantumState): The according QuantumState we want to add to the system.
"""
QuantumState.validate_density_matrix(state.rho)
key = frozenset(entanglement.qubits)
if key in self._com_entanglements:
msg = "Entanglement already exists."
raise ValueError(msg)
for qid in key:
self._validate_qubit(qid, needs_registered=True)
existing_key = self._qubit_to_state_key.get(qid)
if existing_key is not None and len(existing_key) > 1:
msg = (
f"Qubit {qid} is already part of an entangled state "
f"({existing_key}) and cannot be added to a new entanglement."
)
raise ValueError(msg)
for qid in key:
existing_key = self._qubit_to_state_key.get(qid)
if existing_key is not None:
self.states.pop(existing_key, None)
self._qubit_to_state_key.pop(qid, None)
self._com_entanglements[key] = entanglement
self.states[key] = state
for qid in key:
self._qubit_to_state_key[qid] = key
[docs]
def remove_com_entanglement(self, qubits: list[QubitReference]) -> None:
"""Remove communication entanglement from system.
Args:
qubits (list[QubitReference]): The list of qubits we want to remove.
"""
qubit_ids = [self._validate_qubit(qid) for qid in qubits]
key = frozenset(qubit_ids)
self._com_entanglements.pop(key, None)
self.states.pop(key, None)
for qid in key:
self._qubit_to_state_key.pop(qid, None)
[docs]
def allocate_bell_pair( # next
self,
nodes: list[NodeReference],
qubit_ids: list[QubitId | str],
fidelity: float = 1.0,
) -> None:
"""Allocates two new communication qubits and a Bell state.
Args:
nodes (list[NodeReference]): References of the nodes.
qubit_ids (list[QubitId | str]): Identifier of qubits.
fidelity (float): Fidelity of the entangled state. Defaults to 1.0.
"""
if len(nodes) != BELL_PAIR_QUBITS or len(qubit_ids) != BELL_PAIR_QUBITS:
msg = "Generation of Bell pair requires exactly 2 nodes and 2 qubits"
raise ValueError(msg)
nodes = [self._validate_quantum_node(node) for node in nodes]
qubits = [
self.allocate_qubit(
node=nodes[i],
qubit_id=qubit_ids[i],
qubit_type=QubitType.COMMUNICATION,
)
for i in range(BELL_PAIR_QUBITS)
]
bell_state = create_bell_state(fidelity)
entanglement = EntanglementLink(qubits=[qubits[0].qid, qubits[1].qid])
self.add_com_entanglement(entanglement, bell_state)
[docs]
def find_entanglement_between_nodes(
self, source_node: QuantumNode, target_node: QuantumNode
) -> frozenset[QubitId] | None:
"""Find a communication entanglement that involves both given nodes.
Args:
source_node (QuantumNode): First quantum node that should be included in the
entanglement.
target_node (QuantumNode): Second quantum node that should be included in the
entanglement.
Returns:
frozenset[QubitId] | None: A frozenset of the QubitId values participating in
the entanglement, or None if no matching entanglement exists.
"""
for key in self._com_entanglements:
node_ids = {self.qubits[qid].node.id for qid in key}
if {source_node.id, target_node.id}.issubset(node_ids):
return key
return None
[docs]
def create_bell_state(fidelity: float = 1.0) -> QuantumState:
"""Create a Bell state with given fidelity.
Args:
fidelity (float): Desired fidelity (0-1). Defaults to 1.0.
Returns:
QuantumState: Bell state with specified fidelity.
"""
if fidelity > 1.0 or fidelity < 0.0:
msg = "Fidelity must be a float between 0 and 1."
raise ValueError(msg)
perfect_bell = np.zeros((4, 4), dtype=complex)
perfect_bell[0, 0] = 0.5
perfect_bell[0, 3] = 0.5
perfect_bell[3, 0] = 0.5
perfect_bell[3, 3] = 0.5
mixed = np.eye(4) / 4
if fidelity > MIN_FIDELITY_THRESHOLD:
p = (4 * fidelity - 1) / 3
rho = p * perfect_bell + (1 - p) * mixed
else:
rho = mixed
return QuantumState(rho=rho, fidelity=fidelity)