Source code for qkd_key_rate.base.detector

"""Base class for detector objects."""
from __future__ import annotations

from abc import ABC
from typing import Any, List, Optional

import numpy as np
import pandas as pd

# pylint: disable=invalid-name

DEFAULT_POLARIZATION_DRIFT = np.arcsin(np.sqrt(0.015))
DEFAULT_ERROR_DETECTOR = 0


[docs]class Detector(ABC): """Class for the detectors to be used in the key-rate estimates.""" required_fields = [ "name", "jitter_source", "jitter_detector", "dead_time", "detection_window", "efficiency_system", ]
[docs] def __init__( self, name: str, jitter_source: float, jitter_detector: float, dead_time: float, detection_window: float, efficiency_system: float, polarization_drift: float = DEFAULT_POLARIZATION_DRIFT, error_detector: float = DEFAULT_ERROR_DETECTOR, dark_count_frequency: Optional[float] = None, dark_count_rate: Optional[float] = None, detection_frequency: Optional[float] = None, interval: Optional[float] = None, efficiency_detector: Optional[float] = None, efficiency_party: Optional[float] = None, ) -> None: r""" Initialise a Detector instance. Args: name: Label of the detector party jitter_source: Time-delay introduced at the source jitter_detector: Time-delay introduced at the detector dead_time: The recovery time before a new event can be recorded detection_window: Time window used for event detection efficiency_system: Efficiency of the detecting side without the detector polarization_drift: Shift/drift in the encoding of the photons error_detector: Error-rate of detector dark_count_frequency: Number of dark-counts per second dark_count_rate: Number of dark-counts per detection window detection_frequency: Number of detection windows per second interval: Length of a single detection window in seconds efficiency_detector: Efficiency of the detector on the detecting side efficiency_party: Total efficiency of the detecting side If related optional arguments are both given they must satisfy - $\text{interval} = \frac{1}{\text{detection_frequency}}$ - $\text{dark_count_frequency} = \text{dark_count_rate} \cdot \text{detection_frequency}$ - $\text{efficiency_party} = \text{efficiency_system} \cdot \text{efficiency_detector}$ If they are not given, they are calculated using the same formulas. Returns: Detector instance with specified parameters. Raises: ValueError: If a required field is missing. ValueError: If a needed optional field is missing. AssertionError: If inconsistent related optional fields are provided. """ self.name = name self.jitter_source = jitter_source self.jitter_detector = jitter_detector self.dead_time = dead_time self.detection_window = detection_window self.efficiency_system = efficiency_system self.polarization_drift = polarization_drift self.error_detector = error_detector self.dark_count_frequency = dark_count_frequency self.dark_count_rate = dark_count_rate self.detection_frequency = detection_frequency self.interval = interval self.efficiency_detector = efficiency_detector self.efficiency_party = efficiency_party for k in self.required_fields: if self.__dict__[k] is None: raise ValueError(f"Field '{k}' is required field.") # Handle optional fields if interval is None and detection_frequency is None: raise ValueError( "Either the field 'interval' or 'detection_frequency' is required." ) if interval is not None and detection_frequency is not None: assert abs(interval - 1 / detection_frequency) < 10e-6 if interval is None: self.interval = 1 / self.detection_frequency if detection_frequency is None: self.detection_frequency = 1 / self.interval if dark_count_rate is None and dark_count_frequency is None: raise ValueError( "Either the field 'dark_count_rate' or 'dark_count_frequency' is required." ) if dark_count_rate is not None and dark_count_frequency is not None: assert (dark_count_rate - dark_count_frequency * self.interval) < 10e-6 if dark_count_rate is None: self.dark_count_rate = self.dark_count_frequency * self.interval if dark_count_frequency is None: self.dark_count_frequency = self.dark_count_rate * self.detection_frequency if efficiency_party is None and efficiency_detector is None: raise ValueError( "Either the field 'efficiency_party' or 'efficiency_detector' is required." ) if efficiency_party is not None and efficiency_detector is not None: assert ( efficiency_party - self.efficiency_system * efficiency_detector ) < 10e-6 if efficiency_party is None: self.efficiency_party = self.efficiency_system * self.efficiency_detector if efficiency_detector is None: self.efficiency_detector = self.efficiency_party / self.efficiency_system
[docs] @classmethod def from_file(cls, path: str) -> List[Detector]: """ Construct Detectors from csv file. Args: path: Path to csv file. """ df = pd.read_csv(path, delimiter=";") df = df.replace({np.nan: None}) return [cls(**detector_kwargs) for detector_kwargs in df.to_dict("records")]
[docs] def customise( self, name: Optional[str] = None, jitter_source: Optional[float] = None, jitter_detector: Optional[float] = None, dead_time: Optional[float] = None, detection_window: Optional[float] = None, efficiency_system: Optional[float] = None, polarization_drift: Optional[float] = None, error_detector: Optional[float] = None, dark_count_frequency: Optional[float] = None, dark_count_rate: Optional[float] = None, detection_frequency: Optional[float] = None, interval: Optional[float] = None, efficiency_detector: Optional[float] = None, efficiency_party: Optional[float] = None, ) -> Detector: """Create a detector with customised parameter from current detector See :py:meth:`~__init__` for parameter description. """ new_parameters = dict(**self.__dict__) if jitter_source is not None: new_parameters.update(jitter_source=jitter_source) if jitter_detector is not None: new_parameters.update(jitter_detector=jitter_detector) if dead_time is not None: new_parameters.update(dead_time=dead_time) if detection_window is not None: new_parameters.update(detection_window=detection_window) if efficiency_system is not None: new_parameters.update(efficiency_system=efficiency_system) if polarization_drift is not None: new_parameters.update(polarization_drift=polarization_drift) if error_detector is not None: new_parameters.update(error_detector=error_detector) if dark_count_frequency is not None or dark_count_rate is not None: new_parameters.update(dark_count_frequency=dark_count_frequency) new_parameters.update(dark_count_rate=dark_count_rate) if detection_frequency is not None or interval is not None: new_parameters.update(detection_frequency=detection_frequency) new_parameters.update(interval=interval) if efficiency_detector is not None or efficiency_party is not None: new_parameters.update(efficiency_detector=efficiency_detector) new_parameters.update(efficiency_party=efficiency_party) new_name = name if name is not None else self.name + "(adjusted)" new_parameters.update(name=new_name) return Detector(**new_parameters)
[docs] def get_parameters(self) -> dict[str, Any]: """Get all parameters of the Detector""" return dict(**self.__dict__)
def __repr__(self) -> str: return f"Detector:{self.name}"