"""
Basic functions, not specific to PrsData
Notation:
prefix "l": value as measured by Lock-In
prefix "s": standard deviation
"""
import numpy as np
# nm_to_eV etc.
from devctrl.data.spectrum.functions import *
[docs]
def rms_to_pp_sin(rms: float) -> float:
return rms * 2 * np.sqrt(2)
[docs]
def rms_to_pp_square(rms: float) -> float:
return rms * 2
[docs]
def fexp(number):
"""
Get the base 10 exponent of a number
"""
return int(round(np.log10(number), 0))
# Data calculations
[docs]
def dIX(lR, slR, theta, stheta, dI_offset, sdI_offset):
"""
Calculate the in-phase (X) signal component
delta I_X = delta R * cos(theta) - delta U_offset
Currently, the error of the offset is not included in the error of delta I_X.
:param lR: R from lock-in
:param slR: standard error of R from lock-in
:param theta: offset-corrected theta from lock-in
:param stheta: standard error of offset-corrected theta from lock-in
:param dI_offset: R-offset from lock-in
:param sdI_offset: standard error of R-offset from lock-in
:return: delta I_X
"""
dI = lR * np.cos(np.deg2rad(theta))
dI -= dI_offset
# TODO use offset error
sdI = np.sqrt((slR * np.cos(np.deg2rad(theta)))**2 + (lR * np.sin(np.deg2rad(theta)) * np.deg2rad(stheta))**2)
return dI, sdI
[docs]
def dIY(lR, slR, theta, stheta):
"""
Calculate quadrature (out-of-phase, Y) signal component
delta I_Y = delta R * sin(theta)
The lock-in R offset is not substracted, since it is assumed to be in-phase
:param lR: R from lock-in
:param slR: standard error of R from lock-in
:param theta: offset-corrected theta from lock-in
:param stheta: standard error of offset-corrected theta from lock-in
:return: delta I_Y
"""
dIY = lR * np.sin(np.deg2rad(theta))
sdIY = np.sqrt((slR * np.sin(np.deg2rad(theta)))**2 + (lR * np.cos(np.deg2rad(theta)) * np.deg2rad(stheta))**2)
return dIY, sdIY
[docs]
def dI(lR, slR, theta, stheta, dI_offset, sdI_offset):
"""
Calculate the total (R) signal component.
delta I = sqrt((delta I_X)^2 + (delta I_Y)^2) * (-1) if |theta| >= 90
The lock-in R offset is only subtracted from delta I_X, since it is assumed to be in-phase.
The sign is flipped when |theta| >= 90, since that is assumed to be not a 180° phase shift, but a negative signal.
This assumption is valid if the phase never "creeps" towards 90°, but is always either near 0° or near 180°.
If the phase is near 90°, it is not possible to decipher whether this is a phase shift or a sign flip!
:param lR: R from lock-in
:param slR: standard error of R from lock-in
:param theta: offset-corrected theta from lock-in
:param stheta: standard error of offset-corrected theta from lock-in
:param dI_offset: R-offset from lock-in
:param sdI_offset: standard error of R-offset from lock-in
:return: delta I
"""
dI_X, sdI_X = dIX(lR, slR, theta, stheta, dI_offset, sdI_offset)
dI_Y, sdI_Y = dIY(lR, slR, theta, stheta)
dI = np.sqrt(dI_X**2 + dI_Y**2)
# Flip sign when |theta|>90° (negative X)
# Can not use phase here, since we subtract an offset from X, theta is not actually the correct angle.
# The correct angle can be calculated from offset-corrected dI-X and dI-Y
if dI_X < 0:
dI = -dI
sdI = np.sqrt(1 / dI**2 * ((dI_X * sdI_X)**2 + (dI_Y * sdI_Y)**2))
return dI, sdI
[docs]
def dI_I(dI, sdI, I, sI) -> tuple[float, float]:
"""
Calculate normalized change in intensity
:return: value, error
"""
dI_I = dI / I
sdI_I = np.sqrt((sdI / I)**2 + (dI * sI/I**2)**2)
return dI_I, sdI_I
[docs]
def A(I0, sI0, R, sR, T, sT):
"""
Calculate "DC Absorption" value from reference, reflection and transmission
:return: value, error
"""
# 1 = T + R + A
return I0 - T - R, np.sqrt(sI0**2 + sR**2 + sT**2)
[docs]
def dA(dR, sdR, dT, sdT):
"""
Calculate change in absorption absorption from reflection and transmission
:return: value, error
"""
return -dR - dT, np.sqrt(sdR**2 + sdT**2)