"""The Star module defines the :class:`Star` class for representing stellar
compositions and converting them through the dex-to-oxide pipeline.
"""
from . import conversions as conv
from . import constants as const
__all__ = ['Star']
[docs]
class Star(object):
def __init__(self, stellar_dex: dict[str, float] | None = None,
wtpt_oxides: dict[str, float] | None = None,
name: str | None = None,
mass: float | None = None) -> None:
"""
Parameters
----------
stellar_dex : dict[str, float], optional
Stellar composition in dex notation, as elements.
wtpt_oxides : dict[str, float], optional
Stellar composition as wt% oxides. When provided instead of
stellar_dex, the pipeline runs in reverse to recover dex values.
Note that volatile elements (C, O, S) cannot be recovered from
oxide data, and the recovered dex values are shifted by a constant
offset relative to the true stellar dex (see CAVEATS.md). The
interelemental dex ratios are exact. Only one of stellar_dex or
wtpt_oxides may be provided.
name : str, optional
Arbitrary name for your star as a string. Can be anything. Either
aa, either bb, even zombocom. At zombocom you can do anything.
mass : float, optional
Star mass in some units that I don't know because this isn't
implemented yet. So, put whatever float you want here. It won't
make any difference.
"""
if stellar_dex is not None and wtpt_oxides is not None:
raise ValueError("Cannot pass both stellar_dex and wtpt_oxides. "
"Pick one compositional input and the other will "
"be computed for you. Like magic.")
self._name = name
self._mass = mass
# calculated attributes
# typically not needed by user, used for benchmarking and debugging
self._stellar_dex: dict[str, float] | None = None
self._ax: dict[str, float] | None = None
self._atoms_ref_solar: dict[str, float] | None = None
self._total_wt_atoms: dict[str, float] | None = None
self._wtpt_elements: dict[str, float] | None = None
self._wtpt_oxides: dict[str, float] | None = None
if stellar_dex is not None:
self._stellar_dex = const.filter_compositional_keys(
stellar_dex, 'stellar_dex')
if wtpt_oxides is not None:
clean_oxides = const.filter_compositional_keys(
wtpt_oxides, 'wtpt_oxides')
self._wtpt_oxides = clean_oxides
self._wtpt_elements = conv.calculate_wtpt_elements_from_wtpt_oxides(clean_oxides)
self._total_wt_atoms = conv.calculate_total_wt_atoms_from_wtpt_elements(self._wtpt_elements)
self._atoms_ref_solar = conv.calculate_atoms_ref_solar_from_total_wt_atoms(self._total_wt_atoms)
self._ax = conv.calculate_ax_from_atoms_ref_solar(self._atoms_ref_solar)
self._stellar_dex = conv.calculate_dex_from_ax(self._ax)
@property
def stellar_dex(self) -> dict[str, float] | None:
"""dict[str, float] or None : Stellar composition in dex notation.
Auto-computed in reverse from wtpt_oxides if not provided directly.
See CAVEATS.md for the constant-offset ambiguity in recovered dex
values.
"""
return self._stellar_dex
@property
def name(self) -> str | None:
"""str or None : Star name."""
return self._name
@property
def mass(self) -> float | None:
"""float or None : Star mass (not yet used in calculations)."""
return self._mass
@property
def ax(self) -> dict[str, float] | None:
"""dict[str, float] or None : Elemental ratio relative to solar (10^dex)."""
if self._ax is None and self._stellar_dex is not None:
self._ax = conv.calculate_ax_from_dex(self._stellar_dex)
return self._ax
@property
def atoms_ref_solar(self) -> dict[str, float] | None:
"""dict[str, float] or None : Number of atoms referenced to solar abundances."""
if self._atoms_ref_solar is None and self.ax is not None:
self._atoms_ref_solar = conv.calculate_atoms_ref_solar_from_ax(self.ax)
return self._atoms_ref_solar
@property
def total_wt_atoms(self) -> dict[str, float] | None:
"""dict[str, float] or None : Total weight of atoms (element wt scaled by atomic mass)."""
if self._total_wt_atoms is None and self.atoms_ref_solar is not None:
self._total_wt_atoms = conv.calculate_total_wt_atoms_from_atoms_ref_solar(self.atoms_ref_solar)
return self._total_wt_atoms
@property
def wtpt_elements(self) -> dict[str, float] | None:
"""dict[str, float] or None : Composition as wt% elements.
When computed forward from stellar_dex, includes volatile elements
(C, O, S). When computed in reverse from wtpt_oxides, only
rock-forming elements are present — volatiles are irrecoverable
from oxide data.
"""
if self._wtpt_elements is None and self.total_wt_atoms is not None:
self._wtpt_elements = conv.calculate_wtpt_elements_from_total_wt_atoms(self.total_wt_atoms)
return self._wtpt_elements
@property
def wtpt_oxides(self) -> dict[str, float] | None:
"""dict[str, float] or None : Composition as wt% oxides (volatile-free)."""
if self._wtpt_oxides is None and self.wtpt_elements is not None:
self._wtpt_oxides = conv.calculate_wtpt_oxides_from_wtpt_elements(self.wtpt_elements)
return self._wtpt_oxides
[docs]
def get_composition(self, units: str = 'wtpt_oxides') -> dict[str, float]:
"""
Return the star's composition in the requested units.
Output dicts always sum to the target for the requested units —
100 for percent units (``wtpt_*``, ``molpt_*``) and 1.0 for
fraction units (``wtfrac_*``, ``molfrac_*``). ``molfrac_singleO``
is the exception: it is never normalized.
Parameters
----------
units : str
One of 'wtpt_oxides', 'wtpt_elements', 'wtfrac_oxides',
'wtfrac_elements', 'molfrac_oxides', 'molfrac_elements',
'molfrac_singleO', 'molpt_oxides', 'molpt_elements'.
Returns
-------
dict[str, float]
Composition in the requested units.
Raises
------
ValueError
If ``units`` is invalid, or if no compositional data has been
provided to the Star (neither ``stellar_dex`` nor ``wtpt_oxides``),
or the requested element-basis output is unavailable for a Star
initialized from oxides.
Notes
-----
Star element outputs (wtpt_elements, wtfrac_elements) include volatile
elements (C, O, S) from the stellar pipeline when initialized from
stellar_dex. When initialized from wtpt_oxides, volatile elements are
not available — only rock-forming elements are returned.
"""
if units not in conv.VALID_UNITS:
raise ValueError(...)
wtpt_oxides = self.wtpt_oxides
if wtpt_oxides is None:
raise ValueError("Cannot compute composition: no stellar_dex or wtpt_oxides is set.")
return conv.convert_composition(wtpt_oxides, units)