Skip to content

Commit 28e06e5

Browse files
authored
Add pygeosx_tools python package (#1362)
Adding the pygeosx_tools package
1 parent b97d874 commit 28e06e5

File tree

7 files changed

+746
-0
lines changed

7 files changed

+746
-0
lines changed

pygeosx_tools_package/pygeosx_tools/__init__.py

Whitespace-only changes.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
2+
import os
3+
import numpy as np
4+
5+
6+
def save_tables(axes, properties, table_root='./tables', axes_names=[]):
7+
"""
8+
Saves a set of tables in GEOSX format
9+
Notes: The shape of these arrays should match the length of each axis in the specified order
10+
The output directory will be created if it does not exist yet
11+
If axes_names are not supplied, then they will be selected based
12+
on the dimensionality of the grid: 1D=[t]; 3D=[x, y, z]; 4D=[x, y, z, t]
13+
14+
Args:
15+
axes (list): A list of numpy ndarrays defining the table axes
16+
properties (dict): A dict of numpy ndarrays defning the table values
17+
table_root (str): The root path for the output directory
18+
axes_names (list): A list of names for each potential axis (optional)
19+
"""
20+
# Check to see if the axes, properties have consistent shapes
21+
axes_size = tuple([len(x) for x in axes])
22+
axes_dimension = len(axes_size)
23+
for k, p in properties.items():
24+
property_size = np.shape(p)
25+
if (property_size != axes_size):
26+
print('Property:', k)
27+
print('Grid size:', axes_size)
28+
print('Property size', property_size)
29+
raise Exception('Table dimensions do not match proprerties')
30+
31+
# Check the axes names
32+
if axes_names:
33+
if (axes_dimension != len(axes_names)):
34+
print('Axes dimensions:', axes_dimension)
35+
print('Number of axis names provided:', len(axes_names))
36+
raise Exception('The grid dimensions and axes names do not match')
37+
else:
38+
if (axes_dimension == 1):
39+
axes_names = ['t']
40+
elif (axes_dimension == 3):
41+
axes_names = ['x', 'y', 'z']
42+
elif (axes_dimension == 4):
43+
axes_names = ['x', 'y', 'z', 't']
44+
else:
45+
axes_names = ['x%i' % (ii) for ii in range(axes_dimension)]
46+
47+
# Write the axes
48+
os.makedirs(table_root, exist_ok=True)
49+
for g, a in zip(axes, axes_names):
50+
np.savetxt('%s/%s.csv' % (table_root, a),
51+
g,
52+
fmt='%1.5f',
53+
delimiter=',')
54+
55+
for k, p in properties.items():
56+
np.savetxt('%s/%s.csv' % (table_root, k),
57+
np.reshape(p, (-1), order='F'),
58+
fmt='%1.5e',
59+
delimiter=',')
60+
61+
62+
def load_tables(axes_names, property_names, table_root='./tables', extension='csv'):
63+
"""
64+
Load a set of tables in GEOSX format
65+
66+
Args:
67+
axes_names (list): Axis file names in the target directory (with no extension)
68+
property_names (list): Property file names in the target directory (with not extension)
69+
table_root (str): Root path for the table directory
70+
extension (str): Table file extension (default = 'csv')
71+
72+
Returns:
73+
tuple: List of axes values, and dictionary of table values
74+
"""
75+
# Load axes
76+
axes = [np.loadtxt('%s/%s.%s' % (table_root, axis, extension), unpack=True) for axis in axes_names]
77+
N = tuple([len(x) for x in axes])
78+
79+
# Load properties
80+
properties = {p: np.reshape(np.loadtxt('%s/%s.%s' % (table_root, p, extension)), N, order='F') for p in property_names}
81+
82+
return axes, properties
83+
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
2+
import numpy as np
3+
from scipy import stats
4+
5+
6+
def apply_to_bins(fn, position, value, bins, collapse_edges=True):
7+
"""
8+
Apply a function to values that are located within a series of bins
9+
Note: if a bin is empty, this function will fill a nan value
10+
11+
Args:
12+
fn (function): Function that takes a single scalar or array input
13+
position (np.ndarray): A 1D list/array describing the location of each sample
14+
value (np.ndarray): A 1D list/array of values at each location
15+
bins (np.ndarray): The bin edges for the position data
16+
collapse_edges (bool): Controls the behavior of edge-data (default=True)
17+
18+
Returns:
19+
np.ndarray: an array of function results for each bin
20+
"""
21+
# Sort values into bins
22+
Nr = len(bins) + 1
23+
Ibin = np.digitize(position, bins)
24+
25+
if collapse_edges:
26+
Nr -= 2
27+
Ibin -= 1
28+
Ibin[Ibin == -1] = 0
29+
Ibin[Ibin == Nr] = Nr-1
30+
31+
# Apply functions to bins
32+
binned_values = np.zeros(Nr)
33+
for ii in range(Nr):
34+
tmp = (Ibin == ii)
35+
if np.sum(tmp):
36+
binned_values[ii] = fn(value[tmp])
37+
else:
38+
# Empty bin
39+
binned_values[ii] = np.NaN
40+
41+
return binned_values
42+
43+
44+
def extrapolate_nan_values(x, y, slope_scale=0.0):
45+
"""
46+
Fill in any nan values in two 1D arrays by extrapolating
47+
48+
Args:
49+
x (np.ndarray): 1D list/array of positions
50+
y (np.ndarray): 1D list/array of values
51+
slope_scale (float): value to scale the extrapolation slope (default=0.0)
52+
53+
Returns:
54+
np.ndarray: The input array with nan values replaced by extrapolated data
55+
"""
56+
Inan = np.isnan(y)
57+
reg = stats.linregress(x[~Inan], y[~Inan])
58+
y[Inan] = reg[0] * x[Inan] * slope_scale + reg[1]
59+
return y
60+
61+
62+
def get_random_realization(x, bins, value, rand_fill=0, rand_scale=0, slope_scale=0):
63+
"""
64+
Get a random realization for a noisy signal with a set of bins
65+
66+
Args:
67+
x (np.ndarray): 1D list/array of positions
68+
bins (np.ndarray): 1D list/array of bin edges
69+
value (np.ndarray): 1D list/array of values
70+
rand_fill (float): The standard deviation to use where data is not defined (default=0)
71+
rand_scale (float): Value to scale the standard deviation for the realization (default=0)
72+
slope_scale (float): Value to scale the extrapolation slope (default=0.0)
73+
74+
Returns:
75+
np.ndarray: An array containing the random realization
76+
"""
77+
y_mean = apply_to_bins(np.mean, x, value, bins)
78+
y_std = apply_to_bins(np.std, x, value, bins)
79+
80+
# Extrapolate to fill the upper/lower bounds
81+
x_mid = bins[:-1] + 0.5 * (bins[1] - bins[0])
82+
y_mean = extrapolate_nan_values(x_mid, y_mean, slope_scale)
83+
y_std[np.isnan(y_std)] = rand_fill
84+
85+
# Add a random perturbation to the target value to match missing high/lows
86+
y_final = y_mean + (rand_scale * y_std * np.random.randn(len(y_mean)))
87+
return y_final
88+
89+
90+
def get_realizations(x, bins, targets):
91+
"""
92+
Get random realizations for noisy signals on target bins
93+
94+
Args:
95+
x (np.ndarray): 1D list/array of positions
96+
bins (np.ndarray): 1D list/array of bin edges
97+
targets (dict): Dict of geosx target keys, inputs to get_random_realization
98+
99+
Returns:
100+
dict: Dictionary of random realizations
101+
102+
"""
103+
results = {}
104+
for k, t in targets.items():
105+
results[k] = get_random_realization(x, bins, **t)
106+
return results
107+
108+
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
2+
import numpy as np
3+
import re
4+
5+
6+
def parse_las(fname, variable_start='~C', body_start='~A'):
7+
"""
8+
Parse an las format log file
9+
10+
Args:
11+
fname (str): Path to the log file
12+
variable_start (str): A string that indicates the start of variable header information (default = '~CURVE INFORMATION')
13+
body_start (str): a string that indicates the start of the log body (default = '~A')
14+
15+
Returns:
16+
np.ndarray: a dict containing the values and unit definitions for each variable in the log
17+
"""
18+
results = {}
19+
variable_order = []
20+
21+
# The expected format of the varible definition block is:
22+
# name.units code:description
23+
variable_regex = re.compile('\s*([^\.^\s]*)\s*(\.[^ ]*) ([^:]*):(.*)')
24+
25+
with open(fname) as f:
26+
file_location = 0
27+
for line in f:
28+
line = line.split('#')[0]
29+
if line:
30+
# Preamble
31+
if (file_location == 0):
32+
if variable_start in line:
33+
file_location += 1
34+
35+
# Variable definitions
36+
elif (file_location == 1):
37+
# This is not a comment line
38+
if body_start in line:
39+
file_location += 1
40+
else:
41+
match = variable_regex.match(line)
42+
if match:
43+
variable_order.append(match[1])
44+
results[match[1]] = {'units': match[2][0:],
45+
'code': match[3],
46+
'description': match[4],
47+
'values': []}
48+
else:
49+
# As a fall-back use the full line
50+
variable_order.append(line[:-1])
51+
results[line[:-1]] = {'units': '',
52+
'code': '',
53+
'description': '',
54+
'values': []}
55+
56+
# Body
57+
else:
58+
for k, v in zip(variable_order, line.split()):
59+
results[k]['values'].append(float(v))
60+
61+
# Convert values to numpy arrays
62+
for k in results:
63+
results[k]['values'] = np.array(results[k]['values'])
64+
65+
return results
66+
67+
68+
def convert_E_nu_to_K_G(E, nu):
69+
"""
70+
Convert young's modulus and poisson's ratio to bulk and shear modulus
71+
72+
Args:
73+
E (float, np.ndarray): Young's modulus
74+
nu (float, np.ndarray): Poisson's ratio
75+
76+
Returns:
77+
tuple: bulk modulus, shear modulus with same size as inputs
78+
"""
79+
K = E / (3.0 * (1 - 2.0 * nu))
80+
G = E / (2.0 * (1 + nu))
81+
return K, G
82+
83+
84+
def estimate_shmin(z, rho, nu):
85+
"""
86+
Estimate the minimum horizontal stress using the poisson's ratio
87+
88+
Args:
89+
z (float, np.ndarray): Depth
90+
rho (float, np.ndarray): Density
91+
nu (float, np.ndarray): Poisson's ratio
92+
93+
Returns:
94+
float: minimum horizontal stress
95+
"""
96+
k = nu / (1.0 - nu)
97+
sigma_h = k * rho * 9.81 * z
98+
return sigma_h
99+

0 commit comments

Comments
 (0)