from typing import Any, Literal
from astropy import units as u # type: ignore[import-untyped]
from ..enums import EnergyUnit, FrequencyUnit, WavelengthUnit
from .base import BaseSchema
from .exceptions import BandwidthValueError, MinMaxValueError
[docs]
class BaseBandpass(BaseSchema):
"""
A base class for defining bandpass filters with a specified range.
Attributes
----------
filter_name : str or None
The name of the filter, if provided.
min : float or None
The minimum value of the bandpass range.
max : float or None
The maximum value of the bandpass range.
"""
[docs]
filter_name: str | None = None
[docs]
min: float | None = None
[docs]
max: float | None = None
[docs]
class WavelengthBandpass(BaseBandpass):
"""
A class representing a bandpass filter defined in terms of wavelength.
Inherits from `BaseBandpass`, this class specializes the filter to operate in the
wavelength domain and provides additional functionality for wavelength-based filters.
Attributes
----------
type : Literal['WAVELENGTH']
A constant string indicating the type of the bandpass filter.
central_wavelength : float or None
The central wavelength of the filter. It is defined as the midpoint between the min and max.
bandwidth : float or None
The bandwidth of the filter. It is defined as half the difference between the max and min.
unit : WavelengthUnit
The unit of measurement for the wavelength.
"""
[docs]
type: Literal["WAVELENGTH"] = "WAVELENGTH"
[docs]
central_wavelength: float | None = None
[docs]
peak_wavelength: float | None = None
[docs]
bandwidth: float | None = None
[docs]
def model_post_init(self, __context: Any) -> None:
"""
Validates the min and max values of the wavelength bandpass and calculates the central wavelength
and bandwidth if they are not provided. Also ensures the values are positive and that the max
wavelength is greater than the min wavelength. Lastly, it converts the units to angstroms.
Raises
------
ValueError
If the min/max values are invalid or if the calculated values for central wavelength
or bandwidth are non-positive.
"""
if (self.min and not self.max) or (self.max and not self.min):
raise MinMaxValueError("Both min and max must be defined.")
if self.min and self.max:
if self.max < self.min:
raise MinMaxValueError("Max wavelength cannot be less than min wavelength.")
if not all([self.min > 0, self.max > 0]):
raise MinMaxValueError("Wavelength values must be positive.")
self.bandwidth = 0.5 * (self.max - self.min)
self.central_wavelength = self.min + self.bandwidth
if not (self.central_wavelength and self.bandwidth):
raise BandwidthValueError("Both central wavelength and bandwidth must be defined.")
if not all([self.central_wavelength > 0, self.bandwidth > 0]):
raise BandwidthValueError("Central wavelength and bandwidth must be positive.")
if self.bandwidth >= self.central_wavelength:
raise BandwidthValueError("Bandwidth must be less than the central wavelength.")
self.bandwidth = float(
(self.bandwidth * u.Unit(self.unit.value)).to(u.Unit(WavelengthUnit.ANGSTROM.value)).value
)
self.central_wavelength = float(
(self.central_wavelength * u.Unit(self.unit.value))
.to(u.Unit(WavelengthUnit.ANGSTROM.value))
.value
)
self.min = self.central_wavelength - self.bandwidth
self.max = self.central_wavelength + self.bandwidth
self.unit = WavelengthUnit.ANGSTROM
[docs]
class EnergyBandpass(BaseBandpass):
"""
A class representing a bandpass filter defined in terms of energy.
Inherits from `BaseBandpass`, this class specializes the filter to operate in the energy domain.
Attributes
----------
type : Literal['ENERGY']
A constant string indicating the type of the bandpass filter.
unit : EnergyUnit
The unit of measurement for the energy.
"""
[docs]
type: Literal["ENERGY"] = "ENERGY"
[docs]
def model_post_init(self, __context: Any) -> None:
"""
Validates that the min and max energy values are positive.
Raises
------
ValueError
If the min or max energy values are not defined, are non-positive, and max is greater
than min.
"""
if not (self.min and self.max):
raise MinMaxValueError("Both min and max energy values must be defined.")
if not all([self.min > 0, self.max > 0]):
raise MinMaxValueError("Energy values must be positive.")
if self.max < self.min:
raise MinMaxValueError("Max wavelength cannot be less than min wavelength.")
[docs]
class FrequencyBandpass(BaseBandpass):
"""
A class representing a bandpass filter defined in terms of frequency.
Inherits from `BaseBandpass`, this class specializes the filter to operate in the frequency domain.
Attributes
----------
type : Literal['FREQUENCY']
A constant string indicating the type of the bandpass filter.
unit : FrequencyUnit
The unit of measurement for the frequency.
"""
[docs]
type: Literal["FREQUENCY"] = "FREQUENCY"
[docs]
def model_post_init(self, __context: Any) -> None:
"""
Validates that the min and max frequency values are positive.
Raises
------
ValueError
If the min or max energy values are not defined, are non-positive, and max is greater
than min.
"""
if not (self.min and self.max):
raise MinMaxValueError("Both min and max frequency values must be defined.")
if not all([self.min > 0, self.max > 0]):
raise MinMaxValueError("Frequency values must be positive.")
if self.max < self.min:
raise MinMaxValueError("Max wavelength cannot be less than min wavelength.")
[docs]
def convert_to_wave(bandpass: EnergyBandpass | FrequencyBandpass) -> WavelengthBandpass:
"""
Converts a given EnergyBandpass or FrequencyBandpass to a WavelengthBandPass.
Parameters
----------
bandpass : EnergyBandpass or FrequencyBandpass
The bandpass filter in energy or frequency domain.
Returns
-------
WavelengthBandpass
The corresponding bandpass filter in the wavelength domain.
Notes
-----
When converting from Energy/Frequency to wavelength, the min/max values are inverted
in relation to their corresponding wavelengths. Thus, the min/max values are switched during
conversion.
"""
bandpass_min_angstrom = (
(bandpass.max * u.Unit(bandpass.unit.value))
.to(u.Unit(WavelengthUnit.ANGSTROM.value), equivalencies=u.spectral())
.value
)
bandpass_max_angstrom = (
(bandpass.min * u.Unit(bandpass.unit.value))
.to(u.Unit(WavelengthUnit.ANGSTROM.value), equivalencies=u.spectral())
.value
)
return WavelengthBandpass(
min=bandpass_min_angstrom,
max=bandpass_max_angstrom,
unit=WavelengthUnit.ANGSTROM,
filter_name=bandpass.filter_name,
)