Source code for pyfli.phasor.phasorSEPL.config

"""
config.py
=========
Dataclass-based configuration for phasor acquisitions.

All physical parameters are stored here so that every downstream module
receives a single, validated object rather than a loose collection of kwargs.

Units
-----
    Time      : nanoseconds (ns)
    Frequency : MHz  (stored as period T in ns)
    Angles    : radians (computed internally)
"""

import math
from dataclasses import dataclass
from enum import Enum, auto


[docs] class AcquisitionMode(Enum): """ Enumerate phasor acquisition geometries supported by the phasor package. Modes cover continuous, discrete, gated, truncated, and offset acquisition models. """ CONTINUOUS = ( auto() ) # Ideal TCSPC / frequency-domain; canonical universal semicircle DISCRETE = auto() # Binned TCSPC with finite number of bins N GATED_SINGLE = auto() # Single square gate of width W GATED_N = auto() # N equidistant square gates of width W TRUNCATED = auto() # Decay recording window shorter than laser period OFFSET = auto() # IRF / excitation-pulse offset within recording window
[docs] @dataclass class AcquisitionConfig: """ Validate and store physical parameters for phasor locus calculations. The dataclass centralizes acquisition mode, laser period, harmonic, bin counts, gate settings, truncation, offset, and lifetime-grid ranges. Parameters ---------- mode : AcquisitionMode Acquisition, fitting, plotting, or simulator mode. T_ns : float Laser repetition period in nanoseconds. harmonic : int Phasor harmonic index. N_bins : int Number of temporal bins in discrete phasor acquisition. gate_width_frac : float Gate width as a fraction of the laser period. N_gates : int Number of gates in gated acquisition. T_rec_frac : float Recorded decay window as a fraction of the laser period. t0_frac : float Offset as a fraction of the laser period. tau_min_ns : float Minimum lifetime in nanoseconds for locus generation. tau_max_ns : float Maximum lifetime in nanoseconds for locus generation. n_tau_pts : int Number of lifetime samples in a generated locus. """ # ------------------------------------------------------------------ core mode: AcquisitionMode = AcquisitionMode.CONTINUOUS T_ns: float = 12.5 harmonic: int = 1 # ------------------------------------------------------------------ discrete N_bins: int = 64 # ------------------------------------------------------------------ gating gate_width_frac: float = 0.5 N_gates: int = 4 # ------------------------------------------------------------------ truncation T_rec_frac: float = 0.8 # ------------------------------------------------------------------ offset t0_frac: float = 0.1 # ------------------------------------------------------------------ locus tau_min_ns: float = 1e-4 tau_max_ns: float = 10.0 n_tau_pts: int = 600 # ------------------------------------------------------------------ def __post_init__(self) -> None: """ Run the post init routine. Returns ------- None No object is returned; the function perform post init. """ self._validate() # ------------------------------------------------------------------ validation def _validate(self) -> None: """ Run the validate routine. Returns ------- None No object is returned; the function perform validate. """ if self.T_ns <= 0: raise ValueError(f"T_ns must be positive, got {self.T_ns}") if self.harmonic < 1: raise ValueError(f"harmonic must be >= 1, got {self.harmonic}") if self.N_bins < 2: raise ValueError(f"N_bins must be >= 2, got {self.N_bins}") if not (0 < self.gate_width_frac <= 1): raise ValueError( f"gate_width_frac must be in (0,1], got {self.gate_width_frac}" ) if self.N_gates < 1: raise ValueError(f"N_gates must be >= 1, got {self.N_gates}") if not (0 < self.T_rec_frac <= 1): raise ValueError(f"T_rec_frac must be in (0,1], got {self.T_rec_frac}") if not (0 <= self.t0_frac < 1): raise ValueError(f"t0_frac must be in [0,1), got {self.t0_frac}") if self.tau_min_ns <= 0: raise ValueError(f"tau_min_ns must be positive, got {self.tau_min_ns}") if self.tau_max_ns <= self.tau_min_ns: raise ValueError("tau_max_ns must be greater than tau_min_ns") if self.n_tau_pts < 10: raise ValueError(f"n_tau_pts must be >= 10, got {self.n_tau_pts}") # ------------------------------------------------------------------ derived properties @property def omega(self) -> float: """Angular frequency ω = 2π·n/T (rad/ns).""" return 2.0 * math.pi * self.harmonic / self.T_ns @property def frequency_MHz(self) -> float: """Fundamental laser repetition frequency in MHz.""" return 1_000.0 / self.T_ns @property def gate_width_ns(self) -> float: """Absolute gate width W (ns).""" return self.gate_width_frac * self.T_ns @property def T_rec_ns(self) -> float: """Absolute recording window (ns).""" return self.T_rec_frac * self.T_ns @property def t0_ns(self) -> float: """Absolute IRF offset (ns).""" return self.t0_frac * self.T_ns # ------------------------------------------------------------------ helpers
[docs] def describe(self) -> str: """ Run the describe routine. Returns ------- str String path, label, or message produced by describe. """ lines = [ "AcquisitionConfig", f" mode : {self.mode.name}", f" T : {self.T_ns} ns → f = {self.frequency_MHz:.3f} MHz", f" harmonic n : {self.harmonic} → ω = {self.omega:.4f} rad/ns", ] if self.mode is AcquisitionMode.DISCRETE: lines.append(f" N_bins : {self.N_bins}") if self.mode in (AcquisitionMode.GATED_SINGLE, AcquisitionMode.GATED_N): lines.append( f" gate width W : {self.gate_width_ns:.3f} ns ({self.gate_width_frac:.2f}·T)" ) if self.mode is AcquisitionMode.GATED_N: lines.append(f" N_gates : {self.N_gates}") if self.mode is AcquisitionMode.TRUNCATED: lines.append( f" T_rec : {self.T_rec_ns:.3f} ns ({self.T_rec_frac:.2f}·T)" ) if self.mode is AcquisitionMode.OFFSET: lines.append( f" IRF offset t0 : {self.t0_ns:.3f} ns ({self.t0_frac:.2f}·T)" ) lines.append( f" τ range : {self.tau_min_ns} – {self.tau_max_ns} ns ({self.n_tau_pts} pts)" ) return "\n".join(lines)