Source code for giant.photometry.magnitude



import numpy as np
from abc import ABCMeta, abstractmethod
from math import log10

from giant.ray_tracer.scene import SceneObject, Scene
from giant.ray_tracer.shapes import Ellipsoid, Point
from giant.photometry.utilities import au


[docs] class PhaseMagnitudeModel(metaclass=ABCMeta): ''' This is a parent class used to define the magnitude function of a body by the solar phase angle. A custom magnitude model should follow this template. The class must contain a magnitude_function definition that inputs a scene and scene object. ''' @abstractmethod def magnitude_function(self, scene: Scene, target: SceneObject, phase_angle: float | None = None) -> float: """ This method should return the apparent magnitude of the target at the provided phase angle or scene setup. :param scene: used to define the geometry of the scene, primarily phase angle :param target: used to get physical attributes of the target such as diameter :param phase_angle: optional input for phase angle in radians used to override real phase_angle in the scene. This is mainly used for plotting purposes. """ pass
[docs] class LinearPhaseMagnitudeModel(PhaseMagnitudeModel): ''' General Phase-Slope Model used to determine Magnitude of target ''' def __init__(self, phase_slope: float, albedo: float | None = None): """ :param phase_slope: phase slope of object brightness :param albedo: object light reflectance """ self.phase_slope = phase_slope self.albedo = albedo
[docs] def magnitude_function(self, scene: Scene, target: SceneObject, phase_angle: float | None = None) -> float: """ :param scene: used to define the geometry of the scene, primarily phase angle :param target: used to get physical attributes of the target such as diameter :param phase_angle: optional input for phase angle in radians used to override real phase_angle in the scene. This is mainly used for plotting purposes. """ if scene.light_obj is None: raise ValueError('The scene must contain an illumination source to compute the magnitude') # get the solar phase angle if phase_angle is None: try: target_index = scene.target_objs.index(target) except: if len(scene.target_objs) == 1: target_index = 0 else: raise ValueError('The phase angle was not provided and we cannot locate the provided target in the scene') phase_angle = scene.phase_angle(target_index) # determine the diameter of the target if isinstance(target.shape, Ellipsoid): diameter = target.shape.principal_axes.mean() elif isinstance(target.shape, Point): diameter = 0 else: diameter = 0 # this model needs the target albedo albedo = self.albedo if albedo is None: if (s_albedo := getattr(target, 'albedo', None)) is None: raise ValueError('Albedo is required to calculate magnitude using a Linear Phase Slope Model') albedo = s_albedo magnitude = -5 * np.log10(np.sqrt(albedo) * au(diameter)) + \ 5 * np.log10((au(target.distance) - au(scene.light_obj.distance)) * \ au(target.distance)) + self.phase_slope * np.rad2deg(phase_angle) - 26.75 return magnitude
[docs] class HGPhaseMagnitudeModel(PhaseMagnitudeModel): r""" Phase-Slope Model Typically used for Asteroids. This model takes the absolute magnitude and phase-slope brightness of the target into consideration. This takes the form of .. math:: m(\alpha) &= H-2.5\log\left[[1-G]\Phi_1(\alpha)+G\Phi_2(\alpha)\right] \\ W &= \exp(-90.56\tan^2(\alpha/2)) \\ \Phi_1 &=W\phi_{1S}+(1-@)\phi_{1L} \\ \phi_{1S} &= 1-\frac{C_1\sin\alpha}{0.119+1.341\sin\alha-0.754\sin^2\alpha} \\ \phi_{1L} &= \exp(-A_1\left(tan\frac{\alpha}{2}\right)^{B_1}) \\ \Phi_2 &= W\phi_{2S}+(1-W)\phi_{2L} \\ \phi_{2S} &=1-\frac{C_2\sin\alpha}{0.119+1.341\sin\alpha-0.754\sin^2\alpha} \phi_{2L} &= \exp(-A_2\left(tan\frac{\alpha}{2}\right)^{B_2}) where :math:`\alpha` is the phase angle in radian, :math:`H` and :math:`G` are the absolute visual magnitude and phase-slope of brightness respectively, and the cooeficiens take the following values: :math:`A_1` = 3.332 :math:`A_2` = 1.862 :math:`B_1` = 0.631 :math:`B_2` = 1.218 :math:`C_1` = 0.986 :math:`C_2` = 0.238 This model comes from https://adsabs.harvard.edu/full/2010SASS...29..101B For most targets, H and G can be retrieved from JPL's Horizons database. """ def __init__(self, abs_visual_mag: float, phase_slope: float): """ :param abs_visual_mag: Object absolute visual magnitude (H) :param phase_slope: Phase slope of brightness (G) """ self.abs_visual_mag = abs_visual_mag self.phase_slope = phase_slope
[docs] def magnitude_function(self, scene: Scene, target: SceneObject, phase_angle: float | None = None) -> float: """ :param scene: used to define the geometry of the scene, primarily phase angle :param target: used to get physical attributes of the target such as diameter :param phase_angle: optional input for phase angle in radians used to override real phase_angle in the scene. This is mainly used for plotting purposes. """ if scene.light_obj is None: raise ValueError('The scene must contain an illumination source to compute the magnitude') # get the solar phase angle if phase_angle is None: try: target_index = scene.target_objs.index(target) except: if len(scene.target_objs) == 1: target_index = 0 else: raise ValueError('The phase angle was not provided and we cannot locate the provided target in the scene') phase_angle = scene.phase_angle(target_index) # apply the model to get magnitude W = np.exp(-90.56 * (np.tan(phase_angle / 2)) ** 2) A1, B1, C1 = 3.332, 0.631, 0.986 # coefficient th_1s = 1 - ((C1 * np.sin(phase_angle)) / ( 0.119 + (1.341 * np.sin(phase_angle)) - (0.754 * np.sin(phase_angle) ** 2))) th_1l = np.exp(-A1 * (np.tan(phase_angle / 2)) ** B1) th1 = W * th_1s + (1 - W) * th_1l A2, B2, C2 = 1.862, 1.218, 0.238 # coefficients th_2s = 1 - ((C2 * np.sin(phase_angle)) / ( 0.119 + (1.341 * np.sin(phase_angle)) - (0.754 * np.sin(phase_angle) ** 2))) th_2l = np.exp(-A2 * (np.tan(phase_angle / 2)) ** B2) th2 = W * th_2s + (1 - W) * th_2l phaseH = self.abs_visual_mag - 2.5 * np.log10(((1 - self.phase_slope) * th1) + (self.phase_slope * th2)) return phaseH + 5 * np.log10(au(float(np.linalg.norm(target.position - scene.light_obj.position))) * au(target.distance))