gebpy 1.1.3__py3-none-any.whl

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 (254) hide show
  1. gebpy/__init__.py +55 -0
  2. gebpy/__pycache__/__init__.cpython-310.pyc +0 -0
  3. gebpy/adapters/__init__.py +0 -0
  4. gebpy/cli/__init__.py +0 -0
  5. gebpy/core/__init__.py +0 -0
  6. gebpy/core/chemistry/__init__.py +0 -0
  7. gebpy/core/chemistry/common.py +1369 -0
  8. gebpy/core/chemistry/elements.py +317 -0
  9. gebpy/core/chemistry/geochemistry.py +1728 -0
  10. gebpy/core/fluids/__init__.py +0 -0
  11. gebpy/core/io/__init__.py +0 -0
  12. gebpy/core/mathematics/__init__.py +0 -0
  13. gebpy/core/minerals/__init__.py +0 -0
  14. gebpy/core/minerals/carbonates.py +412 -0
  15. gebpy/core/minerals/common.py +555 -0
  16. gebpy/core/minerals/config.py +77 -0
  17. gebpy/core/minerals/cyclosilicates.py +0 -0
  18. gebpy/core/minerals/halides.py +0 -0
  19. gebpy/core/minerals/inosilicates.py +0 -0
  20. gebpy/core/minerals/nesosilicates.py +0 -0
  21. gebpy/core/minerals/organics.py +0 -0
  22. gebpy/core/minerals/oxides.py +589 -0
  23. gebpy/core/minerals/phosphates.py +0 -0
  24. gebpy/core/minerals/phospides.py +0 -0
  25. gebpy/core/minerals/phyllosilicates.py +436 -0
  26. gebpy/core/minerals/sorosilicates.py +0 -0
  27. gebpy/core/minerals/sulfates.py +0 -0
  28. gebpy/core/minerals/sulfides.py +459 -0
  29. gebpy/core/minerals/synthesis.py +201 -0
  30. gebpy/core/minerals/tectosilicates.py +433 -0
  31. gebpy/core/physics/__init__.py +0 -0
  32. gebpy/core/physics/common.py +53 -0
  33. gebpy/core/physics/geophysics.py +351 -0
  34. gebpy/core/rocks/__init__.py +0 -0
  35. gebpy/core/rocks/anisotropic_rocks.py +395 -0
  36. gebpy/core/rocks/common.py +95 -0
  37. gebpy/core/rocks/config.py +77 -0
  38. gebpy/core/rocks/isotropic_rocks.py +395 -0
  39. gebpy/core/rocks/sedimentary.py +385 -0
  40. gebpy/core/subsurface/__init__.py +0 -0
  41. gebpy/data_minerals/__init__.py +0 -0
  42. gebpy/data_minerals/albite.yaml +59 -0
  43. gebpy/data_minerals/anatase.yaml +43 -0
  44. gebpy/data_minerals/ankerite.yaml +47 -0
  45. gebpy/data_minerals/annite.yaml +57 -0
  46. gebpy/data_minerals/anorthite.yaml +59 -0
  47. gebpy/data_minerals/antigorite.yaml +53 -0
  48. gebpy/data_minerals/aragonite.yaml +48 -0
  49. gebpy/data_minerals/argutite.yaml +43 -0
  50. gebpy/data_minerals/arsenolite.yaml +40 -0
  51. gebpy/data_minerals/au3oxide.yaml +46 -0
  52. gebpy/data_minerals/avicennite.yaml +40 -0
  53. gebpy/data_minerals/azurite.yaml +53 -0
  54. gebpy/data_minerals/baddeleyite.yaml +49 -0
  55. gebpy/data_minerals/bismite.yaml +49 -0
  56. gebpy/data_minerals/boehmite.yaml +48 -0
  57. gebpy/data_minerals/brookite.yaml +46 -0
  58. gebpy/data_minerals/brucite.yaml +45 -0
  59. gebpy/data_minerals/bunsenite.yaml +40 -0
  60. gebpy/data_minerals/calcite.yaml +45 -0
  61. gebpy/data_minerals/cassiterite.yaml +43 -0
  62. gebpy/data_minerals/cerussite.yaml +48 -0
  63. gebpy/data_minerals/chamosite.yaml +56 -0
  64. gebpy/data_minerals/chlorite.yaml +75 -0
  65. gebpy/data_minerals/chromite.yaml +42 -0
  66. gebpy/data_minerals/chrysotile.yaml +53 -0
  67. gebpy/data_minerals/claudetite.yaml +49 -0
  68. gebpy/data_minerals/clinochlore.yaml +55 -0
  69. gebpy/data_minerals/cochromite.yaml +42 -0
  70. gebpy/data_minerals/corundum.yaml +43 -0
  71. gebpy/data_minerals/crocoite.yaml +51 -0
  72. gebpy/data_minerals/cuprite.yaml +40 -0
  73. gebpy/data_minerals/cuprospinel.yaml +42 -0
  74. gebpy/data_minerals/diaspore.yaml +48 -0
  75. gebpy/data_minerals/dolomite.yaml +47 -0
  76. gebpy/data_minerals/eastonite.yaml +57 -0
  77. gebpy/data_minerals/eskolaite.yaml +43 -0
  78. gebpy/data_minerals/fechlorite.yaml +61 -0
  79. gebpy/data_minerals/fecolumbite.yaml +48 -0
  80. gebpy/data_minerals/ferberite.yaml +51 -0
  81. gebpy/data_minerals/fetantalite.yaml +48 -0
  82. gebpy/data_minerals/franklinite.yaml +42 -0
  83. gebpy/data_minerals/gahnite.yaml +42 -0
  84. gebpy/data_minerals/galaxite.yaml +42 -0
  85. gebpy/data_minerals/geikielite.yaml +45 -0
  86. gebpy/data_minerals/gibbsite.yaml +51 -0
  87. gebpy/data_minerals/glauconite.yaml +69 -0
  88. gebpy/data_minerals/goethite.yaml +48 -0
  89. gebpy/data_minerals/groutite.yaml +48 -0
  90. gebpy/data_minerals/hematite.yaml +43 -0
  91. gebpy/data_minerals/hercynite.yaml +42 -0
  92. gebpy/data_minerals/huebnerite.yaml +51 -0
  93. gebpy/data_minerals/ikaite.yaml +53 -0
  94. gebpy/data_minerals/illite.yaml +55 -0
  95. gebpy/data_minerals/ilmenite.yaml +45 -0
  96. gebpy/data_minerals/jacobsite.yaml +42 -0
  97. gebpy/data_minerals/kalsilite.yaml +47 -0
  98. gebpy/data_minerals/kaolinite.yaml +59 -0
  99. gebpy/data_minerals/karelianite.yaml +43 -0
  100. gebpy/data_minerals/lime.yaml +40 -0
  101. gebpy/data_minerals/litharge.yaml +43 -0
  102. gebpy/data_minerals/magnesiochromite.yaml +42 -0
  103. gebpy/data_minerals/magnesioferrite.yaml +42 -0
  104. gebpy/data_minerals/magnesite.yaml +45 -0
  105. gebpy/data_minerals/magnetite.yaml +41 -0
  106. gebpy/data_minerals/malachite.yaml +53 -0
  107. gebpy/data_minerals/manganite.yaml +51 -0
  108. gebpy/data_minerals/manganochromite.yaml +42 -0
  109. gebpy/data_minerals/manganosite.yaml +40 -0
  110. gebpy/data_minerals/marialite.yaml +49 -0
  111. gebpy/data_minerals/massicot.yaml +46 -0
  112. gebpy/data_minerals/meionite.yaml +49 -0
  113. gebpy/data_minerals/mgchlorite.yaml +61 -0
  114. gebpy/data_minerals/mgcolumbite.yaml +48 -0
  115. gebpy/data_minerals/mgtantalite.yaml +48 -0
  116. gebpy/data_minerals/microcline.yaml +59 -0
  117. gebpy/data_minerals/minium.yaml +44 -0
  118. gebpy/data_minerals/mnchlorite.yaml +61 -0
  119. gebpy/data_minerals/mncolumbite.yaml +48 -0
  120. gebpy/data_minerals/mntantalite.yaml +48 -0
  121. gebpy/data_minerals/monteponite.yaml +40 -0
  122. gebpy/data_minerals/montmorillonite.yaml +77 -0
  123. gebpy/data_minerals/muscovite.yaml +55 -0
  124. gebpy/data_minerals/nanepheline.yaml +47 -0
  125. gebpy/data_minerals/nichlorite.yaml +61 -0
  126. gebpy/data_minerals/nichromite.yaml +42 -0
  127. gebpy/data_minerals/nimite.yaml +55 -0
  128. gebpy/data_minerals/nontronite.yaml +73 -0
  129. gebpy/data_minerals/orthoclase.yaml +53 -0
  130. gebpy/data_minerals/paratellurite.yaml +43 -0
  131. gebpy/data_minerals/pennantite.yaml +61 -0
  132. gebpy/data_minerals/periclase.yaml +40 -0
  133. gebpy/data_minerals/phlogopite.yaml +57 -0
  134. gebpy/data_minerals/plattnerite.yaml +43 -0
  135. gebpy/data_minerals/powellite.yaml +45 -0
  136. gebpy/data_minerals/pyrite.yaml +40 -0
  137. gebpy/data_minerals/pyrolusite.yaml +43 -0
  138. gebpy/data_minerals/pyrophanite.yaml +45 -0
  139. gebpy/data_minerals/pyrophyllite.yaml +59 -0
  140. gebpy/data_minerals/quartz.yaml +43 -0
  141. gebpy/data_minerals/rhodochrosite.yaml +45 -0
  142. gebpy/data_minerals/rutile.yaml +43 -0
  143. gebpy/data_minerals/saponite.yaml +77 -0
  144. gebpy/data_minerals/scheelite.yaml +45 -0
  145. gebpy/data_minerals/scrutinyite.yaml +46 -0
  146. gebpy/data_minerals/senarmontite.yaml +40 -0
  147. gebpy/data_minerals/siderite.yaml +45 -0
  148. gebpy/data_minerals/siderophyllite.yaml +57 -0
  149. gebpy/data_minerals/smithsonite.yaml +45 -0
  150. gebpy/data_minerals/spinel.yaml +42 -0
  151. gebpy/data_minerals/stishovite.yaml +43 -0
  152. gebpy/data_minerals/stolzite.yaml +45 -0
  153. gebpy/data_minerals/talc.yaml +53 -0
  154. gebpy/data_minerals/tistarite.yaml +43 -0
  155. gebpy/data_minerals/trevorite.yaml +42 -0
  156. gebpy/data_minerals/ulvoespinel.yaml +42 -0
  157. gebpy/data_minerals/uraninite.yaml +40 -0
  158. gebpy/data_minerals/valentinite.yaml +46 -0
  159. gebpy/data_minerals/vermiculite.yaml +69 -0
  160. gebpy/data_minerals/wulfenite.yaml +45 -0
  161. gebpy/data_minerals/wustite.yaml +40 -0
  162. gebpy/data_minerals/zincite.yaml +43 -0
  163. gebpy/data_minerals/zincochromite.yaml +42 -0
  164. gebpy/data_rocks/__init__.py +0 -0
  165. gebpy/data_rocks/dolostone.yaml +40 -0
  166. gebpy/data_rocks/limestone.yaml +40 -0
  167. gebpy/data_rocks/marl.yaml +50 -0
  168. gebpy/data_rocks/sandstone.yaml +39 -0
  169. gebpy/data_rocks/shale.yaml +50 -0
  170. gebpy/gebpy_app.py +8732 -0
  171. gebpy/gui/__init__.py +0 -0
  172. gebpy/lib/images/GebPy_Header.png +0 -0
  173. gebpy/lib/images/GebPy_Icon.png +0 -0
  174. gebpy/lib/images/GebPy_Logo.png +0 -0
  175. gebpy/main.py +29 -0
  176. gebpy/modules/__init__.py +0 -0
  177. gebpy/modules/__pycache__/__init__.cpython-310.pyc +0 -0
  178. gebpy/modules/__pycache__/metamorphics.cpython-310.pyc +0 -0
  179. gebpy/modules/__pycache__/silicates.cpython-310.pyc +0 -0
  180. gebpy/modules/carbonates.py +2658 -0
  181. gebpy/modules/chemistry.py +1369 -0
  182. gebpy/modules/core.py +1805 -0
  183. gebpy/modules/elements.py +317 -0
  184. gebpy/modules/evaporites.py +1299 -0
  185. gebpy/modules/exploration.py +1145 -0
  186. gebpy/modules/fluids.py +339 -0
  187. gebpy/modules/geochemistry.py +1727 -0
  188. gebpy/modules/geophysics.py +351 -0
  189. gebpy/modules/gui.py +9093 -0
  190. gebpy/modules/gui_elements.py +145 -0
  191. gebpy/modules/halides.py +485 -0
  192. gebpy/modules/igneous.py +2241 -0
  193. gebpy/modules/metamorphics.py +3222 -0
  194. gebpy/modules/mineralogy.py +442 -0
  195. gebpy/modules/minerals.py +7954 -0
  196. gebpy/modules/ore.py +1648 -0
  197. gebpy/modules/organics.py +530 -0
  198. gebpy/modules/oxides.py +9057 -0
  199. gebpy/modules/petrophysics.py +98 -0
  200. gebpy/modules/phosphates.py +589 -0
  201. gebpy/modules/phospides.py +194 -0
  202. gebpy/modules/plotting.py +619 -0
  203. gebpy/modules/pyllosilicates.py +380 -0
  204. gebpy/modules/sedimentary_rocks.py +908 -0
  205. gebpy/modules/sequences.py +2166 -0
  206. gebpy/modules/series.py +1625 -0
  207. gebpy/modules/silicates.py +11102 -0
  208. gebpy/modules/siliciclastics.py +1846 -0
  209. gebpy/modules/subsurface_2d.py +179 -0
  210. gebpy/modules/sulfates.py +1629 -0
  211. gebpy/modules/sulfides.py +4786 -0
  212. gebpy/plotting/__init__.py +0 -0
  213. gebpy/ui_nb/__init__.py +0 -0
  214. gebpy/user_data/.gitkeep +0 -0
  215. gebpy-1.1.3.dist-info/LICENSE +165 -0
  216. gebpy-1.1.3.dist-info/METADATA +207 -0
  217. gebpy-1.1.3.dist-info/RECORD +254 -0
  218. gebpy-1.1.3.dist-info/WHEEL +5 -0
  219. gebpy-1.1.3.dist-info/entry_points.txt +2 -0
  220. gebpy-1.1.3.dist-info/top_level.txt +1 -0
  221. modules/__init__.py +0 -0
  222. modules/carbonates.py +2658 -0
  223. modules/chemistry.py +1369 -0
  224. modules/core.py +1805 -0
  225. modules/elements.py +317 -0
  226. modules/evaporites.py +1299 -0
  227. modules/exploration.py +765 -0
  228. modules/fluids.py +339 -0
  229. modules/geochemistry.py +1727 -0
  230. modules/geophysics.py +337 -0
  231. modules/gui.py +9093 -0
  232. modules/gui_elements.py +145 -0
  233. modules/halides.py +485 -0
  234. modules/igneous.py +2196 -0
  235. modules/metamorphics.py +2699 -0
  236. modules/mineralogy.py +442 -0
  237. modules/minerals.py +7954 -0
  238. modules/ore.py +1628 -0
  239. modules/organics.py +530 -0
  240. modules/oxides.py +9057 -0
  241. modules/petrophysics.py +98 -0
  242. modules/phosphates.py +589 -0
  243. modules/phospides.py +194 -0
  244. modules/plotting.py +619 -0
  245. modules/pyllosilicates.py +380 -0
  246. modules/sedimentary_rocks.py +908 -0
  247. modules/sequences.py +2166 -0
  248. modules/series.py +1625 -0
  249. modules/silicates.py +11102 -0
  250. modules/siliciclastics.py +1830 -0
  251. modules/subsurface_2d.py +179 -0
  252. modules/sulfates.py +1629 -0
  253. modules/sulfides.py +4786 -0
  254. notebooks/__init__.py +0 -0
@@ -0,0 +1,459 @@
1
+ #!/usr/bin/env python
2
+ # -*-coding: utf-8 -*-
3
+
4
+ #-----------------------------------------------
5
+
6
+ # Name: sulfides.py
7
+ # Author: Maximilian A. Beeskow
8
+ # Version: 1.0
9
+ # Date: 15.12.2025
10
+
11
+ #-----------------------------------------------
12
+
13
+ """
14
+ Module: sulfides.py
15
+ This module controls the generation of the synthetic data of the sulfide minerals.
16
+ """
17
+
18
+ # PACKAGES
19
+ import yaml
20
+ import numpy as np
21
+ from pathlib import Path
22
+
23
+ from asteval import Interpreter
24
+
25
+ # MODULES
26
+ from ..chemistry.common import PeriodicSystem
27
+ from ..chemistry.geochemistry import MineralChemistry
28
+ from ..physics.geophysics import WellLog as wg
29
+
30
+ from .common import (
31
+ GeophysicalProperties,
32
+ CrystallographicProperties,
33
+ CrystalPhysics,
34
+ MineralGeneration as MinGen
35
+ )
36
+
37
+ # CODE
38
+ BASE_PATH = Path(__file__).resolve().parents[2]
39
+ DATA_PATH = BASE_PATH / "data_minerals"
40
+
41
+ class Sulfides:
42
+ _yaml_cache = {}
43
+ _formula_cache = {}
44
+ _minerals = {
45
+ "Acanthite", "Bornite", "Cattierite", "Chalcocite", "Chalcopyrite", "Cinnabar", "Cobaltite", "Covellite",
46
+ "Fahlore", "Galena", "Gallite", "Laforetite", "Lenaite", "Marcasite", "Marmatite", "Millerite", "Molybdenite",
47
+ "Orpiment", "Pentlandite", "Pyrite", "Pyrrhotite", "Realgar", "Roquesite", "Sphalerite", "Stibnite", "Vaesite"}
48
+
49
+ def __init__(self, name, random_seed, rounding=3) -> None:
50
+ self.name = name
51
+ self.random_seed = random_seed
52
+ self.rng = np.random.default_rng(random_seed)
53
+ self.current_seed = int(np.round(self.rng.uniform(0, 1000), 0))
54
+ self.data_path = DATA_PATH
55
+ self.rounding = rounding
56
+ self.ae = Interpreter()
57
+ self.cache = {}
58
+
59
+ # Chemistry
60
+ self.elements = {
61
+ "H": PeriodicSystem(name="H").get_data(),
62
+ "C": PeriodicSystem(name="C").get_data(),
63
+ "O": PeriodicSystem(name="O").get_data(),
64
+ "Mg": PeriodicSystem(name="Mg").get_data(),
65
+ "Ca": PeriodicSystem(name="Ca").get_data(),
66
+ "S": PeriodicSystem(name="S").get_data(),
67
+ "Mn": PeriodicSystem(name="Mn").get_data(),
68
+ "Fe": PeriodicSystem(name="Fe").get_data(),
69
+ "Cu": PeriodicSystem(name="Cu").get_data(),
70
+ "Zn": PeriodicSystem(name="Zn").get_data(),
71
+ "Pb": PeriodicSystem(name="Pb").get_data(),
72
+ }
73
+
74
+ # Geophysics
75
+ self.geophysical_properties = GeophysicalProperties()
76
+ # Crystallography
77
+ self.crystallographic_properties = CrystallographicProperties()
78
+
79
+ # Mineral-specific data
80
+ if self.name in [
81
+ "Acanthite", "Bornite", "Cattierite", "Chalcocite", "Chalcopyrite", "Cinnabar", "Cobaltite", "Covellite",
82
+ "Fahlore", "Galena", "Gallite", "Laforetite", "Lenaite", "Marcasite", "Marmatite", "Millerite",
83
+ "Molybdenite", "Orpiment", "Pentlandite", "Pyrite", "Pyrrhotite", "Realgar", "Roquesite", "Sphalerite",
84
+ "Stibnite", "Vaesite"]:
85
+ self.yaml_data = self._load_yaml(self.name.lower())
86
+
87
+ def _load_yaml(self, mineral_name: str) -> dict:
88
+ # 1) Cache-Hit
89
+ if mineral_name in Sulfides._yaml_cache:
90
+ return Sulfides._yaml_cache[mineral_name]
91
+
92
+ # 2) Laden von Disk
93
+ yaml_file = self.data_path/f"{mineral_name}.yaml"
94
+ if not yaml_file.exists():
95
+ raise FileNotFoundError(f"No YAML file found for {mineral_name}.")
96
+
97
+ with open(yaml_file, "r") as f:
98
+ data = yaml.safe_load(f)
99
+
100
+ if "chemistry" in data and mineral_name not in Sulfides._formula_cache:
101
+ self._compile_chemistry_formulas(mineral_name, data["chemistry"])
102
+
103
+ # 3) Cache schreiben
104
+ Sulfides._yaml_cache[mineral_name] = data
105
+
106
+ return data
107
+
108
+ def _get_value(self, data: dict, path: list[str], default=None):
109
+ """Safely extract a float or string value from nested YAML data."""
110
+ try:
111
+ for key in path:
112
+ data = data[key]
113
+ if isinstance(data, dict) and "value" in data:
114
+ return float(data["value"])
115
+ else:
116
+ return data # kann z.B. ein String oder eine Zahl sein
117
+ except (KeyError, TypeError):
118
+ return default
119
+
120
+ def _get_variables(self):
121
+ if "variables" not in self.yaml_data:
122
+ return {}
123
+ vars = {}
124
+ for k, v in self.yaml_data["variables"].items():
125
+ if isinstance(v[0], int):
126
+ vars[k] = self.rng.integers(v[0], v[1])
127
+ else:
128
+ vars[k] = round(self.rng.uniform(v[0], v[1]), 2)
129
+ return vars
130
+
131
+ def generate_dataset(self, number: int = 1, as_dataframe=False) -> None:
132
+ fixed = {
133
+ "Acanthite", "Bornite", "Cattierite", "Chalcocite", "Chalcopyrite", "Cinnabar", "Cobaltite", "Covellite",
134
+ "Fahlore", "Galena", "Gallite", "Laforetite", "Lenaite", "Marcasite", "Marmatite", "Millerite",
135
+ "Molybdenite", "Orpiment", "Pentlandite", "Pyrite", "Pyrrhotite", "Realgar", "Roquesite", "Sphalerite",
136
+ "Stibnite", "Vaesite"}
137
+ variable = {}
138
+ endmember = {}
139
+ generators = {
140
+ **{m: MinGen(
141
+ name=self.name, yaml_data=self.yaml_data, elements=self.elements, cache=self.cache,
142
+ geophysical_properties=self.geophysical_properties, rounding=self.rounding
143
+ ).create_mineral_data_fixed_composition for m in fixed},
144
+ **{m: self.create_mineral_data_variable_composition for m in variable},
145
+ **{m: self.create_mineral_data_endmember_series for m in endmember},
146
+ }
147
+
148
+ if self.name not in generators:
149
+ raise ValueError(f"Mineral '{self.name}' not recognized.")
150
+
151
+ dataset = {}
152
+ if self.name in fixed:
153
+ dataset = self._evaluate_mineral(index=1, generators=generators, dataset=dataset)
154
+ else:
155
+ for index in range(number):
156
+ dataset = self._evaluate_mineral(index=index, generators=generators, dataset=dataset)
157
+
158
+ if as_dataframe:
159
+ import pandas as pd
160
+ return pd.DataFrame(dataset)
161
+ else:
162
+ return dataset
163
+
164
+ def _evaluate_mineral(self, index, generators, dataset):
165
+ self.current_seed = np.uint32(self.random_seed + index)
166
+ data_mineral = generators[self.name]()
167
+
168
+ for key, value in data_mineral.items():
169
+ if key in ["M", "rho", "rho_e", "V", "vP", "vS", "vP/vS", "K", "G", "E", "nu", "GR", "PE", "U",
170
+ "p"]:
171
+ if key not in dataset:
172
+ dataset[key] = [value]
173
+ else:
174
+ dataset[key].append(value)
175
+ elif key in ["mineral", "state", "trace elements"] and key not in dataset:
176
+ dataset[key] = value
177
+ elif key in ["chemistry", "compounds"]:
178
+ if key not in dataset:
179
+ dataset[key] = {}
180
+ for key_2, value_2 in value.items():
181
+ dataset[key][key_2] = [value_2]
182
+ else:
183
+ for key_2, value_2 in value.items():
184
+ dataset[key][key_2].append(value_2)
185
+ return dataset
186
+
187
+ def _compile_chemistry_formulas(self, mineral_name: str, chemistry_dict: dict):
188
+ """
189
+ Extracts and compiles all chemistry formulas from the YAML file.
190
+ Stores the compiled ASTs in the global formula cache.
191
+ """
192
+
193
+ compiled = {}
194
+
195
+ for element, entry in chemistry_dict.items():
196
+
197
+ # Fall A: Einfache Zahl (z.B. 2, 3.5)
198
+ if isinstance(entry, (int, float)):
199
+ expr = str(entry)
200
+ # Fall B: dict mit 'formula'
201
+ elif isinstance(entry, dict) and "formula" in entry:
202
+ expr = str(entry["formula"])
203
+ # Fall C: Alles andere ist invalid
204
+ else:
205
+ raise ValueError(
206
+ f"Invalid chemistry entry for element '{element}' in {mineral_name}.yaml: {entry}")
207
+ # AST kompilieren
208
+ compiled[element] = self.ae.parse(expr)
209
+ # Cache schreiben
210
+ Sulfides._formula_cache[mineral_name] = compiled
211
+
212
+ def _evaluate_chemistry(self, chemistry_dict, **variables):
213
+ """
214
+ Evaluates algebraic expressions of element amounts defined in the YAML file.
215
+
216
+ Parameters:
217
+ chemistry_dict (dict): Dictionary from YAML containing element formulas as strings.
218
+ **variables: Variable assignments (e.g. x=0.5, y=1, n=8)
219
+
220
+ Returns:
221
+ dict: {element: calculated_amount}
222
+ """
223
+ self.ae.symtable.clear()
224
+
225
+ # Variablen setzen
226
+ for k, v in variables.items():
227
+ self.ae.symtable[k] = v
228
+
229
+ results = {}
230
+ compiled = Sulfides._formula_cache[self.name.lower()]
231
+
232
+ for el in chemistry_dict:
233
+ results[el] = self.ae.run(compiled[el])
234
+
235
+ return results
236
+
237
+ def _extract_values_from_yaml(self):
238
+ vals = {}
239
+ # Physical parameters
240
+ for key in ["K", "G", "a_K", "b_K", "a_G", "b_G"]:
241
+ if key in self.yaml_data.get("physical_properties", {}):
242
+ vals[key] = float(self.yaml_data["physical_properties"][key]["value"])
243
+ # Cell parameters
244
+ for key in ["a", "b", "c", "alpha", "beta", "gamma", "Z"]:
245
+ if key in self.yaml_data.get("cell_data", {}):
246
+ vals[key] = float(self.yaml_data["cell_data"][key]["value"])
247
+ # Meta data
248
+ vals["key"] = self.yaml_data["metadata"]["key"]
249
+ vals["crystal_system"] = self.yaml_data["metadata"]["crystal_system"]
250
+
251
+ return vals
252
+
253
+ def _determine_majors_data(self):
254
+ majors_data = []
255
+ molar_mass_pure = 0
256
+ vars = self._get_variables()
257
+ amounts_elements = self._evaluate_chemistry(self.yaml_data["chemistry"], **vars)
258
+ for element, amount in amounts_elements.items():
259
+ n_order = int(self.elements[element][1])
260
+ val_amount = float(amount)
261
+ molar_mass = float(self.elements[element][2])
262
+ majors_data.append([element, n_order, val_amount, molar_mass])
263
+ molar_mass_pure += val_amount*molar_mass
264
+ majors_data.sort(key=lambda x: x[1])
265
+ return majors_data, amounts_elements, molar_mass_pure, vars
266
+
267
+ def _calculate_molar_mass_amounts(self, amounts_elements):
268
+ molar_mass = 0
269
+ for element, amount in amounts_elements.items():
270
+ molar_mass += amount*float(self.elements[element][2])
271
+
272
+ amounts = []
273
+ for element, amount in amounts_elements.items():
274
+ value = amount*float(self.elements[element][2])/molar_mass
275
+ amounts.append([element, self.elements[element][1], value])
276
+ element = [self.elements[name] for name, *_ in amounts]
277
+ return molar_mass, amounts, element
278
+
279
+ def _determine_volume_constructor(self, vals):
280
+ val_a = vals["a"]
281
+ val_system = vals["crystal_system"]
282
+ if val_system in ["isometric", "cubic"]:
283
+ constr_vol = CrystalPhysics([[val_a], [], val_system])
284
+ elif val_system in ["tetragonal", "hexagonal", "trigonal"]:
285
+ val_c = vals["c"]
286
+ constr_vol = CrystalPhysics([[val_a, val_c], [], val_system])
287
+ elif val_system in ["orthorhombic"]:
288
+ val_b = vals["b"]
289
+ val_c = vals["c"]
290
+ constr_vol = CrystalPhysics([[val_a, val_b, val_c], [], val_system])
291
+ elif val_system in ["monoclinic"]:
292
+ val_b = vals["b"]
293
+ val_c = vals["c"]
294
+ val_beta = vals["beta"]
295
+ constr_vol = CrystalPhysics([[val_a, val_b, val_c], [val_beta], val_system])
296
+ elif val_system in ["triclinic"]:
297
+ val_b = vals["b"]
298
+ val_c = vals["c"]
299
+ val_alpha = vals["alpha"]
300
+ val_beta = vals["beta"]
301
+ val_gamma = vals["gamma"]
302
+ constr_vol = CrystalPhysics([[val_a, val_b, val_c], [val_alpha, val_beta, val_gamma], val_system])
303
+ return constr_vol
304
+
305
+ def create_mineral_data_variable_composition(self):
306
+ """
307
+ Synthetic mineral data generation for an user-selected mineral.
308
+ All mechanical properties (K, G, E) are stored in Pascals internally.
309
+ For output, they are converted to GPa.
310
+ """
311
+ name_lower = self.name.lower()
312
+ # Chemistry
313
+ val_state = "variable"
314
+ traces_data = []
315
+ # Molar mass, elemental amounts
316
+ majors_data, amounts_elements, molar_mass_pure, vars = self._determine_majors_data()
317
+
318
+ if name_lower not in self.cache:
319
+ vals = self._extract_values_from_yaml()
320
+ self.cache[name_lower] = {"constants": vals}
321
+ else:
322
+ vals = self.cache[name_lower]["constants"]
323
+ constr_vol = self.cache[name_lower]["constr_vol"]
324
+
325
+ # Molar mass, element amounts
326
+ molar_mass, amounts, element = self._calculate_molar_mass_amounts(amounts_elements=amounts_elements)
327
+
328
+ # Reading and assigning the mineral-specific information from the YAML file
329
+ val_key = vals["key"]
330
+ val_Z = vals["Z"]
331
+ if "K" in vals:
332
+ val_K = vals["K"]
333
+ val_G = vals["G"]
334
+ else:
335
+ val_a_K = float(vals["a_K"])
336
+ val_b_K = float(vals["b_K"])
337
+ val_a_G = float(vals["a_G"])
338
+ val_b_G = float(vals["b_G"])
339
+
340
+ # (Molar) Volume
341
+ if "constr_vol" not in self.cache[name_lower]:
342
+ constr_vol = self._determine_volume_constructor(vals=vals)
343
+ self.cache[name_lower]["constr_vol"] = constr_vol
344
+
345
+ constr_minchem = MineralChemistry(w_traces=traces_data, molar_mass_pure=molar_mass_pure, majors=majors_data)
346
+ V, V_m = self.crystallographic_properties.calculate_molar_volume(
347
+ constr_volume=constr_vol, constr_molar_volume=constr_minchem, cell_z=val_Z)
348
+ # Density
349
+ constr_density = CrystalPhysics([molar_mass, val_Z, V])
350
+ rho = self.crystallographic_properties.calculate_mineral_density(constr_density=constr_density)
351
+ constr_electr_density = wg(amounts=amounts, elements=element, rho_b=rho)
352
+ rho_e = self.crystallographic_properties.calculate_electron_density(
353
+ constr_electron_density=constr_electr_density)
354
+
355
+ # Elastic properties
356
+ if "K" not in vals:
357
+ val_K = (val_a_K*rho + val_b_K)*10**9
358
+ val_G = (val_a_G*rho + val_b_G)*10**9
359
+ E, nu = self.geophysical_properties.calculate_elastic_properties(bulk_mod=val_K, shear_mod=val_G)
360
+ # Seismic properties
361
+ vPvS, vP, vS = self.geophysical_properties.calculate_seismic_velocities(
362
+ bulk_mod=val_K, shear_mod=val_G, rho=rho)
363
+ # Radiation properties
364
+ constr_radiation = wg(amounts=amounts, elements=element)
365
+ gamma_ray, pe, U = self.geophysical_properties.calculate_radiation_properties(
366
+ constr_radiation=constr_radiation, rho_electron=rho_e)
367
+ # Electrical resistivity
368
+ p = None
369
+ # Results
370
+ results = {
371
+ "mineral": val_key, "state": val_state, "M": round(molar_mass, self.rounding),
372
+ "chemistry": {name: round(val[1], 6) for name, *val in amounts}, "rho": round(rho, self.rounding),
373
+ "rho_e": round(rho_e, self.rounding), "V": round(V_m, self.rounding), "vP": round(vP, self.rounding),
374
+ "vS": round(vS, self.rounding), "vP/vS": round(vPvS, self.rounding),
375
+ "K": round(val_K*10**(-9), self.rounding), "G": round(val_G*10**(-9), self.rounding),
376
+ "E": round(E*10**(-9), self.rounding), "nu": round(nu, 6), "GR": round(gamma_ray, self.rounding),
377
+ "PE": round(pe, self.rounding), "U": round(U, self.rounding), "p": p}
378
+ return results
379
+
380
+ def create_mineral_data_endmember_series(self):
381
+ """
382
+ Synthetic mineral data generation for a user-selected mineral.
383
+ All mechanical properties (K, G, E) are stored in Pascals internally.
384
+ For output, they are converted to GPa.
385
+ """
386
+ val_state = "variable"
387
+
388
+ if self.name == "Calcite-Group":
389
+ name_lower = self.name.lower()
390
+ val_key = "Cal-group"
391
+ endmember = ["Calcite", "Magnesite", "Siderite", "Rhodochrosite", "Smithsonite"]
392
+ elif self.name == "Dolomite-Group":
393
+ name_lower = self.name.lower()
394
+ val_key = "Dol-group"
395
+ endmember = ["Dolomite", "Ankerite"]
396
+
397
+ if "endmembers" not in self.cache:
398
+ self.cache["endmembers"] = {}
399
+
400
+ endmember_data = {}
401
+ list_elements = []
402
+ for mineral in endmember:
403
+ if mineral not in self.cache["endmembers"]:
404
+ mineral_data = Sulfides(name=mineral, random_seed=self.current_seed).generate_dataset(number=1)
405
+ self.cache["endmembers"][mineral] = mineral_data
406
+ endmember_data[mineral] = self.cache["endmembers"][mineral]
407
+ mineral_data = endmember_data[mineral]
408
+ for element in mineral_data["chemistry"]:
409
+ if element not in list_elements:
410
+ list_elements.append(element)
411
+ weights = self.rng.dirichlet(np.ones(len(endmember)))
412
+ fraction_endmember = dict(zip(endmember, weights))
413
+
414
+ if name_lower not in self.cache:
415
+ self.cache[name_lower] = {
416
+ "endmember_data": endmember_data
417
+ }
418
+
419
+ properties = ["M", "rho", "rho_e", "V", "K", "G"]
420
+ helper_results = {
421
+ prop: sum(fraction_endmember[m]*endmember_data[m][prop][0] for m in endmember)
422
+ for prop in properties
423
+ }
424
+ # Amounts
425
+ amounts = []
426
+ for element in list_elements:
427
+ amount = sum(fraction_endmember[mineral]*endmember_data[mineral]["chemistry"].get(element, [0])[0]
428
+ for mineral in endmember)
429
+ amounts.append([element, self.elements[element][1], amount])
430
+ element = [self.elements[name] for name, *_ in amounts]
431
+ # Elastic properties
432
+ val_K = helper_results["K"]*10**9
433
+ val_G = helper_results["G"]*10**9
434
+ rho = helper_results["rho"]
435
+ rho_e = helper_results["rho_e"]
436
+ E, nu = self.geophysical_properties.calculate_elastic_properties(bulk_mod=val_K, shear_mod=val_G)
437
+ # Seismic properties
438
+ vPvS, vP, vS = self.geophysical_properties.calculate_seismic_velocities(
439
+ bulk_mod=val_K, shear_mod=val_G, rho=rho)
440
+ # Radiation properties
441
+ constr_radiation = wg(amounts=amounts, elements=element)
442
+ gamma_ray, pe, U = self.geophysical_properties.calculate_radiation_properties(
443
+ constr_radiation=constr_radiation, rho_electron=rho_e)
444
+ # Electrical resistivity
445
+ p = None
446
+ # Results
447
+ results = {
448
+ "mineral": val_key, "state": val_state, "M": round(helper_results["M"], self.rounding),
449
+ "chemistry": {name: round(val[1], 6) for name, *val in amounts}, "rho": round(rho, self.rounding),
450
+ "rho_e": round(rho_e, self.rounding), "V": round(helper_results["V"], self.rounding),
451
+ "vP": round(vP, self.rounding), "vS": round(vS, self.rounding), "vP/vS": round(vPvS, self.rounding),
452
+ "K": round(val_K*10**(-9), self.rounding), "G": round(val_G*10**(-9), self.rounding),
453
+ "E": round(E*10**(-9), self.rounding), "nu": round(nu, 6), "GR": round(gamma_ray, self.rounding), #
454
+ "PE": round(pe, self.rounding), "U": round(U, self.rounding), "p": p}
455
+ return results
456
+
457
+ # TEST
458
+ if __name__ == "__main__":
459
+ DEFAULT_DATA = Sulfides(name="Pyrite", random_seed=42).generate_dataset(number=10)
@@ -0,0 +1,201 @@
1
+ #!/usr/bin/env python
2
+ # -*-coding: utf-8 -*-
3
+
4
+ #-----------------------------------------------
5
+
6
+ # Name: synthesis.py
7
+ # Author: Maximilian A. Beeskow
8
+ # Version: 1.0
9
+ # Date: 15.12.2025
10
+
11
+ #-----------------------------------------------
12
+
13
+ """
14
+ Module: synthesis.py
15
+ Generates the synthetic mineral data based on the settings of the previous configuration.
16
+ """
17
+
18
+ # PACKAGES
19
+ import pandas as pd
20
+ from typing import Optional, Union
21
+ from functools import cached_property
22
+
23
+ # MODULES
24
+ from ..minerals.config import DEFAULT_CONFIG
25
+ from ..minerals.carbonates import Carbonates
26
+ from ..minerals.oxides import Oxides
27
+ from ..minerals.phyllosilicates import Phyllosilicates
28
+ from ..minerals.sulfides import Sulfides
29
+ from ..minerals.tectosilicates import Tectosilicates
30
+
31
+ # CODE
32
+ class MineralDataGeneration:
33
+ """
34
+ Generation of the synthetic/simulated mineral data.
35
+ - name: mineral name (e.g., 'Olivine')
36
+ - n_datapoints: Number of generated data points (> 0)
37
+ - random_seed: integer seed; if None, defaults to 42
38
+ """
39
+
40
+ list_carbonates = {
41
+ "Ankerite", "Aragonite", "Azurite", "Calcite", "Cerrusite", "Dolomite", "Ikaite", "Magnesite", "Malachite",
42
+ "Rhodochrosite", "Siderite", "Smithsonite"}
43
+ list_cyclosilicates = {
44
+ "Benitoite", "Beryl", "Cordierite", "Elbaite", "Liddicoatite", "Schorl", "Sekaninaite"}
45
+ list_halides = {"Carnallite", "Fluorite", "Halite", "Sylvite"}
46
+ list_inosilicates = {
47
+ "Actinolite", "Aegirine", "Arfvedsonite", "Augite", "Ca-Amphibole", "Ca-Pyroxene", "Clinopyroxene",
48
+ "Diopside", "Donpeacorite", "Enstatite", "Ferrosilite", "Glaucophane", "Jadeite", "Mg-Fe-Pyroxene",
49
+ "Na-Amphibole", "Na-Pyroxene", "Orthopyroxene", "Spodumene", "Tremolite", "Wollastonite"}
50
+ list_miscellaneous = {"Organic matter"}
51
+ list_nesosilicates = {
52
+ "Al-Garnet", "Almandine", "Andalusite", "Anhadrite", "Ca-Garnet", "Ca-Olivine", "Fayalite", "Forsterite",
53
+ "Grossular", "Kyanite", "Liebenbergite", "Olivine", "Pyrope", "Sillimanite", "Staurolite", "Tephroite",
54
+ "Thorite", "Titanite", "Topaz", "Uvarovite", "Zircon"}
55
+ list_oxides = Oxides._minerals
56
+ list_phosphates = {"Apatite", "Chloroapatite", "Fluoroapatite", "Hydroxyapatite"}
57
+ list_phospides = {"Allabogdanite"}
58
+ list_phyllosilicates = Phyllosilicates._minerals
59
+ list_sorosilicates = {"Epidote", "Gehlenite", "Zoisite"}
60
+ list_sulfates = {
61
+ "Alunite", "Anglesite", "Anhydrite", "Barite", "Celestite", "Chalcanthite", "Gypsum", "Hanksite",
62
+ "Hexahydrite", "Jarosite", "Kainite", "Kieserite", "Scheelite"}
63
+ list_sulfides = {
64
+ "Acanthite", "Bornite", "Cattierite", "Chalcocite", "Chalcopyrite", "Cinnabar", "Cobaltite", "Covellite",
65
+ "Fahlore", "Galena", "Gallite", "Laforetite", "Lenaite", "Marcasite", "Marmatite", "Millerite",
66
+ "Molybdenite", "Orpiment", "Pentlandite", "Pyrite", "Pyrrhotite", "Realgar", "Roquesite", "Sphalerite",
67
+ "Stibnite", "Vaesite"}
68
+ list_tectosilicates = Tectosilicates._minerals
69
+
70
+ mineral_groups = {
71
+ "carbonates": list_carbonates, "cyclosilicates": list_cyclosilicates, "halides": list_halides,
72
+ "inosilicates": list_inosilicates, "miscellaneous": list_miscellaneous, "nesosilicates": list_nesosilicates,
73
+ "oxides": list_oxides, "phosphates": list_phosphates, "phospides": list_phospides,
74
+ "phyllosilicates": list_phyllosilicates, "sorosilicates": list_sorosilicates, "sulfates": list_sulfates,
75
+ "sulfides": list_sulfides, "tectosilicates": list_tectosilicates}
76
+
77
+ def __init__(self,
78
+ name: Optional[str] = None,
79
+ n_datapoints: Optional[int] = None,
80
+ random_seed: Optional[int] = None,
81
+ trace_elements: Optional[list] = None
82
+ ) -> None:
83
+ if name is None and n_datapoints is None and random_seed is None:
84
+ self.name = DEFAULT_CONFIG.name
85
+ self.n_datapoints = DEFAULT_CONFIG.n_datapoints
86
+ self.random_seed = DEFAULT_CONFIG.random_seed
87
+ self.trace_elements = []
88
+ else:
89
+ self.name = name
90
+ self.n_datapoints = n_datapoints
91
+ self.random_seed = 42 if random_seed is None else random_seed
92
+ self.trace_elements = [] if trace_elements is None else trace_elements
93
+
94
+ def __repr__(self) -> str:
95
+ return (
96
+ f"<MineralDataGeneration name={self.name!r}, "
97
+ f"n_datapoints={self.n_datapoints}, "
98
+ f"random_seed={self.random_seed}>"
99
+ )
100
+
101
+ @cached_property
102
+ def mineral_map(self):
103
+ from gebpy_legacy.modules.halides import Halides
104
+ from gebpy_legacy.modules.silicates import (Cyclosilicates, Inosilicates, Nesosilicates, Sorosilicates)
105
+ from gebpy_legacy.modules.phosphates import Phosphates
106
+ from gebpy_legacy.modules.sulfates import Sulfates
107
+ from gebpy_legacy.modules.organics import Organics
108
+ from gebpy_legacy.modules.phospides import Phospides
109
+
110
+ return {
111
+ "carbonates": Carbonates,
112
+ "cyclosilicates": Cyclosilicates,
113
+ "halides": Halides,
114
+ "inosilicates": Inosilicates,
115
+ "miscellaneous": Organics,
116
+ "nesosilicates": Nesosilicates,
117
+ "oxides": Oxides,
118
+ "phosphates": Phosphates,
119
+ "phospides": Phospides,
120
+ "phyllosilicates": Phyllosilicates,
121
+ "sorosilicates": Sorosilicates,
122
+ "sulfates": Sulfates,
123
+ "sulfides": Sulfides,
124
+ "tectosilicates": Tectosilicates
125
+ }
126
+
127
+ def get_all_minerals(self):
128
+ return self.mineral_groups
129
+
130
+ def assign_mineral_group(self,
131
+ name: Optional[str] = None,
132
+ n_datapoints: Optional[int] = None
133
+ ) -> None:
134
+ if name is None:
135
+ name = self.name
136
+ n_datapoints = self.n_datapoints
137
+
138
+ for group, list_group in self.mineral_groups.items():
139
+ if name in list_group:
140
+ cls = self.mineral_map[group]
141
+ if cls.__name__ in ("Phyllosilicates", "Tectosilicates", "Oxides", "Carbonates", "Sulfides"):
142
+ #print(sorted(cls(name=name, random_seed=self.random_seed)._minerals))
143
+ return cls(name=name, random_seed=self.random_seed).generate_dataset(number=n_datapoints)
144
+ else:
145
+ try:
146
+ return cls(
147
+ mineral=name, data_type=True, traces_list=self.trace_elements,
148
+ random_seed=self.random_seed).generate_dataset(
149
+ number=n_datapoints)
150
+ except:
151
+ return cls(mineral=name, data_type=True, traces_list=self.trace_elements).generate_dataset(
152
+ number=n_datapoints)
153
+ raise ValueError(f"Unknown mineral '{name}'. Please check available groups.")
154
+
155
+ def generate_data(self, as_dataframe: bool = True) -> Union[pd.DataFrame, dict]:
156
+ data_dict = self.assign_mineral_group(self.name, self.n_datapoints)
157
+ if not as_dataframe:
158
+ return data_dict
159
+
160
+ data_mineral = self.dict_to_dataframe_fast(data_dict)
161
+ data_mineral = data_mineral.astype("float32", errors="ignore").round(5)
162
+ return data_mineral
163
+
164
+ def dict_to_dataframe_fast(self, data: dict) -> pd.DataFrame:
165
+ """
166
+ Convert nested mineral dataset dictionaries (with lists and subdicts)
167
+ into a flat pandas DataFrame. Handles scalar values gracefully by
168
+ repeating them to match the maximum list length.
169
+ """
170
+ columns = {}
171
+
172
+ # Schritt 1: maximale Listenlänge bestimmen
173
+ max_len = 1
174
+ for val in data.values():
175
+ if isinstance(val, dict):
176
+ for subval in val.values():
177
+ if isinstance(subval, list):
178
+ max_len = max(max_len, len(subval))
179
+ elif isinstance(val, list):
180
+ max_len = max(max_len, len(val))
181
+
182
+ # Schritt 2: flaches Dictionary aufbauen
183
+ for key, val in data.items():
184
+ if isinstance(val, dict):
185
+ for subkey, subval in val.items():
186
+ col_name = f"{key}.{subkey}"
187
+ if isinstance(subval, list):
188
+ columns[col_name] = subval
189
+ else:
190
+ columns[col_name] = [subval] * max_len
191
+ elif isinstance(val, list):
192
+ columns[key] = val
193
+ else:
194
+ columns[key] = [val] * max_len
195
+
196
+ return pd.DataFrame(columns)
197
+
198
+ # DEFAULT EXAMPLE
199
+ data_config = DEFAULT_CONFIG
200
+ data_default = MineralDataGeneration(name=data_config.name, n_datapoints=data_config.n_datapoints)
201
+ DEFAULT_DATA = data_default.generate_data()