Source code for gempy.core.data_modules.stack

from typing import Union, Iterable

import numpy as np
import pandas as pn

from gempy.utils.meta import _setdoc_pro


[docs]class Faults(object): """ Class that encapsulate faulting related content. Mainly, which surfaces/surfaces are faults. The fault network ---i.e. which faults offset other faults---and fault types---finite vs infinite. Args: series_fault(str, list[str]): Name of the series which are faults rel_matrix (numpy.array[bool]): 2D Boolean array with boolean logic. Rows affect (offset) columns Attributes: df (:class:`pn.core.frame.DataFrames`): Pandas data frame containing the series as index and if they are faults or not (otherwise they are lithologies) and in case of being fault if is finite faults_relations_df (:class:`pn.core.frame.DataFrames`): Pandas data frame containing the offsetting relations between each fault and the rest of the series (either other faults or lithologies) n_faults (int): Number of faults in the object """
[docs] def __init__(self, series_fault=None, rel_matrix=None): self.df = pn.DataFrame(np.array([[False, False]]), index=pn.CategoricalIndex(['Default series']), columns=['isFault', 'isFinite'], dtype=bool) self.faults_relations_df = pn.DataFrame(index=pn.CategoricalIndex(['Default series']), columns=pn.CategoricalIndex(['Default series', '']), dtype='bool') self.set_is_fault(series_fault=series_fault) self.set_fault_relation(rel_matrix=rel_matrix) self.n_faults = 0 self._offset_faults = False
def __repr__(self): return self.df.to_string() def _repr_html_(self): return self.df.to_html()
[docs] def set_is_fault(self, series_fault: Union[str, list, np.ndarray] = None, toggle=False, offset_faults=False): """ Set a flag to the series that are faults. Args: series_fault(str, list[str]): Name of the series which are faults toggle (bool): if True, passing a name which is already True will set it False. offset_faults (bool): If True by default faults offset other faults Returns: :class:`gempy.core.data_modules.stack.Faults` """ series_fault = np.atleast_1d(series_fault) self.df['isFault'].fillna(False, inplace=True) if series_fault is None: series_fault = self.count_faults(self.df.index) if series_fault[0] is not None: assert np.isin(series_fault, self.df.index).all(), 'series_faults must already ' \ 'exist in the the series df.' if toggle is True: self.df.loc[series_fault, 'isFault'] = self.df.loc[series_fault, 'isFault'] ^ True else: self.df.loc[series_fault, 'isFault'] = True self.df['isFinite'] = np.bitwise_and(self.df['isFault'], self.df['isFinite']) self.set_default_faults_relations(offset_faults) # Update default fault relations for a_series in series_fault: col_pos = self.faults_relations_df.columns.get_loc(a_series) # set the faults offset all younger self.faults_relations_df.iloc[col_pos, col_pos + 1:] = True if offset_faults is False: # set the faults does not offset the younger faults self.faults_relations_df.iloc[col_pos] = ~self.df['isFault'] & \ self.faults_relations_df.iloc[col_pos] self.n_faults = self.df['isFault'].sum() return self
def set_default_faults_relations(self, offset_faults:bool=None): if offset_faults is not None: self._offset_faults = offset_faults offset_faults = self._offset_faults try: # Update default fault relations for a_series in self.df.groupby('isFault').get_group(True).index: col_pos = self.faults_relations_df.columns.get_loc(a_series) # set the faults offset all younger self.faults_relations_df.iloc[col_pos, col_pos + 1:] = True if offset_faults is False: # set the faults does not offset the younger faults self.faults_relations_df.iloc[col_pos] = ~self.df['isFault'] & \ self.faults_relations_df.iloc[col_pos] return True except KeyError: return False
[docs] def set_is_finite_fault(self, series_finite: Union[str, list, np.ndarray] = None, toggle=False): """ Toggles given series' finite fault property. Args: series_finite (str, list[str]): Name of the series which are finite toggle (bool): if True, passing a name which is already True will set it False. Returns: :class:`gempy.core.data_modules.stack.Faults` """ if series_finite[0] is not None: # check if given series is/are in dataframe assert np.isin(series_finite, self.df.index).all(), "series_fault must already exist" \ "in the series DataFrame." assert self.df.loc[series_finite].isFault.all(), "series_fault contains non-fault series" \ ", which can't be set as finite faults." # if so, toggle True/False for given series or list of series if toggle is True: self.df.loc[series_finite, 'isFinite'] = self.df.loc[series_finite, 'isFinite'] ^ True else: self.df.loc[series_finite, 'isFinite'] = True return self
[docs] def set_fault_relation(self, rel_matrix=None): """Method to set the df that offset a given sequence and therefore also another fault. Args: rel_matrix (numpy.array[bool]): 2D Boolean array with boolean logic. Rows affect (offset) columns Returns: :class:`gempy.core.data_modules.stack.Faults.faults_relations_df` """ # TODO: block the lower triangular matrix of being changed if rel_matrix is None: rel_matrix = np.zeros((self.df.index.shape[0], self.df.index.shape[0])) else: assert type(rel_matrix) is np.ndarray, 'rel_matrix muxt be a 2D numpy array' self.faults_relations_df = pn.DataFrame(rel_matrix, index=self.df.index, columns=self.df.index, dtype='bool') self.faults_relations_df.iloc[np.tril(np.ones(self.df.index.shape[0])).astype(bool)] = False return self.faults_relations_df
[docs] @staticmethod def count_faults(list_of_names): """ Read the string names of the surfaces to detect automatically the number of df if the name fault is on the name. """ faults_series = [] for i in list_of_names: try: if ('fault' in i or 'Fault' in i) and 'Default' not in i: faults_series.append(i) except TypeError: pass return faults_series
[docs]@_setdoc_pro(Faults.__doc__) class Series(object): """ Class that contains the functionality and attributes related to the series. Notice that series does not only refers to stratigraphic series but to any set of surfaces which will be interpolated together (comfortably). Args: faults (:class:`Faults`): [s0] series_names(Optional[list]): name of the series. They are also ordered Attributes: df (:class:`pn.core.frame.DataFrames`): Pandas data frame containing the series and the surfaces contained on them. TODO describe df columns faults (:class:`Faults`) """
[docs] def __init__(self, faults, series_names: list = None): self.faults = faults if series_names is None: series_names = ['Default series'] self.df = pn.DataFrame(np.array([[1, np.nan]]), index=pn.CategoricalIndex(series_names, ordered=False), columns=['order_series', 'BottomRelation']) self.df['order_series'] = self.df['order_series'].astype(int) self.df['BottomRelation'] = pn.Categorical(['Erosion'], categories=['Erosion', 'Onlap', 'Fault']) self.df['isActive'] = False
def __repr__(self): return self.df.to_string() def _repr_html_(self): return self.df.to_html()
[docs] def reset_order_series(self): """ Reset the column order series to monotonic ascendant values. """ self.df.at[:, 'order_series'] = pn.RangeIndex(1, self.df.shape[0] + 1)
[docs] @_setdoc_pro(reset_order_series.__doc__) def set_series_index(self, series_order: Union[list, np.ndarray], reset_order_series=True): """ Rewrite the index of the series df Args: series_order (list): List with names and order of series. reset_order_series (bool): if true [s0] Returns: :class:`Series`: Series """ if type(series_order) is list or type(series_order) is np.ndarray: list_of_series = np.atleast_1d(series_order) else: raise AttributeError('series_order is not neither list or SurfacePoints object.') series_idx = list_of_series # Categorical index does not have inplace # This update the categories self.df.index = self.df.index.set_categories(series_idx, rename=True) self.faults.df.index = self.faults.df.index.set_categories(series_idx, rename=True) self.faults.faults_relations_df.index = self.faults.faults_relations_df.index.set_categories( series_idx, rename=True) self.faults.faults_relations_df.columns = self.faults.faults_relations_df.columns.set_categories( series_idx, rename=True) # But we need to update the values too for c in series_order: try: self.df.loc[c] = [-1, 'Erosion', False, False, False] # This is in case someone use the old series except ValueError: self.df.loc[c] = [-1, 'Erosion', False] self.faults.df.loc[c, ['isFault', 'isFinite']] = [False, False] self.faults.faults_relations_df.loc[c, c] = False self.faults.faults_relations_df.fillna(False, inplace=True) if reset_order_series is True: self.reset_order_series() return self
[docs] def set_bottom_relation(self, series_list: Union[str, list], bottom_relation: Union[str, list]): """Set the bottom relation between the series and the one below. Args: series_list (str, list): name or list of names of the series to apply the functionality bottom_relation (str{Onlap, Erode, Fault}, list[str]): Returns: :class:`gempy.core.data_modules.stack.Stack` """ self.df.loc[series_list, 'BottomRelation'] = bottom_relation if self.faults.df.loc[series_list, 'isFault'] is True: self.faults.set_is_fault(series_list, toggle=True) elif bottom_relation == 'Fault': self.faults.df.loc[series_list, 'isFault'] = True return self
[docs] @_setdoc_pro(reset_order_series.__doc__) def add_series(self, series_list: Union[str, list], reset_order_series=True): """ Add series to the df Args: series_list (str, list): name or list of names of the series to apply the functionality reset_order_series (bool): if true [s0] Returns: Series """ series_list = np.atleast_1d(series_list) # Remove from the list categories that already exist series_list = series_list[~np.in1d(series_list, self.df.index.categories)] idx = self.df.index.add_categories(series_list) self.df.index = idx self.update_faults_index_rename() for c in series_list: # This is in case someone wants to run the old series try: self.df.loc[c] = [-1, 'Erosion', False, False, False] except ValueError: self.df.loc[c] = [-1, 'Erosion', False] self.faults.df.loc[c, ['isFault', 'isFinite']] = [False, False] self.faults.faults_relations_df.loc[c, c] = False self.faults.faults_relations_df.fillna(False, inplace=True) if reset_order_series is True: self.reset_order_series() return self
[docs] @_setdoc_pro([reset_order_series.__doc__, pn.DataFrame.drop.__doc__]) def delete_series(self, indices: Union[str, Iterable], reset_order_series=True): """[s1] Args: indices (str, list): name or list of names of the series to apply the functionality reset_order_series (bool): if true [s0] Returns: Series """ self.df.drop(indices, inplace=True) # If we are using the Stack class it is just one element in memory try: self.faults.df.drop(indices, inplace=True) except KeyError: pass self.faults.faults_relations_df.drop(indices, axis=0, inplace=True) self.faults.faults_relations_df.drop(indices, axis=1, inplace=True) idx = self.df.index.remove_unused_categories() self.df.index = idx self.update_faults_index_rename() if reset_order_series is True: self.reset_order_series() return self
[docs] @_setdoc_pro(pn.CategoricalIndex.rename_categories.__doc__) def rename_series(self, new_categories: Union[dict, list]): """ [s0] Args: new_categories (list, dict): * list-like: all items must be unique and the number of items in the new categories must match the existing number of categories. * dict-like: specifies a mapping from old categories to new. Categories not contained in the mapping are passed through and extra categories in the mapping are ignored. Returns: """ idx = self.df.index.rename_categories(new_categories) self.df.index = idx self.update_faults_index_rename() return self
[docs] @_setdoc_pro([pn.CategoricalIndex.reorder_categories.__doc__, pn.CategoricalIndex.sort_values.__doc__]) def reorder_series(self, new_categories: Union[list, np.ndarray]): """[s0] [s1] Args: new_categories (list): list with all series names in the desired order. Returns: Series """ idx = self.df.index.reorder_categories(new_categories).sort_values() self.df = self.df.reindex(idx, copy=False) self.reset_order_series() self.update_faults_index_reorder() return self
[docs] def modify_order_series(self, new_value: int, series_name: str): """ Replace to the new location the old series Args: new_value (int): New location series_name (str): name of the series to be moved Returns: Series """ group = self.df['order_series'] assert np.isin(new_value, group), 'new_value must exist already in the order_surfaces group.' old_value = group[series_name] self.df['order_series'] = group.replace([new_value, old_value], [old_value, new_value]) self.sort_series() self.update_faults_index_reorder() return self
def sort_series(self): self.df.sort_values(by='order_series', inplace=True) self.df.index = self.df.index.reorder_categories(self.df.index.to_numpy()) def update_faults_index_rename(self): idx = self.df.index self.faults.df.index = idx self.faults.faults_relations_df.index = idx self.faults.faults_relations_df.columns = idx # This is a hack for qgrid # We need to add the qgrid special columns to categories self.faults.faults_relations_df.columns = self.faults.faults_relations_df.columns.add_categories( ['index', 'qgrid_unfiltered_index']) def update_faults_index_reorder(self): idx = self.df.index self.faults.df = self.faults.df.reindex(idx, copy=False) self.faults.faults_relations_df = self.faults.faults_relations_df.reindex(idx, axis=0) self.faults.faults_relations_df = self.faults.faults_relations_df.reindex(idx, axis=1) self.faults.faults_relations_df.columns = self.faults.faults_relations_df.columns.add_categories( ['index', 'qgrid_unfiltered_index']) self.faults.set_default_faults_relations()
class MockFault: pass
[docs]class Stack(Series, Faults): """Class that encapsulates all type of geological features. So far is Series and Faults Args: features_names (Iterable): Names of the features fault_features (Iterable): List of features that are faults rel_matrix: """
[docs] def __init__(self, features_names: Iterable = None, fault_features: Iterable = None, rel_matrix: Iterable = None): if features_names is None: features_names = ['Default series'] # Set unique df df_ = pn.DataFrame(np.array([[1, np.nan, False, False, False]]), index=pn.CategoricalIndex(features_names, ordered=False), columns=['order_series', 'BottomRelation', 'isActive', 'isFault', 'isFinite']) self.df = df_.astype({'order_series': int, 'BottomRelation': 'category', 'isActive': bool, 'isFault': bool, 'isFinite': bool}) self.df['order_series'] = self.df['order_series'].astype(int) self.df['BottomRelation'] = pn.Categorical(['Erosion'], categories=['Erosion', 'Onlap', 'Fault']) self.faults = self self.faults_relations_df = pn.DataFrame(index=pn.CategoricalIndex(['Default series']), columns=pn.CategoricalIndex(['Default series', '']), dtype='bool') self.set_is_fault(series_fault=fault_features) self.set_fault_relation(rel_matrix=rel_matrix) self.n_faults = 0 self._offset_faults = False