rock-physics-open 0.3.2__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 (145) hide show
  1. rock_physics_open/__init__.py +0 -0
  2. rock_physics_open/equinor_utilities/__init__.py +0 -0
  3. rock_physics_open/equinor_utilities/anisotropy.py +211 -0
  4. rock_physics_open/equinor_utilities/classification_functions/__init__.py +17 -0
  5. rock_physics_open/equinor_utilities/classification_functions/class_stats.py +68 -0
  6. rock_physics_open/equinor_utilities/classification_functions/lin_class.py +53 -0
  7. rock_physics_open/equinor_utilities/classification_functions/mahal_class.py +63 -0
  8. rock_physics_open/equinor_utilities/classification_functions/norm_class.py +73 -0
  9. rock_physics_open/equinor_utilities/classification_functions/poly_class.py +45 -0
  10. rock_physics_open/equinor_utilities/classification_functions/post_prob.py +27 -0
  11. rock_physics_open/equinor_utilities/classification_functions/two_step_classification.py +60 -0
  12. rock_physics_open/equinor_utilities/conversions.py +10 -0
  13. rock_physics_open/equinor_utilities/gen_utilities/__init__.py +11 -0
  14. rock_physics_open/equinor_utilities/gen_utilities/dict_to_float.py +38 -0
  15. rock_physics_open/equinor_utilities/gen_utilities/dim_check_vector.py +113 -0
  16. rock_physics_open/equinor_utilities/gen_utilities/filter_input.py +131 -0
  17. rock_physics_open/equinor_utilities/gen_utilities/filter_output.py +88 -0
  18. rock_physics_open/equinor_utilities/machine_learning_utilities/__init__.py +15 -0
  19. rock_physics_open/equinor_utilities/machine_learning_utilities/base_pressure_model.py +170 -0
  20. rock_physics_open/equinor_utilities/machine_learning_utilities/dummy_vars.py +53 -0
  21. rock_physics_open/equinor_utilities/machine_learning_utilities/exponential_model.py +137 -0
  22. rock_physics_open/equinor_utilities/machine_learning_utilities/import_ml_models.py +77 -0
  23. rock_physics_open/equinor_utilities/machine_learning_utilities/polynomial_model.py +132 -0
  24. rock_physics_open/equinor_utilities/machine_learning_utilities/run_regression.py +209 -0
  25. rock_physics_open/equinor_utilities/machine_learning_utilities/sigmoidal_model.py +241 -0
  26. rock_physics_open/equinor_utilities/optimisation_utilities/__init__.py +19 -0
  27. rock_physics_open/equinor_utilities/optimisation_utilities/opt_subst_utilities.py +455 -0
  28. rock_physics_open/equinor_utilities/snapshot_test_utilities/__init__.py +10 -0
  29. rock_physics_open/equinor_utilities/snapshot_test_utilities/compare_snapshots.py +184 -0
  30. rock_physics_open/equinor_utilities/snapshot_test_utilities/snapshots.py +97 -0
  31. rock_physics_open/equinor_utilities/std_functions/__init__.py +43 -0
  32. rock_physics_open/equinor_utilities/std_functions/backus_ave.py +68 -0
  33. rock_physics_open/equinor_utilities/std_functions/dvorkin_nur.py +77 -0
  34. rock_physics_open/equinor_utilities/std_functions/gassmann.py +165 -0
  35. rock_physics_open/equinor_utilities/std_functions/hashin_shtrikman.py +224 -0
  36. rock_physics_open/equinor_utilities/std_functions/hertz_mindlin.py +51 -0
  37. rock_physics_open/equinor_utilities/std_functions/moduli_velocity.py +67 -0
  38. rock_physics_open/equinor_utilities/std_functions/reflection_eq.py +120 -0
  39. rock_physics_open/equinor_utilities/std_functions/rho.py +69 -0
  40. rock_physics_open/equinor_utilities/std_functions/voigt_reuss_hill.py +149 -0
  41. rock_physics_open/equinor_utilities/std_functions/walton.py +45 -0
  42. rock_physics_open/equinor_utilities/std_functions/wood_brie.py +94 -0
  43. rock_physics_open/equinor_utilities/various_utilities/Equinor_logo.gif +0 -0
  44. rock_physics_open/equinor_utilities/various_utilities/Equinor_logo.ico +0 -0
  45. rock_physics_open/equinor_utilities/various_utilities/__init__.py +24 -0
  46. rock_physics_open/equinor_utilities/various_utilities/display_result_statistics.py +90 -0
  47. rock_physics_open/equinor_utilities/various_utilities/gassmann_dry_mod.py +56 -0
  48. rock_physics_open/equinor_utilities/various_utilities/gassmann_mod.py +56 -0
  49. rock_physics_open/equinor_utilities/various_utilities/gassmann_sub_mod.py +64 -0
  50. rock_physics_open/equinor_utilities/various_utilities/hs_average.py +59 -0
  51. rock_physics_open/equinor_utilities/various_utilities/pressure.py +96 -0
  52. rock_physics_open/equinor_utilities/various_utilities/reflectivity.py +101 -0
  53. rock_physics_open/equinor_utilities/various_utilities/timeshift.py +104 -0
  54. rock_physics_open/equinor_utilities/various_utilities/vp_vs_rho_set_statistics.py +170 -0
  55. rock_physics_open/equinor_utilities/various_utilities/vrh_3_min.py +83 -0
  56. rock_physics_open/fluid_models/__init__.py +9 -0
  57. rock_physics_open/fluid_models/brine_model/__init__.py +5 -0
  58. rock_physics_open/fluid_models/brine_model/brine_properties.py +178 -0
  59. rock_physics_open/fluid_models/gas_model/__init__.py +5 -0
  60. rock_physics_open/fluid_models/gas_model/gas_properties.py +319 -0
  61. rock_physics_open/fluid_models/oil_model/__init__.py +5 -0
  62. rock_physics_open/fluid_models/oil_model/dead_oil_density.py +65 -0
  63. rock_physics_open/fluid_models/oil_model/dead_oil_velocity.py +30 -0
  64. rock_physics_open/fluid_models/oil_model/live_oil_density.py +82 -0
  65. rock_physics_open/fluid_models/oil_model/live_oil_velocity.py +24 -0
  66. rock_physics_open/fluid_models/oil_model/oil_bubble_point.py +69 -0
  67. rock_physics_open/fluid_models/oil_model/oil_properties.py +146 -0
  68. rock_physics_open/sandstone_models/__init__.py +59 -0
  69. rock_physics_open/sandstone_models/cemented_shalysand_sandyshale_models.py +304 -0
  70. rock_physics_open/sandstone_models/constant_cement_models.py +204 -0
  71. rock_physics_open/sandstone_models/constant_cement_optimisation.py +125 -0
  72. rock_physics_open/sandstone_models/contact_cement_model.py +138 -0
  73. rock_physics_open/sandstone_models/curvefit_sandstone_models.py +143 -0
  74. rock_physics_open/sandstone_models/friable_models.py +177 -0
  75. rock_physics_open/sandstone_models/friable_optimisation.py +115 -0
  76. rock_physics_open/sandstone_models/friable_shalysand_sandyshale_models.py +235 -0
  77. rock_physics_open/sandstone_models/patchy_cement_fluid_substitution_model.py +477 -0
  78. rock_physics_open/sandstone_models/patchy_cement_model.py +384 -0
  79. rock_physics_open/sandstone_models/patchy_cement_optimisation.py +254 -0
  80. rock_physics_open/sandstone_models/unresolved_cemented_sandshale_models.py +134 -0
  81. rock_physics_open/sandstone_models/unresolved_friable_sandshale_models.py +126 -0
  82. rock_physics_open/shale_models/__init__.py +19 -0
  83. rock_physics_open/shale_models/dem.py +174 -0
  84. rock_physics_open/shale_models/dem_dual_por.py +61 -0
  85. rock_physics_open/shale_models/kus_tok.py +59 -0
  86. rock_physics_open/shale_models/multi_sca.py +133 -0
  87. rock_physics_open/shale_models/pq.py +102 -0
  88. rock_physics_open/shale_models/sca.py +90 -0
  89. rock_physics_open/shale_models/shale4_mineral.py +147 -0
  90. rock_physics_open/shale_models/shale4_mineral_dem_overlay.py +92 -0
  91. rock_physics_open/span_wagner/__init__.py +5 -0
  92. rock_physics_open/span_wagner/co2_properties.py +444 -0
  93. rock_physics_open/span_wagner/coefficients.py +165 -0
  94. rock_physics_open/span_wagner/equations.py +104 -0
  95. rock_physics_open/span_wagner/tables/__init__.py +0 -0
  96. rock_physics_open/span_wagner/tables/carbon_dioxide_density.npz +0 -0
  97. rock_physics_open/span_wagner/tables/lookup_table.py +33 -0
  98. rock_physics_open/t_matrix_models/Equinor_logo.ico +0 -0
  99. rock_physics_open/t_matrix_models/__init__.py +35 -0
  100. rock_physics_open/t_matrix_models/carbonate_pressure_substitution.py +124 -0
  101. rock_physics_open/t_matrix_models/curvefit_t_matrix_exp.py +123 -0
  102. rock_physics_open/t_matrix_models/curvefit_t_matrix_min.py +86 -0
  103. rock_physics_open/t_matrix_models/parse_t_matrix_inputs.py +297 -0
  104. rock_physics_open/t_matrix_models/run_t_matrix.py +243 -0
  105. rock_physics_open/t_matrix_models/t_matrix_C.py +210 -0
  106. rock_physics_open/t_matrix_models/t_matrix_opt_fluid_sub_exp.py +137 -0
  107. rock_physics_open/t_matrix_models/t_matrix_opt_fluid_sub_petec.py +167 -0
  108. rock_physics_open/t_matrix_models/t_matrix_opt_forward_model_exp.py +76 -0
  109. rock_physics_open/t_matrix_models/t_matrix_opt_forward_model_min.py +89 -0
  110. rock_physics_open/t_matrix_models/t_matrix_parameter_optimisation_exp.py +176 -0
  111. rock_physics_open/t_matrix_models/t_matrix_parameter_optimisation_min.py +162 -0
  112. rock_physics_open/t_matrix_models/t_matrix_vector/__init__.py +12 -0
  113. rock_physics_open/t_matrix_models/t_matrix_vector/array_functions.py +75 -0
  114. rock_physics_open/t_matrix_models/t_matrix_vector/calc_c_eff.py +163 -0
  115. rock_physics_open/t_matrix_models/t_matrix_vector/calc_isolated.py +95 -0
  116. rock_physics_open/t_matrix_models/t_matrix_vector/calc_kd.py +40 -0
  117. rock_physics_open/t_matrix_models/t_matrix_vector/calc_kd_eff.py +116 -0
  118. rock_physics_open/t_matrix_models/t_matrix_vector/calc_kd_uuv.py +18 -0
  119. rock_physics_open/t_matrix_models/t_matrix_vector/calc_pressure.py +140 -0
  120. rock_physics_open/t_matrix_models/t_matrix_vector/calc_t.py +71 -0
  121. rock_physics_open/t_matrix_models/t_matrix_vector/calc_td.py +42 -0
  122. rock_physics_open/t_matrix_models/t_matrix_vector/calc_theta.py +43 -0
  123. rock_physics_open/t_matrix_models/t_matrix_vector/calc_x.py +33 -0
  124. rock_physics_open/t_matrix_models/t_matrix_vector/calc_z.py +50 -0
  125. rock_physics_open/t_matrix_models/t_matrix_vector/check_and_tile.py +43 -0
  126. rock_physics_open/t_matrix_models/t_matrix_vector/g_tensor.py +140 -0
  127. rock_physics_open/t_matrix_models/t_matrix_vector/iso_av.py +60 -0
  128. rock_physics_open/t_matrix_models/t_matrix_vector/iso_ave_all.py +55 -0
  129. rock_physics_open/t_matrix_models/t_matrix_vector/pressure_input.py +44 -0
  130. rock_physics_open/t_matrix_models/t_matrix_vector/t_matrix_vec.py +278 -0
  131. rock_physics_open/t_matrix_models/t_matrix_vector/velocity_vti_angles.py +81 -0
  132. rock_physics_open/t_matrix_models/tmatrix_python.dll +0 -0
  133. rock_physics_open/t_matrix_models/tmatrix_python.so +0 -0
  134. rock_physics_open/ternary_plots/__init__.py +3 -0
  135. rock_physics_open/ternary_plots/gen_ternary_plot.py +73 -0
  136. rock_physics_open/ternary_plots/shale_prop_ternary.py +337 -0
  137. rock_physics_open/ternary_plots/ternary_patches.py +277 -0
  138. rock_physics_open/ternary_plots/ternary_plot_utilities.py +197 -0
  139. rock_physics_open/ternary_plots/unconventionals_ternary.py +75 -0
  140. rock_physics_open/version.py +34 -0
  141. rock_physics_open-0.3.2.dist-info/METADATA +90 -0
  142. rock_physics_open-0.3.2.dist-info/RECORD +145 -0
  143. rock_physics_open-0.3.2.dist-info/WHEEL +5 -0
  144. rock_physics_open-0.3.2.dist-info/licenses/LICENSE +165 -0
  145. rock_physics_open-0.3.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,113 @@
1
+ from typing import Any, overload
2
+
3
+ import numpy as np
4
+ import numpy.typing as npt
5
+ import pandas as pd
6
+
7
+
8
+ @overload
9
+ def dim_check_vector(
10
+ args: list[Any] | tuple[Any, ...],
11
+ force_type: np.dtype | None = ...,
12
+ ) -> list[npt.NDArray[Any] | pd.DataFrame]:
13
+ """Overload for when the input is a list or tuple."""
14
+
15
+
16
+ @overload
17
+ def dim_check_vector(
18
+ args: pd.DataFrame,
19
+ force_type: np.dtype | None = ...,
20
+ ) -> pd.DataFrame:
21
+ """Overload for when the input is a pandas DataFrame."""
22
+
23
+
24
+ @overload
25
+ def dim_check_vector(
26
+ args: npt.NDArray[Any],
27
+ force_type: np.dtype | None = ...,
28
+ ) -> npt.NDArray[Any]:
29
+ """Overload for when the input is a numpy array."""
30
+
31
+
32
+ def dim_check_vector(
33
+ args: list[Any] | tuple[Any, ...] | npt.NDArray[Any] | pd.DataFrame,
34
+ force_type: np.dtype | None = None,
35
+ ) -> npt.NDArray[Any] | pd.DataFrame | list[npt.NDArray[Any] | pd.DataFrame]:
36
+ """
37
+ Check that all inputs are of the same (one-dimensional) size. Raise ValueError in case there are several lengths
38
+ present in the inputs. All inputs will be checked and possibly expanded to common length. Only the first dimension
39
+ is harmonised.
40
+
41
+ Parameters
42
+ ----------
43
+ args : list or tuple
44
+ Input list or tuple of scalars, numpy arrays or pandas data frames of numerical or boolean type.
45
+ force_type : np.dtype
46
+ Force all outputs to be of a specific dtype.
47
+
48
+ Returns
49
+ -------
50
+ output_args : list
51
+ List of inputs where all are of the same length.
52
+ """
53
+ single_types = (np.ndarray, pd.DataFrame)
54
+ iterable_types = (list, tuple)
55
+ allowed_types = single_types + iterable_types
56
+ if not isinstance(args, allowed_types): # pyright: ignore[reportUnnecessaryIsInstance] | Kept for backward compatibility
57
+ raise ValueError("dim_check_vector: unknown input type: {}".format(type(args))) # pyright: ignore[reportUnreachable] | Kept for backward compatibility
58
+
59
+ # Single array or dataframe is just returned
60
+ if isinstance(args, single_types):
61
+ if force_type is not None:
62
+ try:
63
+ args = args.astype(force_type)
64
+ except ValueError:
65
+ raise ValueError(
66
+ "dim_check_vector: not possible to force dtype to {}".format(
67
+ force_type
68
+ )
69
+ )
70
+ return args
71
+
72
+ # If any input is a scalar, make it into an array
73
+ if force_type is not None:
74
+ try:
75
+ args = [
76
+ np.array(item, ndmin=1, dtype=force_type)
77
+ if np.isscalar(item)
78
+ else item.astype(force_type)
79
+ for item in args
80
+ ]
81
+ except ValueError:
82
+ raise ValueError(
83
+ "dim_check_vector: not possible to force dtype to {}".format(force_type)
84
+ )
85
+ else:
86
+ args = [np.array(item, ndmin=1) if np.isscalar(item) else item for item in args]
87
+
88
+ # Can now test for length - must either be a scalar or have the same length
89
+ max_length: int = np.max([item.shape[0] for item in args])
90
+ if not np.all([item.shape[0] == max_length or item.shape[0] == 1 for item in args]):
91
+ raise ValueError(
92
+ "dim_check_vector: Unequal array lengths in input to dim_check_vector"
93
+ )
94
+
95
+ output_arg: list[npt.NDArray[Any] | pd.DataFrame] = []
96
+ for item in args:
97
+ if item.shape[0] == max_length:
98
+ output_arg.append(item)
99
+ else:
100
+ item_dim = item.ndim
101
+ repeat_tuple = tuple([max_length] + [1] * (item_dim - 1))
102
+ if isinstance(item, pd.DataFrame):
103
+ output_arg.append(
104
+ pd.DataFrame(
105
+ np.tile(np.array(item), repeat_tuple),
106
+ columns=item.columns,
107
+ index=np.arange(max_length),
108
+ )
109
+ )
110
+ else:
111
+ output_arg.append(np.tile(item, repeat_tuple))
112
+
113
+ return output_arg
@@ -0,0 +1,131 @@
1
+ from sys import byteorder
2
+ from typing import Any
3
+
4
+ import numpy as np
5
+ import numpy.typing as npt
6
+ import pandas as pd
7
+
8
+ WRONG_BYTEORDER = ">" if byteorder == "little" else "<"
9
+
10
+
11
+ def filter_input_log(
12
+ args: list[Any] | tuple[Any, ...] | npt.NDArray[Any] | pd.DataFrame,
13
+ working_int: npt.NDArray[Any] | None = None,
14
+ negative: bool = False,
15
+ no_zero: bool = False,
16
+ positive: bool = True,
17
+ ) -> tuple[npt.NDArray[np.bool_], list[npt.NDArray[Any] | pd.DataFrame]]:
18
+ """
19
+ Check for valid input values in numpy arrays or pandas data frames. Default behaviour is to
20
+ identify missing values - assumed to be NaN and Inf. Other conditions
21
+ can be stated in the key word arguments. Unknown conditions are ignored and a warning
22
+ is issued. Run dim_check_vector to make sure that all inputs have the same length.
23
+ Erroneous values in a sample in one log will remove the sample from all the logs.
24
+ All inputs must have the same array length (data frames the same number of indices).
25
+
26
+ Parameters
27
+ ----------
28
+ args : list or tuple or np.ndarray or pd.DataFrame
29
+ Inputs to be filtered, single array or dataframe or lists of arrays or data frames.
30
+ working_int : np.ndarray
31
+ Valid positions are shown as values > 0.
32
+ negative : bool
33
+ Positive values are excluded (zero values are retained).
34
+ no_zero : bool
35
+ Zero values are excluded.
36
+ positive : bool
37
+ Negative values are excluded.
38
+
39
+ Returns
40
+ -------
41
+ tuple
42
+ idx, output_args : (np.ndarray, list)
43
+ indices of valid values [bool],
44
+ list of input arrays at valid indices.
45
+ """
46
+ type_error = "filter_input_log: unknown input data type: {}".format(type(args))
47
+ size_error = "filter_input_log: inputs of different length"
48
+
49
+ if not isinstance(args, (list, tuple, np.ndarray, pd.DataFrame)): # pyright: ignore[reportUnnecessaryIsInstance] | Kept for backward compatibility
50
+ raise ValueError(type_error) # pyright: ignore[reportUnreachable] | Kept for backward compatibility
51
+ # Make sure that 'args' is iterable
52
+ if isinstance(args, (np.ndarray, pd.DataFrame)):
53
+ args = [args]
54
+
55
+ # Input tuple
56
+ if isinstance(args, tuple):
57
+ args = list(args)
58
+
59
+ # Need to preserve original inputs
60
+ input_args = args.copy()
61
+
62
+ # Test that inputs are of the right types and the same length
63
+ if not np.all([isinstance(log, (np.ndarray, pd.DataFrame)) for log in args]):
64
+ raise ValueError(type_error)
65
+ if not np.all([log.shape[0] == args[0].shape[0] for log in args]):
66
+ raise ValueError(size_error)
67
+
68
+ # Generate pandas series from numpy arrays
69
+ args = [pd.Series(log) if isinstance(log, np.ndarray) else log for log in args]
70
+ # Merge into a data frame
71
+ logs = pd.concat(args, axis=1)
72
+
73
+ # If any of the input logs are of type boolean, False means that they should not be included,
74
+ # regardless of filter flags
75
+ # https://github.com/pandas-dev/pandas/issues/32432
76
+ # idx = ~logs.any(bool_only=True, axis=1)
77
+ # Need to do it the cumbersome way for the time being
78
+ bool_col = logs.dtypes == "bool"
79
+ if any(bool_col):
80
+ idx = ~logs.loc[:, logs.columns[bool_col]].any(axis=1)
81
+ logs.drop(columns=logs.columns[bool_col], inplace=True)
82
+ else:
83
+ idx = pd.Series(index=logs.index, data=np.zeros_like(logs.index).astype(bool))
84
+
85
+ # Standard checks: NaN and Inf
86
+ idx = np.logical_or(idx, logs.isna().any(axis=1))
87
+ idx = np.logical_or(idx, logs.isin([np.inf, -np.inf]).any(axis=1))
88
+
89
+ # Remove columns with dtype that is not numeric
90
+ obj_col = [dd.kind not in ["i", "u", "f", "c"] for dd in logs.dtypes]
91
+ logs.drop(columns=logs.columns[obj_col], inplace=True)
92
+
93
+ # Checks according to the input options input_dict
94
+ # Only consider working interval if it is included or set to some value
95
+ if working_int is not None and not np.all(working_int == 0):
96
+ idx = np.logical_or(idx, working_int == 0)
97
+ if negative:
98
+ # noinspection PyTypeChecker
99
+ idx = np.logical_or(idx, (logs >= 0.0).all(axis=1))
100
+ # idx = np.logical_or(idx, logs.loc[logs > 0.0]).any(axis=1)
101
+ if no_zero:
102
+ idx = np.logical_or(idx, (logs == 0.0).any(axis=1))
103
+ if positive:
104
+ # noinspection PyTypeChecker
105
+ idx = np.logical_or(idx, (logs < 0.0).any(axis=1))
106
+
107
+ # Negate idx to identify samples to retain
108
+ idx = np.logical_not(idx)
109
+ num_valid_samples = idx.sum()
110
+ if num_valid_samples == 0:
111
+ raise ValueError("No acceptable input values")
112
+ for i in range(len(input_args)):
113
+ if isinstance(input_args[i], np.ndarray):
114
+ input_args[i] = input_args[i][idx]
115
+ else: # data frame
116
+ # https://pandas.pydata.org/pandas-docs/stable/user_guide/gotchas.html#byte-ordering-issues
117
+
118
+ check_type = (
119
+ np.array([col_type.byteorder for col_type in input_args[i].dtypes])
120
+ == WRONG_BYTEORDER
121
+ )
122
+ if np.any(check_type):
123
+ tmp_array = (
124
+ input_args[i].to_numpy().byteswap().newbyteorder().astype(float)
125
+ )
126
+ cols = input_args[i].columns
127
+ for j in range(check_type.shape[0]):
128
+ if check_type[j]:
129
+ input_args[i][cols[j]] = tmp_array[:, j]
130
+ input_args[i] = input_args[i].loc[idx]
131
+ return np.array(idx), input_args
@@ -0,0 +1,88 @@
1
+ from typing import Any
2
+
3
+ import numpy as np
4
+ import numpy.typing as npt
5
+ import pandas as pd
6
+
7
+
8
+ def filter_output(
9
+ idx_inp: npt.NDArray[np.bool_],
10
+ inp_log: list[npt.NDArray[Any] | pd.DataFrame]
11
+ | tuple[npt.NDArray[Any] | pd.DataFrame, ...]
12
+ | npt.NDArray[Any]
13
+ | pd.DataFrame,
14
+ ) -> list[npt.NDArray[Any] | pd.DataFrame]:
15
+ """
16
+ Function to restore outputs from a plugin to original length and
17
+ with values at correct positions. The logs are assumed to go through
18
+ matching input filtering done by gen_utilities.filter_input_log earlier.
19
+
20
+ Parameters
21
+ ----------
22
+ idx_inp: np.ndarray
23
+ boolean array which is True at locations to be filled, length idx_inp is returned length of
24
+ arrays or data frames.
25
+ inp_log: tuple or list or np.ndarray or pd.DataFrame
26
+ input numpy array(s) or pandas data frame(s), in list or tuple that are to be expanded to original
27
+ length.
28
+
29
+ Returns
30
+ -------
31
+ return_logs : list
32
+ Expanded inputs.
33
+ """
34
+
35
+ def _expand_array(
36
+ idx: npt.NDArray[np.bool_], inp_single_log: npt.NDArray[Any]
37
+ ) -> npt.NDArray[Any]:
38
+ logs = np.ones(idx.shape, dtype=float) * np.nan
39
+ try:
40
+ logs[idx] = inp_single_log.flatten()
41
+ except ValueError:
42
+ # Assume that the dtype of the input log is not fit for casting to float, set to object and retry
43
+ logs = logs.astype(object)
44
+ logs[idx] = inp_single_log
45
+ return logs.reshape(idx.shape)
46
+
47
+ def _expand_df(idx: npt.NDArray[np.bool_], inp_df: pd.DataFrame) -> pd.DataFrame:
48
+ logs = pd.DataFrame(
49
+ columns=inp_df.columns, index=np.arange(idx.shape[0], dtype=np.intp)
50
+ )
51
+ logs.loc[idx] = inp_df
52
+ return logs
53
+
54
+ if not isinstance(inp_log, (list, tuple, np.ndarray, pd.DataFrame)): # pyright: ignore[reportUnnecessaryIsInstance] | Kept for backward compatibility
55
+ raise ValueError( # pyright: ignore[reportUnreachable] | Kept for backward compatibility
56
+ "filter_output: unknown input data type: {}".format(type(inp_log))
57
+ )
58
+ if not isinstance(idx_inp, (list, np.ndarray)): # pyright: ignore[reportUnnecessaryIsInstance] | Kept for backward compatibility
59
+ raise ValueError( # pyright: ignore[reportUnreachable] | Kept for backward compatibility
60
+ "filter_output: unknown filter array data type: {}".format(type(idx_inp))
61
+ )
62
+
63
+ # Make iterable in case of single input
64
+ if isinstance(inp_log, (np.ndarray, pd.DataFrame)):
65
+ inp_log = [inp_log]
66
+
67
+ if isinstance(idx_inp, np.ndarray): # pyright: ignore[reportUnnecessaryIsInstance] | Kept for backward compatibility
68
+ idx_inp_ = [idx_inp]
69
+
70
+ # Possible to simplify?
71
+ if len(idx_inp_) != len(inp_log):
72
+ if len(idx_inp_) == 1:
73
+ idx_inp_ = idx_inp_ * len(inp_log)
74
+ else:
75
+ raise ValueError(
76
+ "filter_output: mismatch between length of filter arrays and inputs: {} and {}".format(
77
+ len(idx_inp), len(inp_log)
78
+ )
79
+ )
80
+
81
+ return_logs: list[npt.NDArray[Any] | pd.DataFrame] = []
82
+ for this_idx, this_log in zip(idx_inp_, inp_log):
83
+ if isinstance(this_log, np.ndarray):
84
+ return_logs.append(_expand_array(this_idx, this_log))
85
+ elif isinstance(this_log, pd.DataFrame): # pyright: ignore[reportUnnecessaryIsInstance] | Kept for backward compatibility
86
+ return_logs.append(_expand_df(this_idx, this_log))
87
+
88
+ return return_logs
@@ -0,0 +1,15 @@
1
+ from .dummy_vars import generate_dummy_vars
2
+ from .exponential_model import ExponentialPressureModel
3
+ from .import_ml_models import import_model
4
+ from .polynomial_model import PolynomialPressureModel
5
+ from .run_regression import run_regression
6
+ from .sigmoidal_model import SigmoidalPressureModel
7
+
8
+ __all__ = [
9
+ "generate_dummy_vars",
10
+ "import_model",
11
+ "run_regression",
12
+ "ExponentialPressureModel",
13
+ "PolynomialPressureModel",
14
+ "SigmoidalPressureModel",
15
+ ]
@@ -0,0 +1,170 @@
1
+ import pickle
2
+ from abc import ABC, abstractmethod
3
+ from typing import Any, Self
4
+
5
+ import numpy as np
6
+
7
+
8
+ class BasePressureModel(ABC):
9
+ """
10
+ Abstract base class for pressure sensitivity models.
11
+
12
+ All pressure models follow the convention:
13
+ - predict(): returns differential change (depleted - in_situ)
14
+ - predict_abs(): returns absolute values for specified case
15
+ - predict_max(): uses model_max_pressure instead of depleted pressure
16
+
17
+ Input validation is delegated to concrete implementations since
18
+ each model has different column requirements.
19
+ """
20
+
21
+ def __init__(self, model_max_pressure: float | None = None, description: str = ""):
22
+ """
23
+ Initialize base pressure model.
24
+
25
+ Parameters
26
+ ----------
27
+ model_max_pressure : float | None
28
+ Maximum pressure for predict_max method. Required for predict_max to work.
29
+ description : str
30
+ Human-readable description of the model instance.
31
+ """
32
+ self._model_max_pressure: float | None = model_max_pressure
33
+ self._description: str = description
34
+
35
+ @property
36
+ def max_pressure(self) -> float | None:
37
+ """Maximum pressure setting for predict_max method."""
38
+ return self._model_max_pressure
39
+
40
+ @property
41
+ def description(self) -> str:
42
+ """Model description."""
43
+ return self._description
44
+
45
+ def predict(self, inp_arr: np.ndarray) -> np.ndarray:
46
+ """
47
+ Predict differential change: result(depleted) - result(in_situ).
48
+
49
+ Parameters
50
+ ----------
51
+ inp_arr : np.ndarray
52
+ Input array with pressure columns and other model-specific parameters.
53
+
54
+ Returns
55
+ -------
56
+ np.ndarray
57
+ Differential change values.
58
+ """
59
+ arr = self.validate_input(inp_arr)
60
+ return self.predict_abs(arr, case="depleted") - self.predict_abs(
61
+ arr, case="in_situ"
62
+ )
63
+
64
+ def predict_max(self, inp_arr: np.ndarray) -> np.ndarray:
65
+ """
66
+ Predict using model_max_pressure instead of depleted pressure.
67
+
68
+ Parameters
69
+ ----------
70
+ inp_arr : np.ndarray
71
+ Input array where last column (depleted pressure) will be replaced.
72
+
73
+ Returns
74
+ -------
75
+ np.ndarray
76
+ Values at model_max_pressure minus values at in_situ pressure.
77
+
78
+ Raises
79
+ ------
80
+ ValueError
81
+ If model_max_pressure is not set.
82
+ """
83
+ if self._model_max_pressure is None:
84
+ raise ValueError('Field "model_max_pressure" is not set')
85
+
86
+ arr = self.validate_input(inp_arr).copy()
87
+ # Replace last column (assumed to be depleted pressure) with max pressure
88
+ arr[:, -1] = self._model_max_pressure
89
+ return self.predict_abs(arr, case="depleted") - self.predict_abs(
90
+ arr, case="in_situ"
91
+ )
92
+
93
+ @abstractmethod
94
+ def validate_input(self, inp_arr: np.ndarray) -> np.ndarray:
95
+ """
96
+ Validate input array format for this specific model.
97
+
98
+ Parameters
99
+ ----------
100
+ inp_arr : np.ndarray
101
+ Input array to validate.
102
+
103
+ Returns
104
+ -------
105
+ np.ndarray
106
+ Validated input array.
107
+
108
+ Raises
109
+ ------
110
+ ValueError
111
+ If input format is invalid for this model.
112
+ """
113
+
114
+ @abstractmethod
115
+ def predict_abs(self, inp_arr: np.ndarray, case: str = "in_situ") -> np.ndarray:
116
+ """
117
+ Predict absolute values for specified pressure case.
118
+
119
+ Parameters
120
+ ----------
121
+ inp_arr : np.ndarray
122
+ Validated input array.
123
+ case : str
124
+ Either "in_situ" or "depleted" to specify which pressure to use.
125
+
126
+ Returns
127
+ -------
128
+ np.ndarray
129
+ Absolute predicted values.
130
+ """
131
+
132
+ @abstractmethod
133
+ def todict(self) -> dict[str, Any]:
134
+ """
135
+ Convert model to dictionary for serialization.
136
+
137
+ Returns
138
+ -------
139
+ dict[str, Any]
140
+ Dictionary containing all model parameters.
141
+ """
142
+
143
+ def save(self, file: str | bytes) -> None:
144
+ """
145
+ Save model to pickle file.
146
+
147
+ Parameters
148
+ ----------
149
+ file : str | bytes
150
+ File path for saving.
151
+ """
152
+ with open(file, "wb") as f_out:
153
+ pickle.dump(self.todict(), f_out)
154
+
155
+ @classmethod
156
+ @abstractmethod
157
+ def load(cls, file: str | bytes) -> Self:
158
+ """
159
+ Load model from pickle file.
160
+
161
+ Parameters
162
+ ----------
163
+ file : str | bytes
164
+ File path for loading.
165
+
166
+ Returns
167
+ -------
168
+ BasePressureModel
169
+ Loaded model instance.
170
+ """
@@ -0,0 +1,53 @@
1
+ from typing import cast
2
+
3
+ import numpy as np
4
+ import numpy.typing as npt
5
+ import pandas as pd
6
+ from pandas.api.types import is_numeric_dtype
7
+ from sklearn.preprocessing import OneHotEncoder
8
+
9
+
10
+ def generate_dummy_vars(
11
+ inp_frame: pd.DataFrame,
12
+ class_var: str,
13
+ ohe: OneHotEncoder | None = None,
14
+ ) -> tuple[npt.NDArray[np.float64], int, npt.NDArray[np.str_]]:
15
+ """
16
+ From categorical variables generate a one-hot-encoder, i.e. each value in the categorical variable becomes a binary
17
+ variable. See sklearn.preprocessing.OneHotEncoder.
18
+
19
+ Parameters
20
+ ----------
21
+ inp_frame : pd.DataFrame
22
+ Input data containing categorical variables.
23
+ class_var : str
24
+ Name of categorical variable.
25
+ ohe : preprocessing.OneHotEncoder
26
+ One-hot-encoder object.
27
+
28
+ Returns
29
+ -------
30
+ dum_features, no_dummy_cols, dum_var_names : (np.ndarray, int, np.ndarray)
31
+ dum_features: 2D array with transformed dummy variables, no_dummy_cols: number of columns in returned array,
32
+ dum_var_names: automatically generated feature names.
33
+ """
34
+
35
+ if is_numeric_dtype(inp_frame[class_var]):
36
+ # Make sure that the chosen indicator variable contains discrete values
37
+ inp_frame = inp_frame.astype({class_var: "int32"})
38
+
39
+ features_in = np.array(inp_frame[class_var]).reshape(-1, 1)
40
+
41
+ if ohe is None:
42
+ classes = features_in
43
+ ohe = OneHotEncoder(categories="auto", sparse_output=False)
44
+ _ = ohe.fit(classes)
45
+
46
+ dum_features = cast( # Casting since scikit-learn is not yet fully typed. `.transform` returns sparse matrix only if `sparse_output=True`.
47
+ npt.NDArray[np.float64],
48
+ ohe.transform(features_in),
49
+ )
50
+ no_dummy_cols = dum_features.shape[1]
51
+ dum_var_names = ohe.get_feature_names_out()
52
+
53
+ return dum_features, no_dummy_cols, dum_var_names