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.
- gebpy/__init__.py +55 -0
- gebpy/__pycache__/__init__.cpython-310.pyc +0 -0
- gebpy/adapters/__init__.py +0 -0
- gebpy/cli/__init__.py +0 -0
- gebpy/core/__init__.py +0 -0
- gebpy/core/chemistry/__init__.py +0 -0
- gebpy/core/chemistry/common.py +1369 -0
- gebpy/core/chemistry/elements.py +317 -0
- gebpy/core/chemistry/geochemistry.py +1728 -0
- gebpy/core/fluids/__init__.py +0 -0
- gebpy/core/io/__init__.py +0 -0
- gebpy/core/mathematics/__init__.py +0 -0
- gebpy/core/minerals/__init__.py +0 -0
- gebpy/core/minerals/carbonates.py +412 -0
- gebpy/core/minerals/common.py +555 -0
- gebpy/core/minerals/config.py +77 -0
- gebpy/core/minerals/cyclosilicates.py +0 -0
- gebpy/core/minerals/halides.py +0 -0
- gebpy/core/minerals/inosilicates.py +0 -0
- gebpy/core/minerals/nesosilicates.py +0 -0
- gebpy/core/minerals/organics.py +0 -0
- gebpy/core/minerals/oxides.py +589 -0
- gebpy/core/minerals/phosphates.py +0 -0
- gebpy/core/minerals/phospides.py +0 -0
- gebpy/core/minerals/phyllosilicates.py +436 -0
- gebpy/core/minerals/sorosilicates.py +0 -0
- gebpy/core/minerals/sulfates.py +0 -0
- gebpy/core/minerals/sulfides.py +459 -0
- gebpy/core/minerals/synthesis.py +201 -0
- gebpy/core/minerals/tectosilicates.py +433 -0
- gebpy/core/physics/__init__.py +0 -0
- gebpy/core/physics/common.py +53 -0
- gebpy/core/physics/geophysics.py +351 -0
- gebpy/core/rocks/__init__.py +0 -0
- gebpy/core/rocks/anisotropic_rocks.py +395 -0
- gebpy/core/rocks/common.py +95 -0
- gebpy/core/rocks/config.py +77 -0
- gebpy/core/rocks/isotropic_rocks.py +395 -0
- gebpy/core/rocks/sedimentary.py +385 -0
- gebpy/core/subsurface/__init__.py +0 -0
- gebpy/data_minerals/__init__.py +0 -0
- gebpy/data_minerals/albite.yaml +59 -0
- gebpy/data_minerals/anatase.yaml +43 -0
- gebpy/data_minerals/ankerite.yaml +47 -0
- gebpy/data_minerals/annite.yaml +57 -0
- gebpy/data_minerals/anorthite.yaml +59 -0
- gebpy/data_minerals/antigorite.yaml +53 -0
- gebpy/data_minerals/aragonite.yaml +48 -0
- gebpy/data_minerals/argutite.yaml +43 -0
- gebpy/data_minerals/arsenolite.yaml +40 -0
- gebpy/data_minerals/au3oxide.yaml +46 -0
- gebpy/data_minerals/avicennite.yaml +40 -0
- gebpy/data_minerals/azurite.yaml +53 -0
- gebpy/data_minerals/baddeleyite.yaml +49 -0
- gebpy/data_minerals/bismite.yaml +49 -0
- gebpy/data_minerals/boehmite.yaml +48 -0
- gebpy/data_minerals/brookite.yaml +46 -0
- gebpy/data_minerals/brucite.yaml +45 -0
- gebpy/data_minerals/bunsenite.yaml +40 -0
- gebpy/data_minerals/calcite.yaml +45 -0
- gebpy/data_minerals/cassiterite.yaml +43 -0
- gebpy/data_minerals/cerussite.yaml +48 -0
- gebpy/data_minerals/chamosite.yaml +56 -0
- gebpy/data_minerals/chlorite.yaml +75 -0
- gebpy/data_minerals/chromite.yaml +42 -0
- gebpy/data_minerals/chrysotile.yaml +53 -0
- gebpy/data_minerals/claudetite.yaml +49 -0
- gebpy/data_minerals/clinochlore.yaml +55 -0
- gebpy/data_minerals/cochromite.yaml +42 -0
- gebpy/data_minerals/corundum.yaml +43 -0
- gebpy/data_minerals/crocoite.yaml +51 -0
- gebpy/data_minerals/cuprite.yaml +40 -0
- gebpy/data_minerals/cuprospinel.yaml +42 -0
- gebpy/data_minerals/diaspore.yaml +48 -0
- gebpy/data_minerals/dolomite.yaml +47 -0
- gebpy/data_minerals/eastonite.yaml +57 -0
- gebpy/data_minerals/eskolaite.yaml +43 -0
- gebpy/data_minerals/fechlorite.yaml +61 -0
- gebpy/data_minerals/fecolumbite.yaml +48 -0
- gebpy/data_minerals/ferberite.yaml +51 -0
- gebpy/data_minerals/fetantalite.yaml +48 -0
- gebpy/data_minerals/franklinite.yaml +42 -0
- gebpy/data_minerals/gahnite.yaml +42 -0
- gebpy/data_minerals/galaxite.yaml +42 -0
- gebpy/data_minerals/geikielite.yaml +45 -0
- gebpy/data_minerals/gibbsite.yaml +51 -0
- gebpy/data_minerals/glauconite.yaml +69 -0
- gebpy/data_minerals/goethite.yaml +48 -0
- gebpy/data_minerals/groutite.yaml +48 -0
- gebpy/data_minerals/hematite.yaml +43 -0
- gebpy/data_minerals/hercynite.yaml +42 -0
- gebpy/data_minerals/huebnerite.yaml +51 -0
- gebpy/data_minerals/ikaite.yaml +53 -0
- gebpy/data_minerals/illite.yaml +55 -0
- gebpy/data_minerals/ilmenite.yaml +45 -0
- gebpy/data_minerals/jacobsite.yaml +42 -0
- gebpy/data_minerals/kalsilite.yaml +47 -0
- gebpy/data_minerals/kaolinite.yaml +59 -0
- gebpy/data_minerals/karelianite.yaml +43 -0
- gebpy/data_minerals/lime.yaml +40 -0
- gebpy/data_minerals/litharge.yaml +43 -0
- gebpy/data_minerals/magnesiochromite.yaml +42 -0
- gebpy/data_minerals/magnesioferrite.yaml +42 -0
- gebpy/data_minerals/magnesite.yaml +45 -0
- gebpy/data_minerals/magnetite.yaml +41 -0
- gebpy/data_minerals/malachite.yaml +53 -0
- gebpy/data_minerals/manganite.yaml +51 -0
- gebpy/data_minerals/manganochromite.yaml +42 -0
- gebpy/data_minerals/manganosite.yaml +40 -0
- gebpy/data_minerals/marialite.yaml +49 -0
- gebpy/data_minerals/massicot.yaml +46 -0
- gebpy/data_minerals/meionite.yaml +49 -0
- gebpy/data_minerals/mgchlorite.yaml +61 -0
- gebpy/data_minerals/mgcolumbite.yaml +48 -0
- gebpy/data_minerals/mgtantalite.yaml +48 -0
- gebpy/data_minerals/microcline.yaml +59 -0
- gebpy/data_minerals/minium.yaml +44 -0
- gebpy/data_minerals/mnchlorite.yaml +61 -0
- gebpy/data_minerals/mncolumbite.yaml +48 -0
- gebpy/data_minerals/mntantalite.yaml +48 -0
- gebpy/data_minerals/monteponite.yaml +40 -0
- gebpy/data_minerals/montmorillonite.yaml +77 -0
- gebpy/data_minerals/muscovite.yaml +55 -0
- gebpy/data_minerals/nanepheline.yaml +47 -0
- gebpy/data_minerals/nichlorite.yaml +61 -0
- gebpy/data_minerals/nichromite.yaml +42 -0
- gebpy/data_minerals/nimite.yaml +55 -0
- gebpy/data_minerals/nontronite.yaml +73 -0
- gebpy/data_minerals/orthoclase.yaml +53 -0
- gebpy/data_minerals/paratellurite.yaml +43 -0
- gebpy/data_minerals/pennantite.yaml +61 -0
- gebpy/data_minerals/periclase.yaml +40 -0
- gebpy/data_minerals/phlogopite.yaml +57 -0
- gebpy/data_minerals/plattnerite.yaml +43 -0
- gebpy/data_minerals/powellite.yaml +45 -0
- gebpy/data_minerals/pyrite.yaml +40 -0
- gebpy/data_minerals/pyrolusite.yaml +43 -0
- gebpy/data_minerals/pyrophanite.yaml +45 -0
- gebpy/data_minerals/pyrophyllite.yaml +59 -0
- gebpy/data_minerals/quartz.yaml +43 -0
- gebpy/data_minerals/rhodochrosite.yaml +45 -0
- gebpy/data_minerals/rutile.yaml +43 -0
- gebpy/data_minerals/saponite.yaml +77 -0
- gebpy/data_minerals/scheelite.yaml +45 -0
- gebpy/data_minerals/scrutinyite.yaml +46 -0
- gebpy/data_minerals/senarmontite.yaml +40 -0
- gebpy/data_minerals/siderite.yaml +45 -0
- gebpy/data_minerals/siderophyllite.yaml +57 -0
- gebpy/data_minerals/smithsonite.yaml +45 -0
- gebpy/data_minerals/spinel.yaml +42 -0
- gebpy/data_minerals/stishovite.yaml +43 -0
- gebpy/data_minerals/stolzite.yaml +45 -0
- gebpy/data_minerals/talc.yaml +53 -0
- gebpy/data_minerals/tistarite.yaml +43 -0
- gebpy/data_minerals/trevorite.yaml +42 -0
- gebpy/data_minerals/ulvoespinel.yaml +42 -0
- gebpy/data_minerals/uraninite.yaml +40 -0
- gebpy/data_minerals/valentinite.yaml +46 -0
- gebpy/data_minerals/vermiculite.yaml +69 -0
- gebpy/data_minerals/wulfenite.yaml +45 -0
- gebpy/data_minerals/wustite.yaml +40 -0
- gebpy/data_minerals/zincite.yaml +43 -0
- gebpy/data_minerals/zincochromite.yaml +42 -0
- gebpy/data_rocks/__init__.py +0 -0
- gebpy/data_rocks/dolostone.yaml +40 -0
- gebpy/data_rocks/limestone.yaml +40 -0
- gebpy/data_rocks/marl.yaml +50 -0
- gebpy/data_rocks/sandstone.yaml +39 -0
- gebpy/data_rocks/shale.yaml +50 -0
- gebpy/gebpy_app.py +8732 -0
- gebpy/gui/__init__.py +0 -0
- gebpy/lib/images/GebPy_Header.png +0 -0
- gebpy/lib/images/GebPy_Icon.png +0 -0
- gebpy/lib/images/GebPy_Logo.png +0 -0
- gebpy/main.py +29 -0
- gebpy/modules/__init__.py +0 -0
- gebpy/modules/__pycache__/__init__.cpython-310.pyc +0 -0
- gebpy/modules/__pycache__/metamorphics.cpython-310.pyc +0 -0
- gebpy/modules/__pycache__/silicates.cpython-310.pyc +0 -0
- gebpy/modules/carbonates.py +2658 -0
- gebpy/modules/chemistry.py +1369 -0
- gebpy/modules/core.py +1805 -0
- gebpy/modules/elements.py +317 -0
- gebpy/modules/evaporites.py +1299 -0
- gebpy/modules/exploration.py +1145 -0
- gebpy/modules/fluids.py +339 -0
- gebpy/modules/geochemistry.py +1727 -0
- gebpy/modules/geophysics.py +351 -0
- gebpy/modules/gui.py +9093 -0
- gebpy/modules/gui_elements.py +145 -0
- gebpy/modules/halides.py +485 -0
- gebpy/modules/igneous.py +2241 -0
- gebpy/modules/metamorphics.py +3222 -0
- gebpy/modules/mineralogy.py +442 -0
- gebpy/modules/minerals.py +7954 -0
- gebpy/modules/ore.py +1648 -0
- gebpy/modules/organics.py +530 -0
- gebpy/modules/oxides.py +9057 -0
- gebpy/modules/petrophysics.py +98 -0
- gebpy/modules/phosphates.py +589 -0
- gebpy/modules/phospides.py +194 -0
- gebpy/modules/plotting.py +619 -0
- gebpy/modules/pyllosilicates.py +380 -0
- gebpy/modules/sedimentary_rocks.py +908 -0
- gebpy/modules/sequences.py +2166 -0
- gebpy/modules/series.py +1625 -0
- gebpy/modules/silicates.py +11102 -0
- gebpy/modules/siliciclastics.py +1846 -0
- gebpy/modules/subsurface_2d.py +179 -0
- gebpy/modules/sulfates.py +1629 -0
- gebpy/modules/sulfides.py +4786 -0
- gebpy/plotting/__init__.py +0 -0
- gebpy/ui_nb/__init__.py +0 -0
- gebpy/user_data/.gitkeep +0 -0
- gebpy-1.1.3.dist-info/LICENSE +165 -0
- gebpy-1.1.3.dist-info/METADATA +207 -0
- gebpy-1.1.3.dist-info/RECORD +254 -0
- gebpy-1.1.3.dist-info/WHEEL +5 -0
- gebpy-1.1.3.dist-info/entry_points.txt +2 -0
- gebpy-1.1.3.dist-info/top_level.txt +1 -0
- modules/__init__.py +0 -0
- modules/carbonates.py +2658 -0
- modules/chemistry.py +1369 -0
- modules/core.py +1805 -0
- modules/elements.py +317 -0
- modules/evaporites.py +1299 -0
- modules/exploration.py +765 -0
- modules/fluids.py +339 -0
- modules/geochemistry.py +1727 -0
- modules/geophysics.py +337 -0
- modules/gui.py +9093 -0
- modules/gui_elements.py +145 -0
- modules/halides.py +485 -0
- modules/igneous.py +2196 -0
- modules/metamorphics.py +2699 -0
- modules/mineralogy.py +442 -0
- modules/minerals.py +7954 -0
- modules/ore.py +1628 -0
- modules/organics.py +530 -0
- modules/oxides.py +9057 -0
- modules/petrophysics.py +98 -0
- modules/phosphates.py +589 -0
- modules/phospides.py +194 -0
- modules/plotting.py +619 -0
- modules/pyllosilicates.py +380 -0
- modules/sedimentary_rocks.py +908 -0
- modules/sequences.py +2166 -0
- modules/series.py +1625 -0
- modules/silicates.py +11102 -0
- modules/siliciclastics.py +1830 -0
- modules/subsurface_2d.py +179 -0
- modules/sulfates.py +1629 -0
- modules/sulfides.py +4786 -0
- notebooks/__init__.py +0 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*-coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
#-----------------------------------------------
|
|
5
|
+
|
|
6
|
+
# Name: anisotropic_rocks.py
|
|
7
|
+
# Author: Maximilian A. Beeskow
|
|
8
|
+
# Version: 1.0
|
|
9
|
+
# Date: 17.12.2025
|
|
10
|
+
|
|
11
|
+
#-----------------------------------------------
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
Module: anisotropic_rocks.py
|
|
15
|
+
This module controls the generation of synthetic data for anisotropic rocks.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# PACKAGES
|
|
19
|
+
import yaml
|
|
20
|
+
import numpy as np
|
|
21
|
+
import pandas as pd
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
|
|
24
|
+
# MODULES
|
|
25
|
+
from ..minerals.synthesis import MineralDataGeneration
|
|
26
|
+
from ..rocks.common import RockGeneration
|
|
27
|
+
from ..physics.common import Geophysics
|
|
28
|
+
|
|
29
|
+
# Code
|
|
30
|
+
BASE_PATH = Path(__file__).resolve().parents[2]
|
|
31
|
+
DATA_PATH = BASE_PATH / "data_rocks"
|
|
32
|
+
|
|
33
|
+
class AnisotropicRocks:
|
|
34
|
+
_yaml_cache = {}
|
|
35
|
+
_mineralogy_cache = {}
|
|
36
|
+
_mineral_groups_cache = {}
|
|
37
|
+
_rocks = {"Shale"}
|
|
38
|
+
|
|
39
|
+
def __init__(self, name, random_seed) -> None:
|
|
40
|
+
self.name = name
|
|
41
|
+
self.random_seed = random_seed
|
|
42
|
+
self.rng = np.random.default_rng(random_seed)
|
|
43
|
+
self.current_seed = int(np.round(self.rng.uniform(0, 1000), 0))
|
|
44
|
+
self.data_path = DATA_PATH
|
|
45
|
+
self.rock_gen = RockGeneration()
|
|
46
|
+
self.geophysics = Geophysics()
|
|
47
|
+
self.conversion_factors = self.rock_gen._determine_oxide_conversion_factors()
|
|
48
|
+
self.cache = {}
|
|
49
|
+
|
|
50
|
+
def _load_yaml(self, rock_name: str) -> dict:
|
|
51
|
+
# 1) Cache-Hit
|
|
52
|
+
if rock_name in AnisotropicRocks._yaml_cache:
|
|
53
|
+
return AnisotropicRocks._yaml_cache[rock_name]
|
|
54
|
+
|
|
55
|
+
# 2) Laden von Disk
|
|
56
|
+
yaml_file = self.data_path/f"{rock_name}.yaml"
|
|
57
|
+
if not yaml_file.exists():
|
|
58
|
+
raise FileNotFoundError(f"No YAML file found for {rock_name}.")
|
|
59
|
+
|
|
60
|
+
with open(yaml_file, "r") as f:
|
|
61
|
+
data = yaml.safe_load(f)
|
|
62
|
+
|
|
63
|
+
if "mineralogy" in data and rock_name not in AnisotropicRocks._mineralogy_cache:
|
|
64
|
+
self._compile_mineralogy(rock_name, data["mineralogy"])
|
|
65
|
+
|
|
66
|
+
if "mineral_groups" in data:
|
|
67
|
+
self._compile_mineral_groups(rock_name, data["mineral_groups"])
|
|
68
|
+
|
|
69
|
+
# 3) Cache schreiben
|
|
70
|
+
AnisotropicRocks._yaml_cache[rock_name] = data
|
|
71
|
+
|
|
72
|
+
return data
|
|
73
|
+
|
|
74
|
+
def _compile_mineralogy(self, rock_name: str, mineralogy_dict: dict):
|
|
75
|
+
"""
|
|
76
|
+
Extracts and compiles all chemistry formulas from the YAML file.
|
|
77
|
+
Stores the compiled ASTs in the global formula cache.
|
|
78
|
+
"""
|
|
79
|
+
if rock_name not in AnisotropicRocks._mineralogy_cache:
|
|
80
|
+
AnisotropicRocks._mineralogy_cache[rock_name] = {}
|
|
81
|
+
|
|
82
|
+
for element, entry in mineralogy_dict.items():
|
|
83
|
+
mineral = element
|
|
84
|
+
interval = list(entry.values())
|
|
85
|
+
lower_limit = interval[0]
|
|
86
|
+
upper_limit = interval[1]
|
|
87
|
+
compiled = [lower_limit, upper_limit]
|
|
88
|
+
AnisotropicRocks._mineralogy_cache[rock_name][mineral] = compiled
|
|
89
|
+
|
|
90
|
+
def _compile_mineral_groups(self, rock_name: str, group_dict: dict):
|
|
91
|
+
if rock_name not in AnisotropicRocks._mineral_groups_cache:
|
|
92
|
+
AnisotropicRocks._mineral_groups_cache[rock_name] = {}
|
|
93
|
+
|
|
94
|
+
for group, entry in group_dict.items():
|
|
95
|
+
minerals = entry["minerals"]
|
|
96
|
+
min_val = entry["min"]
|
|
97
|
+
max_val = entry["max"]
|
|
98
|
+
|
|
99
|
+
AnisotropicRocks._mineral_groups_cache[rock_name][group] = {
|
|
100
|
+
"minerals": minerals,
|
|
101
|
+
"min": min_val,
|
|
102
|
+
"max": max_val
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
def _sample_mineralogy(self, rock_name: str, number: int):
|
|
106
|
+
has_groups = rock_name in AnisotropicRocks._mineral_groups_cache
|
|
107
|
+
|
|
108
|
+
if not has_groups:
|
|
109
|
+
# ---- Modus A: flach ----
|
|
110
|
+
mineral_limits = AnisotropicRocks._mineralogy_cache[rock_name]
|
|
111
|
+
mins = [v[0] for v in mineral_limits.values()]
|
|
112
|
+
maxs = [v[1] for v in mineral_limits.values()]
|
|
113
|
+
minerals = list(mineral_limits.keys())
|
|
114
|
+
|
|
115
|
+
comp = self._sample_bounded_simplex_batch(mins, maxs, number)
|
|
116
|
+
return minerals, comp
|
|
117
|
+
|
|
118
|
+
# ---- Modus B: gruppiert ----
|
|
119
|
+
groups = AnisotropicRocks._mineral_groups_cache[rock_name]
|
|
120
|
+
mineral_limits = AnisotropicRocks._mineralogy_cache[rock_name]
|
|
121
|
+
|
|
122
|
+
# 1️⃣ Gruppen + freie Minerale
|
|
123
|
+
group_names = list(groups.keys())
|
|
124
|
+
group_mins = [groups[g]["min"] for g in group_names]
|
|
125
|
+
group_maxs = [groups[g]["max"] for g in group_names]
|
|
126
|
+
|
|
127
|
+
free_minerals = [
|
|
128
|
+
m for m in mineral_limits
|
|
129
|
+
if not any(m in groups[g]["minerals"] for g in groups)
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
free_mins = [mineral_limits[m][0] for m in free_minerals]
|
|
133
|
+
free_maxs = [mineral_limits[m][1] for m in free_minerals]
|
|
134
|
+
|
|
135
|
+
labels = group_names + free_minerals
|
|
136
|
+
mins = group_mins + free_mins
|
|
137
|
+
maxs = group_maxs + free_maxs
|
|
138
|
+
|
|
139
|
+
# 2️⃣ Top-Level-Sampling
|
|
140
|
+
top_comp = self._sample_bounded_simplex_batch(mins, maxs, number)
|
|
141
|
+
|
|
142
|
+
# 3️⃣ Gruppen intern auflösen
|
|
143
|
+
mineral_list = []
|
|
144
|
+
mineral_comp = []
|
|
145
|
+
|
|
146
|
+
for i, label in enumerate(labels):
|
|
147
|
+
frac = top_comp[:, i]
|
|
148
|
+
|
|
149
|
+
if label in groups:
|
|
150
|
+
minerals = groups[label]["minerals"]
|
|
151
|
+
n = len(minerals)
|
|
152
|
+
split = self.rng.dirichlet(np.ones(n), size=number)
|
|
153
|
+
|
|
154
|
+
for j, m in enumerate(minerals):
|
|
155
|
+
mineral_list.append(m)
|
|
156
|
+
mineral_comp.append(frac * split[:, j])
|
|
157
|
+
else:
|
|
158
|
+
mineral_list.append(label)
|
|
159
|
+
mineral_comp.append(frac)
|
|
160
|
+
|
|
161
|
+
comp = np.vstack(mineral_comp).T
|
|
162
|
+
return mineral_list, comp
|
|
163
|
+
|
|
164
|
+
def _sample_bounded_simplex_batch(self, min_vals, max_vals, number):
|
|
165
|
+
min_vals = np.asarray(min_vals, dtype=float)
|
|
166
|
+
max_vals = np.asarray(max_vals, dtype=float)
|
|
167
|
+
n = len(min_vals)
|
|
168
|
+
|
|
169
|
+
# --- Consistency ---
|
|
170
|
+
if min_vals.sum() > 1:
|
|
171
|
+
raise ValueError("Sum of minimum fractions > 1")
|
|
172
|
+
|
|
173
|
+
if max_vals.sum() < 1:
|
|
174
|
+
raise ValueError("Sum of maximum fractions < 1")
|
|
175
|
+
|
|
176
|
+
span = max_vals - min_vals
|
|
177
|
+
samples = np.zeros((number, n))
|
|
178
|
+
|
|
179
|
+
for i in range(number):
|
|
180
|
+
remaining = 1.0 - min_vals.sum()
|
|
181
|
+
order = np.arange(n)
|
|
182
|
+
|
|
183
|
+
self.rng.shuffle(order)
|
|
184
|
+
x = np.zeros(n)
|
|
185
|
+
|
|
186
|
+
for j in order[:-1]:
|
|
187
|
+
upper = min(span[j], remaining)
|
|
188
|
+
val = self.rng.uniform(0, upper)
|
|
189
|
+
x[j] = val
|
|
190
|
+
remaining -= val
|
|
191
|
+
|
|
192
|
+
x[order[-1]] = remaining
|
|
193
|
+
samples[i] = min_vals + x
|
|
194
|
+
|
|
195
|
+
return samples
|
|
196
|
+
|
|
197
|
+
def _collect_mineral_data(self, list_minerals, number):
|
|
198
|
+
_mineral_data = []
|
|
199
|
+
for index, mineral in enumerate(list_minerals):
|
|
200
|
+
data_init = MineralDataGeneration(mineral, number)
|
|
201
|
+
data_mineral = data_init.generate_data()
|
|
202
|
+
is_fixed = data_mineral.shape[0] == 1
|
|
203
|
+
if is_fixed and number > 1:
|
|
204
|
+
data_mineral = pd.concat([data_mineral]*number, ignore_index=True)
|
|
205
|
+
elif not is_fixed and data_mineral.shape[0] != number:
|
|
206
|
+
raise ValueError(
|
|
207
|
+
f"Mineral '{mineral}' returned {data_mineral.shape[0]} rows, "
|
|
208
|
+
f"but expected {number}.")
|
|
209
|
+
_mineral_data.append(data_mineral)
|
|
210
|
+
|
|
211
|
+
return _mineral_data
|
|
212
|
+
|
|
213
|
+
def _extract_mineral_property_data(self, list_minerals, data_mineral, property):
|
|
214
|
+
arrays = [data_mineral[i][property].to_numpy() for i in range(len(list_minerals))]
|
|
215
|
+
return np.vstack(arrays)
|
|
216
|
+
|
|
217
|
+
def _extract_element_data(self, data_minerals, list_elements):
|
|
218
|
+
seen = set(list_elements)
|
|
219
|
+
ordered = list(list_elements)
|
|
220
|
+
|
|
221
|
+
for dataset in data_minerals:
|
|
222
|
+
for key in dataset.keys():
|
|
223
|
+
if key.startswith("chemistry."):
|
|
224
|
+
element = key[len("chemistry."):]
|
|
225
|
+
if element not in seen:
|
|
226
|
+
seen.add(element)
|
|
227
|
+
ordered.append(element)
|
|
228
|
+
|
|
229
|
+
return ordered
|
|
230
|
+
|
|
231
|
+
def _extract_oxide_data(self, data_minerals, list_oxides):
|
|
232
|
+
seen = set(list_oxides)
|
|
233
|
+
ordered = list(list_oxides)
|
|
234
|
+
|
|
235
|
+
for dataset in data_minerals:
|
|
236
|
+
for key in dataset.keys():
|
|
237
|
+
if key.startswith("compounds."):
|
|
238
|
+
oxide = key[len("compounds."):]
|
|
239
|
+
if oxide not in seen:
|
|
240
|
+
seen.add(oxide)
|
|
241
|
+
ordered.append(oxide)
|
|
242
|
+
|
|
243
|
+
# Spezialfall Pyrit
|
|
244
|
+
if dataset["mineral"][0] == "Py":
|
|
245
|
+
for oxide in ("Fe2O3", "SO3"):
|
|
246
|
+
if oxide not in seen:
|
|
247
|
+
seen.add(oxide)
|
|
248
|
+
ordered.append(oxide)
|
|
249
|
+
if "FeS2" in seen:
|
|
250
|
+
seen.remove("FeS2")
|
|
251
|
+
ordered = [o for o in ordered if o != "FeS2"]
|
|
252
|
+
|
|
253
|
+
return ordered
|
|
254
|
+
|
|
255
|
+
def _update_chemistry_data(self, _bulk_data, data_minerals, data_composition, element, number):
|
|
256
|
+
n_minerals = len(data_minerals)
|
|
257
|
+
helper = np.zeros((n_minerals, number))
|
|
258
|
+
key_element = "chemistry." + element
|
|
259
|
+
|
|
260
|
+
for i, dataset in enumerate(data_minerals):
|
|
261
|
+
if key_element in dataset:
|
|
262
|
+
helper[i, :] = dataset[key_element].to_numpy()
|
|
263
|
+
|
|
264
|
+
bulk_values = np.sum(data_composition * helper.T, axis=1)
|
|
265
|
+
_bulk_data["w." + element] = bulk_values
|
|
266
|
+
|
|
267
|
+
return _bulk_data
|
|
268
|
+
|
|
269
|
+
def _update_oxide_data(self, bulk_data, list_oxides):
|
|
270
|
+
for oxide in list_oxides:
|
|
271
|
+
cation, anion = self.rock_gen._get_elements_of_compound(compound=oxide)
|
|
272
|
+
if anion == "O":
|
|
273
|
+
key_cation = "w." + cation
|
|
274
|
+
values = self.conversion_factors[oxide]["factor"]*bulk_data[key_cation]
|
|
275
|
+
key_oxide = "w." + oxide
|
|
276
|
+
bulk_data[key_oxide] = values
|
|
277
|
+
else:
|
|
278
|
+
print("There is a non-oxide compound part of the list.")
|
|
279
|
+
|
|
280
|
+
return bulk_data
|
|
281
|
+
|
|
282
|
+
def _assign_mineral_amounts(self, bulk_data, data_amounts):
|
|
283
|
+
for mineral, values in data_amounts.items():
|
|
284
|
+
key_mineral = "phi." + mineral
|
|
285
|
+
bulk_data[key_mineral] = values
|
|
286
|
+
|
|
287
|
+
return bulk_data
|
|
288
|
+
|
|
289
|
+
def collect_geophysical_properties(self, _helper_bulk_data, rho_f, n):
|
|
290
|
+
# Update bulk density data
|
|
291
|
+
(_helper_bulk_data["rho"], _helper_bulk_data["rho_s"],
|
|
292
|
+
_helper_bulk_data["rho_f"]) = self.geophysics.calculate_bulk_density_data(
|
|
293
|
+
v_phi=_helper_bulk_data["porosity"], val_rho=_helper_bulk_data["rho"], val_rho_f=rho_f,
|
|
294
|
+
val_n=n)
|
|
295
|
+
# Update bulk seismic velocity data
|
|
296
|
+
(_helper_bulk_data["vP"], _helper_bulk_data["vS"],
|
|
297
|
+
_helper_bulk_data["vP/vS"]) = self.geophysics.calculate_seismic_velocities(
|
|
298
|
+
val_K=_helper_bulk_data["K"], val_G=_helper_bulk_data["G"], val_rho=_helper_bulk_data["rho"])
|
|
299
|
+
# Update elastic parameter data
|
|
300
|
+
(_helper_bulk_data["E"], _helper_bulk_data["poisson"],
|
|
301
|
+
_helper_bulk_data["lame"]) = self.geophysics.calculate_elastic_parameter_data(
|
|
302
|
+
val_K=_helper_bulk_data["K"], val_G=_helper_bulk_data["G"])
|
|
303
|
+
|
|
304
|
+
return _helper_bulk_data
|
|
305
|
+
|
|
306
|
+
def collect_initial_compositional_data(self, list_minerals, n):
|
|
307
|
+
_helper_elements = []
|
|
308
|
+
_helper_oxides = []
|
|
309
|
+
_mineral_data = self._collect_mineral_data(list_minerals=list_minerals, number=n)
|
|
310
|
+
_helper_elements = self._extract_element_data(data_minerals=_mineral_data, list_elements=_helper_elements)
|
|
311
|
+
_helper_oxides = self._extract_oxide_data(data_minerals=_mineral_data, list_oxides=_helper_oxides)
|
|
312
|
+
|
|
313
|
+
return _mineral_data, _helper_elements, _helper_oxides
|
|
314
|
+
|
|
315
|
+
def collect_initial_bulk_data(self, list_minerals, _mineral_data, _helper_composition):
|
|
316
|
+
_helper_bulk_data = {}
|
|
317
|
+
for property in ["rho", "K", "G", "GR", "PE"]:
|
|
318
|
+
_helper_property = self._extract_mineral_property_data(
|
|
319
|
+
list_minerals=list_minerals, data_mineral=_mineral_data, property=property)
|
|
320
|
+
_helper_bulk_data[property] = np.sum(_helper_composition*_helper_property.T, axis=1)
|
|
321
|
+
|
|
322
|
+
return _helper_bulk_data
|
|
323
|
+
|
|
324
|
+
def update_compositional_bulk_data(
|
|
325
|
+
self, _helper_elements, _helper_bulk_data, _mineral_data, _helper_composition, _helper_oxides,
|
|
326
|
+
_helper_mineral_amounts, n):
|
|
327
|
+
for element in _helper_elements:
|
|
328
|
+
_helper_bulk_data = self._update_chemistry_data(
|
|
329
|
+
_bulk_data=_helper_bulk_data, data_minerals=_mineral_data, data_composition=_helper_composition,
|
|
330
|
+
element=element, number=n)
|
|
331
|
+
# Update oxide data
|
|
332
|
+
_helper_bulk_data = self._update_oxide_data(bulk_data=_helper_bulk_data, list_oxides=_helper_oxides)
|
|
333
|
+
# Update rock composition data
|
|
334
|
+
_helper_bulk_data = self._assign_mineral_amounts(
|
|
335
|
+
bulk_data=_helper_bulk_data, data_amounts=_helper_mineral_amounts)
|
|
336
|
+
|
|
337
|
+
return _helper_bulk_data
|
|
338
|
+
|
|
339
|
+
def generate_dataset(self, number: int = 1, fluid: str = "water", density_fluid=None) -> None:
|
|
340
|
+
if density_fluid is None:
|
|
341
|
+
if fluid == "water":
|
|
342
|
+
density_fluid = 1000
|
|
343
|
+
elif fluid == "oil":
|
|
344
|
+
density_fluid = 800
|
|
345
|
+
elif fluid == "natural gas":
|
|
346
|
+
density_fluid = 750
|
|
347
|
+
|
|
348
|
+
siliciclastics = {"Sandstone"}
|
|
349
|
+
data_yaml = self._load_yaml(rock_name=self.name)
|
|
350
|
+
min_porosity = data_yaml["physical_properties"]["porosity"]["min"]
|
|
351
|
+
max_porosity = data_yaml["physical_properties"]["porosity"]["max"]
|
|
352
|
+
porosity = self.rng.uniform(min_porosity, max_porosity, number)
|
|
353
|
+
mineral_limits = AnisotropicRocks._mineralogy_cache[self.name]
|
|
354
|
+
_limits = {"lower": [], "upper": []}
|
|
355
|
+
|
|
356
|
+
for mineral, values in mineral_limits.items():
|
|
357
|
+
_limits["lower"].append(values[0])
|
|
358
|
+
_limits["upper"].append(values[1])
|
|
359
|
+
|
|
360
|
+
list_minerals = list(AnisotropicRocks._mineralogy_cache[self.name].keys())
|
|
361
|
+
_properties = ["rho", "vP", "vS", "K", "G", "GR", "PE"]
|
|
362
|
+
_bulk_data = {}
|
|
363
|
+
# Collect mineralogical composition data
|
|
364
|
+
n_minerals = len(list_minerals)
|
|
365
|
+
_helper_composition = np.zeros((number, n_minerals))
|
|
366
|
+
_helper_mineral_amounts = {mineral: np.zeros(number) for mineral in list_minerals}
|
|
367
|
+
|
|
368
|
+
_helper_composition = self._sample_bounded_simplex_batch(
|
|
369
|
+
min_vals=_limits["lower"], max_vals=_limits["upper"], number=number)
|
|
370
|
+
_helper_mineral_amounts = {mineral: _helper_composition[:, j] for j, mineral in enumerate(list_minerals)}
|
|
371
|
+
|
|
372
|
+
# Collect mineral data
|
|
373
|
+
_mineral_data, _helper_elements, _helper_oxides = self.collect_initial_compositional_data(
|
|
374
|
+
list_minerals=list_minerals, n=number)
|
|
375
|
+
# Collect bulk data
|
|
376
|
+
_helper_bulk_data = self.collect_initial_bulk_data(
|
|
377
|
+
list_minerals=list_minerals, _mineral_data=_mineral_data, _helper_composition=_helper_composition)
|
|
378
|
+
# Assign porosity data
|
|
379
|
+
_helper_bulk_data["porosity"] = porosity
|
|
380
|
+
# Collect geophysical data
|
|
381
|
+
_helper_bulk_data = self.collect_geophysical_properties(
|
|
382
|
+
_helper_bulk_data=_helper_bulk_data, rho_f=density_fluid, n=number)
|
|
383
|
+
# Update chemistry data
|
|
384
|
+
_helper_bulk_data = self.update_compositional_bulk_data(
|
|
385
|
+
_helper_elements=_helper_elements, _helper_bulk_data=_helper_bulk_data, _mineral_data=_mineral_data,
|
|
386
|
+
_helper_composition=_helper_composition, _helper_oxides=_helper_oxides,
|
|
387
|
+
_helper_mineral_amounts=_helper_mineral_amounts, n=number)
|
|
388
|
+
# Conversion to a pandas dataframe object
|
|
389
|
+
_bulk_data = pd.DataFrame(_helper_bulk_data)
|
|
390
|
+
|
|
391
|
+
return _bulk_data
|
|
392
|
+
|
|
393
|
+
# TEST
|
|
394
|
+
if __name__ == "__main__":
|
|
395
|
+
DEFAULT_DATA = AnisotropicRocks(name="Shale", random_seed=42).generate_dataset(number=10)
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*-coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
#-----------------------------------------------
|
|
5
|
+
|
|
6
|
+
# Name: common.py
|
|
7
|
+
# Author: Maximilian A. Beeskow
|
|
8
|
+
# Version: 1.0
|
|
9
|
+
# Date: 15.12.2025
|
|
10
|
+
|
|
11
|
+
#-----------------------------------------------
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
Module: common.py
|
|
15
|
+
This module contains several routines that are commonly used by the different rock-related modules.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# PACKAGES
|
|
19
|
+
import re
|
|
20
|
+
|
|
21
|
+
# MODULES
|
|
22
|
+
from ..chemistry.common import PeriodicSystem
|
|
23
|
+
|
|
24
|
+
class RockGeneration:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.elements = {
|
|
27
|
+
"H": PeriodicSystem(name="H").get_data(),
|
|
28
|
+
"C": PeriodicSystem(name="C").get_data(),
|
|
29
|
+
"O": PeriodicSystem(name="O").get_data(),
|
|
30
|
+
"Na": PeriodicSystem(name="Na").get_data(),
|
|
31
|
+
"Mg": PeriodicSystem(name="Mg").get_data(),
|
|
32
|
+
"Al": PeriodicSystem(name="Al").get_data(),
|
|
33
|
+
"Si": PeriodicSystem(name="Si").get_data(),
|
|
34
|
+
"S": PeriodicSystem(name="S").get_data(),
|
|
35
|
+
"Cl": PeriodicSystem(name="Cl").get_data(),
|
|
36
|
+
"K": PeriodicSystem(name="K").get_data(),
|
|
37
|
+
"Ca": PeriodicSystem(name="Ca").get_data(),
|
|
38
|
+
"Mn": PeriodicSystem(name="Mn").get_data(),
|
|
39
|
+
"Fe": PeriodicSystem(name="Fe").get_data(),
|
|
40
|
+
"Ni": PeriodicSystem(name="Ni").get_data(),
|
|
41
|
+
"U": PeriodicSystem(name="U").get_data()}
|
|
42
|
+
|
|
43
|
+
def _parse_formula(self, formula: str):
|
|
44
|
+
pattern = r"([A-Z][a-z]?)(\d*)"
|
|
45
|
+
matches = re.findall(pattern, formula)
|
|
46
|
+
|
|
47
|
+
composition = {}
|
|
48
|
+
for elem, amount in matches:
|
|
49
|
+
amount = int(amount) if amount else 1
|
|
50
|
+
composition[elem] = composition.get(elem, 0) + amount
|
|
51
|
+
|
|
52
|
+
return composition
|
|
53
|
+
|
|
54
|
+
def _get_elements_of_compound(self, compound: str) -> str:
|
|
55
|
+
elements = re.findall(r"[A-Z][a-z]?", compound)
|
|
56
|
+
first = elements[0]
|
|
57
|
+
last = elements[-1]
|
|
58
|
+
|
|
59
|
+
return first, last
|
|
60
|
+
|
|
61
|
+
def _get_cation_element(self, oxide: str) -> str:
|
|
62
|
+
first = oxide[0]
|
|
63
|
+
if len(oxide) > 1 and oxide[1].islower():
|
|
64
|
+
return oxide[:2]
|
|
65
|
+
|
|
66
|
+
return first
|
|
67
|
+
|
|
68
|
+
def _get_anion_element(self, compound: str) -> str:
|
|
69
|
+
elements = re.findall(r"[A-Z][a-z]?", compound)
|
|
70
|
+
last = elements[-1]
|
|
71
|
+
return last
|
|
72
|
+
|
|
73
|
+
def _determine_oxide_conversion_factors(self):
|
|
74
|
+
list_oxides = [
|
|
75
|
+
"H2O", "CO", "CO2", "Na2O", "MgO", "Al2O3", "SiO2", "Cl2O", "K2O", "CaO", "MnO", "Mn2O3", "MnO2", "MnO3",
|
|
76
|
+
"Mn2O7", "FeO", "Fe2O3", "FeO3", "NiO", "Ni2O3", "TiO2", "Ti2O3", "VO", "V2O3", "VO2", "V2O10", "CrO",
|
|
77
|
+
"Cr2O3", "CrO3", "CoO", "Co2O3", "Cu2O", "CuO", "ZnO", "GeO2", "As2O3", "As2O10", "ZrO2", "Nb2O3", "Nb2O10",
|
|
78
|
+
"MoO", "Mo2O3", "MoO2", "Mo2O10", "MoO3", "CdO", "SnO", "SnO2", "Sb2O3", "Sb2O10", "TeO2", "TeO3", "Ta2O10",
|
|
79
|
+
"WO", "W2O3", "WO2", "W2O10", "WO3", "Au2O", "Au2O3", "Tl2O", "Tl2O3", "PbO", "PbO2", "Bi2O3", "Bi2O10",
|
|
80
|
+
"U2O3", "UO2", "U2O10", "UO3", "Nb2O5", "Ta2O5", "SO", "SO2", "SO3"]
|
|
81
|
+
mass_oxygen = self.elements["O"][2]
|
|
82
|
+
_conversion_factors = {}
|
|
83
|
+
for oxide in list_oxides:
|
|
84
|
+
_conversion_factors[oxide] = self._parse_formula(formula=oxide)
|
|
85
|
+
cation = self._get_cation_element(oxide=oxide)
|
|
86
|
+
if cation in self.elements:
|
|
87
|
+
mass_cation = self.elements[cation][2]
|
|
88
|
+
_conversion_factors[oxide]["factor"] = (_conversion_factors[oxide][cation]*mass_cation +
|
|
89
|
+
_conversion_factors[oxide]["O"]*mass_oxygen)/(
|
|
90
|
+
_conversion_factors[oxide][cation]*mass_cation)
|
|
91
|
+
else:
|
|
92
|
+
pass
|
|
93
|
+
#print(self.name, ": cation", cation, "not found in chemical container.")
|
|
94
|
+
|
|
95
|
+
return _conversion_factors
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*-coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
#-----------------------------------------------
|
|
5
|
+
|
|
6
|
+
# Name: config.py
|
|
7
|
+
# Author: Maximilian A. Beeskow
|
|
8
|
+
# Version: 1.0
|
|
9
|
+
# Date: 03.12.2025
|
|
10
|
+
|
|
11
|
+
#-----------------------------------------------
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
Module: config.py
|
|
15
|
+
Defines configuration parameters for synthetic rock data generation.
|
|
16
|
+
Used by analysis and synthesis modules within gebpy.core.rocks.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
# PACKAGES
|
|
20
|
+
from typing import Optional
|
|
21
|
+
|
|
22
|
+
# CODE
|
|
23
|
+
class RockConfiguration:
|
|
24
|
+
"""
|
|
25
|
+
Configuration of the rock data generation.
|
|
26
|
+
- name: rock name (e.g., 'Sandstone')
|
|
27
|
+
- n_datapoints: Number of generated data points (> 0)
|
|
28
|
+
- random_seed: integer seed; if None, defaults to 42
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
name: str,
|
|
34
|
+
n_datapoints: int,
|
|
35
|
+
random_seed: Optional[int] = None
|
|
36
|
+
) -> None:
|
|
37
|
+
self.name = name
|
|
38
|
+
self.n_datapoints = n_datapoints
|
|
39
|
+
self.random_seed = 42 if random_seed is None else random_seed
|
|
40
|
+
self._validate()
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
return (
|
|
44
|
+
f"<RockConfiguration name={self.name!r}, "
|
|
45
|
+
f"n_datapoints={self.n_datapoints}, "
|
|
46
|
+
f"random_seed={self.random_seed}>"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
def set_name(self, new_name: str) -> None:
|
|
50
|
+
if not new_name:
|
|
51
|
+
raise ValueError("Rock name must not be empty. Please assign a valid rock name.")
|
|
52
|
+
self.name = new_name
|
|
53
|
+
|
|
54
|
+
def set_number_of_datapoints(self, new_n_datapoints: int) -> None:
|
|
55
|
+
if not isinstance(new_n_datapoints, int):
|
|
56
|
+
raise TypeError("n_datapoints must be an integer.")
|
|
57
|
+
if new_n_datapoints <= 0:
|
|
58
|
+
raise ValueError("The number of data points must be positive.")
|
|
59
|
+
self.n_datapoints = new_n_datapoints
|
|
60
|
+
|
|
61
|
+
def set_random_seed(self, new_random_seed: Optional[int]) -> None:
|
|
62
|
+
self.random_seed = 42 if new_random_seed is None else new_random_seed
|
|
63
|
+
if self.random_seed < 0:
|
|
64
|
+
raise ValueError("The random number seed must be >= 0 or None.")
|
|
65
|
+
|
|
66
|
+
def _validate(self) -> None:
|
|
67
|
+
if not self.name:
|
|
68
|
+
raise ValueError("Rock name must not be empty. Please assign a valid rock name.")
|
|
69
|
+
if not isinstance(self.n_datapoints, int):
|
|
70
|
+
raise TypeError("n_datapoints must be an integer.")
|
|
71
|
+
if self.n_datapoints <= 0:
|
|
72
|
+
raise ValueError("The number of data points must be positive.")
|
|
73
|
+
if self.random_seed < 0:
|
|
74
|
+
raise ValueError("The random number seed must be >= 0 or None.")
|
|
75
|
+
|
|
76
|
+
# DEFAULT EXAMPLE
|
|
77
|
+
DEFAULT_CONFIG = RockConfiguration(name="Sandstone", n_datapoints=10)
|