Isotopic calculations ===================== APAV supports the computation of elemental and molecular isotope distributions. These calculations are important to most mass spectrum analysis and indexing operations. Elemental isotopes are typically quite easy to access as they are tabulated in most periodic table resources (i.e. websites, Cameca poster), however molecular isotopes are not so easily determined. Molecular isotopes are computed based on the elemental composition and isotopic distribution of the molecules constituents. The calculation is combinatorial in nature and can drastically increase in computation time the more complex the molecule is (combinatorial explosion). However, the molecules likely to be found in an atom probe experiment are `not` so complex for this is to be problematic, the computations will be more or less instant. The principle class is ``IsotopeSeries``, signifying that it produces a series of isotopes corresponding to the given composition. As a simple example, get the isotopes of singly charged elemental copper:: >>> import apav as ap >>> cu_isos = ap.IsotopeSeries("Cu", 1) >>> print(cu_isos) IsotopeSeries: Cu +1, threshold: 1.0% Ion Isotope Mass Abs. abundance % Rel. abundance % -- ----- --------- ------- ------------------ ------------------ 1 Cu 63 62.9296 69.17 100 2 Cu 65 64.9278 30.83 44.5713 Under the hood, APAV uses ``Ion`` objects to represent the ions or compositions. This code snippet does the same thing:: >>> cu_isos = ap.IsotopeSeries(ap.Ion("Cu", 1)): >>> print(cu_isos) IsotopeSeries: Cu +1, threshold: 1.0% Ion Isotope Mass Abs. abundance % Rel. abundance % -- ----- --------- ------- ------------------ ------------------ 1 Cu 63 62.9296 69.17 100 2 Cu 65 64.9278 30.83 44.5713 The list of the isotopes (each isotope is an ``Isotope``) can be access with:: >>> cu_isos.isotopes [Isotope: Cu +1 63 @ 62.93 Da 69.17 %, Isotope: Cu +1 65 @ 64.928 Da 30.83 %] The calculation for molecular isotopes is the same, i.e. for CuO2:: >>> import apav as ap >>> ap.IsotopeSeries("CuO2", 1) IsotopeSeries: CuO2 +1, threshold: 1.0% Ion Isotope Mass Abs. abundance % Rel. abundance % -- ----- --------- ------- ------------------ ------------------ 1 CuO2 95 94.9194 68.8342 100 2 CuO2 97 96.9176 30.6803 44.5713 In this way isotopic information of arbitrary compositions can be queried. Atom probe uses time of flight mass spectrometry which means that the mass spectrum is also sensitive to the `charge`. This means that the spectrum of a cuprate oxide material may exhibit both CuO2 1+ as well as CuO2 2+, resulting in two sets of CuO2 peaks that are shifted according to the charge (mass/charge ratio). The charge can be changed to produce the CuO2 2+ set of isotopes:: >>> import apav as ap >>> ap.IsotopeSeries("CuO2", 2) IsotopeSeries: CuO2 +2, threshold: 1.0% Ion Isotope Mass Abs. abundance % Rel. abundance % -- ----- --------- ------- ------------------ ------------------ 1 CuO2 95 47.4597 68.8342 100 2 CuO2 97 48.4588 30.6803 44.5713 Here the theoretical mass/charge ratios are half of the singly charged isotopes, given the doubly charged ion. Now one might notice that the total absolute abundance does not add up to 100%. This is because the isotopes displayed are thresholded by the absolute abundance. This is done to prevent large amounts of isotopes with more complex molecular structures. Take a SnO2 2+ for example, Sn has 10 natural isotopes and oxygen has 3, this eventually produces 60 possible isotopes. If we set the threshold value to 0 all of these isotopes will be generated:: >>> import apav as ap >>> SnO2_isos = ap.IsotopeSeries("SnO2", 2, threshold=0) >>> len(SnO2_isos) 60 Showing all of the isotopes:: >>> print(sno2_isos) IsotopeSeries: O2Sn +2, all isotopes Ion Isotope Mass Abs. abundance % Rel. abundance % -- ----- --------- ------- ------------------ ------------------ 1 O2Sn 144 71.9473 0.965292 2.97729 2 O2Sn 145 72.4494 0.000735409 0.00226825 3 O2Sn 146 72.9463 0.656796 2.02578 4 O2Sn 146 72.9494 0.00396734 0.0122366 5 O2Sn 146 72.9515 1.40068e-07 4.32017e-07 6 O2Sn 147 73.4466 0.33835 1.04359 7 O2Sn 147 73.4484 0.000500381 0.00154335 8 O2Sn 147 73.4516 1.51126e-06 4.66124e-06 9 O2Sn 148 73.9458 14.4694 44.6286 10 O2Sn 148 73.9484 0.00269942 0.00832594 11 O2Sn 148 73.9487 0.000257772 0.000795057 12 O2Sn 148 73.9505 9.5304e-08 2.9395e-07 13 O2Sn 148 73.9516 4.07642e-06 1.25731e-05 14 O2Sn 149 74.4464 7.64272 23.5727 15 O2Sn 149 74.4479 0.0110235 0.0340004 16 O2Sn 149 74.4487 0.00139061 0.00428912 17 O2Sn 149 74.4505 1.02828e-06 3.17156e-06 18 O2Sn 149 74.4508 4.9096e-08 1.51429e-07 19 O2Sn 150 74.9457 24.1024 74.3401 20 O2Sn 150 74.9479 0.0594691 0.183423 21 O2Sn 150 74.9485 0.00582262 0.0179589 22 O2Sn 150 74.95 2.09958e-06 6.4758e-06 23 O2Sn 150 74.9506 2.77365e-06 8.55488e-06 24 O2Sn 150 74.9508 5.2972e-07 1.63384e-06 25 O2Sn 151 75.4466 8.5483 26.3659 26 O2Sn 151 75.4478 0.0183625 0.0566361 27 O2Sn 151 75.4485 0.0314115 0.0968837 28 O2Sn 151 75.45 2.26533e-05 6.98705e-05 29 O2Sn 151 75.4506 1.10899e-06 3.42051e-06 30 O2Sn 151 75.4508 1.42885e-06 4.40706e-06 31 O2Sn 152 75.946 32.4219 100 32 O2Sn 152 75.9478 0.0990607 0.305537 33 O2Sn 152 75.9487 0.00651254 0.0200869 34 O2Sn 152 75.9499 3.49737e-06 1.07871e-05 35 O2Sn 152 75.95 6.11043e-05 0.000188467 36 O2Sn 152 75.9506 1.19654e-05 3.69055e-05 37 O2Sn 153 76.4481 0.0247006 0.0761851 38 O2Sn 153 76.4487 0.0351334 0.108363 39 O2Sn 153 76.4499 3.77348e-05 0.000116387 40 O2Sn 153 76.4506 3.22752e-05 9.95477e-05 41 O2Sn 153 76.4508 1.2404e-06 3.8258e-06 42 O2Sn 154 76.9466 4.60753 14.2112 43 O2Sn 154 76.9481 0.133253 0.410999 44 O2Sn 154 76.95 0.000101785 0.000313938 45 O2Sn 154 76.9502 4.70455e-06 1.45104e-05 46 O2Sn 154 76.9508 1.33832e-05 4.12784e-05 47 O2Sn 155 77.4487 0.00351025 0.0108268 48 O2Sn 155 77.4502 5.07596e-05 0.00015656 49 O2Sn 155 77.4508 3.60995e-05 0.000111343 50 O2Sn 156 77.9476 5.76189 17.7716 51 O2Sn 156 77.9488 0.0189369 0.0584077 52 O2Sn 156 77.9503 0.000136917 0.0004223 53 O2Sn 156 77.9509 6.68572e-07 2.0621e-06 54 O2Sn 157 78.4497 0.00438971 0.0135393 55 O2Sn 157 78.4509 7.21354e-06 2.2249e-05 56 O2Sn 158 78.9497 0.0236813 0.0730412 57 O2Sn 158 78.9509 1.94576e-05 6.00138e-05 58 O2Sn 158 78.9518 8.36076e-07 2.57874e-06 59 O2Sn 159 79.4518 9.02082e-06 2.78233e-05 60 O2Sn 160 79.9518 2.43325e-05 7.50496e-05 We can iterate through all of the isotopes (each isotope is a ``Isotope`` instance) and count the number of isotopes that are less than 1% absolute abundance:: >>> count = sum(1 for i in iso if i.abundance < 0.01) >>> print(count) 53 So 53 out of 60 isotopes are less than 1% theoretical abundance. Looking at the list above, many are considerably below that value. Thus, the default threshold for ``IsotopeSeries`` is 1%:: >>> ap.IsotopeSeries("SnO2", 2) IsotopeSeries: O2Sn +2, threshold: 1.0% Ion Isotope Mass Abs. abundance % Rel. abundance % -- ----- --------- ------- ------------------ ------------------ 1 O2Sn 148 73.9458 14.4694 44.6286 2 O2Sn 149 74.4464 7.64272 23.5727 3 O2Sn 150 74.9457 24.1024 74.3401 4 O2Sn 151 75.4466 8.5483 26.3659 5 O2Sn 152 75.946 32.4219 100 6 O2Sn 154 76.9466 4.60753 14.2112 7 O2Sn 156 77.9476 5.76189 17.7716 Gives all isotopes >1% abundance. This default value was chosen as it is unlikely to have enough counts in the limited statistical size of atom probe experiments to resolve smaller probabilities. The isotope distribution can be visualized using matplotlib:: >>> import apav as ap >>> import matplotlib.pyplot as plt >>> GdO2_isos = ap.IsotopeSeries("GdO2", 1) >>> plt.bar(GdO2_isos.masses, GdO2_isos.abundances*100, width=0.1) >>> plt.xlabel("Mass/Charge Ratio (Da)") >>> plt.ylabel("Absolute abundance (%)") >>> plt.title("GdO2 Isotopologues") >>> plt.show() .. image:: imgs/gdo2_isos.png :align: center :scale: 100