pyrolite 0.0.14__zip

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__init__.py +10 -0
  2. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/__init__.cpython-36.pyc +0 -0
  3. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/_version.cpython-36.pyc +0 -0
  4. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/alteration.cpython-36.pyc +0 -0
  5. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/classification.cpython-36.pyc +0 -0
  6. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/compositions.cpython-36.pyc +0 -0
  7. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/geochem.cpython-36.pyc +0 -0
  8. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/melts.cpython-36.pyc +0 -0
  9. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/norm.cpython-36.pyc +0 -0
  10. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/normalisation.cpython-36.pyc +0 -0
  11. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/__pycache__/plot.cpython-36.pyc +0 -0
  12. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/_version.py +21 -0
  13. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/alteration.py +66 -0
  14. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/classification.py +222 -0
  15. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__init__.py +9 -0
  16. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__pycache__/__init__.cpython-36.pyc +0 -0
  17. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__pycache__/aggregate.cpython-36.pyc +0 -0
  18. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__pycache__/codata.cpython-36.pyc +0 -0
  19. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__pycache__/impute.cpython-36.pyc +0 -0
  20. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/__pycache__/renorm.cpython-36.pyc +0 -0
  21. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/aggregate.py +391 -0
  22. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/codata.py +266 -0
  23. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/impute.py +82 -0
  24. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/comp/renorm.py +40 -0
  25. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/compositions.py +524 -0
  26. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/GEOROC_CFB_Dataset_List.csv +42 -0
  27. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/GEOROC_Convergent_Dataset_List.csv +42 -0
  28. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/GEOROC_OBFB_Dataset_List.csv +5 -0
  29. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/GEOROC_OIB_Dataset_List.csv +49 -0
  30. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/GEOROC_OceanicPlateau_Dataset_List.csv +18 -0
  31. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/georoc/contents.json +1 -0
  32. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/melts/__pycache__/env.cpython-35.pyc +0 -0
  33. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/melts/__pycache__/env.cpython-36.pyc +0 -0
  34. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/melts/env.py +1063 -0
  35. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.Ba.modelfield +0 -0
  36. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.Bs.modelfield +0 -0
  37. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.F.modelfield +0 -0
  38. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.O1.modelfield +0 -0
  39. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.O2.modelfield +0 -0
  40. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.O3.modelfield +0 -0
  41. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.Pc.modelfield +0 -0
  42. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.Ph.modelfield +0 -0
  43. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.R.modelfield +0 -0
  44. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.S1.modelfield +0 -0
  45. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.S2.modelfield +0 -0
  46. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.S3.modelfield +0 -0
  47. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.T1.modelfield +0 -0
  48. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.T2.modelfield +0 -0
  49. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.U1.modelfield +0 -0
  50. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.U2.modelfield +0 -0
  51. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.U3.modelfield +0 -0
  52. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.modelfields +0 -0
  53. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.nan.modelfield +0 -0
  54. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS/TAS.none.modelfield +0 -0
  55. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/TAS.clsf.gz +0 -0
  56. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/aphanitic.clsf.gz +0 -0
  57. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/gabbroic.clsf.gz +0 -0
  58. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/peralkalinity.clsf.gz +0 -0
  59. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/phaneritic.clsf.gz +0 -0
  60. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/models/ultramafic.clsf.gz +0 -0
  61. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/CH_PalmeONeill2014.csv +95 -0
  62. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/DDMM_WorkmanHart2005.csv +105 -0
  63. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/DMM_WorkmanHart2005.csv +105 -0
  64. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/DM_SaltersStrake2004.csv +95 -0
  65. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/EDMM_WorkmanHart2005.csv +105 -0
  66. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/refcomp/PM_PalmeONeill2014.csv +95 -0
  67. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/data/timescale/geotimescale_spans.csv +180 -0
  68. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/geochem.py +821 -0
  69. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/melts.py +92 -0
  70. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__init__.py +10 -0
  71. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__pycache__/__init__.cpython-36.pyc +0 -0
  72. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__pycache__/db.cpython-36.pyc +0 -0
  73. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__pycache__/ions.cpython-36.pyc +0 -0
  74. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__pycache__/mineral.cpython-36.pyc +0 -0
  75. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/__pycache__/sites.cpython-36.pyc +0 -0
  76. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/db.py +88 -0
  77. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/ions.py +78 -0
  78. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/mineral.py +587 -0
  79. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/mineral/sites.py +134 -0
  80. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/norm.py +224 -0
  81. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/normalisation.py +204 -0
  82. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/plot.py +514 -0
  83. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__init__.py +13 -0
  84. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/__init__.cpython-36.pyc +0 -0
  85. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/database.cpython-36.pyc +0 -0
  86. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/env.cpython-36.pyc +0 -0
  87. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/general.cpython-36.pyc +0 -0
  88. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/georoc.cpython-36.pyc +0 -0
  89. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/math.cpython-36.pyc +0 -0
  90. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/melts.cpython-36.pyc +0 -0
  91. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/multip.cpython-36.pyc +0 -0
  92. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/multiprocessing.cpython-36.pyc +0 -0
  93. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/pd.cpython-36.pyc +0 -0
  94. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/plot.cpython-36.pyc +0 -0
  95. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/skl.cpython-36.pyc +0 -0
  96. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/spatial.cpython-36.pyc +0 -0
  97. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/text.cpython-36.pyc +0 -0
  98. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/time.cpython-36.pyc +0 -0
  99. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/__pycache__/wfs.cpython-36.pyc +0 -0
  100. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/database.py +88 -0
  101. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/env.py +81 -0
  102. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/general.py +266 -0
  103. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/georoc.py +444 -0
  104. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/math.py +371 -0
  105. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/melts.py +397 -0
  106. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/multip.py +29 -0
  107. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/multiprocessing.py +29 -0
  108. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/pd.py +214 -0
  109. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/plot.py +345 -0
  110. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/skl.py +847 -0
  111. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/spatial.py +91 -0
  112. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/text.py +207 -0
  113. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/time.py +224 -0
  114. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite/util/wfs.py +10 -0
  115. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite-0.0.14-py3.6.egg-info/PKG-INFO +61 -0
  116. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite-0.0.14-py3.6.egg-info/SOURCES.txt +83 -0
  117. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite-0.0.14-py3.6.egg-info/dependency_links.txt +1 -0
  118. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite-0.0.14-py3.6.egg-info/requires.txt +47 -0
  119. ProgramData/Anaconda3_64/Lib/site-packages/pyrolite-0.0.14-py3.6.egg-info/top_level.txt +1 -0
@@ -0,0 +1,821 @@
1
+ import pandas as pd
2
+ import pandas_flavor as pf
3
+ import numpy as np
4
+ import mpmath
5
+ import periodictable as pt
6
+ import matplotlib.pyplot as plt
7
+ import functools
8
+ import re
9
+ from .comp.codata import renormalise
10
+ from .norm import ReferenceCompositions, RefComp, scale_multiplier
11
+ from .util.text import titlecase
12
+ from .util.pd import to_frame
13
+ from .util.math import *
14
+ from .util.general import iscollection
15
+ import logging
16
+
17
+ logging.getLogger(__name__).addHandler(logging.NullHandler())
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ def get_radii(el):
22
+ """Convenience function for ionic radii."""
23
+ if isinstance(el, list):
24
+ return [get_radii(e) for e in el]
25
+ elif not isinstance(el, str):
26
+ el = str(el)
27
+ return _RADII[el]
28
+
29
+
30
+ def ischem(s):
31
+ """
32
+ Checks if a string corresponds to chemical component (compositional).
33
+ Here simply checking whether it is a common element or oxide.
34
+
35
+ TODO: Implement checking for other compounds, e.g. carbonates.
36
+ """
37
+ chems = set(map(str.upper, (__common_elements__ | __common_oxides__)))
38
+ if isinstance(s, list):
39
+ return [str(st).upper() in chems for st in s]
40
+ else:
41
+ return str(s).upper() in chems
42
+
43
+
44
+ def is_isotoperatio(text):
45
+ """Check if text is plausibly an isotope ratio."""
46
+ if text not in __common_oxides__:
47
+ isotopes = get_isotopes(text)
48
+ return len(isotopes) == 2
49
+ else:
50
+ return False
51
+
52
+
53
+ def get_isotopes(ratio_text):
54
+ """Regex for isotope ratios."""
55
+ forward_isotope = r"([a-zA-Z][a-zA-Z]?[0-9][0-9]?[0-9]?)"
56
+ backward_isotope = r"([0-9][0-9]?[0-9]?[a-zA-Z][a-zA-Z]?)"
57
+ fw = re.findall(forward_isotope, ratio_text)
58
+ bw = re.findall(backward_isotope, ratio_text)
59
+ lfw, lbw = len(fw), len(bw)
60
+ if (lfw > 1 and lbw > 1) or ((lfw < 2) and (lbw < 2)):
61
+ return []
62
+ elif lfw == 2:
63
+ return fw
64
+ elif lbw == 2:
65
+ return bw
66
+
67
+
68
+ def repr_isotope_ratio(isotope_ratio):
69
+ """
70
+ Format an isotope ratio pair as a string.
71
+
72
+ Parameters
73
+ -----------
74
+ isotope_ratio : tuple
75
+ Numerator, denominator pair.
76
+ """
77
+ if not is_isotoperatio(isotope_ratio):
78
+ return isotope_ratio
79
+ else:
80
+ if isinstance(isotope_ratio, str):
81
+ isotope_ratio = get_isotopes(isotope_ratio)
82
+ num, den = isotope_ratio
83
+ isomatch = r"([0-9][0-9]?[0-9]?)"
84
+ elmatch = r"([a-zA-Z][a-zA-Z]?)"
85
+ num_iso, num_el = re.findall(isomatch, num)[0], re.findall(elmatch, num)[0]
86
+ den_iso, den_el = re.findall(isomatch, den)[0], re.findall(elmatch, den)[0]
87
+ return "{}{}{}{}".format(num_iso, titlecase(num_el), den_iso, titlecase(den_el))
88
+
89
+
90
+ def tochem(strings: list, abbrv=["ID", "IGSN"], split_on="[\s_]+"):
91
+ """
92
+ Converts a list of strings containing come chemical compounds to
93
+ appropriate case.
94
+ """
95
+ # accomodate single string passed
96
+ if not type(strings) in [list, pd.core.indexes.base.Index]:
97
+ strings = [strings]
98
+
99
+ # translate elements and oxides
100
+ chems = __common_elements__ | __common_oxides__
101
+ trans = {str(e).upper(): str(e) for e in chems}
102
+ strings = [trans[str(h).upper()] if str(h).upper() in trans else h for h in strings]
103
+
104
+ # translate potential isotope ratios
105
+ strings = [repr_isotope_ratio(h) for h in strings]
106
+ return strings
107
+
108
+
109
+ @pf.register_series_method
110
+ @pf.register_dataframe_method
111
+ def to_molecular(df: pd.DataFrame, renorm=True):
112
+ """
113
+ Converts mass quantities to molar quantities of the same order.
114
+ E.g.:
115
+ mass% --> mol%
116
+ mass-ppm --> mol-ppm
117
+ """
118
+ df = to_frame(df)
119
+ MWs = [pt.formula(c).mass for c in df.columns]
120
+ if renorm:
121
+ return renormalise(df.div(MWs))
122
+ else:
123
+ return df.div(MWs)
124
+
125
+
126
+ @pf.register_series_method
127
+ @pf.register_dataframe_method
128
+ def to_weight(df: pd.DataFrame, renorm=True):
129
+ """
130
+ Converts molar quantities to mass quantities of the same order.
131
+ E.g.:
132
+ mol% --> mass%
133
+ mol-ppm --> mass-ppm
134
+ """
135
+ df = to_frame(df)
136
+ MWs = [pt.formula(c).mass for c in df.columns]
137
+ if renorm:
138
+ return renormalise(df.multiply(MWs))
139
+ else:
140
+ return df.multiply(MWs)
141
+
142
+
143
+ def get_cations(oxide: str, exclude=[]):
144
+ """
145
+ Returns the principal cations in an oxide component.
146
+
147
+ Todo: Consider implementing periodictable style return.
148
+ """
149
+ if "O" not in exclude:
150
+ exclude += ["O"]
151
+ atms = pt.formula(oxide).atoms
152
+ cations = [el for el in atms.keys() if not el.__str__() in exclude]
153
+ return cations
154
+
155
+
156
+ def common_elements(cutoff=92, output="string", order=None, as_set=False):
157
+ """
158
+ Provides a list of elements up to a particular cutoff (default: including U)
159
+ Output options are 'formula', or 'string'.
160
+
161
+ Todo: implement ordering for e.g. incompatibility.
162
+ """
163
+ elements = [el for el in pt.elements if not (str(el) == "n" or el.number > cutoff)]
164
+
165
+ if as_set:
166
+ return set(map(str, elements))
167
+ else:
168
+ if not output == "formula":
169
+ elements = list(map(str, elements))
170
+
171
+ if order is not None:
172
+ sort_function = order
173
+ elements = list(elements).sort(key=sort_function)
174
+
175
+ return elements
176
+
177
+
178
+ def REE(output="string", include_extras=False):
179
+ """
180
+ Provides the list of Rare Earth Elements
181
+ Output options are 'formula', or strings.
182
+
183
+ Todo: add include extras such as Y.
184
+ """
185
+ elements = [
186
+ "La",
187
+ "Ce",
188
+ "Pr",
189
+ "Nd",
190
+ "Pm",
191
+ "Sm",
192
+ "Eu",
193
+ "Gd",
194
+ "Tb",
195
+ "Dy",
196
+ "Ho",
197
+ "Er",
198
+ "Tm",
199
+ "Yb",
200
+ "Lu",
201
+ ]
202
+ if output == "formula":
203
+ elements = [getattr(pt, el) for el in elements]
204
+ return elements
205
+
206
+
207
+ def common_oxides(
208
+ elements: list = [],
209
+ output="string",
210
+ addition: list = ["FeOT", "Fe2O3T", "LOI"],
211
+ exclude=["O", "He", "Ne", "Ar", "Kr", "Xe"],
212
+ as_set=False,
213
+ ):
214
+ """
215
+ Creates a list of oxides based on a list of elements.
216
+ Output options are 'formula', or strings.
217
+
218
+ Note: currently return FeOT and LOI even for element lists
219
+ not including iron or water - potential upgrade!
220
+
221
+ Todo: element verification
222
+ """
223
+ if not elements:
224
+ elements = [
225
+ el for el in common_elements(output="formula") if not str(el) in exclude
226
+ ]
227
+ else:
228
+ # Check that all elements input are indeed elements..
229
+ pass
230
+
231
+ oxides = [ox for el in elements for ox in simple_oxides(el, output=output)]
232
+
233
+ if as_set:
234
+ return set(map(str, oxides + addition))
235
+ else:
236
+ if output != "formula":
237
+ oxides = list(map(str, oxides + addition))
238
+ return oxides
239
+
240
+
241
+ def simple_oxides(cation, output="string"):
242
+ """
243
+ Creates a list of oxides for a cationic element
244
+ (oxide of ions with c=1+ and above).
245
+ """
246
+ try:
247
+ if not isinstance(cation, pt.core.Element):
248
+ catstr = titlecase(cation) # edge case of lowercase str such as 'cs'
249
+ cation = getattr(pt, catstr)
250
+ except AttributeError:
251
+ raise Exception("You must select a cation to obtain oxides.")
252
+ ions = [c for c in cation.ions if c > 0] # Use only positive charges
253
+
254
+ # for 3.6+, could use f'{cation}{1}O{c//2}', f'{cation}{2}O{c}'
255
+ oxides = [
256
+ str(cation) + str(1) + "O" + str(c // 2)
257
+ if not c % 2
258
+ else str(cation) + str(2) + "O" + str(c)
259
+ for c in ions
260
+ ]
261
+ oxides = [pt.formula(ox) for ox in oxides]
262
+ import periodictable
263
+ if not output == "formula":
264
+ oxides = [str(ox) for ox in oxides]
265
+ return oxides
266
+
267
+ @pf.register_series_method
268
+ @pf.register_dataframe_method
269
+ def devolatilise(
270
+ df: pd.DataFrame,
271
+ exclude=["H2O", "H2O_PLUS", "H2O_MINUS", "CO2", "LOI"],
272
+ renorm=True,
273
+ ):
274
+ """
275
+ Recalculates components after exclusion of volatile phases (e.g. H2O, CO2).
276
+ """
277
+ keep = [i for i in df.columns if not i in exclude]
278
+ if renorm:
279
+ return renormalise(df.loc[:, keep])
280
+ else:
281
+ return df.loc[:, keep]
282
+
283
+
284
+ def oxide_conversion(oxin, oxout):
285
+ """
286
+ Generates a function to convert oxide components between
287
+ two elemental oxides, for use in redox recalculations.
288
+ """
289
+ if not (isinstance(oxin, pt.formulas.Formula) or isinstance(oxin, pt.core.Element)):
290
+ oxin = pt.formula(oxin)
291
+ if not (
292
+ isinstance(oxout, pt.formulas.Formula) or isinstance(oxout, pt.core.Element)
293
+ ):
294
+ oxout = pt.formula(oxout)
295
+
296
+ inatoms = {k: v for (k, v) in oxin.atoms.items() if not k.__str__() == "O"}
297
+ in_els = inatoms.keys()
298
+ outatoms = {k: v for (k, v) in oxout.atoms.items() if not k.__str__() == "O"}
299
+ out_els = outatoms.keys()
300
+ assert len(inatoms) == len(outatoms) == 1 # Assertion of simple oxide
301
+ assert in_els == out_els # Need to be dealilng with the same element!
302
+ # Moles of product vs. moles of reactant
303
+ cation_coefficient = list(inatoms.values())[0] / list(outatoms.values())[0]
304
+
305
+ def convert_series(dfser: pd.Series, molecular=False):
306
+ if molecular:
307
+ factor = cation_coefficient
308
+ else:
309
+ factor = cation_coefficient * oxout.mass / oxin.mass
310
+ converted = dfser * factor
311
+ return converted
312
+
313
+ doc = "Convert series from " + str(oxin) + " to " + str(oxout)
314
+ convert_series.__doc__ = doc
315
+ return convert_series
316
+
317
+ @pf.register_series_method
318
+ @pf.register_dataframe_method
319
+ def recalculate_Fe(
320
+ df: pd.DataFrame, to_species="FeOT", renorm=True, total_suffix="T", logdata=False
321
+ ):
322
+ """
323
+ Recalculates abundances of iron, and normalises a dataframe to contain only one
324
+ oxide species.
325
+
326
+ Consider reimplementing total suffix as a lambda formatting function
327
+ to deal with cases of prefixes, capitalisation etc.
328
+
329
+ Automatic generation of multiple redox species from dataframes
330
+ would also be a natural improvement.
331
+
332
+ # todo: update to incorporate Fe and transformation from multiple oxides to one
333
+ """
334
+ # Assuming either (a single column) or (FeO + Fe2O3) are reported
335
+ # Fe columns - FeO, Fe2O3, FeOT, Fe2O3T
336
+ FeO = pt.formula("FeO")
337
+ Fe2O3 = pt.formula("Fe2O3")
338
+ out_species = pt.formula(to_species.strip(total_suffix))
339
+
340
+ dfc = df.copy(deep=True)
341
+ ox_species = ["Fe2O3", "Fe2O3"]
342
+ ox_species += [i + total_suffix for i in ox_species]
343
+ ox_in_df = [i for i in ox_species if i in dfc.columns]
344
+ red_species = ["Fe", "FeO"]
345
+ red_species += [i + total_suffix for i in red_species]
346
+ red_in_df = [i for i in red_species if i in dfc.columns]
347
+
348
+ if logdata:
349
+ dfc.loc[:, ox_in_df + red_in_df] = dfc.loc[:, ox_in_df + red_in_df].applymap(
350
+ np.exp
351
+ )
352
+ fe_species = ox_in_df + red_in_df
353
+
354
+ out_sum = np.zeros(df.index.size)
355
+
356
+ for f in fe_species:
357
+ conv = oxide_conversion(pt.formula(f.strip(total_suffix)), out_species)
358
+ component = dfc.loc[:, f].fillna(0).apply(conv)
359
+ component[component < 0] = 0
360
+ out_sum += component
361
+
362
+ out_sum[out_sum <= 0.0] = np.nan
363
+ if logdata:
364
+ out_sum = np.exp(out_sum)
365
+
366
+ dfc.loc[:, to_species] = out_sum
367
+ dfc = dfc.drop(columns=[i for i in fe_species if not i == to_species])
368
+ if renorm:
369
+ return renormalise(dfc)
370
+ else:
371
+ return dfc
372
+
373
+ @pf.register_series_method
374
+ @pf.register_dataframe_method
375
+ def recalculate_redox(
376
+ df: pd.DataFrame, to_oxidised=True, renorm=True, total_suffix="T", logdata=False
377
+ ):
378
+ """
379
+ Recalculates abundances of redox-sensitive components (particularly Fe),
380
+ and normalises a dataframe to contain only one oxide species for a given
381
+ element.
382
+
383
+ Consider reimplementing total suffix as a lambda formatting function
384
+ to deal with cases of prefixes, capitalisation etc.
385
+
386
+ Automatic generation of multiple redox species from dataframes
387
+ would also be a natural improvement.
388
+
389
+ """
390
+ # Assuming either (a single column) or (FeO + Fe2O3) are reported
391
+ # Fe columns - FeO, Fe2O3, FeOT, Fe2O3T
392
+ FeO = pt.formula("FeO")
393
+ Fe2O3 = pt.formula("Fe2O3")
394
+ dfc = df.copy(deep=True)
395
+ ox_species = ["Fe2O3", "Fe2O3" + total_suffix]
396
+ ox_in_df = [i for i in ox_species if i in dfc.columns]
397
+ red_species = ["FeO", "FeO" + total_suffix]
398
+ red_in_df = [i for i in red_species if i in dfc.columns]
399
+ if logdata:
400
+ dfc.loc[:, ox_in_df + red_in_df] = dfc.loc[:, ox_in_df + red_in_df].applymap(
401
+ np.exp
402
+ )
403
+ if to_oxidised:
404
+ key = "Fe2O3T"
405
+ oxFe = oxide_conversion(FeO, Fe2O3)
406
+ Fe2O3T = dfc.loc[:, ox_in_df].fillna(0).sum(axis=1) + oxFe(
407
+ dfc.loc[:, red_in_df].fillna(0)
408
+ ).sum(axis=1)
409
+ dfc.loc[:, key] = Fe2O3T
410
+ Fe2O3T[Fe2O3T <= 0] = np.nan
411
+ to_drop = red_in_df + [i for i in ox_in_df if not i.endswith(total_suffix)]
412
+ else:
413
+ key = "FeOT"
414
+ reduceFe = oxide_conversion(Fe2O3, FeO)
415
+ FeOT = dfc.loc[:, red_in_df].fillna(0).sum(axis=1) + reduceFe(
416
+ dfc.loc[:, ox_in_df].fillna(0)
417
+ ).sum(axis=1)
418
+ FeOT[FeOT <= 0] = np.nan
419
+ dfc.loc[:, key] = FeOT
420
+ to_drop = ox_in_df + [i for i in red_in_df if not i.endswith(total_suffix)]
421
+
422
+ if logdata:
423
+ dfc.loc[:, key] = np.exp(dfc.loc[:, key].values)
424
+
425
+ dfc = dfc.drop(columns=to_drop)
426
+
427
+ if renorm:
428
+ return renormalise(dfc)
429
+ else:
430
+ return dfc
431
+
432
+ @pf.register_series_method
433
+ @pf.register_dataframe_method
434
+ def aggregate_cation(
435
+ df: pd.DataFrame,
436
+ cation=None,
437
+ oxide=None,
438
+ form="oxide",
439
+ unit_scale=scale_multiplier("Wt%", "Wt%"),
440
+ logdata=False,
441
+ ):
442
+ """
443
+ Aggregates cation information from oxide and elemental components
444
+ to a single series. Allows simultaneous scaling (e.g. from ppm to wt%).
445
+
446
+ Parameters
447
+ ----------
448
+ df : pd.DataFrame
449
+ DataFrame for which to aggregate cation data.
450
+ cation : str
451
+ Name of cation to aggregate.
452
+ oxide:
453
+ Name of oxide to aggregate.
454
+ form: {'oxide', 'element'}
455
+ Whether to aggregate to oxide or elemental form.
456
+ unit_scale:
457
+ The scale factor difference between the components. Unity if both have the same
458
+ units. Can be converted using scale_multiplier: e.g.
459
+ scale_multiplier("Wt%", "ppm")
460
+
461
+
462
+ Todo
463
+ -------
464
+ Needs to also implement a 'molecular' version.
465
+
466
+ """
467
+
468
+ dfc = df.copy()
469
+ # Should first check that neither the element or oxide is present more than once
470
+ assert not ((cation is None) and (oxide is None))
471
+ if cation is not None and oxide is not None:
472
+ elstr = str(cation)
473
+ oxstr = str(oxide)
474
+ elif oxide is not None:
475
+ oxstr = str(oxide)
476
+ elstr = str(get_cations(oxide)[0])
477
+ elif cation is not None:
478
+ elstr = str(cation)
479
+ potential_ox = simple_oxides(elstr)
480
+ oxstr = [o for o in dfc.columns if o in potential_ox][0]
481
+ assert oxstr, "Oxidation state unknown. " \
482
+ "Please specify desired oxide from {}.".format(potential_ox)
483
+
484
+ el, ox = pt.formula(elstr), pt.formula(oxstr)
485
+
486
+ for c in [elstr, oxstr]:
487
+ if not c in df.columns:
488
+ logger.info("Adding {} column.".format(c))
489
+ dfc[c] = np.nan
490
+
491
+ eldata = dfc.loc[:, elstr].values
492
+ oxdata = dfc.loc[:, oxstr].values
493
+ if logdata:
494
+ eldata = np.exp(eldata)
495
+ oxdata = np.exp(oxdata)
496
+
497
+ if form == "oxide":
498
+ if unit_scale is None:
499
+ unit_scale = 1.0
500
+ assert unit_scale > 0
501
+ convert_function = oxide_conversion(ox, el)
502
+ conv_values = convert_function(eldata) * unit_scale
503
+ totals = np.nansum(np.vstack((oxdata, conv_values)), axis=0)
504
+ elif form == "element":
505
+ if unit_scale is None:
506
+ unit_scale = 1.0
507
+ assert unit_scale > 0
508
+ convert_function = oxide_conversion(el, ox)
509
+ conv_values = convert_function(oxdata) * unit_scale
510
+ totals = np.nansum(np.vstack((eldata, conv_values)), axis=0)
511
+
512
+ totals[np.isclose(totals, 0)] = np.nan
513
+
514
+ if logdata:
515
+ totals = np.log(totals)
516
+
517
+ if form == "oxide":
518
+ dfc.loc[:, oxstr] = totals
519
+ dfc.drop(columns=[elstr], inplace=True)
520
+ assert elstr not in dfc.columns
521
+ else:
522
+ dfc.loc[:, elstr] = totals
523
+ dfc.drop(columns=[oxstr], inplace=True)
524
+ assert oxstr not in dfc.columns
525
+
526
+ return dfc
527
+
528
+ @pf.register_series_method
529
+ @pf.register_dataframe_method
530
+ def check_multiple_cation_inclusion(df, exclude=["LOI", "FeOT", "Fe2O3T"]):
531
+ """
532
+ Returns cations which are present in both oxide and elemental form.
533
+
534
+ Todo: Options for output (string/formula).
535
+ """
536
+ major_components = [i for i in __common_oxides__ if i in df.columns]
537
+ elements_as_majors = [
538
+ get_cations(oxide)[0] for oxide in major_components if not oxide in exclude
539
+ ]
540
+ elements_as_traces = [
541
+ c for c in common_elements(output="formula") if str(c) in df.columns
542
+ ]
543
+ return set([el for el in elements_as_majors if el in elements_as_traces])
544
+
545
+ @pf.register_series_method
546
+ @pf.register_dataframe_method
547
+ def convert_chemistry(df, columns=[], logdata=False, renorm=False):
548
+ """
549
+ Tries to convert a dataframe with one set of components to another.
550
+
551
+ Parameters
552
+ -----------
553
+ df : pd.DataFrame
554
+ Dataframe to convert.
555
+ columns : list, set
556
+ Set of columns to try to extract from the dataframe.
557
+ """
558
+ current = df.columns
559
+ ok = [i for i in columns if i in current]
560
+ get = [i for i in columns if i not in current]
561
+ multiples = check_multiple_cation_inclusion(df)
562
+ oxides = common_oxides(addition=[])
563
+ elements = common_elements()
564
+ Fe_parts = ["Fe", "FeO", "Fe2O3", "Fe2O3T", "FeOT"]
565
+
566
+ # Aggregate the columns which are otherwise OK
567
+ for o in ok:
568
+ if o in oxides + elements:
569
+ elem = get_cations(o)[0]
570
+ if elem in multiples:
571
+ if o in oxides:
572
+ df = aggregate_cation(
573
+ df, cation=elem, oxide=o, form="oxide", logdata=logdata
574
+ )
575
+ logger.info("Aggregating from {} to {}".format(elem, o))
576
+ else:
577
+ potential_oxides = simple_oxides(o)
578
+ present_oxides = [p for p in potential_oxides if p in current]
579
+ for ox in present_oxides: # aggregate all the relevant oxides
580
+ df = aggregate_cation(
581
+ df, cation=o, oxide=ox, form="element", logdata=logdata
582
+ )
583
+ logger.info("Aggregating from {} to {}".format(ox, o))
584
+ if o in Fe_parts:
585
+ pass
586
+
587
+ # --- Try to get the new columns ----
588
+ for g in get:
589
+ if g in oxides:
590
+ elem = get_cations(g)[0]
591
+ oxide = g
592
+ logger.info(
593
+ "Getting new column {oxide} from {elem}".format(oxide=oxide, elem=elem)
594
+ )
595
+ df = aggregate_cation(
596
+ df, cation=elem, oxide=oxide, form="oxide", logdata=logdata
597
+ )
598
+
599
+ elif g in elements:
600
+ elem = g
601
+ potential_oxides = simple_oxides(g)
602
+ present_oxides = [p for p in potential_oxides if p in current]
603
+ for ox in present_oxides: # aggregate all the relevant oxides
604
+ logger.info(
605
+ "Getting new column {elem} from {oxide}".format(oxide=ox, elem=elem)
606
+ )
607
+ df = aggregate_cation(
608
+ df, cation=elem, oxide=ox, form="element", logdata=logdata
609
+ )
610
+
611
+ # --- Try to get the new columns - iron redox section ----
612
+ get_fe = [i for i in columns if i in Fe_parts]
613
+ for f in get_fe:
614
+ current_Fe = [i for i in Fe_parts if i in df.columns]
615
+ c_fe_str = ", ".join(current_Fe)
616
+ df = recalculate_Fe(df, to_species=f, renorm=False, logdata=logdata)
617
+ logger.info("Reducing {} to {}.".format(c_fe_str, f))
618
+
619
+ ratios = [i for i in columns if "/" in i and i in get]
620
+
621
+ for r in ratios:
622
+ logger.info("Adding Ratio: {}".format(r))
623
+ num, den = r.split("/")
624
+ df.loc[:, r] = df.loc[:, num] / df.loc[:, den]
625
+ # df = add_ratio(df, r)
626
+
627
+ remaining = [i for i in columns if i not in df.columns]
628
+ assert not len(remaining), "Columns not attained: {}".format(", ".join(remaining))
629
+ if renorm:
630
+ logger.info("Recalculation Done, Renormalising")
631
+ return renormalise(df.loc[:, columns])
632
+ else:
633
+ logger.info("Recalculation Done.")
634
+ return df.loc[:, columns]
635
+
636
+ @pf.register_series_method
637
+ @pf.register_dataframe_method
638
+ def add_ratio(
639
+ df: pd.DataFrame, ratio: str, alias: str = "", norm_to=None, convert=lambda x: x
640
+ ):
641
+ """
642
+ Add a ratio of components A and B, given in the form of string 'A/B'.
643
+ Returned series be assigned an alias name.
644
+
645
+ Parameters
646
+ -----------
647
+ df: pd.DataFrame
648
+ Dataframe to append ratio to.
649
+ ratio: str
650
+ String decription of ratio in the form A/B[_n].
651
+ alias: str
652
+ Alternate name for ratio to be used as column name.
653
+ norm_to: {None, RefComp, str}
654
+ Reference composition to normalise to.
655
+ convert:
656
+ Data processing function to be calculated prior to ratio.
657
+ """
658
+
659
+ num, den = ratio.split("/")
660
+ _to_norm = False
661
+ if den.lower().endswith("_n"):
662
+ den = titlecase(den.lower().replace("_n", ""))
663
+ _to_norm = True
664
+ assert titlecase(num) in df.columns
665
+ assert titlecase(den) in df.columns
666
+
667
+ if _to_norm or (norm_to is not None):
668
+ if isinstance(norm_to, str):
669
+ norm = ReferenceCompositions()[norm_to]
670
+ num_n, den_n = norm[num].value, norm[den].value
671
+ elif isinstance(norm_to, RefComp):
672
+ num_n, den_n = norm_to[num].value, norm_to[den].value
673
+ elif iscollection(norm_to): # list, iterable, pd.Index etc
674
+ num_n, den_n = norm_to
675
+ else:
676
+ norm = ReferenceCompositions()["Chondrite_PON"]
677
+ num_n, den_n = norm[num].value, norm[den].value
678
+
679
+ name = [ratio if not alias else alias][0]
680
+ conv = convert(df.loc[:, [num, den]])
681
+ conv.loc[(conv[den] == 0.0) | (conv[num] == 0.0), den] = np.nan # avoid 0, inf
682
+ df.loc[:, name] = conv.loc[:, num] / conv.loc[:, den]
683
+ return df
684
+
685
+ @pf.register_series_method
686
+ @pf.register_dataframe_method
687
+ def add_MgNo(df: pd.DataFrame, molecularIn=False, elemental=False, components=False):
688
+
689
+ if not molecularIn:
690
+ if components:
691
+ # Iron is split into species
692
+ df.loc[:, "Mg#"] = (
693
+ df["MgO"]
694
+ / pt.formula("MgO").mass
695
+ / (
696
+ df["MgO"] / pt.formula("MgO").mass
697
+ + df["FeO"] / pt.formula("FeO").mass
698
+ )
699
+ )
700
+ else:
701
+ # Total iron is used
702
+ assert "FeOT" in df.columns
703
+ df.loc[:, "Mg#"] = (
704
+ df["MgO"]
705
+ / pt.formula("MgO").mass
706
+ / (
707
+ df["MgO"] / pt.formula("MgO").mass
708
+ + df["FeOT"] / pt.formula("FeO").mass
709
+ )
710
+ )
711
+ else:
712
+ if not elemental:
713
+ # Molecular Oxides
714
+ df.loc[:, "Mg#"] = df["MgO"] / (df["MgO"] + df["FeO"])
715
+ else:
716
+ # Molecular Elemental
717
+ df.loc[:, "Mg#"] = df["Mg"] / (df["Mg"] + df["Fe"])
718
+
719
+ @pf.register_series_method
720
+ @pf.register_dataframe_method
721
+ def lambda_lnREE(
722
+ df,
723
+ norm_to="Chondrite_PON",
724
+ exclude=["Pm", "Eu"],
725
+ params=None,
726
+ degree=5,
727
+ append=[],
728
+ **kwargs
729
+ ):
730
+ """
731
+ Calculates lambda coefficients for a given set of REE data, normalised
732
+ to a specific composition. Lambda factors are given for the
733
+ radii vs. ln(REE/NORM) polynomical combination.
734
+
735
+ TODO: Operate only on valid rows.
736
+ """
737
+ non_null_cols = df.columns[~df.isnull().all(axis=0)]
738
+ ree = [
739
+ i
740
+ for i in REE()
741
+ if i in df.columns
742
+ and (not str(i) in exclude)
743
+ and (str(i) in non_null_cols or i in non_null_cols)
744
+ ] # no promethium
745
+ radii = np.array(get_radii(ree))
746
+
747
+ if params is None:
748
+ params = OP_constants(radii, degree=degree)
749
+ else:
750
+ degree = len(params)
751
+
752
+ null_in_row = pd.isnull(df.loc[:, ree]).any(axis=1)
753
+ norm_df = df.loc[~null_in_row, ree].copy() # initialize normdf
754
+
755
+ labels = [chr(955) + str(d) for d in range(degree)]
756
+
757
+ if norm_to is not None: # None = already normalised data
758
+ if isinstance(norm_to, str):
759
+ norm = ReferenceCompositions()[norm_to]
760
+ norm_abund = np.array([norm[str(el)].value for el in ree])
761
+ elif isinstance(norm_to, RefComp):
762
+ norm_abund = np.array([getattr(norm_to, str(e)) for e in ree])
763
+ else: # list, iterable, pd.Index etc
764
+ norm_abund = np.array([i for i in norm_abund])
765
+ assert len(norm_abund) == len(ree)
766
+
767
+ norm_df.loc[:, ree] = np.divide(norm_df.loc[:, ree].values, norm_abund)
768
+
769
+ norm_df.loc[(norm_df <= 0.0).any(axis=1), :] = np.nan # remove zero or below
770
+ norm_df.loc[:, ree] = norm_df.loc[:, ree].applymap(np.log)
771
+
772
+ lambdadf = pd.DataFrame(index=df.index, columns=labels)
773
+ lambda_partial = functools.partial(
774
+ lambdas, xs=radii, params=params, degree=degree, **kwargs
775
+ ) # pass kwargs to lambdas
776
+ # apply along rows
777
+ lambdadf.loc[~null_in_row, labels] = np.apply_along_axis(
778
+ lambda_partial, 1, norm_df.values
779
+ )
780
+ lambdadf.loc[(lambdadf == 0.0).all(axis=1), :] = np.nan
781
+ if append:
782
+ # append the smooth f(radii) function to the dataframe
783
+ func_partial = functools.partial(
784
+ lambda_poly_func, pxs=radii, params=params, degree=degree
785
+ )
786
+ if "function" in append:
787
+ lambdadf["lambda_poly_func"] = np.apply_along_axis(
788
+ func_partial, 1, lambdadf.values
789
+ )
790
+
791
+ lambdadf = lambdadf.apply(pd.to_numeric, errors="coerce")
792
+ assert lambdadf.index.size == df.index.size
793
+ return lambdadf
794
+
795
+
796
+ __common_oxides__ = common_oxides(as_set=True)
797
+ __common_elements__ = common_elements(as_set=True)
798
+ __REE = REE()
799
+ _RADII = {
800
+ str(k): v
801
+ for (k, v) in zip(
802
+ REE(),
803
+ [
804
+ 1.160,
805
+ 1.143,
806
+ 1.126,
807
+ 1.109,
808
+ 1.093,
809
+ 1.079,
810
+ 1.066,
811
+ 1.053,
812
+ 1.040,
813
+ 1.027,
814
+ 1.015,
815
+ 1.004,
816
+ 0.994,
817
+ 0.985,
818
+ 0.977,
819
+ ],
820
+ )
821
+ }