"""Photodiode quantum noise detector."""
import numpy as np
from finesse.exceptions import FinesseException
from finesse.detectors.compute.quantum import (
    QND0Workspace,
    QNDNWorkspace,
    QShot0Workspace,
    QShotNWorkspace,
    QuantumNoiseDetectorWorkspace,
)
from finesse.detectors.general import Detector, NoiseDetector
from finesse.components.general import NoiseType
from finesse.parameter import float_parameter
[docs]class QuantumNoiseDetector(Detector, NoiseDetector):
    """Represents a quantum noise detector with no RF demodulations.
    It calculates the amplitude spectral density of the photocurrent
    noise of a photodiode output demodulated at the signal frequency.
    Parameters
    ----------
    name : str
        Name of newly created quantum noise detector.
    node : :class:`.Node`
        Node to read output from.
    nsr : bool, optional
        If true, calculate the noise-to-signal ratio rather than the absolute
        noise value.
    sources : list of :class:`.Connector`, optional
        If given, only detect quantum noise contributions from these components.
    exclude_sources : list of :class:`.Connector`, optional
        If given, this will not detect quantum noise contributions from any of
        these components, even if explicitly specified in `sources`.
    """
    def __init__(self, name, node, nsr=False, sources=None, exclude_sources=None):
        Detector.__init__(
            self, name, node, dtype=np.float64, unit="1/sqrt(Hz)", label="ASD"
        )
        NoiseDetector.__init__(self, NoiseType.QUANTUM)
        self.nsr = nsr
        self.sources = sources
        self.exclude_sources = exclude_sources
        self._request_selection_vector(name)
    def _has_sources(self):
        return self.sources is not None or self.exclude_sources is not None
    def _get_workspace(self, sims):
        if sims.signal is None:
            raise FinesseException(
                f"Detector {self} requires an `fsig` frequency to be set"
            )
        ws = QND0Workspace(self, sims, self.nsr, self.sources, self.exclude_sources)
        return ws 
[docs]@float_parameter("f", "Frequency")
@float_parameter("phase", "Phase")
class QuantumNoiseDetectorDemod1(Detector, NoiseDetector):
    """Represents a quantum noise detector with 1 RF demodulation.
    It calculates the amplitude spectral density of the photocurrent
    noise of a photodiode output demodulated at the signal frequency.
    Parameters
    ----------
    name : str
        Name of newly created quantum noise detector.
    node : :class:`.Node`
        Node to read output from.
    f : float
        Demodulation frequency in Hz
    phase : float
        Demodulation phase in degrees
    nsr : bool, optional
        If true, calculate the noise-to-signal ratio rather than the absolute
        noise value.
    sources : list of :class:`.Connector`, optional
        If given, only detect quantum noise contributions from these components.
    """
    def __init__(
        self, name, node, f, phase, nsr=False, sources=None, exclude_sources=None
    ):
        Detector.__init__(
            self, name, node, dtype=np.float64, unit="1/sqrt(Hz)", label="ASD"
        )
        NoiseDetector.__init__(self, NoiseType.QUANTUM)
        self.f = f
        self.phase = phase
        self.nsr = nsr
        self.sources = sources
        self.exclude_sources = exclude_sources
        self._request_selection_vector(name)
    def _has_sources(self):
        return self.sources is not None or self.exclude_sources is not None
    def _get_workspace(self, sims):
        if sims.signal is None:
            raise FinesseException(
                f"Detector {self} requires an `fsig` frequency to be set"
            )
        ws = QNDNWorkspace(
            self,
            sims,
            [(self.f, self.phase)],
            self.nsr,
            self.sources,
            self.exclude_sources,
        )
        return ws 
[docs]@float_parameter("f1", "Frequency")
@float_parameter("f2", "Frequency")
@float_parameter("phase1", "Phase")
@float_parameter("phase2", "Phase")
class QuantumNoiseDetectorDemod2(Detector, NoiseDetector):
    """Represents a quantum noise detector with 2 RF demodulations.
    It calculates the amplitude spectral density of the photocurrent
    noise of a photodiode output demodulated at the signal frequency.
    Parameters
    ----------
    name : str
        Name of newly created quantum noise detector.
    node : :class:`.Node`
        Node to read output from.
    f1 : float
        First demodulation frequency in Hz
    phase1 : float
        First demodulation phase in degrees
    f2 : float
        Second demodulation frequency in Hz
    phase2 : float
        Second demodulation phase in degrees
    nsr : bool, optional
        If true, calculate the noise-to-signal ratio rather than the absolute
        noise value.
    sources : list of :class:`.Connector`, optional
        If given, only detect quantum noise contributions from these components.
    """
    def __init__(
        self,
        name,
        node,
        f1,
        phase1,
        f2,
        phase2,
        nsr=False,
        sources=None,
        exclude_sources=None,
    ):
        Detector.__init__(
            self, name, node, dtype=np.float64, unit="1/sqrt(Hz)", label="ASD"
        )
        NoiseDetector.__init__(self, NoiseType.QUANTUM)
        self.f1 = f1
        self.f2 = f2
        self.phase1 = phase1
        self.phase2 = phase2
        self.nsr = nsr
        self.sources = sources
        self.exclude_sources = exclude_sources
        self._request_selection_vector(name)
    def _has_sources(self):
        return self.sources is not None or self.exclude_sources is not None
    def _get_workspace(self, sims):
        if sims.signal is None:
            raise FinesseException(
                f"Detector {self} requires an `fsig` frequency to be set"
            )
        ws = QNDNWorkspace(
            self,
            sims,
            [(self.f1, self.phase1), (self.f2, self.phase2)],
            self.nsr,
            self.sources,
            self.exclude_sources,
        )
        return ws 
[docs]class QuantumShotNoiseDetector(Detector):
    """Represents a quantum shot noise detector with no RF demodulations.
    It calculates the amplitude spectral density of the photocurrent
    noise of a photodiode output demodulated at the signal frequency,
    considering only vacuum noise contributions (ignoring radiation
    pressure and squeezing effects).
    Parameters
    ----------
    name : str
        Name of newly created quantum shot noise detector.
    node : :class:`.Node`
        Node to read output from.
    nsr : bool, optional
        If true, calculate the noise-to-signal ratio rather than the absolute
        noise value.
    """
    def __init__(self, name, node, nsr=False):
        self.nsr = nsr
        Detector.__init__(
            self, name, node, dtype=np.float64, unit="W/sqrt(Hz)", label="ASD"
        )
    def _get_workspace(self, sim):
        if sim.signal is None:
            raise FinesseException(
                f"Detector {self} requires an `fsig` frequency to be set"
            )
        ws = QShot0Workspace(self, sim, self.nsr)
        return ws 
[docs]@float_parameter("f", "Frequency")
@float_parameter("phase", "Phase")
class QuantumShotNoiseDetectorDemod1(Detector):
    """Represents a quantum shot noise detector with 1 RF demodulation.
    It calculates the amplitude spectral density of the photocurrent
    noise of a photodiode output demodulated at the signal frequency,
    considering only vacuum noise contributions (ignoring radiation
    pressure and squeezing effects).
    Parameters
    ----------
    name : str
        Name of newly created quantum shot noise detector.
    node : :class:`.Node`
        Node to read output from.
    f : float
        Demodulation frequency in Hz
    phase : float
        Demodulation phase in degrees
    nsr : bool, optional
        If true, calculate the noise-to-signal ratio rather than the absolute
        noise value.
    """
    def __init__(self, name, node, f, phase, nsr=False):
        Detector.__init__(
            self, name, node, dtype=np.float64, unit="W/sqrt(Hz)", label="ASD"
        )
        self.f = f
        self.phase = phase
        self.nsr = nsr
    def _get_workspace(self, sims):
        if sims.signal is None:
            raise FinesseException(
                f"Detector {self} requires an `fsig` frequency to be set"
            )
        ws = QShotNWorkspace(
            self,
            sims,
            [
                (self.f, self.phase),
            ],
            self.nsr,
        )
        return ws 
[docs]@float_parameter("f1", "Frequency")
@float_parameter("f2", "Frequency")
@float_parameter("phase1", "Phase")
@float_parameter("phase2", "Phase")
class QuantumShotNoiseDetectorDemod2(Detector):
    """Represents a quantum shot noise detector with 2 RF demodulations.
    It calculates the amplitude spectral density of the photocurrent
    noise of a photodiode output demodulated at the signal frequency,
    considering only vacuum noise contributions (ignoring radiation
    pressure and squeezing effects).
    Parameters
    ----------
    name : str
        Name of newly created quantum shot noise detector.
    node : :class:`.Node`
        Node to read output from.
    f1 : float
        First demodulation frequency in Hz
    phase1 : float
        First demodulation phase in degrees
    f2 : float
        Second demodulation frequency in Hz
    phase2 : float
        Second demodulation phase in degrees
    nsr : bool, optional
        If true, calculate the noise-to-signal ratio rather than the absolute
        noise value.
    """
    def __init__(self, name, node, f1, phase1, f2, phase2, nsr=False):
        Detector.__init__(
            self, name, node, dtype=np.float64, unit="1/sqrt(Hz)", label="ASD"
        )
        self.f1 = f1
        self.f2 = f2
        self.phase1 = phase1
        self.phase2 = phase2
        self.nsr = nsr
    def _get_workspace(self, sims):
        if sims.signal is None:
            raise FinesseException(
                f"Detector {self} requires an `fsig` frequency to be set"
            )
        ws = QShotNWorkspace(
            self, sims, [(self.f1, self.phase1), (self.f2, self.phase2)], self.nsr
        )
        return ws 
[docs]class GeneralQuantumNoiseDetector(Detector, NoiseDetector):
    """Represents a quantum noise detector.
    This detector calculates the amplitude spectral density of the
    photocurrent noise of a DC or demodulated photodiode output.
    Parameters
    ----------
    name : str
        Name of newly created quantum noise detector.
    node : :class:`.Node`
        Node to read output from.
    freqs : list of float or :class:`.Frequency`, optional
        List of mixer demodulation frequencies (in Hz).
    phases : list of float, optional
        List of mixer demodulation phases (in Hz).
    shot_only : bool, optional
        If True, detect only vacuum noise contributions.
    """
    def __init__(self, name, node, freqs=None, phases=None, shot_only=False):
        if freqs is None:
            freqs = []
        if phases is None:
            phases = []
        Detector.__init__(
            self, name, node, dtype=np.float64, unit="1/sqrt(Hz)", label="ASD"
        )
        NoiseDetector.__init__(self, NoiseType.QUANTUM)
        if len(freqs) == 0:
            self.__mode = "dc"
        elif len(phases) == len(freqs):
            self.__mode = "mixer"
        else:
            raise ValueError("'phases' must be as long as 'freqs'.")
        self.freqs = np.array(freqs)
        self.phases = np.array(phases)
        self.shot_only = shot_only
        self._request_selection_vector(name)
    def _get_workspace(self, sims):
        if sims.signal is None:
            raise FinesseException(
                f"Detector {self} requires an `fsig` frequency to be set"
            )
        ws = QuantumNoiseDetectorWorkspace(self, sims)
        return ws