geoloop 0.0.1__py3-none-any.whl → 1.0.0__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.
- geoloop/axisym/AxisymetricEL.py +751 -0
- geoloop/axisym/__init__.py +3 -0
- geoloop/bin/Flowdatamain.py +89 -0
- geoloop/bin/Lithologymain.py +84 -0
- geoloop/bin/Loadprofilemain.py +100 -0
- geoloop/bin/Plotmain.py +250 -0
- geoloop/bin/Runbatch.py +81 -0
- geoloop/bin/Runmain.py +86 -0
- geoloop/bin/SingleRunSim.py +928 -0
- geoloop/bin/__init__.py +3 -0
- geoloop/cli/__init__.py +0 -0
- geoloop/cli/batch.py +106 -0
- geoloop/cli/main.py +105 -0
- geoloop/configuration.py +946 -0
- geoloop/constants.py +112 -0
- geoloop/geoloopcore/CoaxialPipe.py +503 -0
- geoloop/geoloopcore/CustomPipe.py +727 -0
- geoloop/geoloopcore/__init__.py +3 -0
- geoloop/geoloopcore/b2g.py +739 -0
- geoloop/geoloopcore/b2g_ana.py +516 -0
- geoloop/geoloopcore/boreholedesign.py +683 -0
- geoloop/geoloopcore/getloaddata.py +112 -0
- geoloop/geoloopcore/pyg_ana.py +280 -0
- geoloop/geoloopcore/pygfield_ana.py +519 -0
- geoloop/geoloopcore/simulationparameters.py +130 -0
- geoloop/geoloopcore/soilproperties.py +152 -0
- geoloop/geoloopcore/strat_interpolator.py +194 -0
- geoloop/lithology/__init__.py +3 -0
- geoloop/lithology/plot_lithology.py +277 -0
- geoloop/lithology/process_lithology.py +695 -0
- geoloop/loadflowdata/__init__.py +3 -0
- geoloop/loadflowdata/flow_data.py +161 -0
- geoloop/loadflowdata/loadprofile.py +325 -0
- geoloop/plotting/__init__.py +3 -0
- geoloop/plotting/create_plots.py +1142 -0
- geoloop/plotting/load_data.py +432 -0
- geoloop/utils/RunManager.py +164 -0
- geoloop/utils/__init__.py +0 -0
- geoloop/utils/helpers.py +841 -0
- geoloop-1.0.0.dist-info/METADATA +120 -0
- geoloop-1.0.0.dist-info/RECORD +46 -0
- geoloop-1.0.0.dist-info/entry_points.txt +2 -0
- geoloop-0.0.1.dist-info/licenses/LICENSE → geoloop-1.0.0.dist-info/licenses/LICENSE.md +2 -1
- geoloop-0.0.1.dist-info/METADATA +0 -10
- geoloop-0.0.1.dist-info/RECORD +0 -6
- {geoloop-0.0.1.dist-info → geoloop-1.0.0.dist-info}/WHEEL +0 -0
- {geoloop-0.0.1.dist-info → geoloop-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from geoloop.configuration import SingleRunConfig
|
|
4
|
+
from geoloop.geoloopcore.strat_interpolator import StratInterpolator, TgInterpolator
|
|
5
|
+
from geoloop.lithology.process_lithology import ProcessLithologyToThermalConductivity
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SoilProperties:
|
|
9
|
+
"""
|
|
10
|
+
Store soil thermal properties, including geotherm (temperature vs. depth)
|
|
11
|
+
and thermal conductivity profiles (depth-dependent or lithology-based).
|
|
12
|
+
|
|
13
|
+
Supports:
|
|
14
|
+
- Depth-based interpolation of ground temperature Tg(z)
|
|
15
|
+
- Depth-based or lithology-based interpolation of k_s(z)
|
|
16
|
+
- Scaling of conductivity profiles
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
Tg: list | float,
|
|
22
|
+
k_s: list | float,
|
|
23
|
+
z_Tg: list = None,
|
|
24
|
+
Tgrad: float = 0.0,
|
|
25
|
+
z_k_s: list = None,
|
|
26
|
+
k_s_scale: float = 1.0,
|
|
27
|
+
lithology_to_k: ProcessLithologyToThermalConductivity = None,
|
|
28
|
+
alfa: float = 1e-6,
|
|
29
|
+
) -> None:
|
|
30
|
+
"""
|
|
31
|
+
Initialize SoilProperties, containing ground temperature and thermal
|
|
32
|
+
conductivity as functions of depth.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
Tg : float or list
|
|
37
|
+
Ground temperature(s) used for interpolation.
|
|
38
|
+
- If array-list: temperature at depths specified by `z_Tg`.
|
|
39
|
+
- If float: constant surface temperature for linear gradient computation.
|
|
40
|
+
k_s : float or list
|
|
41
|
+
Thermal conductivity values (W/m·K), possibly depth-dependent.
|
|
42
|
+
If list-like, entries correspond to depths in `z_k_s`.
|
|
43
|
+
z_Tg : list or None, optional
|
|
44
|
+
Depth values (m) corresponding to `Tg` samples.
|
|
45
|
+
If None, temperature is assumed constant or determined by `Tgrad`.
|
|
46
|
+
Tgrad : float, optional
|
|
47
|
+
Vertical geothermal gradient (K/m). Default is 0 (no gradient).
|
|
48
|
+
z_k_s : list or None, optional
|
|
49
|
+
Depth values (m) corresponding to `k_s` samples.
|
|
50
|
+
If None, conductivity is assumed constant at all depths.
|
|
51
|
+
k_s_scale : float, optional
|
|
52
|
+
Scaling factor applied to thermal conductivity profiles (default 1.0).
|
|
53
|
+
lithology_to_k : lithology_to_k or None, optional
|
|
54
|
+
If provided, conductivity is derived from lithology-based profiles
|
|
55
|
+
using the lithology_to_k lookup structure instead of `k_s`/`z_k_s`.
|
|
56
|
+
alfa : float, optional
|
|
57
|
+
Thermal diffusivity of the ground (m²/s). Default is 1e-6.
|
|
58
|
+
|
|
59
|
+
Notes
|
|
60
|
+
-----
|
|
61
|
+
- If `lithology_to_k` is provided, the lithology-based thermal conductivity
|
|
62
|
+
profile overrides `k_s` and `z_k_s`.
|
|
63
|
+
- This constructor initializes two interpolators:
|
|
64
|
+
* `interpolatorTg` – for temperature vs. depth
|
|
65
|
+
* `interpolatorKs` – for conductivity vs. depth
|
|
66
|
+
"""
|
|
67
|
+
# subsurface temperature and/or geothermal gradient
|
|
68
|
+
self.Tg = Tg
|
|
69
|
+
self.z_Tg = z_Tg
|
|
70
|
+
self.Tgrad = Tgrad
|
|
71
|
+
self.interpolatorTg = TgInterpolator(self.z_Tg, self.Tg, self.Tgrad)
|
|
72
|
+
|
|
73
|
+
# subsurface bulk thermal conductivity
|
|
74
|
+
self.k_s = np.asarray(k_s)
|
|
75
|
+
if z_k_s == None:
|
|
76
|
+
self.z_k_s = np.ones_like(k_s)
|
|
77
|
+
else:
|
|
78
|
+
self.z_k_s = np.asarray(z_k_s)
|
|
79
|
+
self.k_s_scale = k_s_scale
|
|
80
|
+
self.lithology_to_k = lithology_to_k
|
|
81
|
+
|
|
82
|
+
self.alfa = alfa # thermal diffusivity
|
|
83
|
+
|
|
84
|
+
# interpolate thermal conductivity
|
|
85
|
+
if isinstance(self.lithology_to_k, ProcessLithologyToThermalConductivity):
|
|
86
|
+
# lithology-based thermal conductivity profile
|
|
87
|
+
zstart, zend = self.lithology_to_k.get_start_end_depths()
|
|
88
|
+
zval = self.lithology_to_k.get_thermcon_sample_profile(0).kh_bulk
|
|
89
|
+
self.interpolatorKs = StratInterpolator(zend, zval)
|
|
90
|
+
else:
|
|
91
|
+
self.interpolatorKs = StratInterpolator(
|
|
92
|
+
self.z_k_s, self.k_s, stepvalue=False
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def getTg(self, z: float | np.ndarray) -> float | np.ndarray:
|
|
96
|
+
"""Return ground temperature at depth `z`."""
|
|
97
|
+
return self.interpolatorTg.getTg(z)
|
|
98
|
+
|
|
99
|
+
def get_k_s(
|
|
100
|
+
self, zstart: np.ndarray, zend: np.ndarray, isample: int = 0
|
|
101
|
+
) -> np.ndarray:
|
|
102
|
+
"""
|
|
103
|
+
Retrieves and interpolates thermal conductivity-depth profile for depth-segments in the simulation, for the basecase
|
|
104
|
+
in a single simulation or for the specified sample in a stochastic simulation.
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
zstart : np.ndarray
|
|
109
|
+
Array of starting depths of each segment.
|
|
110
|
+
zend : np.ndarray
|
|
111
|
+
Array of ending depths of each segment.
|
|
112
|
+
isample : int, optional
|
|
113
|
+
Index selecting conductivity sample from lithology_to_k (default 0).
|
|
114
|
+
|
|
115
|
+
Returns
|
|
116
|
+
-------
|
|
117
|
+
np.ndarray
|
|
118
|
+
Thermal conductivity-depth profile with depth-resolution for the depth-segments in the simulation.
|
|
119
|
+
"""
|
|
120
|
+
if isinstance(self.lithology_to_k, ProcessLithologyToThermalConductivity):
|
|
121
|
+
zval = self.lithology_to_k.get_thermcon_sample_profile(isample).kh_bulk
|
|
122
|
+
self.interpolatorKs.zval = zval
|
|
123
|
+
|
|
124
|
+
ks = self.interpolatorKs.interp(zstart, zend)
|
|
125
|
+
return ks * self.k_s_scale
|
|
126
|
+
|
|
127
|
+
@classmethod
|
|
128
|
+
def from_config(cls, config: SingleRunConfig) -> "SoilProperties":
|
|
129
|
+
"""
|
|
130
|
+
Build a SoilProperties object from a configuration object.
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
config : SingleRunConfig
|
|
135
|
+
Object containing entries including Tg, k_s, stratigraphy, scaling,
|
|
136
|
+
lithology parameters, and diffusivity.
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
SoilProperties
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
return SoilProperties(
|
|
144
|
+
Tg=config.Tg,
|
|
145
|
+
z_Tg=config.z_Tg,
|
|
146
|
+
Tgrad=config.Tgrad,
|
|
147
|
+
k_s=config.k_s,
|
|
148
|
+
z_k_s=config.z_k_s,
|
|
149
|
+
k_s_scale=config.k_s_scale,
|
|
150
|
+
alfa=config.alfa,
|
|
151
|
+
lithology_to_k=config.lithology_to_k, # <-- already built upstream
|
|
152
|
+
)
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
ArrayLike = Union[np.ndarray, list, float, int]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TgInterpolator:
|
|
9
|
+
"""
|
|
10
|
+
class to manage interpolation of temperature over depth.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, z_Tg: ArrayLike | None, Tg: ArrayLike, Tgrad: float) -> None:
|
|
14
|
+
"""
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
z_Tg : array_like or None
|
|
18
|
+
Depth values (m) at which temperature samples in `Tg` are defined.
|
|
19
|
+
If None, `Tg` is treated as a scalar.
|
|
20
|
+
Tg : float or array_like
|
|
21
|
+
Ground temperature value(s).
|
|
22
|
+
- If float: temperature at surface.
|
|
23
|
+
- If array_like: temperature profile at depths `z_Tg`.
|
|
24
|
+
Tgrad : float
|
|
25
|
+
Geothermal gradient (C/m). Used only when `Tg` is scalar.
|
|
26
|
+
"""
|
|
27
|
+
self.z_Tg = z_Tg
|
|
28
|
+
self.Tg = Tg
|
|
29
|
+
self.Tgrad = Tgrad
|
|
30
|
+
|
|
31
|
+
def interp_Tg(self, z: ArrayLike) -> np.ndarray:
|
|
32
|
+
"""
|
|
33
|
+
Interpolate temperature at depth.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
z : array_like
|
|
38
|
+
Depth(s) in meters.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
np.ndarray
|
|
43
|
+
Interpolated temperature values.
|
|
44
|
+
"""
|
|
45
|
+
return np.interp(z, self.z_Tg, self.Tg)
|
|
46
|
+
|
|
47
|
+
def getTg(self, z: ArrayLike) -> ArrayLike:
|
|
48
|
+
"""
|
|
49
|
+
Get ground temperature at depth.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
z : float or array_like
|
|
54
|
+
Depth(s) in meters.
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
float or np.ndarray
|
|
59
|
+
Temperature at depth.
|
|
60
|
+
|
|
61
|
+
Notes
|
|
62
|
+
-----
|
|
63
|
+
- If `Tg` is scalar: Tg + Tgrad * 0.01 * z
|
|
64
|
+
- If `Tg` is array: interpolated from depth-temperature profile
|
|
65
|
+
"""
|
|
66
|
+
if np.isscalar(self.Tg):
|
|
67
|
+
return self.Tg + self.Tgrad * 0.01 * z
|
|
68
|
+
else:
|
|
69
|
+
return self.interp_Tg(z)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class StratInterpolator:
|
|
73
|
+
"""
|
|
74
|
+
class to manage interpolation of soil properties to averaged segment properties.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(
|
|
78
|
+
self,
|
|
79
|
+
zcoord: ArrayLike,
|
|
80
|
+
zval: ArrayLike,
|
|
81
|
+
nz: int = 10000,
|
|
82
|
+
stepvalue: bool = True,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
zcoord : array_like
|
|
88
|
+
Depth coordinates (m) defining the property profile.
|
|
89
|
+
zval : array_like
|
|
90
|
+
Property values corresponding to `zcoord`.
|
|
91
|
+
nz : int, optional
|
|
92
|
+
Number of points for the internal fine-resolution discretization.
|
|
93
|
+
stepvalue : bool, optional
|
|
94
|
+
If True:
|
|
95
|
+
Interpret `zval` as piecewise-constant over intervals
|
|
96
|
+
(stepwise profile).
|
|
97
|
+
If False:
|
|
98
|
+
Use linear interpolation between points.
|
|
99
|
+
"""
|
|
100
|
+
self.zcoord = zcoord
|
|
101
|
+
self.zval = zval
|
|
102
|
+
self.nz = nz
|
|
103
|
+
|
|
104
|
+
# high-resolution depth array
|
|
105
|
+
self.zp = np.linspace(0, self.zcoord[-1], num=nz)
|
|
106
|
+
self.dz = self.zp[1] - self.zp[0]
|
|
107
|
+
|
|
108
|
+
self.stepvalue = stepvalue
|
|
109
|
+
if self.stepvalue:
|
|
110
|
+
self.init_indices()
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def zval(self) -> np.ndarray:
|
|
114
|
+
return self._zval
|
|
115
|
+
|
|
116
|
+
@zval.setter
|
|
117
|
+
def zval(self, value: ArrayLike) -> None:
|
|
118
|
+
self._zval = value
|
|
119
|
+
|
|
120
|
+
def init_indices(self) -> None:
|
|
121
|
+
"""
|
|
122
|
+
For the high resolution array self.zp get indices on the courser intervalled zcoord array.
|
|
123
|
+
The indices are determined by searchsorted of np, giving the lower index when on interval zlith[0] to zlith[1].
|
|
124
|
+
This is only used if stepvalue=True.
|
|
125
|
+
"""
|
|
126
|
+
self.indexl = self.zcoord.searchsorted(self.zp)
|
|
127
|
+
|
|
128
|
+
def interp(self, zstart: np.ndarray, zend: np.ndarray) -> np.ndarray:
|
|
129
|
+
"""
|
|
130
|
+
Interpolate for zstart, zend arrays of the segments, the average value of zval.
|
|
131
|
+
Compute average property value for each depth segment.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
zstart : np.ndarray
|
|
136
|
+
Segment start depths (m).
|
|
137
|
+
zend : np.ndarray
|
|
138
|
+
Segment end depths (m).
|
|
139
|
+
|
|
140
|
+
Returns
|
|
141
|
+
-------
|
|
142
|
+
np.ndarray
|
|
143
|
+
Average property value per segment.
|
|
144
|
+
"""
|
|
145
|
+
val = zstart * 0.0
|
|
146
|
+
ilstart = max(0, int(zstart[0] / self.dz))
|
|
147
|
+
for i in range(len(zend)):
|
|
148
|
+
valsum = 0
|
|
149
|
+
ilend = min(self.nz, int(round(zend[i] / self.dz)))
|
|
150
|
+
ilrange = max(1, ilend - ilstart)
|
|
151
|
+
if self.stepvalue:
|
|
152
|
+
for il in range(ilrange):
|
|
153
|
+
valsum += self.zval[self.indexl[il + ilstart]]
|
|
154
|
+
val[i] = valsum / ilrange
|
|
155
|
+
else:
|
|
156
|
+
vals = np.interp(self.zp[ilstart:ilend], self.zcoord, self.zval)
|
|
157
|
+
val[i] = np.sum(vals) / ilrange
|
|
158
|
+
ilstart = min(self.nz - 1, ilend)
|
|
159
|
+
return val
|
|
160
|
+
|
|
161
|
+
def interp_plot(self, zstart: np.ndarray, zend: np.ndarray) -> np.ndarray:
|
|
162
|
+
"""
|
|
163
|
+
Generate a high-resolution vector of interpolated property values.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
zstart : np.ndarray
|
|
168
|
+
Segment start depths (m).
|
|
169
|
+
zend : np.ndarray
|
|
170
|
+
Segment end depths (m).
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
np.ndarray
|
|
175
|
+
Interpolated property values along internal grid `zp`.
|
|
176
|
+
"""
|
|
177
|
+
val = np.zeros_like(self.zp)
|
|
178
|
+
ilstart = max(0, int(zstart[0] / self.dz))
|
|
179
|
+
|
|
180
|
+
for i in range(len(zend)):
|
|
181
|
+
ilend = min(self.nz, int(round(zend[i] / self.dz)))
|
|
182
|
+
if ilend > ilstart:
|
|
183
|
+
if self.stepvalue:
|
|
184
|
+
val[ilstart:ilend] = self.zval[self.indexl[ilstart]]
|
|
185
|
+
else:
|
|
186
|
+
vals = np.interp(self.zp[ilstart:ilend], self.zcoord, self.zval)
|
|
187
|
+
val[ilstart:ilend] = vals
|
|
188
|
+
ilstart = ilend
|
|
189
|
+
|
|
190
|
+
# Fill the remaining 0 values with the last used value
|
|
191
|
+
last_nonzero_index = (val != 0).nonzero()[0][-1]
|
|
192
|
+
val[last_nonzero_index + 1 :] = val[last_nonzero_index]
|
|
193
|
+
|
|
194
|
+
return val
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import xarray as xr
|
|
6
|
+
from matplotlib import pyplot as plt
|
|
7
|
+
from matplotlib.colors import to_hex, to_rgb
|
|
8
|
+
|
|
9
|
+
from geoloop.constants import lithology_colors, lithology_names_english
|
|
10
|
+
from geoloop.geoloopcore.strat_interpolator import StratInterpolator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def mix_hex_colors(colors: list[str], fractions: list[float]) -> str:
|
|
14
|
+
"""
|
|
15
|
+
Combine multiple hex colors using given fractional weights.
|
|
16
|
+
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
colors : list of str
|
|
20
|
+
Hex color strings (e.g. '#aabbcc').
|
|
21
|
+
fractions : list of float
|
|
22
|
+
Fractions for each color. Typically sums to 1. Values may be 0..1.
|
|
23
|
+
|
|
24
|
+
Returns
|
|
25
|
+
-------
|
|
26
|
+
str
|
|
27
|
+
Resulting mixed color as a hex string.
|
|
28
|
+
"""
|
|
29
|
+
mixed_color = np.zeros(3)
|
|
30
|
+
for color, fraction in zip(colors, fractions):
|
|
31
|
+
mixed_color += np.array(to_rgb(color)) * fraction
|
|
32
|
+
return to_hex(mixed_color)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def plot_lithology_and_thermcon(
|
|
36
|
+
litho_ds: xr.Dataset,
|
|
37
|
+
kx_plotting_base: np.ndarray,
|
|
38
|
+
interp_obj: StratInterpolator,
|
|
39
|
+
kx_plotting_max: np.ndarray,
|
|
40
|
+
kx_plotting_min: np.ndarray,
|
|
41
|
+
litho_h5path: str | Path,
|
|
42
|
+
out_dir: Path,
|
|
43
|
+
) -> None:
|
|
44
|
+
"""
|
|
45
|
+
Create and save a combined lithology "layer-cake" plot with thermal
|
|
46
|
+
conductivity profiles plotted alongside.
|
|
47
|
+
|
|
48
|
+
The main panel shows lithology as vertically stacked colored bands
|
|
49
|
+
(mixed color when two lithologies are present). A narrow side panel
|
|
50
|
+
shows horizontally stacked bars for lithology fractions. Thermal
|
|
51
|
+
conductivity curves are plotted on a twin x-axis (using the internal
|
|
52
|
+
grid from the StratInterpolator).
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
litho_ds : xarray.Dataset
|
|
57
|
+
Dataset containing fields `depth`, lithology type a `lithology_a`, lithology type b `lithology_b`,
|
|
58
|
+
and the fraction of type a `lithology_a_fraction`.
|
|
59
|
+
kx_plotting_base : np.ndarray
|
|
60
|
+
Fine-grid, interpolated base-case thermal conductivity values (aligned with interp_obj.zp).
|
|
61
|
+
interp_obj : StratInterpolator
|
|
62
|
+
Interpolator object; used for the internal z-grid (zp) and interpolation.
|
|
63
|
+
kx_plotting_max : np.ndarray
|
|
64
|
+
Fine-grid, interpolated maximum thermal conductivity values.
|
|
65
|
+
kx_plotting_min : np.ndarray
|
|
66
|
+
Fine-grid, interpolated minimum thermal conductivity values.
|
|
67
|
+
litho_h5path : str | Path
|
|
68
|
+
Path to the source lithology .h5 file (used to build output filename).
|
|
69
|
+
out_dir : Path
|
|
70
|
+
Directory where the figure will be written.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
None
|
|
75
|
+
|
|
76
|
+
"""
|
|
77
|
+
plt.rcParams.update({"font.size": 14})
|
|
78
|
+
|
|
79
|
+
# Create main + side axes: wide main column + narrow side bar
|
|
80
|
+
fig, (ax_main, ax_side) = plt.subplots(
|
|
81
|
+
1, 2, figsize=(8, 8), gridspec_kw={"width_ratios": [4, 0.8]}
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Prepare lists for plotting
|
|
85
|
+
current_depth = 0
|
|
86
|
+
depths = []
|
|
87
|
+
fractions_a = []
|
|
88
|
+
fractions_b = []
|
|
89
|
+
colors_main = []
|
|
90
|
+
|
|
91
|
+
# Build mixed colors and fraction lists
|
|
92
|
+
for i in range(len(litho_ds["depth"])):
|
|
93
|
+
# depth value for this layer (end depth)
|
|
94
|
+
depth = litho_ds["depth"].values[i]
|
|
95
|
+
|
|
96
|
+
lithology_a = litho_ds["lithology_a"].isel(depth=i).item()
|
|
97
|
+
lithology_b = litho_ds["lithology_b"].isel(depth=i).item()
|
|
98
|
+
fraction_a = litho_ds["lithology_a_fraction"].isel(depth=i).item()
|
|
99
|
+
fraction_b = 1 - fraction_a
|
|
100
|
+
|
|
101
|
+
# Prepare data for the side plot
|
|
102
|
+
depths.append((current_depth + depth) / 2) # Center of the bar
|
|
103
|
+
fractions_a.append(fraction_a)
|
|
104
|
+
fractions_b.append(fraction_b)
|
|
105
|
+
|
|
106
|
+
# Prepare data for the main plot
|
|
107
|
+
mixed_color = mix_hex_colors(
|
|
108
|
+
[lithology_colors[lithology_a], lithology_colors[lithology_b]],
|
|
109
|
+
[fraction_a, fraction_b],
|
|
110
|
+
)
|
|
111
|
+
colors_main.append(mixed_color)
|
|
112
|
+
|
|
113
|
+
current_depth = depth
|
|
114
|
+
|
|
115
|
+
# Plot the main lithology column with mixed colors
|
|
116
|
+
for i in range(len(litho_ds["depth"])):
|
|
117
|
+
depth_start = litho_ds["depth"].values[i - 1] if i > 0 else 0
|
|
118
|
+
depth_end = litho_ds["depth"].values[i]
|
|
119
|
+
ax_main.fill_betweenx(
|
|
120
|
+
[depth_start, depth_end], 0, 1, color=colors_main[i], edgecolor=None
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# Adjust axis limits to cover all the plotted data
|
|
124
|
+
ax_main.set_xlim(0, 1) # Set x-axis to range from 0 to 1 for consistency
|
|
125
|
+
ax_main.set_ylim(
|
|
126
|
+
min(litho_ds["depth"].values), max(litho_ds["depth"].values)
|
|
127
|
+
) # Make sure the depth range covers all data
|
|
128
|
+
|
|
129
|
+
# Calculate depth ranges
|
|
130
|
+
depth_ranges = [
|
|
131
|
+
(litho_ds["depth"].values[i - 1] if i > 0 else 0, litho_ds["depth"].values[i])
|
|
132
|
+
for i in range(len(litho_ds["depth"]))
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
# Create the side plot (horizontally stacked bars for fractions)
|
|
136
|
+
for i, (depth_start, depth_end) in enumerate(depth_ranges):
|
|
137
|
+
bar_height = depth_end - depth_start
|
|
138
|
+
# Left segment (fraction A)
|
|
139
|
+
ax_side.barh(
|
|
140
|
+
y=depth_start, # Start of the depth range
|
|
141
|
+
width=fractions_a[i],
|
|
142
|
+
height=bar_height,
|
|
143
|
+
color=lithology_colors[litho_ds["lithology_a"].isel(depth=i).item()],
|
|
144
|
+
align="edge",
|
|
145
|
+
)
|
|
146
|
+
# Right segment (fraction B), stacked by using left=fraction_a
|
|
147
|
+
ax_side.barh(
|
|
148
|
+
y=depth_start, # Start of the depth range
|
|
149
|
+
width=fractions_b[i],
|
|
150
|
+
height=bar_height,
|
|
151
|
+
left=fractions_a[i],
|
|
152
|
+
color=lithology_colors[litho_ds["lithology_b"].isel(depth=i).item()],
|
|
153
|
+
align="edge",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Style the side axis
|
|
157
|
+
ax_side.set_xlabel("Lithology fractions")
|
|
158
|
+
ax_side.set_xlim(0, 1)
|
|
159
|
+
ax_side.set_xticks([0, 0.5, 1])
|
|
160
|
+
ax_side.set_xticklabels(["0", "0.5", "1.0"])
|
|
161
|
+
ax_side.set_ylim(ax_main.get_ylim()) # Align y-axes
|
|
162
|
+
ax_side.invert_yaxis()
|
|
163
|
+
|
|
164
|
+
# Remove y-axis ticks and labels
|
|
165
|
+
ax_side.set_yticks([]) # Remove ticks
|
|
166
|
+
ax_side.set_yticklabels([]) # Remove labels
|
|
167
|
+
|
|
168
|
+
# Spines visibility
|
|
169
|
+
ax_side.spines["top"].set_visible(True)
|
|
170
|
+
ax_side.spines["right"].set_visible(True)
|
|
171
|
+
ax_side.spines["bottom"].set_visible(True)
|
|
172
|
+
ax_side.spines["left"].set_visible(True)
|
|
173
|
+
|
|
174
|
+
# Plot thermal conductivity on a secondary x-axis for the main plot
|
|
175
|
+
ax_main.set_xticks([])
|
|
176
|
+
ax_main.set_ylabel("Depth [m]")
|
|
177
|
+
ax_kx = ax_main.twiny()
|
|
178
|
+
ax_kx.plot(
|
|
179
|
+
kx_plotting_base, interp_obj.zp, color="black", linestyle="-", label="Basecase"
|
|
180
|
+
)
|
|
181
|
+
ax_kx.plot(kx_plotting_max, interp_obj.zp, color="blue", linestyle=":", label="Max")
|
|
182
|
+
ax_kx.plot(kx_plotting_min, interp_obj.zp, color="blue", linestyle=":", label="Min")
|
|
183
|
+
ax_kx.set_xlabel("Thermal Conductivity [W/mk]")
|
|
184
|
+
ax_kx.invert_yaxis()
|
|
185
|
+
ax_kx.legend(bbox_to_anchor=(0.5, -0.01), title="Thermal Conductivity")
|
|
186
|
+
|
|
187
|
+
# Create legend for lithologies
|
|
188
|
+
handles = []
|
|
189
|
+
labels = []
|
|
190
|
+
used_lithologies = set(litho_ds["lithology_a"].values) | set(
|
|
191
|
+
litho_ds["lithology_b"].values
|
|
192
|
+
)
|
|
193
|
+
for lithology_type in used_lithologies:
|
|
194
|
+
english_name = lithology_names_english.get(lithology_type, lithology_type)
|
|
195
|
+
handles.append(
|
|
196
|
+
plt.Rectangle((0, 0), 1, 1, color=lithology_colors[lithology_type])
|
|
197
|
+
)
|
|
198
|
+
labels.append(lithology_type)
|
|
199
|
+
ax_main.legend(
|
|
200
|
+
handles,
|
|
201
|
+
labels,
|
|
202
|
+
loc="upper right",
|
|
203
|
+
bbox_to_anchor=(0.9, -0.01),
|
|
204
|
+
title="Lithology",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
plt.tight_layout()
|
|
208
|
+
|
|
209
|
+
# Save the figure
|
|
210
|
+
h5_file_name = Path(litho_h5path).name
|
|
211
|
+
fig_name = Path(h5_file_name).stem
|
|
212
|
+
fig_path = out_dir / fig_name
|
|
213
|
+
|
|
214
|
+
plt.savefig(fig_path)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def main_Plot_lithology(litho_h5_path: str) -> None:
|
|
218
|
+
"""
|
|
219
|
+
Main function to load data, process thermal conductivity profiles, and generate lithology plots.
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
litho_h5_path : str
|
|
224
|
+
Path to the .h5 file containing the dataset of subsurface properties.
|
|
225
|
+
|
|
226
|
+
Returns
|
|
227
|
+
--------
|
|
228
|
+
None
|
|
229
|
+
|
|
230
|
+
"""
|
|
231
|
+
base_dir = Path(litho_h5_path).parent
|
|
232
|
+
|
|
233
|
+
# Open dataset (group 'litho_k' expected)
|
|
234
|
+
litho_k_ds = xr.open_dataset(litho_h5_path, group="litho_k", engine="netcdf4")
|
|
235
|
+
|
|
236
|
+
# select only basecase, sample 0
|
|
237
|
+
litho_k_ds_base = litho_k_ds.sel(n_samples=0)
|
|
238
|
+
|
|
239
|
+
z = litho_k_ds_base.depth.values
|
|
240
|
+
zval = litho_k_ds_base.kx.values
|
|
241
|
+
|
|
242
|
+
zend = z
|
|
243
|
+
zstart = z[:-1] * 1
|
|
244
|
+
|
|
245
|
+
# Append 0 at the beginning of zstart (so first segment starts at 0)
|
|
246
|
+
zstart = np.insert(zstart, 0, 0)
|
|
247
|
+
|
|
248
|
+
# Build interpolator & compute fine-grid, interpolated conductivity profiles
|
|
249
|
+
interp_obj = StratInterpolator(zend, zval)
|
|
250
|
+
|
|
251
|
+
# Interpolate basecase thermal conductivity values
|
|
252
|
+
kx_plotting_base = interp_obj.interp_plot(zstart, zend)
|
|
253
|
+
|
|
254
|
+
# Calculate max and min thermal conductivity profiles and interpolate
|
|
255
|
+
kx_max = litho_k_ds.kx.max(dim="n_samples").values
|
|
256
|
+
interp_obj.zval = kx_max
|
|
257
|
+
kx_plotting_max = interp_obj.interp_plot(zstart, zend)
|
|
258
|
+
|
|
259
|
+
kx_min = litho_k_ds.kx.min(dim="n_samples").values
|
|
260
|
+
interp_obj.zval = kx_min
|
|
261
|
+
kx_plotting_min = interp_obj.interp_plot(zstart, zend)
|
|
262
|
+
|
|
263
|
+
# Create the layer cake plot
|
|
264
|
+
plot_lithology_and_thermcon(
|
|
265
|
+
litho_k_ds_base,
|
|
266
|
+
kx_plotting_base,
|
|
267
|
+
interp_obj,
|
|
268
|
+
kx_plotting_max,
|
|
269
|
+
kx_plotting_min,
|
|
270
|
+
litho_h5_path,
|
|
271
|
+
base_dir,
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
if __name__ == "__main__":
|
|
276
|
+
litho_h5_path = sys.argv[1]
|
|
277
|
+
main_Plot_lithology(litho_h5_path)
|