Installation and Usage ====================== Dependencies ------------ This package is tested with python versions 3.7 - 3.10. It might work on older python version as well, however, compatibility is not guaranteed. The only dependency at the moment for running the ``iniabu`` package is ``numpy``. There is currently no pinned ``numpy`` version and the latest one should be working great. Installation ------------ To install the latest stable version of ``iniabu``, run this command in your terminal: .. code-block:: console $ pip install iniabu Alternatively you can install ``iniabu`` directly from GitHub. This will install the latest version. To do so, type in your terminal: .. code-block:: console $ pip install git+https://github.com/galactic-forensics/iniabu.git Available databases ------------------- Several databases are available to work with. The current default database is called "lodders09" and is based on `Lodders et al. (2009) `_. This might be updated in the future without considering it a breaking change. Old databases will always stay available. Further databases, listed by the string used to call them, are as following: - "asplund09": `Asplund et al. (2009) `_ - "lodders09" (current default): `Lodders et al. (2009) `_ - "nist": `NIST database `_ The solar abundances of all databases were converted to number abundances and are relative to Si = 10\ :sup:`6`. Conversion to other units, as described below, is possible. *Note*: Not all databases mentioned here contain the solar abundances for every isotope. If an operation you are trying to perform encounters a solar abundance value that is not availagle in the currently loaded database, the result will be returned as an ``np.nan``, i.e., as not a number. Usage ----- Here we give a short overview of the ``iniabu`` module. Please also have a look at the :doc:`API Reference `. There, each module is described in detail, often with examples on how to use the specific function. Furthermore, some examples in the form of Jupyter notebooks can be found on GitHub in `docs/jupyter_examples `_. Importing the module ~~~~~~~~~~~~~~~~~~~~ Once installed, you can simply import the package from your python session as: .. code-block:: python >>> from iniabu import ini This is the recommended import and will be used throughout the rest of this documentation, unless otherwise noted. Here, the :code:`ini` instance will be loaded with the default database (currently Lodders et al., 2009) and using linear, number abundances. Alternatively you can directly import the database using number, logarithmic abundances or mass fractions. The respective imports for these are: .. code-block:: python >>> from iniabu import inilog # number logarithmic abundances >>> from iniabu import inimf # mass fraction .. note:: While the current default database is Lodders et al. (2009) we will not consider loading a different default database a breaking change. If you want to always load Lodders et al. (2009), use the specific import below. In case multiple databases are required at the same time, e.g., :code:`db1` using Lodders et al. (2009) and :code:`db2` using Asplund et al. (2009) values and number logarithmic units, the following import could be used: .. code-block:: python >>> import iniabu >>> db1 = iniabu.IniAbu(database="lodders09") >>> db2 = iniabu.IniAbu(database="asplund09", unit="num_log") Loading a database ~~~~~~~~~~~~~~~~~~ Switching the data base from a given instance :code:`ini` can be easily accomplished. For example, the "asplund09" database can easily be loaded into a given instance by calling: .. code-block:: python >>> ini.database = "asplund09" .. note:: Switching a database does not reset the units. For example: If "lodders09" is loaded using mass fractions and you load "asplund09" as the new database, the units will stay the same that are used by default. A message will be printed to reflect this. .. code-block:: python >>> ini.database = 'asplund09' iniabu loaded database: 'asplund09', current units: 'mass_fraction' Available abundance units ~~~~~~~~~~~~~~~~~~~~~~~~~ Abundance units can easily be switched between linear number abundances, logarithmic number abundances, and mass fraction units. In the linear number abundances case all abundances are linear with respect to each other and are normalized such that the abundance of silicon is equal to 10\ :sup:`6` by number. The logarithmic number abundances are generally used in astronomy. For an element X, the logarithmic abundance is defined with respect to the abundance of hydrogen as: .. math:: \log_{10}(\epsilon_X) = \log_{10} \left(\frac{\mathrm{N}_\mathrm{X}}{\mathrm{N}_\mathrm{H}}\right) + 12 Mass fraction values are common in nucleosynthesis calculations. To return mass fraction values the database can be switched to `mass_fraction`. The abundances are then defined as following: .. math:: X_{i} = \frac{N_{i} m_{i}}{\rho N_{A}} \\ Here :math:`X_{i}` is the mass fraction of element :math:`i`, :math:`N_{i}` its number abundance, :math:`m_{i}` its molecular mass, and :math:`N_{N}` Avogadro's constant. The density :math:`\rho` is defined as: .. math:: \rho = \frac{1}{N_{A}} \sum_i N_{i} m_{i} To switch a given database between linear number abundance ("num_lin"), logarithmic number abundance ("num_log") mode, and mass fraction mode ("mass_fraction") the following property can be set: .. code-block:: python >>> ini.unit == "num_log" In this case, we would switch to logarithmic number abundance mode. To check what abundance unit is currently set, the following command can be used: .. code-block:: python >>> ini.unit "num_log" By default, linear number abundance values are used. .. note:: To use "num_log" or "mass_fraction" mode by default you can import the module in the following ways: .. code-block:: python from iniabu import inilog # "num_log" units from iniabu import inimf # "mass_fraction" units .. note:: If you use "mass_fraction" units, the relative abundances of the isotopes are also given in mass fractions! Element and isotope properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Properties of an element are independent from the loaded database and are taken from the `NIST database `_. To query the loaded database for relative or solar abundances, see the next two sections. Querying an element: ~~~~~~~~~~~~~~~~~~~~ To query an element's properties with respect to the solar abundance, it can be loaded into a temporary variable. For example: To query silicon the element and its properties can be loaded into a variable as following: .. code-block:: python >>> ele = ini.ele["Si"] Note that element names are not case sensitive. The following properties can now be queried from the element: - The name of an element, which just returns that same abbreviation used to call the element, can be queried with ``name``. - The mass of the element, calculated using the isotope masses and the currently loaded abundances, using ``mass``. - The solar abundance of the element itself using ``abu_solar``, normed as discussed above - The mass number of its (stable) isotopes using ``iso_a`` - The relative abundances of its (stable) isotopes using ``iso_abu_rel``. If you are using "mass_fractions" as units, the relative abundances will also be given as mass fractions! - The solar abundances of its (stable) isotopes using ``iso_abu_solar`` - The number of protons of an element can be queried with ``z``. For example, to query the solar abundance of iron one could run the following statement: .. code-block:: python >>> ele = ini.ele["Fe"] >>> ele.abu_solar 847990.0 .. note:: You can query multiple elements as once. To do so, simply pass a list of the elements to be queried. Querying an isotope ~~~~~~~~~~~~~~~~~~~ To query an isotope's properties with respect to the solar abundance, it can be loaded into a temporary variable, similar to when loading an element. For example: To query :sup:`54`\Fe, the isotope can be loaded as a variable as following: .. code-block:: python >>> iso = ini.iso["Fe-54"] You can also use alternative spellings for the isotope name, e.g., ``"54Fe"`` or ``"Fe54"``. Furthermore, none of these spellings are case sensitive. The following properties can then be queried from this isotope: - The number of nucleons / mass number of an isotope can be queried with ``a``. - The name of the isotope(s) requested can be queried with ``name``. These names will always be in the standard format, e.g., ``"Fe-54"``. - The mass of a specific isotope using ``mass``. - The solar abundance of the isotope itself using ``abu_solar``, normed as discussed above - The relative abundance of the specific isotope with respect to the element using ``abu_rel``. *Note*: All isotopes of an element would sum up to a relative abundance of 1. If you are using "mass_fractions" as units, the relative abundances will also be given as mass fractions! - The number of protons of an isotope can be queried with ``z``. .. note:: All isotope functions can be suffixed with `_all`. This will return information on all available isotopes, including unstable ones. Of course, solar system abundances for these are not available and will be returned as zeros, however, this might be useful to query masses. For example: To query the solar and the relative abundances of :sup:`54`\Fe one could run the following two commands in python: .. code-block:: python >>> iso = ini.iso["Fe-54"] >>> iso.abu_solar 49600.0 >>> iso.abu_rel 0.058449999999999995 .. note:: To query all isotopes of an element, you can query the isotope as following: .. code-block:: python >>> iso = ini.iso["Ne"] >>> iso.name ['Ne-20', 'Ne-21', 'Ne-22'] .. note:: You can query multiple isotopes at once. To do so, simply pass a list of the isotopes (or even elements in case of all isotopes) to be queried. Element and isotope ratios ~~~~~~~~~~~~~~~~~~~~~~~~~~ This function is used to calculate element and isotope ratios. Sure, the same can be accomplished by simply dividing the abundances of two isotopes. However, this function has some added benefits: - Select if ratio is number fraction (default) or mass fraction - Return multiple elements or isotopes at once Some additional benefits when calculating isotope ratios: - Choosing an element as the nominator selects all isotopes of the given element for the nominator - Choosing an element as the denominator calculates the ratio for every isotope in the nominator with respect to the most abundant isotope of the element given as the denominator. This might sound complicated, but can be very useful since isotope ratios are often given with the most abundant isotope in the denominator .. note:: If multiple isotope ratios are returned the function automatically returns them as a numpy array. This facilitates subsequent mathematical operations using these ratios. The functions to calculate these ratios are called ``ele_ratio`` and ``iso_ratio``. Below are some examples that describe some standard usage of these routines: .. caution:: In these examples we assume that the database is loaded with "num_lin" units. If you are using "mass_fraction" units, you will get "mass_fraction" units back, even if you do not set :code:`mass_fraction=True`. However, you could overwrite this behavior (the same way you can return `mass_fractions` even if you are in "num_lin" mode) by setting :code:`mass_fraction=False`. Some examples for elemental ratios: - Calculate He to Pb ratio using number fraction and mass fraction: Here we assume that number, linear units are loaded: .. code-block:: python >>> ini.ele_ratio("He", "Pb") # number fraction 759537205.0816697 >>> ini.ele_ratio("He", "Pb", mass_fraction=True) 39321659726.58637 - Calculate multiple element ratios with the same denominator. The specific example here ratios Fe and Ni to Si: .. code-block:: python >>> ini.ele_ratio(["Fe", "Ni"], "Si") array([0.84824447, 0.04910773]) - Calculate multiple element ratios that have individual nominators and denominators. Here Si to Fe and Ni to Zr is calculated: .. code-block:: python >>> ini.ele_ratio(["Si", "Ni"], ["Fe", "Zr"]) array([1.17890541e+00, 4.55450413e+03]) Some examples for isotope ratios: - Calculate the isotope ratios of :sup:`6`\Li to :sup:`7`\Li as number fractions and as mass fractions. Here we assume that number, linear units are loaded: .. code-block:: python >>> ini.iso_ratio("Li-6", "Li-7") # number fractions by default 0.08212225817272835 >>> ini.iso_ratio("Li-6", "Li-7", mass_fraction=True) 0.09578691181324486 - Calculate isotope fractions of :sup:`3`\He to :sup:`4`\He and :sup:`21`\Ne to :sup:`20`\Ne: .. code-block:: python >>> ini.iso_ratio(["He-3", "Ne-21"], ["He-4", "Ne-20"]) array([0.00016603, 0.00239717]) - Calculate the isotope ratios of all Si isotopes with respect to :sup:`28`\Si. Three methods, all identical, are specified as following: - Method 1: The manual way specifying each isotope individually - Method 2: Select element in nominator chooses all isotopes of specified element - Method 3: The fastest way for this specific case is to choose `'Si'` as the element in the nominator and to choose `'Si'` in the denominator. The latter will pick the most abundant isotope of silicon, which is :sup:`28`\Si. .. code-block:: python >>> ini.iso_ratio(["Si-28", "Si-29", "Si-30"], "Si-28") # Method 1 array([1. , 0.05077524, 0.03347067]) >>> ini.iso_ratio("Si", "Si-28") # Method 2 array([1. , 0.05077524, 0.03347067]) >>> ini.iso_ratio("Si", "Si") # Method 3 array([1. , 0.05077524, 0.03347067]) δ-values ~~~~~~~~ .. note:: A detailed discussion of δ-values can be found in the :doc:`Background Information ` The δ-value of a given isotope ratio, generally used in cosmo- and geochemistry, is defined as: .. math:: \delta \left( \frac{^{i}X}{^{j}X} \right) = \left(\frac{\left(\frac{^{i}X}{^{j}X}\right)_{\mathrm{measured}}} {\left(\frac{^{i}X}{^{j}X}\right)_{\mathrm{solar}}} - 1\right) \times f Here the isotopes chosen for the ratio are :math:`^{i}X` and :math:`^{j}X`. The measured isotope ratio, which is in the nominator, is a value that must be provided to the function. The solar isotope ratio (denominator) will be taken from the solar abundance table using the isotope ratios provided to the routine. The factor :math:`f` is by default set to 1000. This means that δ-values are by default returned as parts-per-thousand (‰). Choosing a different factor can be done by setting the keyword argument ``delta_factor`` accordingly. Furthermore, the keyword argument ``mass_fraction`` can also be used as for ratios. Setting this keyword to ``True`` or ``False`` allows the user to overwrite the behavior of the loaded units. While δ-values are commonly calculated for isotopes of one individual element, the routine allows to calculate δ-values between isotopes of different elements. To calculate a δ-values of two elements, the ``ele_delta`` function should be used. The equation given above represents a specific, but most commonly used case. Finally: The ``iso_delta`` and ``ele_delta`` functions have the same features for specifying the nominator and denominator as the ``iso_ratio`` and ``ele_ratio`` functions mentioned above. .. caution:: The values must be given in the same shape as the number of ratios provided. Otherwise the routine will return a ``ValueError`` specifying that there was a length mismatch. Some examples for calculating δ-values for isotopes: - Calculate one δ-value with a given measurement value. Here for :sup:`29`\Si/:sup:`28`\Si. First calculated in parts per thousand (default), then as percent. .. code-block:: python >>> ini.iso_delta("Si-30", "Si-28", 0.04) # parts per thousand (default) 195.0761256883704 >>> ini.iso_delta("Si-30", "Si-28", 0.04, delta_factor=100) # percent 19.50761256883704 - Calculate multiple δ-values as mass fractions. Here we calculate all Si isotopes with respect to :sup:`28`\Si. Measurements are defined first. Three versions are provided that yield the same result. See description on calculating isotope ratios above for more detail. .. code-block:: python >>> msr = [1., 0.01, 0.04] # measurement >>> ini.iso_delta(["Si-28", "Si-29", "Si-30"], "Si-28", msr) array([ 0. , -803.05359812, 195.07612569]) >>> ini.iso_delta("Si", "Si-28", msr) array([ 0. , -803.05359812, 195.07612569]) >>> ini.iso_delta("Si", "Si", msr) array([ 0. , -803.05359812, 195.07612569]) - Calculate the δ-value for :sup:`84`\Sr with respect to the major Sr isotope (:sup:`86`\Sr). The measurement value is provided as a mass fraction (assumption), but the database is loaded using number, linear units: .. code-block:: python >>> ini.iso_delta("Sr-84", "Sr", 0.01, mass_fraction=True) 414.3962670607242 Some examples for calculating δ-values for elements: - Calculate a δ-value for multiple elements, here Si and Ne with respect to Fe: .. code-block:: python >>> ini.ele_delta(["Si", "Ne"], "Fe", [2, 4]) array([696.48894668, 30.26124356]) Bracket-notation ~~~~~~~~~~~~~~~~ The bracket notation, generally used in astronomy, for a given elemental ratio is defined as: .. math:: [\mathrm{X}/\mathrm{Y}] = \log_{10} \left( \frac{N_\mathrm{X}}{N_\mathrm{Y}} \right)_\mathrm{star} - \log_{10} \left( \frac{N_\mathrm{X}}{N_\mathrm{Y}} \right)_\mathrm{solar} Here, star stands for an arbitrary measurement, e.g., of a given star. X and Y are the elements of interest in this case, :math:`N_\mathrm{X}` and :math:`N_\mathrm{Y}` represent the respective number abundances of elements X and Y. Calculations with mass fractions are also allowed by the routine. While bracket notation is commonly used with elements, there is no mathematical reason to prohibit using it for isotopes. Therefore, two routines are provided, namely ``ele_bracket`` and ``iso_bracket``. Finally: The ``ele_bracket`` and ``iso_bracket`` functions have the same features for specifying the nominator and denominator as the ``iso_ratio`` and ``ele_ratio`` functions mentioned above. Some examples for calculating bracket notation values for elements: - Calculate bracket notation value for Fe / H for a given measurement. First we calculate it as a number fraction (default setting) then as a mass fraction while having the database loaded in number linear mode. .. code-block:: python >>> ini.ele_bracket("Fe", "H", 0.005) # number fraction 2.183887471873783 >>> ini.ele_bracket("Fe", "H", 0.005, mass_fraction=True) # mass fraction 3.9274378849968263 - Calculate bracket notation value for multiple measurements. Here, for O and Fe with respect to Fe. .. code-block:: python >>> ini.ele_bracket(["O", "Fe"], "H", [0.02, 0.005]) array([1.51740521, 2.18388747]) Some examples for calculating bracket notation values for isotopes: - Calculate a bracket notation values for multiple isotopes. Here for all Si isotopes with respect to :sup:`28`\Si. *Note*: See ``ratio_isotopes`` for a detailed description of the possibilities. .. code-block:: python >>> msr = [1., 0.01, 0.04] >>> ini.iso_bracket(["Si-28", "Si-29", "Si-30"], "Si-28", msr) array([ 0. , -0.70565195, 0.07739557]) >>> ini.iso_bracket("Si", "Si-28", msr) array([ 0. , -0.70565195, 0.07739557]) >>> ini.iso_bracket("Si", "Si", msr) array([ 0. , -0.70565195, 0.07739557]) Internal normalization ~~~~~~~~~~~~~~~~~~~~~~ Internal normalization normalizes isotope ratios to two isotopes in order to remove any effects due to mass-dependent fractionation. A detailed explanation and further references can be found in the section :doc:`Background Information `. .. note:: Internal normalization is only available for isotopes at this point. Elemental measurements generally suffer from effects other than mass-dependent fractionation. The math could of course be applied to elements as well, however, can currently not be done with ``iniabu``. Several inputs are required for internal normalization. These are: - The nominator isotope(s) - The major and minor normalization isotopes - The nominator isotope abundance(s) in the sample - The normalization isotope abundances The normalization isotopes and respective abundances must be given as a tuple or list with the main normalization isotope first. The minor normalization isotope (second) is the one used to correct mass-dependent fractionation. You can also select the ``delta_factor``. This is the multiplier by which the internally normalized value is multiplied at the end. By default, this factor is set to 10,000 and thus gives deviations in parts per 10,000. In geo- and cosmochemistry these deviations are often referred to as ε-values. By default, an internally normalized value is calculated using the exponential law ``law="exp"``. However, you can also choose to use the linear law by setting ``law="lin"``. Some examples: - Normalize :sup:`60`\ Ni internally with respect to :sup:`58`\ Ni and :sup:`62`\ Ni. Use some made-up values for the data. .. code-block:: python >>> ni58_counts = 1000000 >>> ni60_counts = 250000 >>> ni62_counts = 10000 >>> norm_counts = (ni58_counts, ni62_counts) >>> ini.iso_int_norm("Ni-60", ("Ni-58", "Ni-62"), ni60_counts, norm_counts) 5145.864708640091 - Now this value is large to express in parts per 10,000. Let's switch the units to permil. .. code-block:: python >>> ini.iso_int_norm("Ni-60", ("Ni-58", "Ni-62"), ni60_counts, norm_counts, delta_factor=1000) 514.5864708640091 - If all nickel isotopes have been measured, the internally normalized values can be calculated for all isotopes at once: .. code-block:: python >>> msrs = (1000000, 250000, 2600, 10000, 2000) >>> norm_msrs = (msrs[0], msrs[3]) # Ni-58 and Ni-62 >>> ini.iso_int_norm("Ni", ("Ni-58", "Ni-62"), msrs, norm_msrs, delta_factor=1000) array([ 0.00000000e+00, 5.14586471e+02, -4.49223918e+02, 2.22044605e-13, 7.41295081e+02]) As expected, the internally normalized values for :sup:`58`\ Ni and :sup:`62`\ Ni are zero within numerical precisions.