Source code for iadpython.sphere

# pylint: disable=invalid-name
# pylint: disable=too-many-instance-attributes
# pylint: disable=too-many-arguments
# pylint: disable=consider-using-f-string
# pylint: disable=line-too-long

"""Class for managing integrating spheres.

Example::

    >>> import iadpython
    >>> s = iadpython.Sphere(250,20)
    >>> print(s)
"""

import numpy as np


[docs] class Sphere(): """Container class for an integrating sphere. Attributes: - d_sphere: diameter of integrating sphere [mm] - d_sample: diameter of the sample port [mm] - d_entrance: diameter of the port that light enters the sphere [mm] - d_detector: diameter of the detector port [mm] - r_detector: reflectivity of the detector - r_wall: reflectivity of the wall - r_std: reflectivity of the standard used with the sphere Example:: >>> import iadpython as iad >>> s = iad.Sphere(200, 20) >>> print(s) """ def __init__(self, d_sphere, d_sample, d_entrance=0, d_detector=0, r_detector=0, r_wall=0.99, r_std=0.99): """Object initialization.""" self._d_sphere = d_sphere self._d_sample = d_sample self._d_entrance = d_entrance self._d_detector = d_detector self.a_sample = self.relative_cap_area(d_sample) self.a_detector = self.relative_cap_area(d_detector) self.a_entrance = self.relative_cap_area(d_entrance) self._a_wall = 1 - self.a_sample - self.a_entrance - self.a_detector self.r_detector = r_detector self._r_wall = r_wall self._r_std = r_std
[docs] def cap_area(self, d_port): """Calculate area of spherical cap.""" R = self.d_sphere / 2 r = d_port / 2 h = R - np.sqrt(R**2 - r**2) return 2 * np.pi * R * h
[docs] def approx_relative_cap_area(self, d_port): """Calculate approx relative area of spherical cap.""" R = self.d_sphere / 2 r = d_port / 2 return r**2 / (4 * R**2)
[docs] def relative_cap_area(self, d_port): """Calculate relative area of spherical cap.""" # R = self.d_sphere/2 # r = d_port/2 # h = R - np.sqrt(R**2-r**2) # return 2*np.pi*R*h / (4*np.pi*R**2) h = (self.d_sphere - np.sqrt(self.d_sphere**2 - d_port**2)) / 2 return h / self.d_sphere
def __str__(self): """Return basic details as a string for printing.""" s = "" s += "Sphere diameter = %.1f mm\n" % self._d_sphere s += "Port diameters\n" s += " sample = %.1f mm\n" % self._d_sample s += " entrance = %.1f mm\n" % self._d_entrance s += " detector = %.1f mm\n" % self._d_detector s += "Fractional areas of sphere\n" s += " walls = %.5f\n" % self.a_wall s += " sample = %.5f\n" % self.a_sample s += " entrance = %.5f\n" % self.a_entrance s += " detector = %.5f\n" % self.a_detector s += "Diffuse reflectivities\n" s += " walls = %.1f%%\n" % (self.r_wall * 100) s += " detector = %.1f%%\n" % (self.r_detector * 100) s += " standard = %.1f%%\n" % (self.r_std * 100) s += "Gain\n" s += " nothing = %.1f\n" % self.multiplier(0, 0) s += " standard = %.1f\n" % self.multiplier(self.r_std, self.r_std) return s
[docs] def gain(self, URU, r_wall=None): r"""Determine the gain relative to a black sphere. If the walls of the sphere are black then the light falling on the detector is the diffuse light entering the sphere divided by the surface area on the sphere (P/A). If the walls are perfectly white (and ports are perfectly absorbing) then all the entering light exits through the ports. (P/A_ports) The gain caused by 0% reflecting sphere walls (no port refl) is .. math:: \mbox{gain} = \frac{(P/A)}{(P/A)} = 1 The gain caused by 100% reflecting sphere walls (no port refl) is .. math:: \mbox{gain} = \frac{(P/A_ports)}{(P/A)} = \frac{A_total}{A_ports} Args: URU: reflectance from sample port for diffuse light r_wall: wall reflectance Returns: gain on detector caused by bounces inside sphere """ if r_wall is None: r_wall = self.r_wall tmp = self.a_detector * self.r_detector + self.a_sample * URU tmp = r_wall * (self._a_wall + (1 - self.a_entrance) * tmp) # if tmp == 1.0: # G = np.inf # else: # G = 1.0 + tmp / (1.0 - tmp) # print(URU, 1 - stmp) return 1 - tmp
[docs] def multiplier(self, UR1=None, URU=None, r_wall=None): """Determine the average reflectance of a sphere. The idea here is that UR1 is the reflection of the incident light for the first bounce. Three cases come to mind 1. If the light hits the sample first, then UR1 should be the sample reflectance for collimated illumination. 2. If light hits the sphere wall first, then UR1 should be the wall reflectance. 3. If light is enters the sphere completely diffuse then UR1=1 As defined by LabSphere, "Technical Guide: integrating Sphere Theory and application" using equation 14 Args: UR1: sample reflectance for normal collimated irradiance URU: sample reflectance for diffuse irradiance r_wall: wall reflectance Returns: sphere multiplier """ if r_wall is None: r_wall = self._r_wall if UR1 is None: UR1 = r_wall if URU is None: URU = UR1 denom = 1 denom -= self._a_wall * r_wall denom -= self.a_sample * URU denom -= self.a_detector * self.r_detector if np.isscalar(denom): if denom < 1e-8: m = np.inf else: m = UR1 / denom else: m = np.where(denom > 1e-8, UR1 / denom, np.inf) return m
@property def d_sphere(self): """Getter property for sphere diameter.""" return self._d_sphere @d_sphere.setter def d_sphere(self, value): """When size is changed ratios become invalid.""" assert self.d_sample <= value, "sphere must be bigger than sample port" assert self.d_entrance <= value, "sphere must be bigger than entrance port" assert self.d_detector <= value, "sphere must be bigger than detector port" self._d_sphere = value self.a_sample = self.relative_cap_area(self._d_sample) self.a_detector = self.relative_cap_area(self._d_detector) self.a_entrance = self.relative_cap_area(self._d_entrance) self._a_wall = 1 - self.a_sample - self.a_entrance - self.a_detector @property def d_sample(self): """Getter property for sample port diameter.""" return self._d_sample @d_sample.setter def d_sample(self, value): """When size is changed ratios become invalid.""" assert 0 <= value <= self._d_sphere, "sample port must be between 0 and sphere diameter" self._d_sample = value self.a_sample = self.relative_cap_area(value) self._a_wall = 1 - self.a_sample - self.a_entrance - self.a_detector @property def d_entrance(self): """Getter property for entrance port diameter.""" return self._d_entrance @d_entrance.setter def d_entrance(self, value): """When size is changed ratios become invalid.""" assert 0 <= value <= self._d_sphere, "entrance port must be between 0 and sphere diameter" self._d_entrance = value self.a_entrance = self.relative_cap_area(value) self._a_wall = 1 - self.a_sample - self.a_entrance - self.a_detector @property def d_detector(self): """Getter property for detector port diameter.""" return self._d_detector @d_detector.setter def d_detector(self, value): """When size is changed ratios become invalid.""" assert 0 <= value <= self._d_sphere, "detector port must be between 0 and sphere diameter" self._d_detector = value self.a_detector = self.relative_cap_area(value) self._a_wall = 1 - self.a_sample - self.a_entrance - self.a_detector @property def a_wall(self): """Getter property for detector port diameter.""" return self._a_wall @a_wall.setter def a_wall(self, value): """Change the relative wall area: a bit crazy.""" assert 0 <= value <= 1, "relative wall are must be between 0 and 1" # Find the diameter of a spherical cap assuming all non-wall # port area is assigned to a single sample port self._d_sample = 2 * self._d_sphere * np.sqrt(value - value**2) self._d_entrance = 0 self._d_detector = 0 self.a_entrance = 0 self.a_detector = 0 self._a_wall = value self.a_sample = 1 - value @property def r_std(self): """Getter property for reflectance standard.""" return self._r_std @r_std.setter def r_std(self, value): """Change the reflectance standard used.""" if np.isscalar(value): assert 0 <= value <= 1, "Reflectivity of standard must be between 0 and 1" else: assert 0 <= value.all() <= 1, "Reflectivity of standard must be between 0 and 1" self._r_std = value self.gain_std = self.multiplier(self.r_std, self.r_std) @property def r_wall(self): """Getter property for wall reflectivity.""" return self._r_wall @r_wall.setter def r_wall(self, value): """Change the wall reflectivity.""" if np.isscalar(value): assert 0 <= value <= 1, "Reflectivity of standard must be between 0 and 1" else: assert 0 <= value.all() <= 1, "Reflectivity of standard must be between 0 and 1" self._r_wall = value
[docs] def Gain_11(RS, TS, URU, tdiffuse): r"""Net gain for on detector in reflection sphere for two sphere configuration. The light on the detector in the reflectance sphere is affected by interactions between the two spheres. This function calculates the net gain on a detector in the reflection sphere for diffuse light starting in the reflectance sphere. .. math:: G_{11} = \frac{(P_1/A_d)}{(P/A)} then the full expression for the gain is .. math:: \frac{G(r_s)}{(1-a_s a_s' r_w r_w' (1-a_e)(1-a_e') G(r_s) G'(r_s)t_s^2)} """ G = RS.gain(URU) GP = TS.gain(URU) areas = RS.a_sample * TS.a_sample * (1 - RS.a_entrance) * (1 - TS.a_entrance) G11 = G / (1 - areas * RS.r_wall * TS.r_wall * G * GP * tdiffuse**2) return G11
[docs] def Gain_22(RS, TS, URU, tdiffuse): r"""Two sphere gain in T sphere for light starting in T sphere. Similarly, when the light starts in the second sphere, the gain for light on the detector in the second sphere :math:`G_{22}` is found by switching all primed variables to unprimed. Thus :math:`G_{21}(r_s,t_s)` is .. math:: G_{22}(r_s,t_s) =\frac{G'(r_s)}{1-a_s a_s'r_w r_w'(1-a_e)(1-a_e')G(r_s)G'(r_s)t_s^2} """ G = RS.gain(URU) GP = TS.gain(URU) areas = RS.a_sample * TS.a_sample * (1 - RS.a_entrance) * (1 - TS.a_entrance) G22 = GP / (1 - areas * RS.r_wall * TS.r_wall * G * GP * tdiffuse**2) return G22
[docs] def Two_Sphere_R(RS, TS, UR1, URU, UT1, UTU, f=0): """Total gain in R sphere for two sphere configuration. The light on the detector in the reflection sphere arises from three sources: the fraction of light directly reflected off the sphere wall .. math:: f r_w^2 (1-a_e) P, the fraction of light reflected by the sample .. math:: (1-f) r_{direct} r_w^2 (1-a_e) P, and the light transmitted through the sample .. math:: (1-f) t_{direct} r_w' (1-a_e') P, If we use the gain for each part then we add .. math:: G_{11} a_d (1-a_e) r_w^2 f P to .. math:: G_{11} a_d (1-a_e) r_w (1-f) r_{direct} P and .. math:: G_{21} a_d (1-a_e') r_w' (1-f) t_{direct} P which simplifies slightly to the formula used below """ GP = TS.gain(URU) G11 = Gain_11(RS, TS, URU, UTU) x = RS.a_detector * (1 - RS.a_entrance) * RS.r_wall * G11 p1 = (1 - f) * UR1 p2 = RS.r_wall * f p3 = (1 - f) * TS.a_sample * (1 - TS.a_entrance) * TS.r_wall * UT1 * UTU * GP return x * (p1 + p2 + p3)
[docs] def Two_Sphere_T(RS, TS, UR1, URU, UT1, UTU, f=0): """Total gain in T sphere for two sphere configuration. For the power on the detector in the transmission (second) sphere we have the same three sources. The only difference is that the subscripts on the gain terms now indicate that the light ends up in the second sphere .. math:: G_{12} a_d' (1-a_e) r_w^2 f P plus .. math:: G_{12} a_d' (1-a_e) r_w (1-f) r_{direct} P plus .. math:: G_{22} a_d' (1-a_e') r_w' (1-f) t_{direct} P which simplifies slightly to the formula used below """ G = RS.gain(URU) G22 = Gain_11(RS, TS, URU, UTU) x = TS.a_detector * (1 - TS.a_entrance) * TS.r_wall * G22 x *= (1 - f) * UT1 + (1 - RS.a_entrance) * RS.r_wall * \ RS.a_sample * UTU * (f * RS.r_wall + (1 - f) * UR1) * G return x