plesty.fss ========== .. py:module:: plesty.fss .. autoapi-nested-parse:: Fine-structure analyzer module: exciton fine-structure splitting from polarization sweeps. Submodules ---------- .. toctree:: :maxdepth: 1 /reference/plesty/fss/analyzer/index /reference/plesty/fss/data/index /reference/plesty/fss/energy/index /reference/plesty/fss/line_fit/index /reference/plesty/fss/monitor/index /reference/plesty/fss/plot/index /reference/plesty/fss/report/index /reference/plesty/fss/sine/index /reference/plesty/fss/tracking/index Attributes ---------- .. autoapisummary:: plesty.fss.HARMONICS Classes ------- .. autoapisummary:: plesty.fss.FineStructure plesty.fss.FineStructureData plesty.fss.SineFit Functions --------- .. autoapisummary:: plesty.fss.fit_sine plesty.fss.load_config Package Contents ---------------- .. py:class:: FineStructure(angle: str = 'hwp_deg', rotator: str = 'hwp', fit_options: Mapping[str, Any] | None = None) Bases: :py:obj:`plesty.lib.analyzer.Analyzer` Fine-structure splitting of every line in a polarization sweep. The spectrum must carry the rotator angle of each frame as a coordinate (``SpectrumData.from_run(run, coordinates=("hwp_deg",))``). Frames whose line fit fails are left out of that line's sweep fit. The splitting is the peak-to-peak amplitude of the oscillation of each line's fitted position with the rotator angle, divided by the rotator's contrast (:data:`~plesty.fss.sine.CONTRAST`: 1 for a half-wave plate or a polarizer, ½ for a quarter-wave plate). That measures a doublet only while its two orthogonally polarized components are closer than the linewidth and appear as one line. A line whose position oscillates by as much as its linewidth or more is marked ``fss_valid = 0`` and named in a warning: its fit followed a different line from frame to frame, and its ``fss`` is not a splitting. Outputs: * ``fine_structure`` — :class:`~plesty.fss.FineStructureData`, one entry per line: the splitting in µeV and as a wavelength, the polarization axis and the mean position, each ± σ, with the linewidth and the ``fss_valid`` flag. * ``fit`` — the :class:`~plesty.lib.data.SpectrumFitData` of the line fits. * ``curve`` — the fitted position at each fitted frame's angle, shaped like ``fit["center"]`` so the two plot against each other directly. Validate and store the sweep parameters. :param angle: Name of the spectrum coordinate holding the rotator angle. :param rotator: ``"hwp"`` (half-wave plate, 90° period), ``"polarizer"`` (180° period), or ``"qwp"`` (quarter-wave plate in front of a fixed polarizer, 90° period at half the contrast, so the splitting is twice the oscillation). :param fit_options: Keyword arguments of :class:`~plesty.lib.analyzer.SpectrumFit` (``peaks``, ``model``, ``window``, ``center_tolerance``, …). Without ``order``, frames are fitted in angle order. :raises ValueError: If the rotator is unknown or a fit option is invalid. .. py:attribute:: input_schema name → schema entry. :type: Declared inputs of :meth:`analyze` .. py:attribute:: output_schema name → schema entry. :type: Declared outputs of :meth:`analyze` .. py:method:: analyze(spectrum: plesty.lib.data.SpectrumData) -> dict[str, Any] Fit every line in every frame, then each line's centre against the angle. :param spectrum: The polarization sweep. :returns: ``fine_structure``, ``fit`` and ``curve``, see the class description. :raises ValueError: If the angle coordinate is missing or not an angle, or the wavelength unit is not a length. .. py:method:: report(outputs: Mapping[str, Any]) -> plesty.lib.report.Report Say what the sweep found: a sentence, a table of the lines, and the sweep drawn. See :mod:`plesty.fss.report`. Only ``fine_structure`` and ``fit`` are read, so a report can be rebuilt from the two saved outputs. :param outputs: What calling the analyzer returned, or at least its ``fine_structure`` and ``fit``. :returns: The report, stamped with this analyzer's provenance. .. py:class:: FineStructureData(values: Mapping[str, numpy.typing.ArrayLike], wavelength_unit: str | None = 'nm', metadata: dict[str, Any] | None = None, provenance: dict[str, Any] | None = None) Bases: :py:obj:`plesty.lib.data.ExperimentData` Fine structure of each fitted line, one entry per line. Produced by :class:`plesty.fss.FineStructure`. Every array has shape ``(peaks,)``, ``nan`` where a line's sweep could not be fitted. ====================== ==================================================== ``fss`` fine-structure splitting (µeV) ``fss_wavelength`` the same splitting as a wavelength (wavelength unit) ``axis_deg`` rotator angle of the longer-wavelength line (deg) ``mean_center`` mean line position over the sweep (wavelength unit) ``r_squared`` coefficient of determination of the sweep fit ``n_points`` sweep points the fit used ``fwhm`` median fitted linewidth over the sweep (wavelength unit) ``fss_valid`` 1 when ``fss_wavelength < fwhm`` (``fss`` is a splitting), else 0 ====================== ==================================================== ``fss``, ``fss_wavelength``, ``axis_deg`` and ``mean_center`` each have a one-sigma ``_err`` array. ``metadata`` records the ``rotator``, the sweep coordinate (``angle``) and its ``period_deg``. The splitting is the peak-to-peak amplitude of the oscillation of one fitted line's position with the rotator angle, divided by the rotator's contrast (½ for a quarter-wave plate, else 1). It measures a doublet only while the doublet's two components are closer than the linewidth and appear as one line, whose position cannot then move by more than that linewidth. An oscillation at least as wide as the line (``fss_valid == 0``) means the fit followed a different line from frame to frame — typically because the two components are further apart than their linewidth and appear as two separate lines — and ``fss`` is then not a splitting. Validate the arrays and attach their units. :param values: Every name in :meth:`names`, as arrays. :param wavelength_unit: Unit of the fitted spectrum's axis. :param metadata: Sweep context (``rotator``, ``angle``, ``period_deg``). :param provenance: Origin of the data. :raises ValueError: If a name is missing, the metadata lacks ``period_deg``, or the shapes disagree. .. py:attribute:: kind :value: 'fine_structure' Registry key and schema dtype token, e.g. ``"spectrum"``. .. py:attribute:: schema_version :value: 1 Version of the array layout this class writes and reads. .. py:attribute:: QUANTITIES :type: ClassVar[tuple[str, ...]] :value: ('fss', 'fss_wavelength', 'axis_deg', 'mean_center') .. py:attribute:: columns array name → entry, in reading order. An entry's keys, all optional: * ``label`` — the column heading, inline markup and ``$math$`` allowed; the array name when left out. * ``unit`` — used when the array carries none of its own. * ``description`` — one line of meaning. * ``result`` — ``False`` for an array the analysis kept but a reader does not need (a fit quality, a point count); default ``True``. * ``error`` — name of the array holding this one's one-sigma uncertainty, so the two are shown as one ``value ± error``. Arrays left out are not tabulated. :type: What each array means to a reader .. py:attribute:: values :type: dict[str, plesty.lib.data.PlestyArray] Every array by name. .. py:method:: names() -> tuple[str, ...] :classmethod: Return every array name, in storage order. .. py:method:: _units(wavelength: str | None) -> dict[str, str | None] :staticmethod: Return the unit of each array. .. py:method:: __getitem__(name: str) -> plesty.lib.data.PlestyArray Return the array called *name*, e.g. ``result["fss"]``. .. py:property:: n_peaks :type: int Number of lines. .. py:property:: period_deg :type: float Rotator angle over which each line's position repeats. .. py:method:: evaluate(angle_deg: numpy.typing.ArrayLike) -> numpy.ndarray Evaluate each line's fitted position at rotator angles. :param angle_deg: Rotator angles in degrees, 1-D. :returns: Positions of shape ``(angles, peaks)``, in the wavelength unit. .. py:method:: to_arrays() -> dict[str, plesty.lib.data.PlestyArray] Return every array by name. .. py:method:: from_arrays(arrays: dict[str, plesty.lib.data.PlestyArray], metadata: dict[str, Any], provenance: dict[str, Any]) -> Self :classmethod: Rebuild the result from :meth:`to_arrays`; units travel with the arrays. .. py:method:: example() -> Self :classmethod: Return one line at 775 nm with a 40 µeV splitting seen through a half-wave plate. .. py:data:: HARMONICS :type: dict[str, int] .. py:class:: SineFit Result of :func:`fit_sine`, each value with its one-sigma error. :ivar mean: Mean line position (the unit of the positions). :ivar amplitude: Half the peak-to-peak oscillation of the position, ``≥ 0``. :ivar axis_deg: Rotator angle of the longest position, in ``[0, period)``. :ivar harmonic: ``k`` of the model. :ivar r_squared: Coefficient of determination. :ivar n_points: Finite points the fit used. :ivar values: The raw parameters ``(mean, a, b)`` of ``mean + a·sin(kθ) + b·cos(kθ)``. :ivar covariance: Their covariance, in :attr:`values` order. .. py:attribute:: mean :type: float .. py:attribute:: mean_err :type: float .. py:attribute:: amplitude :type: float .. py:attribute:: amplitude_err :type: float .. py:attribute:: axis_deg :type: float .. py:attribute:: axis_deg_err :type: float .. py:attribute:: harmonic :type: int .. py:attribute:: r_squared :type: float .. py:attribute:: n_points :type: int .. py:attribute:: values :type: tuple[float, float, float] .. py:attribute:: covariance :type: numpy.ndarray .. py:property:: period_deg :type: float Rotator angle over which the position repeats. .. py:method:: evaluate(angle_deg: numpy.typing.ArrayLike) -> numpy.ndarray Evaluate the fitted position at rotator angles in degrees. .. py:function:: fit_sine(angle_deg: numpy.typing.ArrayLike, position: numpy.typing.ArrayLike, harmonic: int, sigma: numpy.typing.ArrayLike | None = None) -> SineFit Fit ``mean + A·cos(k·(θ − axis))`` to positions over rotator angles. Non-finite points are dropped. With *sigma* the fit is weighted by ``1/σ²``; as in :func:`plesty.lib.analyzer.fit_spectrum`, the covariance is scaled by the reduced chi-square, so the weights are relative. A point without a positive, finite error is dropped too — an unweighted point cannot be combined with weighted ones — unless no point has one, when the fit is unweighted. :param angle_deg: Rotator angle of each point, in degrees. :param position: Line position at each angle. :param harmonic: ``k`` — 4 for a half-wave plate, 2 for a polarizer. :param sigma: One-sigma error of each position. :returns: The fit; every value is ``nan`` when fewer than four points remain or the angles do not determine the model. :raises ValueError: If the arrays differ in shape or *harmonic* is not positive. .. py:function:: load_config() -> dict[str, Any] Load the packaged module configuration (constants and running defaults). :returns: The parsed ``config.yaml`` shipped with this module.