From d1476f67ca5d62c0cf62fc6561b5a39d3fd1f681 Mon Sep 17 00:00:00 2001 From: Leguark Date: Mon, 26 Oct 2020 16:43:55 +0100 Subject: [PATCH 01/10] [CLN] reorder data classes --- gempy_lite/__init__.py | 7 +- gempy_lite/core/data_modules/__init__.py | 0 gempy_lite/core/interpolator.py | 1045 ----------------- .../core/{data.py => kernel_data/__init__.py} | 418 +------ .../geometric_data.py | 338 +----- .../{data_modules => kernel_data}/stack.py | 8 +- gempy_lite/core/model.py | 63 +- gempy_lite/core/model_data.py | 485 ++++++++ gempy_lite/core/solution.py | 5 +- gempy_lite/core/structured_data.py | 235 ++++ gempy_lite/core/unstructured_data.py | 60 - gempy_lite/gempy_api.py | 4 +- test/test_input_data/test_data_classes.py | 26 +- test/test_input_data/test_data_mutation2.py | 5 +- test/test_input_data/test_input.py | 12 - 15 files changed, 802 insertions(+), 1909 deletions(-) delete mode 100644 gempy_lite/core/data_modules/__init__.py delete mode 100644 gempy_lite/core/interpolator.py rename gempy_lite/core/{data.py => kernel_data/__init__.py} (68%) rename gempy_lite/core/{data_modules => kernel_data}/geometric_data.py (75%) rename gempy_lite/core/{data_modules => kernel_data}/stack.py (98%) create mode 100644 gempy_lite/core/model_data.py delete mode 100644 gempy_lite/core/unstructured_data.py diff --git a/gempy_lite/__init__.py b/gempy_lite/__init__.py index 874a50b..75ebc57 100644 --- a/gempy_lite/__init__.py +++ b/gempy_lite/__init__.py @@ -25,9 +25,10 @@ from gempy_lite.api_modules.getters import * from gempy_lite.api_modules.setters import * from gempy_lite.api_modules.io import * -from gempy_lite.core.model import Project, ImplicitCoKriging, AdditionalData, Faults, Grid, \ - Orientations, RescaledData, Series, SurfacePoints, \ - Surfaces, Options, Structure, KrigingParameters +from gempy_lite.core.model import Project, ImplicitCoKriging, Faults, Grid, \ + Orientations, Series, SurfacePoints +from gempy_lite.core.kernel_data import Surfaces, Structure, KrigingParameters +from gempy_lite.core.model_data import Options, RescaledData, AdditionalData from gempy_lite.core.solution import Solution from gempy_lite.addons.gempy_to_rexfile import geomodel_to_rex diff --git a/gempy_lite/core/data_modules/__init__.py b/gempy_lite/core/data_modules/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/gempy_lite/core/interpolator.py b/gempy_lite/core/interpolator.py deleted file mode 100644 index e40fcf1..0000000 --- a/gempy_lite/core/interpolator.py +++ /dev/null @@ -1,1045 +0,0 @@ -from typing import Union -from gempy_lite.core.data import Grid, Surfaces, AdditionalData -from gempy_lite.core.data_modules.geometric_data import SurfacePoints, Orientations -from gempy_lite.core.data_modules.stack import Faults, Series -from gempy_lite.utils.meta import _setdoc_pro, _setdoc -import gempy_lite.utils.docstring as ds - -import numpy as np - - - -@_setdoc_pro([SurfacePoints.__doc__, Orientations.__doc__, Grid.__doc__, Surfaces.__doc__, Series.__doc__, - Faults.__doc__, AdditionalData.__doc__]) -class Interpolator(object): - """Class that act as: - 1) linker between the data objects and the theano graph - 2) container of theano graphs + shared variables - 3) container of theano function - - Args: - surface_points (SurfacePoints): [s0] - orientations (Orientations): [s1] - grid (Grid): [s2] - surfaces (Surfaces): [s3] - series (Series): [s4] - faults (Faults): [s5] - additional_data (AdditionalData): [s6] - kwargs: - - compile_theano: if true, the function is compile at the creation of the class - - Attributes: - surface_points (SurfacePoints) - orientations (Orientations) - grid (Grid) - surfaces (Surfaces) - faults (Faults) - additional_data (AdditionalData) - dtype (['float32', 'float64']): float precision - theano_graph: theano graph object with the properties from AdditionalData -> Options - theano function: python function to call the theano code - - """ - # TODO assert passed data is rescaled - - def __init__(self, surface_points: "SurfacePoints", orientations: "Orientations", grid: "Grid", - surfaces: "Surfaces", series: Series, faults: "Faults", additional_data: "AdditionalData", **kwargs): - # Test - self.surface_points = surface_points - self.orientations = orientations - self.grid = grid - self.additional_data = additional_data - self.surfaces = surfaces - self.series = series - self.faults = faults - - self.dtype = additional_data.options.df.loc['values', 'dtype'] - self.theano_graph = self.create_theano_graph(additional_data, inplace=False) - self.theano_function = None - - self._compute_len_series() - - def _compute_len_series(self): - self.len_series_i = self.additional_data.structure_data.df.loc['values', 'len series surface_points'] - \ - self.additional_data.structure_data.df.loc['values', 'number surfaces per series'] - if self.len_series_i.shape[0] == 0: - self.len_series_i = np.zeros(1, dtype=int) - - self.len_series_o = self.additional_data.structure_data.df.loc['values', 'len series orientations'].astype( - 'int32') - if self.len_series_o.shape[0] == 0: - self.len_series_o = np.zeros(1, dtype=int) - - self.len_series_u = self.additional_data.kriging_data.df.loc['values', 'drift equations'].astype('int32') - if self.len_series_u.shape[0] == 0: - self.len_series_u = np.zeros(1, dtype=int) - - self.len_series_f = self.faults.faults_relations_df.sum(axis=0).values.astype('int32')[ - :self.additional_data.get_additional_data()['values']['Structure', 'number series']] - if self.len_series_f.shape[0] == 0: - self.len_series_f = np.zeros(1, dtype=int) - - self.len_series_w = self.len_series_i + self.len_series_o * 3 + self.len_series_u + self.len_series_f - - #@_setdoc_pro([AdditionalData.__doc__, ds.inplace, ds.theano_graph_pro]) - # def create_theano_graph(self, additional_data: "AdditionalData" = None, inplace=True, - # output=None, **kwargs): - # """ - # Create the graph accordingly to the options in the AdditionalData object - # - # Args: - # additional_data (AdditionalData): [s0] - # inplace (bool): [s1] - # - # Returns: - # TheanoGraphPro: [s2] - # """ - # if output is None: - # output = ['geology'] - # - # if additional_data is None: - # additional_data = self.additional_data - # - # self.dtype = additional_data.options.df.loc['values', 'dtype'] - # - # graph = tg.TheanoGraphPro(optimizer=additional_data.options.df.loc['values', 'theano_optimizer'], - # verbose=additional_data.options.df.loc['values', 'verbosity'], - # output=output, - # **kwargs) - # if inplace is True: - # self.theano_graph = graph - # else: - # return graph - - # @_setdoc_pro([ds.theano_graph_pro]) - # def set_theano_graph(self, th_graph): - # """ - # Attach an already create theano graph. - # - # Args: - # th_graph (TheanoGraphPro): [s0] - # - # Returns: - # True - # """ - # self.theano_graph = th_graph - # return True - # - # def set_theano_shared_kriging(self): - # """ - # Set to the theano_graph attribute the shared variables of kriging values from the linked - # :class:`AdditionalData`. - # - # Returns: - # True - # """ - # # Range - # # TODO add rescaled range and co into the rescaling data df? - # self.theano_graph.a_T.set_value(np.cast[self.dtype] - # (self.additional_data.kriging_data.df.loc['values', 'range'] / - # self.additional_data.rescaling_data.df.loc[ - # 'values', 'rescaling factor'])) - # # Covariance at 0 - # self.theano_graph.c_o_T.set_value(np.cast[self.dtype]( - # self.additional_data.kriging_data.df.loc['values', '$C_o$'] / - # self.additional_data.rescaling_data.df.loc[ - # 'values', 'rescaling factor'] - # )) - # # universal grades - # self.theano_graph.n_universal_eq_T.set_value( - # list(self.additional_data.kriging_data.df.loc['values', 'drift equations'].astype('int32')[self.non_zero])) - # - # self.set_theano_shared_nuggets() - # - # def set_theano_shared_nuggets(self): - # # nugget effect - # # len_orientations = self.additional_data.structure_data.df.loc['values', 'len series orientations'] - # # len_orientations_len = np.sum(len_orientations) - # - # self.theano_graph.nugget_effect_grad_T.set_value( - # np.cast[self.dtype](np.tile( - # self.orientations.df['smooth'], 3))) - # - # # len_rest_form = (self.additional_data.structure_data.df.loc['values', 'len surfaces surface_points']) - # # len_rest_len = np.sum(len_rest_form) - # self.theano_graph.nugget_effect_scalar_T.set_value( - # np.cast[self.dtype](self.surface_points.df['smooth'])) - # return True - # - # def set_theano_shared_structure_surfaces(self): - # """ - # Set to the theano_graph attribute the shared variables of structure from the linked - # :class:`AdditionalData`. - # - # Returns: - # True - # """ - # len_rest_form = (self.additional_data.structure_data.df.loc['values', 'len surfaces surface_points'] - 1) - # self.theano_graph.number_of_points_per_surface_T.set_value(len_rest_form.astype('int32')) - - -class InterpolatorWeights(Interpolator): - - def __init__(self, surface_points: "SurfacePoints", orientations: "Orientations", grid: "Grid", - surfaces: "Surfaces", series, faults: "Faults", additional_data: "AdditionalData", **kwargs): - - super(InterpolatorWeights, self).__init__(surface_points, orientations, grid, surfaces, series, faults, - additional_data, **kwargs) - - # def get_python_input_weights(self, fault_drift=None): - # """ - # Get values from the data objects used during the interpolation: - # - dip positions XYZ - # - dip angles - # - azimuth - # - polarity - # - surface_points coordinates XYZ - # Returns: - # (list) - # """ - # # orientations, this ones I tile them inside theano. PYTHON VAR - # dips_position = self.orientations.df[['X_r', 'Y_r', 'Z_r']].values - # dip_angles = self.orientations.df["dip"].values - # azimuth = self.orientations.df["azimuth"].values - # polarity = self.orientations.df["polarity"].values - # surface_points_coord = self.surface_points.df[['X_r', 'Y_r', 'Z_r']].values - # if fault_drift is None: - # fault_drift = np.zeros((0, self.grid.values.shape[0] + 2 * self.len_series_i.sum())) - # - # # fault_drift = np.zeros((0, surface_points_coord.shape[0])) - # - # # Set all in a list casting them in the chosen dtype - # idl = [np.cast[self.dtype](xs) for xs in (dips_position, dip_angles, azimuth, polarity, surface_points_coord, - # fault_drift)] - # return idl - # - # def compile_th_fn(self, inplace=False, debug=False): - # - # self.set_theano_shared_kriging() - # self.set_theano_shared_structure_surfaces() - # # This are the shared parameters and the compilation of the function. This will be hidden as well at some point - # input_data_T = self.theano_graph.input_parameters_kriging - # print('Compiling theano function...') - # - # th_fn = theano.function(input_data_T, - # self.theano_graph.compute_weights(), - # # mode=NanGuardMode(nan_is_error=True), - # on_unused_input='warn', - # allow_input_downcast=False, - # profile=False) - # if inplace is True: - # self.theano_function = th_fn - # - # if debug is True: - # print('Level of Optimization: ', theano.config.optimizer) - # print('Device: ', theano.config.device) - # print('Precision: ', self.dtype) - # print('Number of faults: ', self.additional_data.structure_data.df.loc['values', 'number faults']) - # print('Compilation Done!') - # return th_fn - - -class InterpolatorScalar(Interpolator): - - def __init__(self, surface_points: "SurfacePoints", orientations: "Orientations", grid: "Grid", - surfaces: "Surfaces", series, faults: "Faults", additional_data: "AdditionalData", **kwargs): - - super(InterpolatorScalar, self).__init__(surface_points, orientations, grid, surfaces, series, faults, - additional_data, **kwargs) - - def get_python_input_zx(self, fault_drift=None): - """ - Get values from the data objects used during the interpolation: - - dip positions XYZ - - dip angles - - azimuth - - polarity - - surface_points coordinates XYZ - Returns: - (list) - """ - # orientations, this ones I tile them inside theano. PYTHON VAR - dips_position = self.orientations.df[['X_r', 'Y_r', 'Z_r']].values - dip_angles = self.orientations.df["dip"].values - azimuth = self.orientations.df["azimuth"].values - polarity = self.orientations.df["polarity"].values - surface_points_coord = self.surface_points.df[['X_r', 'Y_r', 'Z_r']].values - grid = self.grid.values_r - - if fault_drift is None: - fault_drift = np.zeros((0, grid.shape[0] + 2 * self.len_series_i.sum())) - - # fault_drift = np.zeros((0, grid.shape[0] + surface_points_coord.shape[0])) - - # Set all in a list casting them in the chosen dtype - idl = [np.cast[self.dtype](xs) for xs in (dips_position, dip_angles, azimuth, polarity, surface_points_coord, - fault_drift, grid)] - return idl - - def compile_th_fn(self, weights=None, grid=None, inplace=False, debug=False): - """ - - Args: - weights: Constant weights - grid: Constant grids - inplace: - debug: - - Returns: - # - # """ - # self.set_theano_shared_kriging() - # self.set_theano_shared_structure_surfaces() - # # This are the shared parameters and the compilation of the function. This will be hidden as well at some point - # input_data_T = self.theano_graph.input_parameters_kriging_export - # print('Compiling theano function...') - # - # if weights is None: - # weights = self.theano_graph.compute_weights() - # else: - # weights = theano.shared(weights) - # - # if grid is None: - # grid = self.theano_graph.grid_val_T - # else: - # grid = theano.shared(grid) - # - # th_fn = theano.function(input_data_T, - # self.theano_graph.compute_scalar_field(weights, grid), - # # mode=NanGuardMode(nan_is_error=True), - # on_unused_input='ignore', - # allow_input_downcast=False, - # profile=False) - # - # if inplace is True: - # self.theano_function = th_fn - # - # if debug is True: - # print('Level of Optimization: ', theano.config.optimizer) - # print('Device: ', theano.config.device) - # print('Precision: ', theano.config.floatX) - # print('Number of faults: ', self.additional_data.structure_data.df.loc['values', 'number faults']) - # print('Compilation Done!') - # return th_fn - - -class InterpolatorBlock(Interpolator): - - def __init__(self, surface_points: "SurfacePoints", orientations: "Orientations", grid: "Grid", - surfaces: "Surfaces", series: Series, faults: "Faults", additional_data: "AdditionalData", **kwargs): - - super(InterpolatorBlock, self).__init__(surface_points, orientations, grid, surfaces, series, - faults, additional_data, **kwargs) - self.theano_function_formation = None - self.theano_function_faults = None - - def get_python_input_block(self, fault_drift=None): - """ - Get values from the data objects used during the interpolation: - - dip positions XYZ - - dip angles - - azimuth - - polarity - - surface_points coordinates XYZ - Returns: - (list) - """ - # orientations, this ones I tile them inside theano. PYTHON VAR - dips_position = self.orientations.df[['X_r', 'Y_r', 'Z_r']].values - dip_angles = self.orientations.df["dip"].values - azimuth = self.orientations.df["azimuth"].values - polarity = self.orientations.df["polarity"].values - surface_points_coord = self.surface_points.df[['X_r', 'Y_r', 'Z_r']].values - grid = self.grid.values_r - if fault_drift is None: - fault_drift = np.zeros((0, grid.shape[0] + 2 * self.len_series_i.sum())) - - values_properties = self.surfaces.df.iloc[:, self.surfaces._n_properties:].values.astype(self.dtype).T - - # Set all in a list casting them in the chosen dtype - idl = [np.cast[self.dtype](xs) for xs in (dips_position, dip_angles, azimuth, polarity, surface_points_coord, - fault_drift, grid, values_properties)] - return idl - -# def compile_th_fn_formation_block(self, Z_x=None, weights=None, grid=None, values_properties=None, inplace=False, -# debug=False): -# """ -# -# Args: -# weights: Constant weights -# grid: Constant grids -# inplace: -# debug: -# -# Returns: -# -# """ -# self.set_theano_shared_kriging() -# self.set_theano_shared_structure_surfaces() -# # This are the shared parameters and the compilation of the function. This will be hidden as well at some point -# input_data_T = self.theano_graph.input_parameters_block -# print('Compiling theano function...') -# -# if weights is None: -# weights = self.theano_graph.compute_weights() -# else: -# weights = theano.shared(weights) -# -# if grid is None: -# grid = self.theano_graph.grid_val_T -# else: -# grid = theano.shared(grid) -# -# if values_properties is None: -# values_properties = self.theano_graph.values_properties_op -# else: -# values_properties = theano.shared(values_properties) -# -# if Z_x is None: -# Z_x = self.theano_graph.compute_scalar_field(weights, grid) -# else: -# Z_x = theano.shared(Z_x) -# -# th_fn = theano.function(input_data_T, -# self.theano_graph.compute_formation_block( -# Z_x, -# self.theano_graph.get_scalar_field_at_surface_points(Z_x), -# values_properties -# ), -# on_unused_input='ignore', -# allow_input_downcast=False, -# profile=False) -# -# if inplace is True: -# self.theano_function_formation = th_fn -# -# if debug is True: -# print('Level of Optimization: ', theano.config.optimizer) -# print('Device: ', theano.config.device) -# print('Precision: ', self.dtype) -# print('Number of faults: ', self.additional_data.structure_data.df.loc['values', 'number faults']) -# print('Compilation Done!') -# -# return th_fn -# -# def compile_th_fn_fault_block(self, Z_x=None, weights=None, grid=None, values_properties=None, -# inplace=False, debug=False): -# """ -# -# Args: -# weights: Constant weights -# grid: Constant grids -# inplace: -# debug: -# -# Returns: -# -# """ -# self.set_theano_shared_kriging() -# self.set_theano_shared_structure_surfaces() -# -# # This are the shared parameters and the compilation of the function. This will be hidden as well at some point -# input_data_T = self.theano_graph.input_parameters_block -# print('Compiling theano function...') -# -# if weights is None: -# weights = self.theano_graph.compute_weights() -# else: -# weights = theano.shared(weights) -# -# if grid is None: -# grid = self.theano_graph.grid_val_T -# else: -# grid = theano.shared(grid) -# -# if values_properties is None: -# values_properties = self.theano_graph.values_properties_op -# else: -# values_properties = theano.shared(values_properties) -# -# if Z_x is None: -# Z_x = self.theano_graph.compute_scalar_field(weights, grid) -# else: -# Z_x = theano.shared(Z_x) -# -# th_fn = theano.function(input_data_T, -# self.theano_graph.compute_fault_block( -# Z_x, -# self.theano_graph.get_scalar_field_at_surface_points(Z_x), -# values_properties, -# 0, -# grid -# ), -# # mode=NanGuardMode(nan_is_error=True), -# on_unused_input='ignore', -# allow_input_downcast=False, -# profile=False) -# -# if inplace is True: -# self.theano_function_faults = th_fn -# -# if debug is True: -# print('Level of Optimization: ', theano.config.optimizer) -# print('Device: ', theano.config.device) -# print('Precision: ', self.dtype) -# print('Number of faults: ', self.additional_data.structure_data.df.loc['values', 'number faults']) -# print('Compilation Done!') -# return th_fn -# -# -# class InterpolatorGravity: -# def set_theano_shared_tz_kernel(self, tz=None): -# """Set the theano component tz to each voxel""" -# -# if tz is None or tz is 'auto': -# try: -# tz = self.calculate_tz(self.grid.centered_grid) -# except AttributeError: -# raise AttributeError('You need to calculate or pass tz first.') -# -# self.theano_graph.tz.set_value(tz.astype(self.dtype)) -# -# def calculate_tz(self, centered_grid): -# from gempy_lite.assets.geophysics import GravityPreprocessing -# g = GravityPreprocessing(centered_grid) -# -# return g.set_tz_kernel() -# -# def set_theano_shared_pos_density(self, pos_density): -# self.theano_graph.pos_density.set_value(pos_density) -# -# def set_theano_shared_l0_l1(self): -# self.theano_graph.lg0.set_value(self.grid.get_grid_args('centered')[0]) -# self.theano_graph.lg1.set_value(self.grid.get_grid_args('centered')[1]) -# -# def set_theano_shared_gravity(self, tz='auto', pos_density=1): -# self.set_theano_shared_tz_kernel(tz) -# self.set_theano_shared_pos_density(pos_density) -# self.set_theano_shared_l0_l1() - - -class InterpolatorMagnetics: -# def set_theano_shared_Vs_kernel(self, V=None): -# -# if V is None or V is 'auto': -# try: -# V = self.calculate_V(self.grid.centered_grid) -# except AttributeError: -# raise AttributeError('You need to calculate or pass V first.') -# -# self.theano_graph.V.set_value(V.astype(self.dtype)) -# -# def calculate_V(self, centered_grid): -# from gempy_lite.assets.geophysics import MagneticsPreprocessing -# Vmodel = MagneticsPreprocessing(centered_grid).set_Vs_kernel() -# -# return Vmodel -# -# def set_theano_shared_pos_magnetics(self, pos_magnetics): -# self.theano_graph.pos_magnetics.set_value(pos_magnetics) -# -# def set_theano_shared_magnetic_cts(self, incl, decl, B_ext=52819.8506939139e-9): -# """ -# Args: -# B_ext : External magnetic field in [T], in magnetic surveys this is the geomagnetic field - varies temporaly -# incl : Dip of the geomagnetic field in degrees- varies spatially -# decl : Angle between magnetic and true North in degrees - varies spatially -# """ -# -# self.theano_graph.incl.set_value(incl) -# self.theano_graph.decl.set_value(decl) -# self.theano_graph.B_ext.set_value(B_ext) -# -# def set_theano_shared_l0_l1(self): -# self.theano_graph.lg0.set_value(self.grid.get_grid_args('centered')[0]) -# self.theano_graph.lg1.set_value(self.grid.get_grid_args('centered')[1]) -# -# def set_theano_shared_magnetics(self, V='auto', pos_magnetics=1, -# incl=None, decl=None, B_ext=52819.8506939139e-9): -# self.set_theano_shared_Vs_kernel(V) -# self.set_theano_shared_pos_magnetics(pos_magnetics) -# self.set_theano_shared_magnetic_cts(incl, decl, B_ext) -# self.set_theano_shared_l0_l1() -# -# @_setdoc_pro(ds.ctrl) -# @_setdoc([Interpolator.__doc__]) -# class InterpolatorModel(Interpolator, InterpolatorGravity, InterpolatorMagnetics): -# """ -# Child class of :class:`Interpolator` which set the shared variables and compiles the theano -# graph to compute the geological model, i.e. lithologies. -# -# Attributes: -# compute_weights_ctrl (list[bool]): [s0] -# compute_scalar_ctrl (list[bool]): -# compute_block_ctrl (list[bool]): -# -# Interpolator Doc -# """ -# def __init__(self, surface_points: "SurfacePoints", orientations: "Orientations", grid: "Grid", -# surfaces: "Surfaces", series, faults: "Faults", additional_data: "AdditionalData", **kwargs): -# -# super().__init__(surface_points, orientations, grid, surfaces, series, faults, -# additional_data, **kwargs) -# self.len_series_i = np.zeros(1) -# self.len_series_o = np.zeros(1) -# self.len_series_u = np.zeros(1) -# self.len_series_f = np.zeros(1) -# self.len_series_w = np.zeros(1) -# -# self.set_initial_results() -# -# n_series = 1000 -# -# self.compute_weights_ctrl = np.ones(n_series, dtype=bool) -# self.compute_scalar_ctrl = np.ones(n_series, dtype=bool) -# self.compute_block_ctrl = np.ones(n_series, dtype=bool) -# -# def reset_flow_control_initial_results(self, reset_weights=True, reset_scalar=True, reset_block=True): -# """ -# Method to reset to the initial state all the recompute ctrl. After calling this method next time -# gp.compute_model is called, everything will be computed. Panic bottom. -# -# Args: -# reset_weights (bool): -# reset_scalar (bool): -# reset_block (bool): -# -# Returns: -# True -# """ -# n_series = self.len_series_i.shape[0]#self.additional_data.get_additional_data()['values']['Structure', 'number series'] -# x_to_interp_shape = self.grid.values_r.shape[0] + 2 * self.len_series_i.sum() -# -# if reset_weights is True: -# self.compute_weights_ctrl = np.ones(1000, dtype=bool) -# self.theano_graph.weights_vector.set_value(np.zeros((self.len_series_w.sum()), dtype=self.dtype)) -# -# if reset_scalar is True: -# self.compute_scalar_ctrl = np.ones(1000, dtype=bool) -# self.theano_graph.scalar_fields_matrix.set_value( -# np.zeros((n_series, x_to_interp_shape), dtype=self.dtype)) -# -# if reset_block is True: -# self.compute_block_ctrl = np.ones(1000, dtype=bool) -# self.theano_graph.mask_matrix.set_value(np.zeros((n_series, x_to_interp_shape), dtype='bool')) -# self.theano_graph.block_matrix.set_value( -# np.zeros((n_series, self.surfaces.df.iloc[:, self.surfaces._n_properties:].values.shape[1], -# x_to_interp_shape), dtype=self.dtype)) -# return True -# -# def set_flow_control(self): -# """ -# Initialize the ctrl vectors to the number of series size. -# -# Returns: -# True -# """ -# n_series = 1000 -# self.compute_weights_ctrl = np.ones(n_series, dtype=bool) -# self.compute_scalar_ctrl = np.ones(n_series, dtype=bool) -# self.compute_block_ctrl = np.ones(n_series, dtype=bool) -# return True -# -# @_setdoc_pro(reset_flow_control_initial_results.__doc__) -# def set_all_shared_parameters(self, reset_ctrl=False): -# """ -# Set all theano shared parameters required for the computation of lithology -# -# Args: -# reset_ctrl (bool): If true, [s0] -# -# Returns: -# True -# """ -# self.set_theano_shared_loop() -# self.set_theano_shared_relations() -# self.set_theano_shared_kriging() -# self.set_theano_shared_structure_surfaces() -# # self.set_theano_shared_topology() -# if reset_ctrl is True: -# self.reset_flow_control_initial_results() -# -# return True -# -# def set_theano_shared_topology(self): -# -# max_lith = self.surfaces.df.groupby('isFault')['id'].count()[False] -# if type(max_lith) != int: -# max_lith = 0 -# -# self.theano_graph.max_lith.set_value(max_lith) -# self.theano_graph.regular_grid_res.set_value(self.grid.regular_grid.resolution) -# self.theano_graph.dxdydz.set_value(np.array(self.grid.regular_grid.get_dx_dy_dz(), dtype=self.dtype)) -# -# @_setdoc_pro(reset_flow_control_initial_results.__doc__) -# def set_theano_shared_structure(self, reset_ctrl=False): -# """ -# Set all theano shared variable dependent on :class:`Structure`. -# -# Args: -# reset_ctrl (bool): If true, [s0] -# -# Returns: -# True -# -# """ -# self.set_theano_shared_loop() -# self.set_theano_shared_relations() -# self.set_theano_shared_structure_surfaces() -# # universal grades -# # self.theano_graph.n_universal_eq_T.set_value( -# # list(self.additional_data.kriging_data.df.loc['values', 'drift equations'].astype('int32'))) -# -# if reset_ctrl is True: -# self.reset_flow_control_initial_results() -# return True -# -# def remove_series_without_data(self): -# len_series_i = self.additional_data.structure_data.df.loc['values', 'len series surface_points'] - \ -# self.additional_data.structure_data.df.loc['values', 'number surfaces per series'] -# -# len_series_o = self.additional_data.structure_data.df.loc['values', 'len series orientations'].astype( -# 'int32') -# -# # Remove series without data -# non_zero_i = len_series_i.nonzero()[0] -# non_zero_o = len_series_o.nonzero()[0] -# non_zero = np.intersect1d(non_zero_i, non_zero_o) -# -# self.non_zero = non_zero -# return self.non_zero -# -# def _compute_len_series(self): -# -# self.len_series_i = self.additional_data.structure_data.df.loc['values', 'len series surface_points'] - \ -# self.additional_data.structure_data.df.loc['values', 'number surfaces per series'] -# -# self.len_series_o = self.additional_data.structure_data.df.loc['values', 'len series orientations'].astype( -# 'int32') -# -# # Remove series without data -# non_zero_i = self.len_series_i.nonzero()[0] -# non_zero_o = self.len_series_o.nonzero()[0] -# non_zero = np.intersect1d(non_zero_i, non_zero_o) -# -# self.non_zero = non_zero -# -# self.len_series_u = self.additional_data.kriging_data.df.loc['values', 'drift equations'].astype('int32') -# try: -# len_series_f_ = self.faults.faults_relations_df.values[non_zero][:, non_zero].sum(axis=0) -# -# except np.AxisError: -# print('np.axis error') -# len_series_f_ = self.faults.faults_relations_df.values.sum(axis=0) -# -# self.len_series_f = np.atleast_1d(len_series_f_.astype('int32'))#[:self.additional_data.get_additional_data()['values']['Structure', 'number series']] -# -# self._old_len_series = self.len_series_i -# -# self.len_series_i = self.len_series_i[non_zero] -# self.len_series_o = self.len_series_o[non_zero] -# # self.len_series_f = self.len_series_f[non_zero] -# self.len_series_u = self.len_series_u[non_zero] -# -# if self.len_series_i.shape[0] == 0: -# self.len_series_i = np.zeros(1, dtype=int) -# self._old_len_series = self.len_series_i -# -# if self.len_series_o.shape[0] == 0: -# self.len_series_o = np.zeros(1, dtype=int) -# if self.len_series_u.shape[0] == 0: -# self.len_series_u = np.zeros(1, dtype=int) -# if self.len_series_f.shape[0] == 0: -# self.len_series_f = np.zeros(1, dtype=int) -# -# self.len_series_w = self.len_series_i + self.len_series_o * 3 + self.len_series_u + self.len_series_f -# -# def set_theano_shared_loop(self): -# """Set the theano shared variables that are looped for each series.""" -# self._compute_len_series() -# -# self.theano_graph.len_series_i.set_value(np.insert(self.len_series_i.cumsum(), 0, 0).astype('int32')) -# self.theano_graph.len_series_o.set_value(np.insert(self.len_series_o.cumsum(), 0, 0).astype('int32')) -# self.theano_graph.len_series_w.set_value(np.insert(self.len_series_w.cumsum(), 0, 0).astype('int32')) -# -# # Number of surfaces per series. The function is not pretty but the result is quite clear -# n_surfaces_per_serie = np.insert( -# self.additional_data.structure_data.df.loc['values', 'number surfaces per series'][self.non_zero].cumsum(), 0, 0). \ -# astype('int32') -# self.theano_graph.n_surfaces_per_series.set_value(n_surfaces_per_serie) -# self.theano_graph.n_universal_eq_T.set_value( -# list(self.additional_data.kriging_data.df.loc['values', 'drift equations'].astype('int32')[self.non_zero])) -# -# @_setdoc_pro(set_theano_shared_loop.__doc__) -# def set_theano_shared_weights(self): -# """Set the theano shared weights and [s0]""" -# self.set_theano_shared_loop() -# self.theano_graph.weights_vector.set_value(np.zeros((self.len_series_w.sum()), dtype=self.dtype)) -# -# def set_theano_shared_fault_relation(self): -# self.remove_series_without_data() -# """Set the theano shared variable with the fault relation""" -# self.theano_graph.fault_relation.set_value( -# self.faults.faults_relations_df.values[self.non_zero][:, self.non_zero]) -# -# def set_theano_shared_is_fault(self): -# """Set theano shared variable which controls if a series is fault or not""" -# is_fault_ = self.faults.df['isFault'].values[self.non_zero] -# self.theano_graph.is_fault.set_value(is_fault_) -# -# def set_theano_shared_is_finite(self): -# """Set theano shared variable which controls if a fault is finite or not""" -# self.theano_graph.is_finite_ctrl.set_value(self.faults.df['isFinite'].values.astype(bool)) -# -# def set_theano_shared_onlap_erode(self): -# """Set the theano variables which control the masking patterns according to the uncomformity relation""" -# self.remove_series_without_data() -# -# is_erosion = self.series.df['BottomRelation'].values[self.non_zero] == 'Erosion' -# is_onlap = np.roll(self.series.df['BottomRelation'].values[self.non_zero] == 'Onlap', 1) -# -# if len(is_erosion) != 0: -# is_erosion[-1] = False -# # this comes from the series df -# self.theano_graph.is_erosion.set_value(is_erosion) -# self.theano_graph.is_onlap.set_value(is_onlap) -# -# def set_theano_shared_faults(self): -# """Set all theano shared variables wich controls the faults behaviour""" -# -# self.set_theano_shared_fault_relation() -# # This comes from the faults df -# self.set_theano_shared_is_fault() -# self.set_theano_shared_is_finite() -# -# def set_theano_shared_relations(self): -# """Set all theano shared variables that control all the series interactions with each other""" -# self.set_theano_shared_fault_relation() -# # This comes from the faults df -# self.set_theano_shared_is_fault() -# self.set_theano_shared_is_finite() -# self.set_theano_shared_onlap_erode() -# -# def set_initial_results(self): -# """ -# Initialize all the theano shared variables where we store the final results of the interpolation. -# This function must be called always after set_theano_shared_loop -# -# Returns: -# True -# """ -# self._compute_len_series() -# -# x_to_interp_shape = self.grid.values_r.shape[0] + 2 * self.len_series_i.sum() -# n_series = self.len_series_i.shape[0]#self.additional_data.structure_data.df.loc['values', 'number series'] -# -# self.theano_graph.weights_vector.set_value(np.zeros((self.len_series_w.sum()), dtype=self.dtype)) -# self.theano_graph.scalar_fields_matrix.set_value( -# np.zeros((n_series, x_to_interp_shape), dtype=self.dtype)) -# -# self.theano_graph.mask_matrix.set_value(np.zeros((n_series, x_to_interp_shape), dtype='bool')) -# self.theano_graph.block_matrix.set_value( -# np.zeros((n_series, self.surfaces.df.iloc[:, self.surfaces._n_properties:].values.shape[1], -# x_to_interp_shape), dtype=self.dtype)) -# return True -# -# def set_initial_results_matrices(self): -# """ -# Initialize all the theano shared variables where we store the final results of the interpolation except the -# kriging weights vector. -# -# -# Returns: -# True -# """ -# self._compute_len_series() -# -# x_to_interp_shape = self.grid.values_r.shape[0] + 2 * self.len_series_i.sum() -# n_series = self.len_series_i.shape[0]#self.additional_data.structure_data.df.loc['values', 'number series'] -# -# self.theano_graph.scalar_fields_matrix.set_value( -# np.zeros((n_series, x_to_interp_shape), dtype=self.dtype)) -# -# self.theano_graph.mask_matrix.set_value(np.zeros((n_series, x_to_interp_shape), dtype='bool')) -# self.theano_graph.block_matrix.set_value( -# np.zeros((n_series, self.surfaces.df.iloc[:, self.surfaces._n_properties:].values.shape[1], -# x_to_interp_shape), dtype=self.dtype)) -# -# def set_theano_shared_grid(self, grid=None): -# if grid == 'shared': -# grid_sh = self.grid.values_r -# self.theano_graph.grid_val_T = theano.shared(grid_sh.astype(self.dtype), 'Constant values to interpolate.') -# elif grid is not None: -# self.theano_graph.grid_val_T = theano.shared(grid.astype(self.dtype), 'Constant values to interpolate.') - - def modify_results_matrices_pro(self): - """ - Modify all theano shared matrices to the right size according to the structure data. This method allows - to change the size of the results without having the recompute all series""" - - old_len_i = self._old_len_series - new_len_i = self.additional_data.structure_data.df.loc['values', 'len series surface_points'] - \ - self.additional_data.structure_data.df.loc['values', 'number surfaces per series'] - if new_len_i.shape[0] < old_len_i.shape[0]: - self.set_initial_results() - old_len_i = old_len_i[old_len_i != 0] - elif new_len_i.shape[0] > old_len_i.shape[0]: - self.set_initial_results() - new_len_i = new_len_i[new_len_i != 0] - else: - scalar_fields_matrix = self.theano_graph.scalar_fields_matrix.get_value() - mask_matrix = self.theano_graph.mask_matrix.get_value() - block_matrix = self.theano_graph.block_matrix.get_value() - - len_i_diff = new_len_i - old_len_i - for e, i in enumerate(len_i_diff): - loc = self.grid.values_r.shape[0] + old_len_i[e] - i *= 2 - if i == 0: - pass - elif i > 0: - self.theano_graph.scalar_fields_matrix.set_value( - np.insert(scalar_fields_matrix, [loc], np.zeros(i), axis=1)) - self.theano_graph.mask_matrix.set_value(np.insert( - mask_matrix, [loc], np.zeros(i, dtype=self.dtype), axis=1)) - self.theano_graph.block_matrix.set_value(np.insert( - block_matrix, [loc], np.zeros(i, dtype=self.dtype), axis=2)) - - else: - self.theano_graph.scalar_fields_matrix.set_value( - np.delete(scalar_fields_matrix, np.arange(loc, loc+i, -1) - 1, axis=1)) - self.theano_graph.mask_matrix.set_value( - np.delete(mask_matrix, np.arange(loc, loc+i, -1) - 1, axis=1)) - self.theano_graph.block_matrix.set_value( - np.delete(block_matrix, np.arange(loc, loc+i, -1) - 1, axis=2)) - - self.modify_results_weights() - - def modify_results_weights(self): - """Modify the theano shared weights vector according to the structure. - """ - old_len_w = self.len_series_w - self._compute_len_series() - new_len_w = self.len_series_w - if new_len_w.shape[0] != old_len_w[0]: - self.set_initial_results() - else: - weights = self.theano_graph.weights_vector.get_value() - len_w_diff = new_len_w - old_len_w - for e, i in enumerate(len_w_diff): - # print(len_w_diff, weights) - if i == 0: - pass - elif i > 0: - self.theano_graph.weights_vector.set_value(np.insert(weights, old_len_w[e], np.zeros(i))) - else: - # print(np.delete(weights, np.arange(old_len_w[e], old_len_w[e] + i, -1)-1)) - self.theano_graph.weights_vector.set_value( - np.delete(weights, np.arange(old_len_w[e], old_len_w[e] + i, -1)-1)) - - def get_python_input_block(self, append_control=True, fault_drift=None): - """ - Get values from the data objects used during the interpolation: - - dip positions XYZ - - dip angles - - azimuth - - polarity - - surface_points coordinates XYZ - - Args: - append_control (bool): If true append the ctrl vectors to the input list - fault_drift (Optional[np.array]): matrix with per computed faults to drift the model - - Returns: - list: list of arrays with all the input parameters to the theano function - """ - # orientations, this ones I tile them inside theano. PYTHON VAR - dips_position = self.orientations.df[['X_r', 'Y_r', 'Z_r']].values - dip_angles = self.orientations.df["dip"].values - azimuth = self.orientations.df["azimuth"].values - polarity = self.orientations.df["polarity"].values - surface_points_coord = self.surface_points.df[['X_r', 'Y_r', 'Z_r']].values - grid = self.grid.values_r - if fault_drift is None: - fault_drift = np.zeros((0, grid.shape[0] + 2 * self.len_series_i.sum())) - - # values_properties = np.array([[]], dtype='float32') - # g = self.surfaces.df.groupby('series') - # for series_ in self.series.df.index.values[self.non_zero]: - # values_properties = np.append(values_properties, - # g.get_group(series_).iloc[:, self.surfaces._n_properties:].values. - # astype(self.dtype).T, axis=1) - - # values_properties = self.surfaces.df.iloc[:, self.surfaces._n_properties:].values.astype(self.dtype).T - - values_properties = self.surfaces.df.groupby('isActive').get_group( - True).iloc[:, self.surfaces._n_properties:].values.astype(self.dtype).T - # Set all in a list casting them in the chosen dtype - idl = [np.cast[self.dtype](xs) for xs in (dips_position, dip_angles, azimuth, polarity, - surface_points_coord, - fault_drift, grid, values_properties)] - if append_control is True: - idl.append(self.compute_weights_ctrl) - idl.append(self.compute_scalar_ctrl) - idl.append(self.compute_block_ctrl) - - return idl - - def print_theano_shared(self): - """Print many of the theano shared variables""" - - print('len sereies i', self.theano_graph.len_series_i.get_value()) - print('len sereies o', self.theano_graph.len_series_o.get_value()) - print('len sereies w', self.theano_graph.len_series_w.get_value()) - print('n surfaces per series', self.theano_graph.n_surfaces_per_series.get_value()) - print('n universal eq',self.theano_graph.n_universal_eq_T.get_value()) - print('is finite', self.theano_graph.is_finite_ctrl.get_value()) - print('is erosion', self.theano_graph.is_erosion.get_value()) - print('is onlap', self.theano_graph.is_onlap.get_value()) - - # def compile_th_fn_geo(self, inplace=False, debug=True, grid: Union[str, np.ndarray] = None): - # """ - # Compile and create the theano function which can be evaluated to compute the geological models - # - # Args: - # - # inplace (bool): If true add the attribute theano.function to the object inplace - # debug (bool): If true print some of the theano flags - # grid: If None, grid will be passed as variable. If shared or np.ndarray the grid will be treated as - # constant (if shared the grid will be taken of grid) - # - # Returns: - # theano.function: function that computes the whole interpolation - # """ - # - # self.set_all_shared_parameters(reset_ctrl=False) - # # This are the shared parameters and the compilation of the function. This will be hidden as well at some point - # input_data_T = self.theano_graph.input_parameters_loop - # print('Compiling theano function...') - # if grid == 'shared' or grid is not None: - # self.set_theano_shared_grid(grid) - # - # th_fn = theano.function(input_data_T, - # self.theano_graph.theano_output(), - # updates=[ - # (self.theano_graph.block_matrix, self.theano_graph.new_block), - # (self.theano_graph.weights_vector, self.theano_graph.new_weights), - # (self.theano_graph.scalar_fields_matrix, self.theano_graph.new_scalar), - # (self.theano_graph.mask_matrix, self.theano_graph.new_mask) - # ], - # on_unused_input='ignore', - # allow_input_downcast=False, - # profile=False) - # - # if inplace is True: - # self.theano_function = th_fn - # - # if debug is True: - # print('Level of Optimization: ', theano.config.optimizer) - # print('Device: ', theano.config.device) - # print('Precision: ', theano.config.floatX) - # print('Number of faults: ', self.additional_data.structure_data.df.loc['values', 'number faults']) - # print('Compilation Done!') - # - # return th_fn - # diff --git a/gempy_lite/core/data.py b/gempy_lite/core/kernel_data/__init__.py similarity index 68% rename from gempy_lite/core/data.py rename to gempy_lite/core/kernel_data/__init__.py index 7ee2e07..4d05047 100644 --- a/gempy_lite/core/data.py +++ b/gempy_lite/core/kernel_data/__init__.py @@ -7,274 +7,7 @@ import pandas as pn import seaborn as sns -try: - import ipywidgets as widgets - - ipywidgets_import = True -except ModuleNotFoundError: - VTK_IMPORT = False - -# This is for sphenix to find the packages -from gempy_lite.core.grid_modules import grid_types -from gempy_lite.core.grid_modules import topography -from gempy_lite.utils.meta import _setdoc, _setdoc_pro -import gempy_lite.utils.docstring as ds - -pn.options.mode.chained_assignment = None - - -class MetaData(object): - """Class containing metadata of the project. - - Set of attributes and methods that are not related directly with the geological model but more with the project - - Args: - project_name (str): Name of the project. This is use as default value for some I/O actions - - Attributes: - date (str): Time of the creations of the project - project_name (str): Name of the project. This is use as default value for some I/O actions - - """ - - def __init__(self, project_name='default_project'): - import datetime - now = datetime.datetime.now() - self.date = now.strftime(" %Y-%m-%d %H:%M") - - if project_name == 'default_project': - project_name += self.date - - self.project_name = project_name - - -@_setdoc_pro([grid_types.RegularGrid.__doc__, grid_types.CustomGrid.__doc__]) -class Grid(object): - """ Class to generate grids. - - This class is used to create points where to - evaluate the geological model. This class serves a container which transmit the XYZ coordinates to the - interpolator. There are several type of grids objects will feed into the Grid class - - Args: - **kwargs: See below - - Keyword Args: - regular (:class:`gempy.core.grid_modules.grid_types.RegularGrid`): [s0] - custom (:class:`gempy.core.grid_modules.grid_types.CustomGrid`): [s1] - topography (:class:`gempy_lite.core.grid_modules.grid_types.Topography`): [s2] - sections (:class:`gempy.core.grid_modules.grid_types.Sections`): [s3] - gravity (:class:`gempy_lite.core.grid_modules.grid_types.Gravity`): - - Attributes: - values (np.ndarray): coordinates where the model is going to be evaluated. This are the coordinates - concatenation of all active grids. - values_r (np.ndarray): rescaled coordinates where the model is going to be evaluated - length (np.ndarray):I a array which contain the slicing index for each grid type in order. The first element will - be 0, the second the length of the regular grid; the third custom and so on. This can be used to slice the - solutions correspondent to each of the grids - grid_types(np.ndarray[str]): names of the current grids of GemPy - active_grids(np.ndarray[bool]): boolean array which control which type of grid is going to be computed and - hence on the property `values`. - regular_grid (:class:`gempy.core.grid_modules.grid_types.RegularGrid`) - custom_grid (:class:`gempy.core.grid_modules.grid_types.CustomGrid`) - topography (:class:`gempy_lite.core.grid_modules.grid_types.Topography`) - sections (:class:`gempy.core.grid_modules.grid_types.Sections`) - gravity_grid (:class:`gempy_lite.core.grid_modules.grid_types.Gravity`) - """ - - def __init__(self, **kwargs): - - self.values = np.empty((0, 3)) - self.values_r = np.empty((0, 3)) - self.length = np.empty(0) - self.grid_types = np.array(['regular', 'custom', 'topography', 'sections', 'centered']) - self.active_grids = np.zeros(5, dtype=bool) - # All grid types must have values - - # Init optional grids - self.custom_grid = None - self.custom_grid_grid_active = False - self.topography = None - self.topography_grid_active = False - self.sections_grid_active = False - self.centered_grid = None - self.centered_grid_active = False - - # Init basic grid empty - self.regular_grid = self.create_regular_grid(set_active=False, **kwargs) - self.regular_grid_active = False - - # Init optional sections - self.sections = grid_types.Sections(regular_grid=self.regular_grid) - - self.update_grid_values() - - def __str__(self): - return 'Grid Object. Values: \n' + np.array2string(self.values) - - def __repr__(self): - return 'Grid Object. Values: \n' + np.array_repr(self.values) - - @_setdoc(grid_types.RegularGrid.__doc__) - def create_regular_grid(self, extent=None, resolution=None, set_active=True, *args, **kwargs): - """ - Set a new regular grid and activate it. - - Args: - extent (np.ndarray): [x_min, x_max, y_min, y_max, z_min, z_max] - resolution (np.ndarray): [nx, ny, nz] - - RegularGrid Docs - """ - self.regular_grid = grid_types.RegularGrid(extent, resolution, **kwargs) - if set_active is True: - self.set_active('regular') - return self.regular_grid - - @_setdoc_pro(ds.coord) - def create_custom_grid(self, custom_grid: np.ndarray): - """ - Set a new regular grid and activate it. - - Args: - custom_grid (np.array): [s0] - - """ - self.custom_grid = grid_types.CustomGrid(custom_grid) - self.set_active('custom') - - def create_topography(self, source='random', **kwargs): - """Create a topography grid and activate it. - - Args: - source: - * 'gdal': Load topography from a raster file. - * 'random': Generate random topography (based on a fractal grid). - * 'saved': Load topography that was saved with the topography.save() function. - This is useful after loading and saving a heavy raster file with gdal once or after saving a - random topography with the save() function. This .npy file can then be set as topography. - - Keyword Args: - source = 'gdal': - * filepath: path to raster file, e.g. '.tif', (for all file formats see - https://gdal.org/drivers/raster/index.html) - - source = 'random': - * fd: fractal dimension, defaults to 2.0 - * d_z: maximum height difference. If none, last 20% of the model in z direction - * extent: extent in xy direction. If none, geo_model.grid.extent - * resolution: desired resolution of the topography array. If none, geo_model.grid.resoution - - source = 'saved': - * filepath: path to the .npy file that was created using the topography.save() function - - Returns: - :class:gempy_lite.core.data.Topography - """ - self.topography = topography.Topography(self.regular_grid) - - if source == 'random': - self.topography.load_random_hills(**kwargs) - elif source == 'gdal': - filepath = kwargs.get('filepath', None) - if filepath is not None: - self.topography.load_from_gdal(filepath) - else: - print('to load a raster file, a path to the file must be provided') - elif source == 'saved': - filepath = kwargs.get('filepath', None) - if filepath is not None: - self.topography.load_from_saved(filepath) - else: - print('path to .npy file must be provided') - else: - raise AttributeError('source must be random, gdal or saved') - - self.set_active('topography') - - @_setdoc(grid_types.Sections.__doc__) - def create_section_grid(self, section_dict): - self.sections = grid_types.Sections(regular_grid=self.regular_grid, section_dict=section_dict) - self.set_active('sections') - return self.sections - - @_setdoc(grid_types.CenteredGrid.set_centered_grid.__doc__) - def create_centered_grid(self, centers, radius, resolution=None): - """Initialize gravity grid. Deactivate the rest of the grids""" - self.centered_grid = grid_types.CenteredGrid(centers, radius, resolution) - # self.active_grids = np.zeros(4, dtype=bool) - self.set_active('centered') - - def deactivate_all_grids(self): - """ - Deactivates the active grids array - :return: - """ - self.active_grids = np.zeros(5, dtype=bool) - self.update_grid_values() - return self.active_grids - - def set_active(self, grid_name: Union[str, np.ndarray]): - """ - Set active a given or several grids - Args: - grid_name (str, list): - - """ - where = self.grid_types == grid_name - self.active_grids[where] = True - self.update_grid_values() - return self.active_grids - - def set_inactive(self, grid_name: str): - where = self.grid_types == grid_name - self.active_grids *= ~where - self.update_grid_values() - return self.active_grids - - def update_grid_values(self): - """ - Copy XYZ coordinates from each specific grid to Grid.values for those which are active. - - Returns: - values - - """ - self.length = np.empty(0) - self.values = np.empty((0, 3)) - lengths = [0] - try: - for e, grid_types in enumerate( - [self.regular_grid, self.custom_grid, self.topography, self.sections, self.centered_grid]): - if self.active_grids[e]: - self.values = np.vstack((self.values, grid_types.values)) - lengths.append(grid_types.values.shape[0]) - else: - lengths.append(0) - except AttributeError: - raise AttributeError('Grid type does not exist yet. Set the grid before activating it.') - - self.length = np.array(lengths).cumsum() - return self.values - - def get_grid_args(self, grid_name: str): - assert type(grid_name) is str, 'Only one grid type can be retrieved' - assert grid_name in self.grid_types, 'possible grid types are ' + str(self.grid_types) - where = np.where(self.grid_types == grid_name)[0][0] - return self.length[where], self.length[where + 1] - - def get_grid(self, grid_name: str): - assert type(grid_name) is str, 'Only one grid type can be retrieved' - - l_0, l_1 = self.get_grid_args(grid_name) - return self.values[l_0:l_1] - - def get_section_args(self, section_name: str): - # assert type(section_name) is str, 'Only one section type can be retrieved' - l0, l1 = self.get_grid_args('sections') - where = np.where(self.sections.names == section_name)[0][0] - return l0 + self.sections.length[where], l0 + self.sections.length[where + 1] +from gempy_lite.utils.meta import _setdoc_pro class Colors: @@ -354,6 +87,14 @@ def change_colors(self, colordict: dict = None): colordict (dict, optional): dict with surface names mapped to hex color codes, e.g. {'layer1':'#6b0318'} if None: opens jupyter widget to change colors interactively. Defaults to None. """ + try: + from gempy_lite.core.kernel_data import ipywidgets_import + from IPython.display import display + import ipywidgets as widgets + + except ImportError: + raise ImportError('You need to install IPython and for interactive color picking.') + assert ipywidgets_import, 'ipywidgets not imported. Make sure the library is installed.' if colordict: @@ -366,11 +107,7 @@ def change_colors(self, colordict: dict = None): ] colbox = widgets.VBox(items) print('Click to select new colors.') - try: - from IPython.display import display - display(colbox) - except ImportError: - raise ImportError('You need to install IPython for interactive color picking.') + display(colbox) def on_change(v): self.colordict[v['owner'].description] = v['new'] # update colordict @@ -431,7 +168,6 @@ def reset_default_colors(self): return self.surfaces -# @_setdoc_pro(Series.__doc__) class Surfaces(object): """ Class that contains the surfaces of the model and the values of each of them. @@ -855,7 +591,6 @@ def modify_surface_values(self, idx, properties_names, values): return self -# @_setdoc_pro([SurfacePoints.__doc__, Orientations.__doc__, Surfaces.__doc__, Faults.__doc__]) class Structure(object): """ The structure_data class analyse the different lengths of subset in the interface and orientations categories_df @@ -1037,71 +772,7 @@ def set_is_lith_is_fault(self): return self.df -class Options(object): - """The class options contains the auxiliary user editable flags mainly independent to the model. - - Attributes: - df (:class:`pn.DataFrame`): df containing the flags. All fields are pandas categories allowing the user to - change among those categories. - - """ - - def __init__(self): - df_ = pn.DataFrame(np.array(['float32', 'geology', 'fast_compile', 'cpu', None]).reshape(1, -1), - index=['values'], - columns=['dtype', 'output', 'theano_optimizer', 'device', 'verbosity']) - - self.df = df_.astype({'dtype': 'category', 'output': 'category', - 'theano_optimizer': 'category', 'device': 'category', - 'verbosity': object}) - - self.df['dtype'].cat.set_categories(['float32', 'float64'], inplace=True) - self.df['theano_optimizer'].cat.set_categories(['fast_run', 'fast_compile'], inplace=True) - self.df['device'].cat.set_categories(['cpu', 'cuda'], inplace=True) - - self.default_options() - - def __repr__(self): - return self.df.T.to_string() - - def _repr_html_(self): - return self.df.T.to_html() - - def modify_options(self, attribute, value): - """Method to modify a given field - - Args: - attribute (str): Name of the field to modify - value: new value of the field. It will have to exist in the category in order for pandas to modify it. - - Returns: - :class:`pandas.DataFrame`: df where options data is stored - """ - - assert np.isin(attribute, self.df.columns).all(), 'Valid properties are: ' + np.array2string(self.df.columns) - self.df.loc['values', attribute] = value - return self.df - - def default_options(self): - """Set default options. - - Returns: - bool: True - """ - - # We want to have an infer function here - self.df.loc['values', 'device'] = 'cpu' - - if self.df.loc['values', 'device'] == 'cpu': - self.df.loc['values', 'dtype'] = 'float64' - else: - self.df.loc['values', 'dtype'] = 'float32' - - self.df.loc['values', 'theano_optimizer'] = 'fast_compile' - return True - - -@_setdoc_pro([Grid.__doc__, Structure.__doc__]) +@_setdoc_pro([Structure.__doc__]) class KrigingParameters(object): """ Class that stores and computes the default values for the kriging parameters used during the interpolation. @@ -1117,7 +788,7 @@ class KrigingParameters(object): structure (:class:`Structure`): [s1] """ - def __init__(self, grid: Grid, structure: Structure): + def __init__(self, structure: Structure, grid=None): self.structure = structure self.grid = grid @@ -1277,68 +948,3 @@ def set_u_grade(self, u_grade: list = None): return self.df['drift equations'] -# @_setdoc_pro([Grid.__doc__, Surfaces.__doc__, Faults.__doc__, -# Structure.__doc__, KrigingParameters.__doc__, Options.__doc__]) -class AdditionalData(object): - """ - Container class that encapsulate :class:`Structure`, :class:`KrigingParameters`, :class:`Options` and - rescaling parameters - - Args: - surface_points (:class:`SurfacePoints`): [s0] - orientations (:class:`Orientations`): [s1] - grid (:class:`Grid`): [s2] - faults (:class:`Faults`): [s4] - surfaces (:class:`Surfaces`): [s3] - rescaling (:class:`RescaledData`): [s5] - - Attributes: - structure_data (:class:`Structure`): [s6] - options (:class:`Options`): [s8] - kriging_data (:class:`Structure`): [s7] - rescaling_data (:class:`RescaledData`): - - """ - - def __init__(self, surface_points, orientations, grid: Grid, - faults, surfaces: Surfaces, rescaling): - self.structure_data = Structure(surface_points, orientations, surfaces, faults) - self.options = Options() - self.kriging_data = KrigingParameters(grid, self.structure_data) - self.rescaling_data = rescaling - - def __repr__(self): - concat_ = self.get_additional_data() - return concat_.to_string() - - def _repr_html_(self): - concat_ = self.get_additional_data() - return concat_.to_html() - - def get_additional_data(self): - """ - Concatenate all linked data frames and transpose them for a nice visualization. - - Returns: - pn.DataFrame: concatenated and transposed dataframe - """ - concat_ = pn.concat([self.structure_data.df, self.options.df, self.kriging_data.df, self.rescaling_data.df], - axis=1, keys=['Structure', 'Options', 'Kriging', 'Rescaling']) - return concat_.T - - def update_default_kriging(self): - """ - Update default kriging values. - """ - self.kriging_data.set_default_range() - self.kriging_data.set_default_c_o() - self.kriging_data.set_u_grade() - - def update_structure(self): - """ - Update fields dependent on input data sucha as structure and universal kriging grade - """ - self.structure_data.update_structure_from_input() - if len(self.kriging_data.df.loc['values', 'drift equations']) < \ - self.structure_data.df.loc['values', 'number series']: - self.kriging_data.set_u_grade() diff --git a/gempy_lite/core/data_modules/geometric_data.py b/gempy_lite/core/kernel_data/geometric_data.py similarity index 75% rename from gempy_lite/core/data_modules/geometric_data.py rename to gempy_lite/core/kernel_data/geometric_data.py index a0a7385..c91796d 100644 --- a/gempy_lite/core/data_modules/geometric_data.py +++ b/gempy_lite/core/kernel_data/geometric_data.py @@ -1,12 +1,11 @@ import sys import warnings -from typing import Union, Iterable +from typing import Union import numpy as np import pandas as pn -from gempy_lite.core.data import Surfaces, Grid - +from gempy_lite.core.kernel_data import Surfaces from gempy_lite.core.checkers import check_for_nans from gempy_lite.utils import docstring as ds from gempy_lite.utils.meta import _setdoc_pro, _setdoc @@ -266,7 +265,7 @@ def add_surface_points(self, x: Union[float, np.ndarray], y: Union[float, np.nda idx (Optional[int, list[int]): [s4] Returns: - :class:`gempy_lite.core.data_modules.geometric_data.SurfacePoints` + :class:`gempy_lite.core.kernel_data.geometric_data.SurfacePoints` """ max_idx = self.df.index.max() @@ -321,7 +320,7 @@ def del_surface_points(self, idx: Union[int, list, np.ndarray]): idx (int, list[int]): [s0] Returns: - :class:`gempy_lite.core.data_modules.geometric_data.SurfacePoints` + :class:`gempy_lite.core.kernel_data.geometric_data.SurfacePoints` """ self.df.drop(idx, inplace=True) @@ -340,7 +339,7 @@ def modify_surface_points(self, idx: Union[int, list, np.ndarray], **kwargs): * surface: [s4] Returns: - :class:`gempy_lite.core.data_modules.geometric_data.SurfacePoints` + :class:`gempy_lite.core.kernel_data.geometric_data.SurfacePoints` """ idx = np.array(idx, ndmin=1) @@ -629,7 +628,7 @@ def del_orientation(self, idx): idx: [s_idx_sp] Returns: - :class:`gempy_lite.core.data_modules.geometric_data.Orientations` + :class:`gempy_lite.core.kernel_data.geometric_data.Orientations` """ self.df.drop(idx, inplace=True) @@ -654,7 +653,7 @@ def modify_orientations(self, idx, **kwargs): * surface (str): [s1] Returns: - :class:`gempy_lite.core.data_modules.geometric_data.Orientations` + :class:`gempy_lite.core.kernel_data.geometric_data.Orientations` """ @@ -956,326 +955,3 @@ def plane_fit(point_list): return ctr, normal -@_setdoc_pro([SurfacePoints.__doc__, Orientations.__doc__, Grid.__doc__]) -class RescaledData(object): - """ - Auxiliary class to rescale the coordinates between 0 and 1 to increase float stability. - - Attributes: - df (:class:`pn.DataFrame`): Data frame containing the rescaling factor and centers - surface_points (:class:`SurfacePoints`): [s0] - orientations (:class:`Orientations`): [s1] - grid (:class:`Grid`): [s2] - - Args: - surface_points (:class:`SurfacePoints`): - orientations (:class:`Orientations`): - grid (:class:`Grid`): - rescaling_factor (float): value which divide all coordinates - centers (list[float]): New center of the coordinates after shifting - """ - - def __init__(self, surface_points: SurfacePoints, orientations: Orientations, grid: Grid, - rescaling_factor: float = None, centers: Union[list, pn.DataFrame] = None): - - self.surface_points = surface_points - self.orientations = orientations - self.grid = grid - - self.df = pn.DataFrame(np.array([rescaling_factor, centers]).reshape(1, -1), - index=['values'], - columns=['rescaling factor', 'centers']) - - self.rescale_data(rescaling_factor=rescaling_factor, centers=centers) - - def __repr__(self): - return self.df.T.to_string() - - def _repr_html_(self): - return self.df.T.to_html() - - @_setdoc_pro([ds.centers, ds.rescaling_factor]) - def modify_rescaling_parameters(self, attribute, value): - """ - Modify the parameters used to rescale data - - Args: - attribute (str): Attribute to be modified. It can be: centers, rescaling factor - * centers: [s0] - * rescaling factor: [s1] - value (float, list[float]) - - - Returns: - :class:`gempy_lite.core.data_modules.geometric_data.Rescaling` - - """ - assert np.isin(attribute, self.df.columns).all(), 'Valid attributes are: ' + np.array2string(self.df.columns) - - if attribute == 'centers': - try: - assert value.shape[0] == 3 - - self.df.loc['values', attribute] = value - - except AssertionError: - print('centers length must be 3: XYZ') - - else: - self.df.loc['values', attribute] = value - - return self - - @_setdoc_pro([ds.centers, ds.rescaling_factor]) - def rescale_data(self, rescaling_factor=None, centers=None): - """ - Rescale inplace: surface_points, orientations---adding columns in the categories_df---and grid---adding values_r - attributes. The rescaled values will get stored on the linked objects. - - Args: - rescaling_factor: [s1] - centers: [s0] - - Returns: - - """ - max_coord, min_coord = self.max_min_coord(self.surface_points, self.orientations) - if rescaling_factor is None: - self.df['rescaling factor'] = self.compute_rescaling_factor(self.surface_points, self.orientations, - max_coord, min_coord) - else: - self.df['rescaling factor'] = rescaling_factor - if centers is None: - self.df.at['values', 'centers'] = self.compute_data_center(self.surface_points, self.orientations, - max_coord, min_coord) - else: - self.df.at['values', 'centers'] = centers - - self.set_rescaled_surface_points() - self.set_rescaled_orientations() - self.set_rescaled_grid() - return True - - def get_rescaled_surface_points(self): - """ - Get the rescaled coordinates. return an image of the interface and orientations categories_df with the X_r.. - columns - - Returns: - :attr:`SurfacePoints.df[['X_r', 'Y_r', 'Z_r']]` - """ - return self.surface_points.df[['X_r', 'Y_r', 'Z_r']], - - def get_rescaled_orientations(self): - """ - Get the rescaled coordinates. return an image of the interface and orientations categories_df with the X_r.. - columns. - - Returns: - :attr:`Orientations.df[['X_r', 'Y_r', 'Z_r']]` - """ - return self.orientations.df[['X_r', 'Y_r', 'Z_r']] - - @staticmethod - @_setdoc_pro([SurfacePoints.__doc__, Orientations.__doc__]) - def max_min_coord(surface_points=None, orientations=None): - """ - Find the maximum and minimum location of any input data in each cartesian coordinate - - Args: - surface_points (:class:`SurfacePoints`): [s0] - orientations (:class:`Orientations`): [s1] - - Returns: - tuple: max[XYZ], min[XYZ] - """ - if surface_points is None: - if orientations is None: - raise AttributeError('You must pass at least one Data object') - else: - df = orientations.df - else: - if orientations is None: - df = surface_points.df - else: - df = pn.concat([orientations.df, surface_points.df], sort=False) - - max_coord = df.max()[['X', 'Y', 'Z']] - min_coord = df.min()[['X', 'Y', 'Z']] - return max_coord, min_coord - - @_setdoc_pro([SurfacePoints.__doc__, Orientations.__doc__, ds.centers]) - def compute_data_center(self, surface_points=None, orientations=None, - max_coord=None, min_coord=None, inplace=True): - """ - Calculate the center of the data once it is shifted between 0 and 1. - - Args: - surface_points (:class:`SurfacePoints`): [s0] - orientations (:class:`Orientations`): [s1] - max_coord (float): Max XYZ coordinates of all GeometricData - min_coord (float): Min XYZ coordinates of all GeometricData - inplace (bool): if True modify the self.df rescaling factor attribute - - Returns: - np.array: [s2] - """ - - if max_coord is None or min_coord is None: - max_coord, min_coord = self.max_min_coord(surface_points, orientations) - - # Get the centers of every axis - centers = ((max_coord + min_coord) / 2).astype(float).values - if inplace is True: - self.df.at['values', 'centers'] = centers - return centers - - # def update_centers(self, surface_points=None, orientations=None, max_coord=None, min_coord=None): - # # TODO this should update the additional data - # self.compute_data_center(surface_points, orientations, max_coord, min_coord, inplace=True) - - @_setdoc_pro([SurfacePoints.__doc__, Orientations.__doc__, ds.rescaling_factor]) - def compute_rescaling_factor(self, surface_points=None, orientations=None, - max_coord=None, min_coord=None, inplace=True): - """ - Calculate the rescaling factor of the data to keep all coordinates between 0 and 1 - - Args: - surface_points (:class:`SurfacePoints`): [s0] - orientations (:class:`Orientations`): [s1] - max_coord (float): Max XYZ coordinates of all GeometricData - min_coord (float): Min XYZ coordinates of all GeometricData - inplace (bool): if True modify the self.df rescaling factor attribute - - Returns: - float: [s2] - """ - - if max_coord is None or min_coord is None: - max_coord, min_coord = self.max_min_coord(surface_points, orientations) - rescaling_factor_val = (2 * np.max(max_coord - min_coord)) - if inplace is True: - self.df['rescaling factor'] = rescaling_factor_val - return rescaling_factor_val - - # def update_rescaling_factor(self, surface_points=None, orientations=None, - # max_coord=None, min_coord=None): - # self.compute_rescaling_factor(surface_points, orientations, max_coord, min_coord, inplace=True) - - @staticmethod - @_setdoc_pro([SurfacePoints.__doc__, compute_data_center.__doc__, compute_rescaling_factor.__doc__, ds.idx_sp]) - def rescale_surface_points(surface_points, rescaling_factor, centers, idx: list = None): - """ - Rescale inplace: surface_points. The rescaled values will get stored on the linked objects. - - Args: - surface_points (:class:`SurfacePoints`): [s0] - rescaling_factor: [s2] - centers: [s1] - idx (int, list of int): [s3] - - Returns: - - """ - - if idx is None: - idx = surface_points.df.index - - # Change the coordinates of surface_points - new_coord_surface_points = (surface_points.df.loc[idx, ['X', 'Y', 'Z']] - - centers) / rescaling_factor + 0.5001 - - new_coord_surface_points.rename(columns={"X": "X_r", "Y": "Y_r", "Z": 'Z_r'}, inplace=True) - return new_coord_surface_points - - @_setdoc_pro(ds.idx_sp) - def set_rescaled_surface_points(self, idx: Union[list, np.ndarray] = None): - """ - Set the rescaled coordinates into the surface_points categories_df - - Args: - idx (int, list of int): [s0] - - Returns: - - """ - if idx is None: - idx = self.surface_points.df.index - idx = np.atleast_1d(idx) - - self.surface_points.df.loc[idx, ['X_r', 'Y_r', 'Z_r']] = self.rescale_surface_points( - self.surface_points, self.df.loc['values', 'rescaling factor'], self.df.loc['values', 'centers'], idx=idx) - - return self.surface_points - - def rescale_data_point(self, data_points: np.ndarray, rescaling_factor=None, centers=None): - """This method now is very similar to set_rescaled_surface_points passing an index""" - if rescaling_factor is None: - rescaling_factor = self.df.loc['values', 'rescaling factor'] - if centers is None: - centers = self.df.loc['values', 'centers'] - - rescaled_data_point = (data_points - centers) / rescaling_factor + 0.5001 - - return rescaled_data_point - - @staticmethod - @_setdoc_pro([Orientations.__doc__, compute_data_center.__doc__, compute_rescaling_factor.__doc__, ds.idx_sp]) - def rescale_orientations(orientations, rescaling_factor, centers, idx: list = None): - """ - Rescale inplace: surface_points. The rescaled values will get stored on the linked objects. - - Args: - orientations (:class:`Orientations`): [s0] - rescaling_factor: [s2] - centers: [s1] - idx (int, list of int): [s3] - - Returns: - - """ - if idx is None: - idx = orientations.df.index - - # Change the coordinates of orientations - new_coord_orientations = (orientations.df.loc[idx, ['X', 'Y', 'Z']] - - centers) / rescaling_factor + 0.5001 - - new_coord_orientations.rename(columns={"X": "X_r", "Y": "Y_r", "Z": 'Z_r'}, inplace=True) - - return new_coord_orientations - - @_setdoc_pro(ds.idx_sp) - def set_rescaled_orientations(self, idx: Union[list, np.ndarray] = None): - """ - Set the rescaled coordinates into the surface_points categories_df - - Args: - idx (int, list of int): [s0] - - Returns: - - """ - if idx is None: - idx = self.orientations.df.index - idx = np.atleast_1d(idx) - - self.orientations.df.loc[idx, ['X_r', 'Y_r', 'Z_r']] = self.rescale_orientations( - self.orientations, self.df.loc['values', 'rescaling factor'], self.df.loc['values', 'centers'], idx=idx) - return True - - @staticmethod - def rescale_grid(grid, rescaling_factor, centers: pn.DataFrame): - new_grid_extent = (grid.regular_grid.extent - np.repeat(centers, 2)) / rescaling_factor + 0.5001 - new_grid_values = (grid.values - centers) / rescaling_factor + 0.5001 - return new_grid_extent, new_grid_values, - - def set_rescaled_grid(self): - """ - Set the rescaled coordinates and extent into a grid object - """ - - self.grid.extent_r, self.grid.values_r = self.rescale_grid( - self.grid, self.df.loc['values', 'rescaling factor'], self.df.loc['values', 'centers']) - - self.grid.regular_grid.extent_r, self.grid.regular_grid.values_r = self.grid.extent_r, self.grid.values_r \ No newline at end of file diff --git a/gempy_lite/core/data_modules/stack.py b/gempy_lite/core/kernel_data/stack.py similarity index 98% rename from gempy_lite/core/data_modules/stack.py rename to gempy_lite/core/kernel_data/stack.py index 00b27be..697e7f2 100644 --- a/gempy_lite/core/data_modules/stack.py +++ b/gempy_lite/core/kernel_data/stack.py @@ -54,7 +54,7 @@ def set_is_fault(self, series_fault: Union[str, list, np.ndarray] = None, offset_faults (bool): If True by default faults offset other faults Returns: - :class:`gempy_lite.core.data_modules.stack.Faults` + :class:`gempy_lite.core.kernel_data.stack.Faults` """ series_fault = np.atleast_1d(series_fault) @@ -120,7 +120,7 @@ def set_is_finite_fault(self, series_finite: Union[str, list, np.ndarray] = None toggle (bool): if True, passing a name which is already True will set it False. Returns: - :class:`gempy_lite.core.data_modules.stack.Faults` + :class:`gempy_lite.core.kernel_data.stack.Faults` """ if series_finite[0] is not None: # check if given series is/are in dataframe @@ -144,7 +144,7 @@ def set_fault_relation(self, rel_matrix=None): Rows affect (offset) columns Returns: - :class:`gempy_lite.core.data_modules.stack.Faults.faults_relations_df` + :class:`gempy_lite.core.kernel_data.stack.Faults.faults_relations_df` """ # TODO: block the lower triangular matrix of being changed @@ -269,7 +269,7 @@ def set_bottom_relation(self, series_list: Union[str, list], bottom_relation: Un bottom_relation (str{Onlap, Erode, Fault}, list[str]): Returns: - :class:`gempy_lite.core.data_modules.stack.Stack` + :class:`gempy_lite.core.kernel_data.stack.Stack` """ self.df.loc[series_list, 'BottomRelation'] = bottom_relation diff --git a/gempy_lite/core/model.py b/gempy_lite/core/model.py index 41967df..b63b12f 100644 --- a/gempy_lite/core/model.py +++ b/gempy_lite/core/model.py @@ -8,11 +8,12 @@ from typing import Union, Iterable import warnings -from gempy_lite.core.data_modules.geometric_data import Orientations, SurfacePoints, \ - RescaledData, Surfaces, Grid -from gempy_lite.core.data_modules.stack import Stack, Faults, Series -from gempy_lite.core.data import AdditionalData, MetaData, Options, Structure, KrigingParameters +from gempy_lite.core.kernel_data.geometric_data import Orientations, SurfacePoints, Surfaces +from gempy_lite.core.kernel_data import KrigingParameters +from gempy_lite.core.kernel_data.stack import Stack, Faults, Series +from gempy_lite.core.model_data import MetaData, Options, AdditionalData, RescaledData from gempy_lite.core.solution import Solution +from gempy_lite.core.structured_data import Grid from gempy_lite.utils.meta import _setdoc, _setdoc_pro import gempy_lite.utils.docstring as ds from gempy_lite.plot.decorators import * @@ -45,11 +46,11 @@ class ImplicitCoKriging(object): Attributes: _grid (:class:`gempy.core.data.Grid`): [s0] _faults (:class:`gempy.core.data.Grid`): [s1] - _stack (:class:`gempy.core.data_modules.stack.Stack`): [s2] + _stack (:class:`gempy.core.kernel_data.stack.Stack`): [s2] _surfaces (:class:`gempy.core.data.Surfaces`): [s3] - _surface_points (:class:`gempy.core.data_modules.geometric_data.SurfacePoints`): [s4] - _orientations (:class:`gempy.core.data_modules.geometric_data.Orientations`): [s5] - _rescaling (:class:`gempy_lite.core.data_modules.geometric_data.Rescaling`): [s6] + _surface_points (:class:`gempy.core.kernel_data.geometric_data.SurfacePoints`): [s4] + _orientations (:class:`gempy.core.kernel_data.geometric_data.Orientations`): [s5] + _rescaling (:class:`gempy_lite.core.kernel_data.geometric_data.Rescaling`): [s6] _additional_data (:class:`gempy.core.data.AdditionalData`): [s7] _interpolator (:class:`gempy.core.interpolator.InterpolatorModel`): [s8] solutions (:class:`gempy_lite.core.solutions.Solutions`): [s9] @@ -99,14 +100,14 @@ def grid(self): @_setdoc_pro(Faults.__doc__) @property def faults(self): - """:class:`gempy_lite.core.data_modules.stack.Faults` [s0]""" + """:class:`gempy_lite.core.kernel_data.stack.Faults` [s0]""" return RestrictingWrapper(self._faults, accepted_members=['__repr__', '_repr_html_', 'faults_relations_df']) @_setdoc_pro(Stack.__doc__) @property def stack(self): - """:class:`gempy_lite.core.data_modules.stack.Stack` [s0]""" + """:class:`gempy_lite.core.kernel_data.stack.Stack` [s0]""" return RestrictingWrapper(self._stack, accepted_members=['__repr__', '_repr_html_', 'df']) @@ -129,21 +130,21 @@ def surfaces(self): @_setdoc_pro(SurfacePoints.__doc__) @property def surface_points(self): - """:class:`gempy_lite.core.data_modules.geometric_data.SurfacePoints` [s0]""" + """:class:`gempy_lite.core.kernel_data.geometric_data.SurfacePoints` [s0]""" return RestrictingWrapper(self._surface_points, accepted_members=['__repr__', '_repr_html_', 'df']) @_setdoc_pro(Orientations.__doc__) @property def orientations(self): - """:class:`gempy_lite.core.data_modules.geometric_data.Orientations` [s0]""" + """:class:`gempy_lite.core.kernel_data.geometric_data.Orientations` [s0]""" return RestrictingWrapper(self._orientations, accepted_members=['__repr__', '_repr_html_', 'df']) @_setdoc_pro(RescaledData.__doc__) @property def rescaling(self): - """:class:`gempy_lite.core.data_modules.geometric_data.Rescaling` [s0]""" + """:class:`gempy_lite.core.kernel_data.geometric_data.Rescaling` [s0]""" return RestrictingWrapper(self._rescaling) @_setdoc_pro(AdditionalData.__doc__) @@ -481,7 +482,7 @@ def add_features(self, features_list: Union[str, list], reset_order_series=True) reset_order_series: if true [s0] Returns: - :class:`gempy_lite.core.data_modules.stack.Stack` + :class:`gempy_lite.core.kernel_data.stack.Stack` """ self._stack.add_series(features_list, reset_order_series) @@ -509,7 +510,7 @@ def delete_features(self, indices: Union[str, list], reset_order_features=True, remove_data (bool): if True remove the geometric data associated with the feature Returns: - :class:`gempy_lite.core.data_modules.stack.Stack` + :class:`gempy_lite.core.kernel_data.stack.Stack` """ indices = np.atleast_1d(indices) @@ -552,7 +553,7 @@ def rename_features(self, new_categories: Union[dict, list]): not contained in the mapping are passed through and extra categories in the mapping are ignored. Returns: - :class:`gempy_lite.core.data_modules.stack.Stack` + :class:`gempy_lite.core.kernel_data.stack.Stack` """ @@ -578,7 +579,7 @@ def modify_order_features(self, new_value: int, idx: str): idx (str): name of the feature to be moved Returns: - :class:`gempy_lite.core.data_modules.stack.Stack` + :class:`gempy_lite.core.kernel_data.stack.Stack` """ self._stack.modify_order_series(new_value, idx) @@ -613,7 +614,7 @@ def reorder_features(self, new_categories: Iterable[str]): new_categories (list): list with all series names in the desired order. Returns: - :class:`gempy_lite.core.data_modules.stack.Stack` + :class:`gempy_lite.core.kernel_data.stack.Stack` """ self._stack.reorder_series(new_categories) self._surfaces.df['series'].cat.reorder_categories(np.asarray(self._stack.df.index), @@ -660,10 +661,10 @@ def set_is_fault(self, feature_fault: Union[str, list] = None, toggle: bool = Fa change_color (bool): If True faults surfaces get the default fault color (light gray) Returns: - :class:`gempy_lite.core.data_modules.stack.Faults` + :class:`gempy_lite.core.kernel_data.stack.Faults` See Also: - :class:`gempy_lite.core.data_modules.stack.Faults.set_is_fault` + :class:`gempy_lite.core.kernel_data.stack.Faults.set_is_fault` """ feature_fault = np.atleast_1d(feature_fault) @@ -920,7 +921,7 @@ def set_surface_points(self, table: pn.DataFrame, **kwargs): bool add_basement: add a basement surface to the df. See Also: - :class:`gempy_lite.core.data_modules.geometric_data.SurfacePoints` + :class:`gempy_lite.core.kernel_data.geometric_data.SurfacePoints` """ @@ -963,10 +964,10 @@ def set_orientations(self, table: pn.DataFrame, **kwargs): table (pn.Dataframe): table with surface points data. Returns: - :class:`gempy_lite.core.data_modules.geometric_data.Orientations` + :class:`gempy_lite.core.kernel_data.geometric_data.Orientations` See Also: - :class:`gempy_lite.core.data_modules.geometric_data.Orientations` + :class:`gempy_lite.core.kernel_data.geometric_data.Orientations` """ g_x_name = kwargs.get('G_x_name', 'G_x') @@ -1070,7 +1071,7 @@ def modify_surface_points(self, indices: Union[int, list], * surface: [s_surface_sp] Returns: - :class:`gempy_lite.core.data_modules.geometric_data.SurfacePoints` + :class:`gempy_lite.core.kernel_data.geometric_data.SurfacePoints` """ @@ -1124,7 +1125,7 @@ def add_orientations(self, X, Y, Z, surface, pole_vector: Iterable = None, recompute_rescale_factor: [s_recompute_rf] Returns: - :class:`gempy_lite.core.data_modules.geometric_data.Orientations` + :class:`gempy_lite.core.kernel_data.geometric_data.Orientations` """ @@ -1211,10 +1212,10 @@ def set_default_surface_point(self, **kwargs): type of functionality such as qgrid. Args: - **kwargs: Same as :func:`gempy_lite.core.data_modules.geometric_data.SurfacePoints.add_surface_points` + **kwargs: Same as :func:`gempy_lite.core.kernel_data.geometric_data.SurfacePoints.add_surface_points` Returns: - :class:`gempy_lite.core.data_modules.geometric_data.SurfacePoints` + :class:`gempy_lite.core.kernel_data.geometric_data.SurfacePoints` """ if self._surface_points.df.shape[0] == 0: @@ -1226,10 +1227,10 @@ def set_default_orientation(self, **kwargs): """Set a default orientation if the df is empty. This is necessary for some type of functionality such as qgrid Args: - **kwargs: Same as :func::class:`gempy_lite.core.data_modules.geometric_data.Orientations.add_orientation` + **kwargs: Same as :func::class:`gempy_lite.core.kernel_data.geometric_data.Orientations.add_orientation` Returns: - :class:`gempy_lite.core.data_modules.geometric_data.Orientations` + :class:`gempy_lite.core.kernel_data.geometric_data.Orientations` """ if self._orientations.df.shape[0] == 0: @@ -1619,9 +1620,9 @@ def read_data(self, source_i=None, source_o=None, add_basement=True, **kwargs): See Also: - * :class:`gempy_lite.core.data_modules.geometric_data.SurfacePoints.read_surface_points.` + * :class:`gempy_lite.core.kernel_data.geometric_data.SurfacePoints.read_surface_points.` - * :class:`gempy_lite.core.data_modules.geometric_data.Orientations.read_orientations` + * :class:`gempy_lite.core.kernel_data.geometric_data.Orientations.read_orientations` """ if 'update_surfaces' not in kwargs: kwargs['update_surfaces'] = True diff --git a/gempy_lite/core/model_data.py b/gempy_lite/core/model_data.py new file mode 100644 index 0000000..7f600e2 --- /dev/null +++ b/gempy_lite/core/model_data.py @@ -0,0 +1,485 @@ +from typing import Union + +import numpy as np +import pandas as pn + +from gempy_lite.core.kernel_data.geometric_data import SurfacePoints, Orientations, Surfaces +from gempy_lite.core.kernel_data import Structure, KrigingParameters +from gempy_lite.core.structured_data import Grid +from gempy_lite.utils import docstring as ds +from gempy_lite.utils.meta import _setdoc_pro + + +class MetaData(object): + """Class containing metadata of the project. + + Set of attributes and methods that are not related directly with the geological model but more with the project + + Args: + project_name (str): Name of the project. This is use as default value for some I/O actions + + Attributes: + date (str): Time of the creations of the project + project_name (str): Name of the project. This is use as default value for some I/O actions + + """ + + def __init__(self, project_name='default_project'): + import datetime + now = datetime.datetime.now() + self.date = now.strftime(" %Y-%m-%d %H:%M") + + if project_name == 'default_project': + project_name += self.date + + self.project_name = project_name + + +class Options(object): + """The class options contains the auxiliary user editable flags mainly independent to the model. + + Attributes: + df (:class:`pn.DataFrame`): df containing the flags. All fields are pandas categories allowing the user to + change among those categories. + + """ + + def __init__(self): + df_ = pn.DataFrame(np.array(['float32', 'geology', 'fast_compile', 'cpu', None]).reshape(1, -1), + index=['values'], + columns=['dtype', 'output', 'theano_optimizer', 'device', 'verbosity']) + + self.df = df_.astype({'dtype': 'category', 'output': 'category', + 'theano_optimizer': 'category', 'device': 'category', + 'verbosity': object}) + + self.df['dtype'].cat.set_categories(['float32', 'float64'], inplace=True) + self.df['theano_optimizer'].cat.set_categories(['fast_run', 'fast_compile'], inplace=True) + self.df['device'].cat.set_categories(['cpu', 'cuda'], inplace=True) + + self.default_options() + + def __repr__(self): + return self.df.T.to_string() + + def _repr_html_(self): + return self.df.T.to_html() + + def modify_options(self, attribute, value): + """Method to modify a given field + + Args: + attribute (str): Name of the field to modify + value: new value of the field. It will have to exist in the category in order for pandas to modify it. + + Returns: + :class:`pandas.DataFrame`: df where options data is stored + """ + + assert np.isin(attribute, self.df.columns).all(), 'Valid properties are: ' + np.array2string(self.df.columns) + self.df.loc['values', attribute] = value + return self.df + + def default_options(self): + """Set default options. + + Returns: + bool: True + """ + + # We want to have an infer function here + self.df.loc['values', 'device'] = 'cpu' + + if self.df.loc['values', 'device'] == 'cpu': + self.df.loc['values', 'dtype'] = 'float64' + else: + self.df.loc['values', 'dtype'] = 'float32' + + self.df.loc['values', 'theano_optimizer'] = 'fast_compile' + return True + + +@_setdoc_pro([SurfacePoints.__doc__, Orientations.__doc__, Grid.__doc__]) +class RescaledData(object): + """ + Auxiliary class to rescale the coordinates between 0 and 1 to increase float stability. + + Attributes: + df (:class:`pn.DataFrame`): Data frame containing the rescaling factor and centers + surface_points (:class:`SurfacePoints`): [s0] + orientations (:class:`Orientations`): [s1] + grid (:class:`Grid`): [s2] + + Args: + surface_points (:class:`SurfacePoints`): + orientations (:class:`Orientations`): + grid (:class:`Grid`): + rescaling_factor (float): value which divide all coordinates + centers (list[float]): New center of the coordinates after shifting + """ + + def __init__(self, surface_points: SurfacePoints, orientations: Orientations, grid: Grid, + rescaling_factor: float = None, centers: Union[list, pn.DataFrame] = None): + + self.surface_points = surface_points + self.orientations = orientations + self.grid = grid + + self.df = pn.DataFrame(np.array([rescaling_factor, centers]).reshape(1, -1), + index=['values'], + columns=['rescaling factor', 'centers']) + + self.rescale_data(rescaling_factor=rescaling_factor, centers=centers) + + def __repr__(self): + return self.df.T.to_string() + + def _repr_html_(self): + return self.df.T.to_html() + + @_setdoc_pro([ds.centers, ds.rescaling_factor]) + def modify_rescaling_parameters(self, attribute, value): + """ + Modify the parameters used to rescale data + + Args: + attribute (str): Attribute to be modified. It can be: centers, rescaling factor + * centers: [s0] + * rescaling factor: [s1] + value (float, list[float]) + + + Returns: + :class:`gempy_lite.core.kernel_data.geometric_data.Rescaling` + + """ + assert np.isin(attribute, self.df.columns).all(), 'Valid attributes are: ' + np.array2string(self.df.columns) + + if attribute == 'centers': + try: + assert value.shape[0] == 3 + + self.df.loc['values', attribute] = value + + except AssertionError: + print('centers length must be 3: XYZ') + + else: + self.df.loc['values', attribute] = value + + return self + + @_setdoc_pro([ds.centers, ds.rescaling_factor]) + def rescale_data(self, rescaling_factor=None, centers=None): + """ + Rescale inplace: surface_points, orientations---adding columns in the categories_df---and grid---adding values_r + attributes. The rescaled values will get stored on the linked objects. + + Args: + rescaling_factor: [s1] + centers: [s0] + + Returns: + + """ + max_coord, min_coord = self.max_min_coord(self.surface_points, self.orientations) + if rescaling_factor is None: + self.df['rescaling factor'] = self.compute_rescaling_factor(self.surface_points, self.orientations, + max_coord, min_coord) + else: + self.df['rescaling factor'] = rescaling_factor + if centers is None: + self.df.at['values', 'centers'] = self.compute_data_center(self.surface_points, self.orientations, + max_coord, min_coord) + else: + self.df.at['values', 'centers'] = centers + + self.set_rescaled_surface_points() + self.set_rescaled_orientations() + self.set_rescaled_grid() + return True + + def get_rescaled_surface_points(self): + """ + Get the rescaled coordinates. return an image of the interface and orientations categories_df with the X_r.. + columns + + Returns: + :attr:`SurfacePoints.df[['X_r', 'Y_r', 'Z_r']]` + """ + return self.surface_points.df[['X_r', 'Y_r', 'Z_r']], + + def get_rescaled_orientations(self): + """ + Get the rescaled coordinates. return an image of the interface and orientations categories_df with the X_r.. + columns. + + Returns: + :attr:`Orientations.df[['X_r', 'Y_r', 'Z_r']]` + """ + return self.orientations.df[['X_r', 'Y_r', 'Z_r']] + + @staticmethod + @_setdoc_pro([SurfacePoints.__doc__, Orientations.__doc__]) + def max_min_coord(surface_points=None, orientations=None): + """ + Find the maximum and minimum location of any input data in each cartesian coordinate + + Args: + surface_points (:class:`SurfacePoints`): [s0] + orientations (:class:`Orientations`): [s1] + + Returns: + tuple: max[XYZ], min[XYZ] + """ + if surface_points is None: + if orientations is None: + raise AttributeError('You must pass at least one Data object') + else: + df = orientations.df + else: + if orientations is None: + df = surface_points.df + else: + df = pn.concat([orientations.df, surface_points.df], sort=False) + + max_coord = df.max()[['X', 'Y', 'Z']] + min_coord = df.min()[['X', 'Y', 'Z']] + return max_coord, min_coord + + @_setdoc_pro([SurfacePoints.__doc__, Orientations.__doc__, ds.centers]) + def compute_data_center(self, surface_points=None, orientations=None, + max_coord=None, min_coord=None, inplace=True): + """ + Calculate the center of the data once it is shifted between 0 and 1. + + Args: + surface_points (:class:`SurfacePoints`): [s0] + orientations (:class:`Orientations`): [s1] + max_coord (float): Max XYZ coordinates of all GeometricData + min_coord (float): Min XYZ coordinates of all GeometricData + inplace (bool): if True modify the self.df rescaling factor attribute + + Returns: + np.array: [s2] + """ + + if max_coord is None or min_coord is None: + max_coord, min_coord = self.max_min_coord(surface_points, orientations) + + # Get the centers of every axis + centers = ((max_coord + min_coord) / 2).astype(float).values + if inplace is True: + self.df.at['values', 'centers'] = centers + return centers + + @_setdoc_pro([SurfacePoints.__doc__, Orientations.__doc__, ds.rescaling_factor]) + def compute_rescaling_factor(self, surface_points=None, orientations=None, + max_coord=None, min_coord=None, inplace=True): + """ + Calculate the rescaling factor of the data to keep all coordinates between 0 and 1 + + Args: + surface_points (:class:`SurfacePoints`): [s0] + orientations (:class:`Orientations`): [s1] + max_coord (float): Max XYZ coordinates of all GeometricData + min_coord (float): Min XYZ coordinates of all GeometricData + inplace (bool): if True modify the self.df rescaling factor attribute + + Returns: + float: [s2] + """ + + if max_coord is None or min_coord is None: + max_coord, min_coord = self.max_min_coord(surface_points, orientations) + rescaling_factor_val = (2 * np.max(max_coord - min_coord)) + if inplace is True: + self.df['rescaling factor'] = rescaling_factor_val + return rescaling_factor_val + + # def update_rescaling_factor(self, surface_points=None, orientations=None, + # max_coord=None, min_coord=None): + # self.compute_rescaling_factor(surface_points, orientations, max_coord, min_coord, inplace=True) + + @staticmethod + @_setdoc_pro([SurfacePoints.__doc__, compute_data_center.__doc__, compute_rescaling_factor.__doc__, ds.idx_sp]) + def rescale_surface_points(surface_points, rescaling_factor, centers, idx: list = None): + """ + Rescale inplace: surface_points. The rescaled values will get stored on the linked objects. + + Args: + surface_points (:class:`SurfacePoints`): [s0] + rescaling_factor: [s2] + centers: [s1] + idx (int, list of int): [s3] + + Returns: + + """ + + if idx is None: + idx = surface_points.df.index + + # Change the coordinates of surface_points + new_coord_surface_points = (surface_points.df.loc[idx, ['X', 'Y', 'Z']] - + centers) / rescaling_factor + 0.5001 + + new_coord_surface_points.rename(columns={"X": "X_r", "Y": "Y_r", "Z": 'Z_r'}, inplace=True) + return new_coord_surface_points + + @_setdoc_pro(ds.idx_sp) + def set_rescaled_surface_points(self, idx: Union[list, np.ndarray] = None): + """ + Set the rescaled coordinates into the surface_points categories_df + + Args: + idx (int, list of int): [s0] + + Returns: + + """ + if idx is None: + idx = self.surface_points.df.index + idx = np.atleast_1d(idx) + + self.surface_points.df.loc[idx, ['X_r', 'Y_r', 'Z_r']] = self.rescale_surface_points( + self.surface_points, self.df.loc['values', 'rescaling factor'], self.df.loc['values', 'centers'], idx=idx) + + return self.surface_points + + def rescale_data_point(self, data_points: np.ndarray, rescaling_factor=None, centers=None): + """This method now is very similar to set_rescaled_surface_points passing an index""" + if rescaling_factor is None: + rescaling_factor = self.df.loc['values', 'rescaling factor'] + if centers is None: + centers = self.df.loc['values', 'centers'] + + rescaled_data_point = (data_points - centers) / rescaling_factor + 0.5001 + + return rescaled_data_point + + @staticmethod + @_setdoc_pro([Orientations.__doc__, compute_data_center.__doc__, compute_rescaling_factor.__doc__, ds.idx_sp]) + def rescale_orientations(orientations, rescaling_factor, centers, idx: list = None): + """ + Rescale inplace: surface_points. The rescaled values will get stored on the linked objects. + + Args: + orientations (:class:`Orientations`): [s0] + rescaling_factor: [s2] + centers: [s1] + idx (int, list of int): [s3] + + Returns: + + """ + if idx is None: + idx = orientations.df.index + + # Change the coordinates of orientations + new_coord_orientations = (orientations.df.loc[idx, ['X', 'Y', 'Z']] - + centers) / rescaling_factor + 0.5001 + + new_coord_orientations.rename(columns={"X": "X_r", "Y": "Y_r", "Z": 'Z_r'}, inplace=True) + + return new_coord_orientations + + @_setdoc_pro(ds.idx_sp) + def set_rescaled_orientations(self, idx: Union[list, np.ndarray] = None): + """ + Set the rescaled coordinates into the surface_points categories_df + + Args: + idx (int, list of int): [s0] + + Returns: + + """ + if idx is None: + idx = self.orientations.df.index + idx = np.atleast_1d(idx) + + self.orientations.df.loc[idx, ['X_r', 'Y_r', 'Z_r']] = self.rescale_orientations( + self.orientations, self.df.loc['values', 'rescaling factor'], self.df.loc['values', 'centers'], idx=idx) + return True + + @staticmethod + def rescale_grid(grid, rescaling_factor, centers: pn.DataFrame): + new_grid_extent = (grid.regular_grid.extent - np.repeat(centers, 2)) / rescaling_factor + 0.5001 + new_grid_values = (grid.values - centers) / rescaling_factor + 0.5001 + return new_grid_extent, new_grid_values, + + def set_rescaled_grid(self): + """ + Set the rescaled coordinates and extent into a grid object + """ + + self.grid.extent_r, self.grid.values_r = self.rescale_grid( + self.grid, self.df.loc['values', 'rescaling factor'], self.df.loc['values', 'centers']) + + self.grid.regular_grid.extent_r, self.grid.regular_grid.values_r = self.grid.extent_r, self.grid.values_r + + +class AdditionalData(object): + """ + Container class that encapsulate :class:`Structure`, :class:`KrigingParameters`, :class:`Options` and + rescaling parameters + + Args: + surface_points (:class:`SurfacePoints`): [s0] + orientations (:class:`Orientations`): [s1] + grid (:class:`Grid`): [s2] + faults (:class:`Faults`): [s4] + surfaces (:class:`Surfaces`): [s3] + rescaling (:class:`RescaledData`): [s5] + + Attributes: + structure_data (:class:`Structure`): [s6] + options (:class:`gempy_lite.Options`): [s8] + kriging_data (:class:`Structure`): [s7] + rescaling_data (:class:`RescaledData`): + + """ + + def __init__(self, surface_points, orientations, grid: Grid, + faults, surfaces: Surfaces, rescaling): + self.structure_data = Structure(surface_points, orientations, surfaces, faults) + self.options = Options() + self.kriging_data = KrigingParameters(self.structure_data, grid) + self.rescaling_data = rescaling + + def __repr__(self): + concat_ = self.get_additional_data() + return concat_.to_string() + + def _repr_html_(self): + concat_ = self.get_additional_data() + return concat_.to_html() + + def get_additional_data(self): + """ + Concatenate all linked data frames and transpose them for a nice visualization. + + Returns: + pn.DataFrame: concatenated and transposed dataframe + """ + concat_ = pn.concat([self.structure_data.df, self.options.df, self.kriging_data.df, self.rescaling_data.df], + axis=1, keys=['Structure', 'Options', 'Kriging', 'Rescaling']) + return concat_.T + + def update_default_kriging(self): + """ + Update default kriging values. + """ + self.kriging_data.set_default_range() + self.kriging_data.set_default_c_o() + self.kriging_data.set_u_grade() + + def update_structure(self): + """ + Update fields dependent on input data sucha as structure and universal kriging grade + """ + self.structure_data.update_structure_from_input() + if len(self.kriging_data.df.loc['values', 'drift equations']) < \ + self.structure_data.df.loc['values', 'number series']: + self.kriging_data.set_u_grade() \ No newline at end of file diff --git a/gempy_lite/core/solution.py b/gempy_lite/core/solution.py index 99c9e5e..728b0a0 100644 --- a/gempy_lite/core/solution.py +++ b/gempy_lite/core/solution.py @@ -3,8 +3,9 @@ import warnings # from skimage import measure from gempy_lite.utils.input_manipulation import find_interfaces_from_block_bottoms -from gempy_lite.core.data import Grid, Surfaces -from gempy_lite.core.data_modules.stack import Series +from gempy_lite.core.structured_data import Grid +from gempy_lite.core.kernel_data import Surfaces +from gempy_lite.core.kernel_data.stack import Series from gempy_lite.utils.meta import _setdoc, _setdoc_pro import gempy_lite.utils.docstring as ds diff --git a/gempy_lite/core/structured_data.py b/gempy_lite/core/structured_data.py index 3f59141..9ee3311 100644 --- a/gempy_lite/core/structured_data.py +++ b/gempy_lite/core/structured_data.py @@ -6,3 +6,238 @@ """ +from typing import Union + +from gempy_lite.core.grid_modules import grid_types, topography +from gempy_lite.utils.meta import _setdoc_pro, _setdoc +import gempy_lite.utils.docstring as ds +import numpy as np + + +@_setdoc_pro([grid_types.RegularGrid.__doc__, grid_types.CustomGrid.__doc__]) +class Grid(object): + """ Class to generate grids. + + This class is used to create points where to + evaluate the geological model. This class serves a container which transmit the XYZ coordinates to the + interpolator. There are several type of grids objects will feed into the Grid class + + Args: + **kwargs: See below + + Keyword Args: + regular (:class:`gempy.core.grid_modules.grid_types.RegularGrid`): [s0] + custom (:class:`gempy.core.grid_modules.grid_types.CustomGrid`): [s1] + topography (:class:`gempy_lite.core.grid_modules.grid_types.Topography`): [s2] + sections (:class:`gempy.core.grid_modules.grid_types.Sections`): [s3] + gravity (:class:`gempy_lite.core.grid_modules.grid_types.Gravity`): + + Attributes: + values (np.ndarray): coordinates where the model is going to be evaluated. This are the coordinates + concatenation of all active grids. + values_r (np.ndarray): rescaled coordinates where the model is going to be evaluated + length (np.ndarray):I a array which contain the slicing index for each grid type in order. The first element will + be 0, the second the length of the regular grid; the third custom and so on. This can be used to slice the + solutions correspondent to each of the grids + grid_types(np.ndarray[str]): names of the current grids of GemPy + active_grids(np.ndarray[bool]): boolean array which control which type of grid is going to be computed and + hence on the property `values`. + regular_grid (:class:`gempy.core.grid_modules.grid_types.RegularGrid`) + custom_grid (:class:`gempy.core.grid_modules.grid_types.CustomGrid`) + topography (:class:`gempy_lite.core.grid_modules.grid_types.Topography`) + sections (:class:`gempy.core.grid_modules.grid_types.Sections`) + gravity_grid (:class:`gempy_lite.core.grid_modules.grid_types.Gravity`) + """ + + def __init__(self, **kwargs): + + self.values = np.empty((0, 3)) + self.values_r = np.empty((0, 3)) + self.length = np.empty(0) + self.grid_types = np.array(['regular', 'custom', 'topography', 'sections', 'centered']) + self.active_grids = np.zeros(5, dtype=bool) + # All grid types must have values + + # Init optional grids + self.custom_grid = None + self.custom_grid_grid_active = False + self.topography = None + self.topography_grid_active = False + self.sections_grid_active = False + self.centered_grid = None + self.centered_grid_active = False + + # Init basic grid empty + self.regular_grid = self.create_regular_grid(set_active=False, **kwargs) + self.regular_grid_active = False + + # Init optional sections + self.sections = grid_types.Sections(regular_grid=self.regular_grid) + + self.update_grid_values() + + def __str__(self): + return 'Grid Object. Values: \n' + np.array2string(self.values) + + def __repr__(self): + return 'Grid Object. Values: \n' + np.array_repr(self.values) + + @_setdoc(grid_types.RegularGrid.__doc__) + def create_regular_grid(self, extent=None, resolution=None, set_active=True, *args, **kwargs): + """ + Set a new regular grid and activate it. + + Args: + extent (np.ndarray): [x_min, x_max, y_min, y_max, z_min, z_max] + resolution (np.ndarray): [nx, ny, nz] + + RegularGrid Docs + """ + self.regular_grid = grid_types.RegularGrid(extent, resolution, **kwargs) + if set_active is True: + self.set_active('regular') + return self.regular_grid + + @_setdoc_pro(ds.coord) + def create_custom_grid(self, custom_grid: np.ndarray): + """ + Set a new regular grid and activate it. + + Args: + custom_grid (np.array): [s0] + + """ + self.custom_grid = grid_types.CustomGrid(custom_grid) + self.set_active('custom') + + def create_topography(self, source='random', **kwargs): + """Create a topography grid and activate it. + + Args: + source: + * 'gdal': Load topography from a raster file. + * 'random': Generate random topography (based on a fractal grid). + * 'saved': Load topography that was saved with the topography.save() function. + This is useful after loading and saving a heavy raster file with gdal once or after saving a + random topography with the save() function. This .npy file can then be set as topography. + + Keyword Args: + source = 'gdal': + * filepath: path to raster file, e.g. '.tif', (for all file formats see + https://gdal.org/drivers/raster/index.html) + + source = 'random': + * fd: fractal dimension, defaults to 2.0 + * d_z: maximum height difference. If none, last 20% of the model in z direction + * extent: extent in xy direction. If none, geo_model.grid.extent + * resolution: desired resolution of the topography array. If none, geo_model.grid.resoution + + source = 'saved': + * filepath: path to the .npy file that was created using the topography.save() function + + Returns: + :class:gempy_lite.core.data.Topography + """ + self.topography = topography.Topography(self.regular_grid) + + if source == 'random': + self.topography.load_random_hills(**kwargs) + elif source == 'gdal': + filepath = kwargs.get('filepath', None) + if filepath is not None: + self.topography.load_from_gdal(filepath) + else: + print('to load a raster file, a path to the file must be provided') + elif source == 'saved': + filepath = kwargs.get('filepath', None) + if filepath is not None: + self.topography.load_from_saved(filepath) + else: + print('path to .npy file must be provided') + else: + raise AttributeError('source must be random, gdal or saved') + + self.set_active('topography') + + @_setdoc(grid_types.Sections.__doc__) + def create_section_grid(self, section_dict): + self.sections = grid_types.Sections(regular_grid=self.regular_grid, section_dict=section_dict) + self.set_active('sections') + return self.sections + + @_setdoc(grid_types.CenteredGrid.set_centered_grid.__doc__) + def create_centered_grid(self, centers, radius, resolution=None): + """Initialize gravity grid. Deactivate the rest of the grids""" + self.centered_grid = grid_types.CenteredGrid(centers, radius, resolution) + # self.active_grids = np.zeros(4, dtype=bool) + self.set_active('centered') + + def deactivate_all_grids(self): + """ + Deactivates the active grids array + :return: + """ + self.active_grids = np.zeros(5, dtype=bool) + self.update_grid_values() + return self.active_grids + + def set_active(self, grid_name: Union[str, np.ndarray]): + """ + Set active a given or several grids + Args: + grid_name (str, list): + + """ + where = self.grid_types == grid_name + self.active_grids[where] = True + self.update_grid_values() + return self.active_grids + + def set_inactive(self, grid_name: str): + where = self.grid_types == grid_name + self.active_grids *= ~where + self.update_grid_values() + return self.active_grids + + def update_grid_values(self): + """ + Copy XYZ coordinates from each specific grid to Grid.values for those which are active. + + Returns: + values + + """ + self.length = np.empty(0) + self.values = np.empty((0, 3)) + lengths = [0] + try: + for e, grid_types in enumerate( + [self.regular_grid, self.custom_grid, self.topography, self.sections, self.centered_grid]): + if self.active_grids[e]: + self.values = np.vstack((self.values, grid_types.values)) + lengths.append(grid_types.values.shape[0]) + else: + lengths.append(0) + except AttributeError: + raise AttributeError('Grid type does not exist yet. Set the grid before activating it.') + + self.length = np.array(lengths).cumsum() + return self.values + + def get_grid_args(self, grid_name: str): + assert type(grid_name) is str, 'Only one grid type can be retrieved' + assert grid_name in self.grid_types, 'possible grid types are ' + str(self.grid_types) + where = np.where(self.grid_types == grid_name)[0][0] + return self.length[where], self.length[where + 1] + + def get_grid(self, grid_name: str): + assert type(grid_name) is str, 'Only one grid type can be retrieved' + + l_0, l_1 = self.get_grid_args(grid_name) + return self.values[l_0:l_1] + + def get_section_args(self, section_name: str): + # assert type(section_name) is str, 'Only one section type can be retrieved' + l0, l1 = self.get_grid_args('sections') + where = np.where(self.sections.names == section_name)[0][0] + return l0 + self.sections.length[where], l0 + self.sections.length[where + 1] diff --git a/gempy_lite/core/unstructured_data.py b/gempy_lite/core/unstructured_data.py deleted file mode 100644 index d0426ac..0000000 --- a/gempy_lite/core/unstructured_data.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Unstructured data module, i.e.: -- Faults -- Series/Features -- Surfaces -- SurfacePoints -- Orientations - -=== -DERIVATIVE DATA -=== -- Rescaled Data -- Additional Data - - Structure - - Kriging Parameters - - Options -=== -- Interpolation - - - """ - -from dataclasses import dataclass -import xarray as xr -import pandas as pd -import numpy as np - - -@dataclass -class UnstructGemPy: - """This should be one df and everything and I mean everything should be views - - """ - - - - - # vertex: np.ndarray - # edges: np.ndarray - # attributes: Optional[pd.DataFrame] = None - - # def __init__(self, vertex: np.ndarray, edges: np.ndarray, - # attributes: Optional[pd.DataFrame] = None, - # points_attributes: Optional[pd.DataFrame] = None): - # v = xr.DataArray(vertex, dims=['points', 'XYZ']) - # e = xr.DataArray(edges, dims=['edge', 'nodes']) - # - # if attributes is None: - # attributes = pd.DataFrame(np.zeros((edges.shape[0], 0))) - # - # if points_attributes is None: - # points_attributes = pd.DataFrame(np.zeros((vertex.shape[0], 0))) - # - # a = xr.DataArray(attributes, dims=['edge', 'attribute']) - # pa = xr.DataArray(points_attributes, dims=['points', 'points_attribute']) - # - # c = xr.Dataset({'vertex': v, 'edges': e, - # 'attributes': a, 'points_attributes': pa}) - # self.data = c.reset_index('edge') - # - # self.validate() diff --git a/gempy_lite/gempy_api.py b/gempy_lite/gempy_api.py index c9448e3..e65705b 100644 --- a/gempy_lite/gempy_api.py +++ b/gempy_lite/gempy_api.py @@ -158,8 +158,8 @@ def init_data(geo_model: Project, extent: Union[list, ndarray] = None, """Initialize some of the main functions such as: - Regular grid (:class:`gempy_lite.core.data.Grid`). - - read_csv: :class:`gempy_lite.core.data_modules.geometric_data.SurfacePoints` - and :class:`gempy_lite.core.data_modules.geometric_data.Orientations` From csv files + - read_csv: :class:`gempy_lite.core.kernel_data.geometric_data.SurfacePoints` + and :class:`gempy_lite.core.kernel_data.geometric_data.Orientations` From csv files - set_values to default Args: diff --git a/test/test_input_data/test_data_classes.py b/test/test_input_data/test_data_classes.py index 6388485..8f53687 100644 --- a/test/test_input_data/test_data_classes.py +++ b/test/test_input_data/test_data_classes.py @@ -8,13 +8,15 @@ import matplotlib.pyplot as plt import pytest -import gempy_lite.core.data_modules.geometric_data -import gempy_lite.core.data_modules.stack +import gempy_lite.core.kernel_data +import gempy_lite.core.kernel_data.geometric_data +import gempy_lite.core.kernel_data.stack +import gempy_lite.core.model_data @pytest.fixture(scope='module') def create_faults(): - faults = gempy_lite.core.data_modules.stack.Faults() + faults = gempy_lite.core.kernel_data.stack.Faults() return faults @@ -22,7 +24,7 @@ def create_faults(): def create_series(create_faults): faults = create_faults - series = gempy_lite.core.data_modules.stack.Series(faults) + series = gempy_lite.core.kernel_data.stack.Series(faults) series.set_series_index(['foo', 'foo2', 'foo5', 'foo7']) series.add_series('foo3') series.delete_series('foo2') @@ -43,7 +45,7 @@ def create_series(create_faults): @pytest.fixture(scope='module') def create_surfaces(create_series): series = create_series - surfaces = gp.Surfaces(series) + surfaces = gempy_lite.core.kernel_data.Surfaces(series) surfaces.set_surfaces_names(['foo', 'foo2', 'foo5']) print(series) @@ -140,7 +142,7 @@ def create_surface_points(create_surfaces, create_series): # These two DataFrames (df from now on) will contain the individual information of each point at an interface or # orientation. Some properties of this table are mapped from the *df* below. surfaces = create_surfaces - surface_points = gempy_lite.core.data_modules.geometric_data.SurfacePoints(surfaces) + surface_points = gempy_lite.core.kernel_data.geometric_data.SurfacePoints(surfaces) print(surface_points) @@ -169,7 +171,7 @@ def create_orientations(create_surfaces, create_series): surfaces = create_surfaces # ### Orientations - orientations = gempy_lite.core.data_modules.geometric_data.Orientations(surfaces) + orientations = gempy_lite.core.kernel_data.geometric_data.Orientations(surfaces) print(orientations) @@ -204,7 +206,7 @@ def create_orientations(create_surfaces, create_series): def test_add_orientation_with_pole(create_surfaces): - orientations = gempy_lite.core.data_modules.geometric_data.Orientations(create_surfaces) + orientations = gempy_lite.core.kernel_data.geometric_data.Orientations(create_surfaces) orientations.add_orientation(1, 1, 1, 'foo', pole_vector=(1, 0, 1)) orientations.add_orientation(2, 2, 2, 'foo', orientation=(0, 0, 1)) orientations.add_orientation(1, 1, 1, 'foo', pole_vector=(.45, 0, .45)) @@ -224,7 +226,7 @@ def create_grid(): @pytest.fixture(scope='module') def create_rescaling(create_surface_points, create_orientations, create_grid): - rescaling = gempy_lite.core.data_modules.geometric_data.RescaledData(create_surface_points, create_orientations, create_grid) + rescaling = gempy_lite.core.model_data.RescaledData(create_surface_points, create_orientations, create_grid) return rescaling @@ -232,8 +234,8 @@ def create_rescaling(create_surface_points, create_orientations, create_grid): def create_additional_data(create_surface_points, create_orientations, create_grid, create_faults, create_surfaces, create_rescaling): - ad = gp.AdditionalData(create_surface_points, create_orientations, create_grid, create_faults, - create_surfaces, create_rescaling) + ad = gempy_lite.core.model_data.AdditionalData(create_surface_points, create_orientations, create_grid, create_faults, + create_surfaces, create_rescaling) return ad @@ -259,7 +261,7 @@ def test_additional_data(self, create_additional_data): def test_stack(): - stack = gempy_lite.core.data_modules.stack.Stack() + stack = gempy_lite.core.kernel_data.stack.Stack() stack.set_series_index(['foo', 'foo2', 'foo5', 'foo7']) stack.add_series('foo3') stack.delete_series('foo2') diff --git a/test/test_input_data/test_data_mutation2.py b/test/test_input_data/test_data_mutation2.py index 32b8759..c7a8129 100644 --- a/test/test_input_data/test_data_mutation2.py +++ b/test/test_input_data/test_data_mutation2.py @@ -2,6 +2,9 @@ # These two lines are necessary only if GemPy is not installed import sys, os + +import gempy_lite.core.kernel_data + sys.path.append("../..") # Importing GemPy @@ -32,7 +35,7 @@ def test_add_point(): def test_restricting_wrapper(): from gempy_lite.core.model import RestrictingWrapper - surface = gp.Surfaces(gp.core.data_modules.stack.Series(gp.core.data_modules.stack.Faults())) + surface = gempy_lite.core.kernel_data.Surfaces(gp.core.kernel_data.stack.Series(gp.core.kernel_data.stack.Faults())) s = RestrictingWrapper(surface) diff --git a/test/test_input_data/test_input.py b/test/test_input_data/test_input.py index 2b58342..933de9f 100644 --- a/test/test_input_data/test_input.py +++ b/test/test_input_data/test_input.py @@ -1,4 +1,3 @@ -from gempy_lite.core.unstructured_data import UnstructGemPy import xarray as xr import pytest @@ -7,17 +6,6 @@ def test_all_running(model_horizontal_two_layers): print(model_horizontal_two_layers.surfaces) -@pytest.mark.skip -def test_unstruct_gempy(model_horizontal_two_layers): - ug = UnstructGemPy( - model_horizontal_two_layers.surfaces.df, - model_horizontal_two_layers.stack.df, - model_horizontal_two_layers.surface_points.df - ) - - print(ug) - - @pytest.mark.skip def test_combine_stack_surfaces(model_horizontal_two_layers): surf = model_horizontal_two_layers.surfaces.df From 7741168ea8d692b790a1cb517cfc5fbb2c214104 Mon Sep 17 00:00:00 2001 From: Leguark Date: Tue, 27 Oct 2020 08:04:52 +0100 Subject: [PATCH 02/10] [ENH] stack refactoring --- gempy_lite/api_modules/io.py | 12 +- gempy_lite/core/kernel_data/__init__.py | 8 +- gempy_lite/core/kernel_data/geometric_data.py | 30 ++--- gempy_lite/core/kernel_data/stack.py | 106 +++++++++--------- gempy_lite/core/model.py | 6 +- gempy_lite/utils/input_manipulation.py | 4 +- test/input_data/test_stack.csv | 6 + test/test_input_data/test_data_classes.py | 16 ++- 8 files changed, 99 insertions(+), 89 deletions(-) create mode 100644 test/input_data/test_stack.csv diff --git a/gempy_lite/api_modules/io.py b/gempy_lite/api_modules/io.py index 0e7ce3c..464bb55 100644 --- a/gempy_lite/api_modules/io.py +++ b/gempy_lite/api_modules/io.py @@ -121,7 +121,7 @@ def load_model(name=None, path=None, recompile=False): # do series properly - this needs proper check # Load series - s = pn.read_csv(f'{path}/{name}_series.csv', index_col=0, dtype={'order_series': 'int32', + s = pn.read_csv(f'{path}/{name}_series.csv', index_col=0, dtype={'OrderFeature': 'int32', 'BottomRelation': 'category'}) f = pn.read_csv(f'{path}/{name}_faults.csv', index_col=0, @@ -174,7 +174,7 @@ def load_model(name=None, path=None, recompile=False): 'X_r': 'float64', 'Y_r': 'float64', 'Z_r': 'float64', 'dip': 'float64', 'azimuth': 'float64', 'polarity': 'float64', 'surface': 'category', 'series': 'category', - 'id': 'int64', 'order_series': 'int64'}) + 'id': 'int64', 'OrderFeature': 'int64'}) geo_model._orientations.df['surface'].cat.set_categories(cat_surfaces, inplace=True) geo_model._orientations.df['series'].cat.set_categories(cat_series, inplace=True) @@ -183,7 +183,7 @@ def load_model(name=None, path=None, recompile=False): dtype={'X': 'float64', 'Y': 'float64', 'Z': 'float64', 'X_r': 'float64', 'Y_r': 'float64', 'Z_r': 'float64', 'surface': 'category', 'series': 'category', - 'id': 'int64', 'order_series': 'int64'}) + 'id': 'int64', 'OrderFeature': 'int64'}) geo_model._surface_points.df['surface'].cat.set_categories(cat_surfaces, inplace=True) geo_model._surface_points.df['series'].cat.set_categories(cat_series, inplace=True) @@ -247,7 +247,7 @@ def load_model(name=None, path=None, recompile=False): # def load_series(geo_model, path, name): # # do series properly - this needs proper check # geo_model._stack.df = pn.read_csv(f'{path}/{name}_series.csv', index_col=0, -# dtype={'order_series': 'int32', 'BottomRelation': 'category'}) +# dtype={'OrderFeature': 'int32', 'BottomRelation': 'category'}) # series_index = pn.CategoricalIndex(geo_model._stack.df.index.values) # # geo_model.series.df.index = pn.CategoricalIndex(series_index) # geo_model._stack.df.index = series_index @@ -289,7 +289,7 @@ def load_model(name=None, path=None, recompile=False): # 'X_r': 'float64', 'Y_r': 'float64', 'Z_r': 'float64', # 'dip': 'float64', 'azimuth': 'float64', 'polarity': 'float64', # 'surface': 'category', 'series': 'category', -# 'id': 'int64', 'order_series': 'int64'}) +# 'id': 'int64', 'OrderFeature': 'int64'}) # geo_model._orientations.df['surface'].cat.set_categories(cat_surfaces, inplace=True) # geo_model._orientations.df['series'].cat.set_categories(cat_series, inplace=True) # @@ -300,7 +300,7 @@ def load_model(name=None, path=None, recompile=False): # dtype={'X': 'float64', 'Y': 'float64', 'Z': 'float64', # 'X_r': 'float64', 'Y_r': 'float64', 'Z_r': 'float64', # 'surface': 'category', 'series': 'category', -# 'id': 'int64', 'order_series': 'int64'}) +# 'id': 'int64', 'OrderFeature': 'int64'}) # geo_model._surface_points.df['surface'].cat.set_categories(cat_surfaces, inplace=True) # geo_model._surface_points.df['series'].cat.set_categories(cat_series, inplace=True) # diff --git a/gempy_lite/core/kernel_data/__init__.py b/gempy_lite/core/kernel_data/__init__.py index 4d05047..4e9f91c 100644 --- a/gempy_lite/core/kernel_data/__init__.py +++ b/gempy_lite/core/kernel_data/__init__.py @@ -668,7 +668,7 @@ def set_length_surfaces_i(self): # Extracting lengths # ================== # Array containing the size of every surface. SurfacePoints - lssp = self.surface_points.df.groupby('id')['order_series'].count().values + lssp = self.surface_points.df.groupby('id')['OrderFeature'].count().values lssp_nonzero = lssp[np.nonzero(lssp)] self.df.at['values', 'len surfaces surface_points'] = lssp_nonzero @@ -687,7 +687,7 @@ def set_series_and_length_series_i(self): len_series = self.surfaces.series.df.shape[0] # Array containing the size of every series. SurfacePoints. - points_count = self.surface_points.df['order_series'].value_counts(sort=False) + points_count = self.surface_points.df['OrderFeature'].value_counts(sort=False) len_series_i = np.zeros(len_series, dtype=int) len_series_i[points_count.index - 1] = points_count.values @@ -709,7 +709,7 @@ def set_length_series_o(self): # Array containing the size of every series. orientations. len_series_o = np.zeros(self.surfaces.series.df.shape[0], dtype=int) - ori_count = self.orientations.df['order_series'].value_counts(sort=False) + ori_count = self.orientations.df['OrderFeature'].value_counts(sort=False) len_series_o[ori_count.index - 1] = ori_count.values self.df.at['values', 'len series orientations'] = len_series_o @@ -725,7 +725,7 @@ def set_number_of_surfaces_per_series(self): """ len_sps = np.zeros(self.surfaces.series.df.shape[0], dtype=int) - surf_count = self.surface_points.df.groupby('order_series'). \ + surf_count = self.surface_points.df.groupby('OrderFeature'). \ surface.nunique() len_sps[surf_count.index - 1] = surf_count.values diff --git a/gempy_lite/core/kernel_data/geometric_data.py b/gempy_lite/core/kernel_data/geometric_data.py index c91796d..69c9fd3 100644 --- a/gempy_lite/core/kernel_data/geometric_data.py +++ b/gempy_lite/core/kernel_data/geometric_data.py @@ -46,7 +46,7 @@ def init_dependent_properties(self): # series self.df['series'] = 'Default series' self.df['series'] = self.df['series'].astype('category', copy=True) - #self.df['order_series'] = self.df['order_series'].astype('category', copy=True) + #self.df['OrderFeature'] = self.df['OrderFeature'].astype('category', copy=True) self.df['series'].cat.set_categories(self.surfaces.df['series'].cat.categories, inplace=True) @@ -54,7 +54,7 @@ def init_dependent_properties(self): self.df['id'] = np.nan # order_series - self.df['order_series'] = 1 + self.df['OrderFeature'] = 1 return self @staticmethod @@ -74,7 +74,7 @@ def sort_table(self): """ # We order the pandas table by surface (also by series in case something weird happened) - self.df.sort_values(by=['order_series', 'id'], + self.df.sort_values(by=['OrderFeature', 'id'], ascending=True, kind='mergesort', inplace=True) return self.df @@ -124,15 +124,15 @@ def map_data_from_series(self, series, attribute: str, idx=None): idx = self.df.index idx = np.atleast_1d(idx) - if attribute in ['id', 'order_series']: + if attribute in ['id', 'OrderFeature']: self.df.loc[idx, attribute] = self.df['series'].map(series.df[attribute]).astype(int) else: self.df.loc[idx, attribute] = self.df['series'].map(series.df[attribute]) - if type(self.df['order_series'].dtype) is pn.CategoricalDtype: + if type(self.df['OrderFeature'].dtype) is pn.CategoricalDtype: - self.df['order_series'].cat.remove_unused_categories(inplace=True) + self.df['OrderFeature'].cat.remove_unused_categories(inplace=True) return self @_setdoc_pro(Surfaces.__doc__) @@ -159,7 +159,7 @@ def map_data_from_surfaces(self, surfaces, attribute: str, idx=None): 'Surfaces.map_series_from_series.') self.df.loc[idx, attribute] = self.df.loc[idx, 'surface'].map(surfaces.df.set_index('surface')[attribute]) - elif attribute in ['id', 'order_series']: + elif attribute in ['id', 'OrderFeature']: self.df.loc[idx, attribute] = (self.df.loc[idx, 'surface'].map(surfaces.df.set_index('surface')[attribute])).astype(int) else: @@ -205,10 +205,10 @@ def __init__(self, surfaces: Surfaces, coord=None, surface=None): super().__init__(surfaces) self._columns_i_all = ['X', 'Y', 'Z', 'surface', 'series', 'X_std', 'Y_std', 'Z_std', - 'order_series', 'surface_number'] + 'OrderFeature', 'surface_number'] self._columns_i_1 = ['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r', 'surface', 'series', 'id', - 'order_series', 'isFault', 'smooth'] + 'OrderFeature', 'isFault', 'smooth'] self._columns_rep = ['X', 'Y', 'Z', 'surface', 'series'] self._columns_i_num = ['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r'] @@ -307,7 +307,7 @@ def add_surface_points(self, x: Union[float, np.ndarray], y: Union[float, np.nda self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) - self.map_data_from_series(self.surfaces.series, 'order_series', idx=idx) + self.map_data_from_series(self.surfaces.series, 'OrderFeature', idx=idx) self.sort_table() return self, idx @@ -348,7 +348,7 @@ def modify_surface_points(self, idx: Union[int, list, np.ndarray], **kwargs): self.df.loc[idx, ['surface']] = surface_names self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) - self.map_data_from_series(self.surfaces.series, 'order_series', idx=idx) + self.map_data_from_series(self.surfaces.series, 'OrderFeature', idx=idx) self.sort_table() except KeyError: pass @@ -485,9 +485,9 @@ class Orientations(GeometricData): def __init__(self, surfaces: Surfaces, coord=None, pole_vector=None, orientation=None, surface=None): super().__init__(surfaces) self._columns_o_all = ['X', 'Y', 'Z', 'G_x', 'G_y', 'G_z', 'dip', 'azimuth', 'polarity', - 'surface', 'series', 'id', 'order_series', 'surface_number'] + 'surface', 'series', 'id', 'OrderFeature', 'surface_number'] self._columns_o_1 = ['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r', 'G_x', 'G_y', 'G_z', 'dip', 'azimuth', 'polarity', - 'surface', 'series', 'id', 'order_series', 'isFault'] + 'surface', 'series', 'id', 'OrderFeature', 'isFault'] self._columns_o_num = ['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r', 'G_x', 'G_y', 'G_z', 'dip', 'azimuth', 'polarity'] self._columns_rend = ['X', 'Y', 'Z', 'G_x', 'G_y', 'G_z', 'smooth', 'surface'] @@ -615,7 +615,7 @@ def add_orientation(self, x, y, z, surface, pole_vector: Union[list, tuple, np.n self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) - self.map_data_from_series(self.surfaces.series, 'order_series', idx=idx) + self.map_data_from_series(self.surfaces.series, 'OrderFeature', idx=idx) self.sort_table() return self @@ -663,7 +663,7 @@ def modify_orientations(self, idx, **kwargs): self.df.loc[idx, ['surface']] = surface_names self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) - self.map_data_from_series(self.surfaces.series, 'order_series', idx=idx) + self.map_data_from_series(self.surfaces.series, 'OrderFeature', idx=idx) self.sort_table() except KeyError: pass diff --git a/gempy_lite/core/kernel_data/stack.py b/gempy_lite/core/kernel_data/stack.py index 697e7f2..c9204cf 100644 --- a/gempy_lite/core/kernel_data/stack.py +++ b/gempy_lite/core/kernel_data/stack.py @@ -65,7 +65,7 @@ def set_is_fault(self, series_fault: Union[str, list, np.ndarray] = None, 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.' + 'exist in the the series df.' if toggle is True: self.df.loc[series_fault, 'isFault'] = self.df.loc[series_fault, 'isFault'] ^ True else: @@ -89,7 +89,7 @@ def set_is_fault(self, series_fault: Union[str, list, np.ndarray] = None, return self - def set_default_faults_relations(self, offset_faults:bool=None): + def set_default_faults_relations(self, offset_faults: bool = None): if offset_faults is not None: self._offset_faults = offset_faults @@ -193,15 +193,17 @@ class Series(object): def __init__(self, faults, series_names: list = None): + self._default_values = [-1, 'Erosion', False] + 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']) + columns=['OrderFeature', 'BottomRelation']) - self.df['order_series'] = self.df['order_series'].astype(int) + self.df['OrderFeature'] = self.df['OrderFeature'].astype(int) self.df['BottomRelation'] = pn.Categorical(['Erosion'], categories=['Erosion', 'Onlap', 'Fault']) self.df['isActive'] = False @@ -215,7 +217,7 @@ 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) + self.df.at[:, 'OrderFeature'] = pn.RangeIndex(1, self.df.shape[0] + 1) @_setdoc_pro(reset_order_series.__doc__) def set_series_index(self, series_order: Union[list, np.ndarray], reset_order_series=True): @@ -247,12 +249,8 @@ def set_series_index(self, series_order: Union[list, np.ndarray], reset_order_se # 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.df.loc[c] = self._default_values + 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) @@ -302,11 +300,8 @@ def add_series(self, series_list: Union[str, list], reset_order_series=True): 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.df.loc[c] = self._default_values + 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) @@ -393,17 +388,17 @@ def modify_order_series(self, new_value: int, series_name: str): Returns: Series """ - group = self.df['order_series'] + group = self.df['OrderFeature'] 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.df['OrderFeature'] = 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.sort_values(by='OrderFeature', inplace=True) self.df.index = self.df.index.reorder_categories(self.df.index.to_numpy()) def update_faults_index_rename(self): @@ -428,10 +423,6 @@ def update_faults_index_reorder(self): self.faults.set_default_faults_relations() -class MockFault: - pass - - class Stack(Series, Faults): """Class that encapsulates all type of geological features. So far is Series and Faults @@ -442,47 +433,56 @@ class Stack(Series, Faults): rel_matrix: """ - def __init__(self, features_names: Iterable = None, fault_features: Iterable = None, - rel_matrix: Iterable = None): + + def __init__( + self, + features_names: Iterable = None, + fault_features: Iterable = None, + rel_matrix: Iterable = None + ): + + self._public_attr = ['OrderFeature', 'BottomRelation', 'Level', + 'isActive', 'isFault', 'isFinite'] + self._private_attr = ['isComputed'] + + self._default_values = [1, np.nan, 0, False, False, False, False] 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']) + df_ = pn.DataFrame(np.array([self._default_values]), + index=pn.CategoricalIndex(features_names, ordered=False), + columns=['OrderFeature', 'BottomRelation', 'Level', + 'isActive', 'isFault', 'isFinite', 'isComputed']) - self.df = df_.astype({'order_series': int, + # Setting attribute types: + self.df = df_.astype({'OrderFeature': int, 'BottomRelation': 'category', + 'Level': int, '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.df['isActive'] = False - # self.df['isFault'] = False - # self.df['isFinite'] = False - # self.faults = MockFault() - # self.faults.df = self.df - - self.faults = self - self.faults_relations_df = pn.DataFrame(index=pn.CategoricalIndex(['Default series']), - columns=pn.CategoricalIndex(['Default series', '']), dtype='bool') + 'isFinite': bool, + 'isComputed': bool}) + + #self.df['OrderFeature'] = self.df['OrderFeature'].astype(int) + self.df['BottomRelation'] = pn.Categorical( + ['Erosion'], + categories=['Erosion', 'Onlap', 'Fault'] + ) + 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 - - - - - - - - - + # Property that controls if by default ALL younger faults offset ALL + # older features + self._offset_faults = False + @property + def faults(self): + return self diff --git a/gempy_lite/core/model.py b/gempy_lite/core/model.py index b63b12f..5134969 100644 --- a/gempy_lite/core/model.py +++ b/gempy_lite/core/model.py @@ -1306,8 +1306,8 @@ def update_from_series(self, reorder_series=True, sort_geometric_data=True, self._surface_points.set_series_categories_from_series(self._stack) self._orientations.set_series_categories_from_series(self._stack) - self._surface_points.map_data_from_series(self._stack, 'order_series') - self._orientations.map_data_from_series(self._stack, 'order_series') + self._surface_points.map_data_from_series(self._stack, 'OrderFeature') + self._orientations.map_data_from_series(self._stack, 'OrderFeature') if sort_geometric_data is True: self._surface_points.sort_table() @@ -1443,7 +1443,7 @@ def map_geometric_data_df(self, d: pn.DataFrame): """ d['series'] = d['surface'].map(self._surfaces.df.set_index('surface')['series']) d['id'] = d['surface'].map(self._surfaces.df.set_index('surface')['id']).astype(int) - d['order_series'] = d['series'].map(self._stack.df['order_series']).astype(int) + d['OrderFeature'] = d['series'].map(self._stack.df['OrderFeature']).astype(int) return d def set_surface_order_from_solution(self): diff --git a/gempy_lite/utils/input_manipulation.py b/gempy_lite/utils/input_manipulation.py index 1b4cc13..11f1ef8 100644 --- a/gempy_lite/utils/input_manipulation.py +++ b/gempy_lite/utils/input_manipulation.py @@ -94,13 +94,13 @@ def surface_points_from_surface_points_block(block_bool, block_grid, formation=' # Init dataframe p = pn.DataFrame(columns=['X', 'Y', 'Z', 'formation', 'series', 'formation_number', - 'order_series', 'isFault']) + 'OrderFeature', 'isFault']) p[['X', 'Y', 'Z']] = pn.DataFrame(coord_select[loc_points]) p['formation'] = formation p['series'] = series p['formation_number'] = formation_number - p['order_series'] = order_series + p['OrderFeature'] = order_series return p diff --git a/test/input_data/test_stack.csv b/test/input_data/test_stack.csv new file mode 100644 index 0000000..2f5df66 --- /dev/null +++ b/test/input_data/test_stack.csv @@ -0,0 +1,6 @@ +,OrderFeature,BottomRelation,Level,isActive,isFault,isFinite,isComputed +foo3,1,,0,False,False,False,False +boo,2,,0,False,True,False,False +foo7,3,,0,False,False,False,False +foo5,4,,0,False,False,False,False +foo20,5,,0,False,False,False,False diff --git a/test/test_input_data/test_data_classes.py b/test/test_input_data/test_data_classes.py index 8f53687..8a177e0 100644 --- a/test/test_input_data/test_data_classes.py +++ b/test/test_input_data/test_data_classes.py @@ -1,10 +1,12 @@ # Importing GemPy +import json + import gempy_lite as gp # Importing auxiliary libraries import numpy as np -import pandas as pn +import pandas as pd import matplotlib.pyplot as plt import pytest @@ -146,7 +148,8 @@ def create_surface_points(create_surfaces, create_series): print(surface_points) - surface_points.set_surface_points(pn.DataFrame(np.random.rand(6, 3)), ['foo', 'foo5', 'lala', 'foo5', 'lala', 'feeeee']) + surface_points.set_surface_points(pd.DataFrame(np.random.rand(6, 3)), + ['foo', 'foo5', 'lala', 'foo5', 'lala', 'feeeee']) print(surface_points) @@ -158,7 +161,7 @@ def create_surface_points(create_surfaces, create_series): print(surface_points) - surface_points.map_data_from_series(create_series, 'order_series') + surface_points.map_data_from_series(create_series, 'OrderFeature') print(surface_points) surface_points.sort_table() @@ -198,7 +201,7 @@ def create_orientations(create_surfaces, create_series): orientations.map_data_from_surfaces(surfaces, 'id') print(orientations) - orientations.map_data_from_series(create_series, 'order_series') + orientations.map_data_from_series(create_series, 'OrderFeature') print(orientations) orientations.update_annotations() @@ -260,7 +263,7 @@ def test_additional_data(self, create_additional_data): return create_additional_data -def test_stack(): +def test_stack(save=True): stack = gempy_lite.core.kernel_data.stack.Stack() stack.set_series_index(['foo', 'foo2', 'foo5', 'foo7']) stack.add_series('foo3') @@ -268,7 +271,6 @@ def test_stack(): stack.rename_series({'foo': 'boo'}) stack.reorder_series(['foo3', 'boo', 'foo7', 'foo5']) stack.set_is_fault(['boo']) - faults = stack faults.set_is_fault(['boo']) @@ -277,3 +279,5 @@ def test_stack(): faults.set_fault_relation(fr) stack.add_series('foo20') + print(stack.df) + assert stack.df.iloc[1, 4] == True \ No newline at end of file From d31c78e2bf487fb1a7ca396ecb4e3f8aee133626 Mon Sep 17 00:00:00 2001 From: Leguark Date: Tue, 27 Oct 2020 08:51:50 +0100 Subject: [PATCH 03/10] [CLN] Surfaces --- gempy_lite/core/kernel_data/__init__.py | 43 ++++++++++++++++------- gempy_lite/core/kernel_data/stack.py | 10 ++++-- gempy_lite/core/model.py | 4 +-- test/test_input_data/test_data_classes.py | 2 ++ 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/gempy_lite/core/kernel_data/__init__.py b/gempy_lite/core/kernel_data/__init__.py index 4e9f91c..258eb4d 100644 --- a/gempy_lite/core/kernel_data/__init__.py +++ b/gempy_lite/core/kernel_data/__init__.py @@ -188,32 +188,48 @@ class Surfaces(object): def __init__(self, series, surface_names=None, values_array=None, properties_names=None): + # Other data objects + self.series = series + + # Dataframe views self._columns = ['surface', 'series', 'order_surfaces', 'isBasement', 'isFault', 'isActive', 'hasData', 'color', 'vertices', 'edges', 'sfai', 'id'] self._columns_vis_drop = ['vertices', 'edges', 'sfai', 'isBasement', 'isFault', 'isActive', 'hasData'] - self._n_properties = len(self._columns) - 1 - self.series = series - self.colors = Colors(self) + self._properites_vals = [] + self._private_attr = [ + 'vertices', 'edges', 'sfai', 'isBasement', 'hasData', 'isFault'] + + # ---- + + # Init df df_ = pn.DataFrame(columns=self._columns) self.df = df_.astype({'surface': str, 'series': 'category', 'order_surfaces': int, 'isBasement': bool, 'isFault': bool, 'isActive': bool, 'hasData': bool, 'color': bool, 'id': int, 'vertices': object, 'edges': object}) - if (np.array(sys.version_info[:2]) <= np.array([3, 6])).all(): - self.df: pn.DataFrame - self.df['series'].cat.add_categories(['Default series'], inplace=True) + + # Set initial values if surface_names is not None: self.set_surfaces_names(surface_names) if values_array is not None: self.set_surfaces_values(values_array=values_array, properties_names=properties_names) + # Initialize aux objects + self.colors = Colors(self) + + @property + def _public_attr(self): + """Properties values are arbitrary given by the user. e.g. porosity""" + fixed = ['surface', 'series', 'order_surfaces', 'isActive', 'color', 'id'] + return fixed.append(self._properites_vals) + def __repr__(self): c_ = self.df.columns[~(self.df.columns.isin(self._columns_vis_drop))] @@ -443,16 +459,16 @@ def set_basement(self): # set_series def map_series(self, mapping_object: Union[dict, pn.DataFrame] = None): """ - Method to map to which series every surface belongs to. This step is necessary to assign differenct tectonics - such as unconformities or faults. - + Method to map to which series every surface belongs to. This step is + necessary to assign differenct tectonics such as unconformities or faults. Args: mapping_object (dict, :class:`pn.DataFrame`): - * dict: keys are the series and values the surfaces belonging to that series + * dict: keys are the series and values the surfaces belonging to + that series - * pn.DataFrame: Dataframe with surfaces as index and a column series with the correspondent series name - of each surface + * pn.DataFrame: Dataframe with surfaces as index and a column + series with the correspondent series name of each surface Returns: :class:`Surfaces` @@ -463,7 +479,8 @@ def map_series(self, mapping_object: Union[dict, pn.DataFrame] = None): self.df['series'].cat.set_categories(self.series.df.index, inplace=True) # TODO Fixing this. It is overriding the formations already mapped if mapping_object is not None: - # If none is passed and series exist we will take the name of the first series as a default + # If none is passed and series exist we will take the name of the + # first series as a default if type(mapping_object) is dict: diff --git a/gempy_lite/core/kernel_data/stack.py b/gempy_lite/core/kernel_data/stack.py index c9204cf..44c219f 100644 --- a/gempy_lite/core/kernel_data/stack.py +++ b/gempy_lite/core/kernel_data/stack.py @@ -259,7 +259,8 @@ def set_series_index(self, series_order: Union[list, np.ndarray], reset_order_se self.reset_order_series() return self - def set_bottom_relation(self, series_list: Union[str, list], bottom_relation: Union[str, list]): + 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: @@ -441,16 +442,18 @@ def __init__( rel_matrix: Iterable = None ): + # Dataframe views self._public_attr = ['OrderFeature', 'BottomRelation', 'Level', 'isActive', 'isFault', 'isFinite'] self._private_attr = ['isComputed'] + # Default values self._default_values = [1, np.nan, 0, False, False, False, False] if features_names is None: features_names = ['Default series'] - # Set unique df + # Init df df_ = pn.DataFrame(np.array([self._default_values]), index=pn.CategoricalIndex(features_names, ordered=False), columns=['OrderFeature', 'BottomRelation', 'Level', @@ -470,12 +473,15 @@ def __init__( ['Erosion'], categories=['Erosion', 'Onlap', 'Fault'] ) + + # Init aux df 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) diff --git a/gempy_lite/core/model.py b/gempy_lite/core/model.py index 5134969..abca3dc 100644 --- a/gempy_lite/core/model.py +++ b/gempy_lite/core/model.py @@ -1339,8 +1339,8 @@ def update_from_surfaces(self, set_categories_from_series=True, set_categories_f """ # Add categories from series if set_categories_from_series is True: - self._surface_points.set_series_categories_from_series(self._surfaces.series) - self._orientations.set_series_categories_from_series(self._surfaces.series) + self._surface_points.set_series_categories_from_series(self.series) + self._orientations.set_series_categories_from_series(self.series) # Add categories from surfaces if set_categories_from_surfaces is True: diff --git a/test/test_input_data/test_data_classes.py b/test/test_input_data/test_data_classes.py index 8a177e0..0fcd51f 100644 --- a/test/test_input_data/test_data_classes.py +++ b/test/test_input_data/test_data_classes.py @@ -273,6 +273,8 @@ def test_stack(save=True): stack.set_is_fault(['boo']) faults = stack faults.set_is_fault(['boo']) + stack.set_is_finite_fault(['boo']) + stack.set_bottom_relation(['foo7', 'foo5'], ['Onlap', 'Fault']) fr = np.zeros((4, 4)) fr[2, 2] = True From e603c099aa78c45dd0780df29b7bdac8d8ceaa5a Mon Sep 17 00:00:00 2001 From: Leguark Date: Tue, 27 Oct 2020 11:10:14 +0100 Subject: [PATCH 04/10] [CLN] Working surfaces - struct --- gempy_lite/core/kernel_data/__init__.py | 92 ++++++++++++------- gempy_lite/core/kernel_data/geometric_data.py | 8 +- gempy_lite/core/kernel_data/stack.py | 8 ++ test/test_input_data/test_data_classes.py | 19 ++-- 4 files changed, 78 insertions(+), 49 deletions(-) diff --git a/gempy_lite/core/kernel_data/__init__.py b/gempy_lite/core/kernel_data/__init__.py index 258eb4d..02be360 100644 --- a/gempy_lite/core/kernel_data/__init__.py +++ b/gempy_lite/core/kernel_data/__init__.py @@ -186,19 +186,23 @@ class Surfaces(object): """ + def __repr__(self): + return self.df[self._public_attr].to_string() + + def _repr_html_(self): + return self.df[self._public_attr].style.applymap(self.background_color, + subset=['color']).render() + def __init__(self, series, surface_names=None, values_array=None, properties_names=None): # Other data objects - self.series = series + self.stack = series # Dataframe views - self._columns = ['surface', 'series', 'order_surfaces', + self._columns = ['surface', 'feature', 'order_surfaces', 'isBasement', 'isFault', 'isActive', 'hasData', 'color', 'vertices', 'edges', 'sfai', 'id'] - self._columns_vis_drop = ['vertices', 'edges', 'sfai', 'isBasement', 'isFault', - 'isActive', 'hasData'] - self._properites_vals = [] self._private_attr = [ 'vertices', 'edges', 'sfai', 'isBasement', 'hasData', 'isFault'] @@ -207,19 +211,20 @@ def __init__(self, series, surface_names=None, values_array=None, properties_nam # Init df df_ = pn.DataFrame(columns=self._columns) - self.df = df_.astype({'surface': str, 'series': 'category', + self.df = df_.astype({'surface': str, 'feature': 'category', 'order_surfaces': int, 'isBasement': bool, 'isFault': bool, 'isActive': bool, 'hasData': bool, 'color': bool, 'id': int, 'vertices': object, 'edges': object}) - self.df['series'].cat.add_categories(['Default series'], inplace=True) + self.df['feature'].cat.add_categories(['Default series'], inplace=True) # Set initial values if surface_names is not None: self.set_surfaces_names(surface_names) if values_array is not None: - self.set_surfaces_values(values_array=values_array, properties_names=properties_names) + self.set_surfaces_values(values_array=values_array, + properties_names=properties_names) # Initialize aux objects self.colors = Colors(self) @@ -227,18 +232,35 @@ def __init__(self, series, surface_names=None, values_array=None, properties_nam @property def _public_attr(self): """Properties values are arbitrary given by the user. e.g. porosity""" - fixed = ['surface', 'series', 'order_surfaces', 'isActive', 'color', 'id'] - return fixed.append(self._properites_vals) + fixed = ['surface', 'feature', 'order_surfaces', 'isActive', 'color', 'id', *self._properites_vals] + return fixed - def __repr__(self): - c_ = self.df.columns[~(self.df.columns.isin(self._columns_vis_drop))] + @property + def series(self): + warnings.warn('Series will be deprecated use stack intead', DeprecationWarning) + return self.stack - return self.df[c_].to_string() + @property + def number_surfaces(self): + return self.df['surface'].nunique() - def _repr_html_(self): - c_ = self.df.columns[~(self.df.columns.isin(self._columns_vis_drop))] + @property + def number_surfaces_per_feature(self): + """ + Set number of surfaces for each series - return self.df[c_].style.applymap(self.background_color, subset=['color']).render() + Returns: + :class:`pn.DataFrame`: df where Structural data is stored + + """ + + # DEP 27.10 + # len_sps = np.zeros(self.stack.number_features, dtype=int) + # surf_count = self.df.groupby('OrderFeature').surface.nunique() + # len_sps[surf_count.index - 1] = surf_count.values + # + # self.df.at['values', 'number surfaces per series'] = len_sps + return self.df.groupby('feature').surface.nunique().values def update_id(self, id_list: list = None): """ @@ -260,7 +282,7 @@ def update_id(self, id_list: list = None): return self def map_faults(self): - self.df['isFault'] = self.df['series'].map(self.series.faults.df['isFault']) + self.df['isFault'] = self.df['feature'].map(self.stack.faults.df['isFault']) @staticmethod def background_color(value): @@ -307,9 +329,10 @@ def set_default_surface_name(self): :class:`Surfaces`: """ + if self.df.shape[0] == 0: # TODO DEBUG: I am not sure that surfaces always has at least one entry. Check it - self.set_surfaces_names(['surface1', 'basement']) + self.set_surfaces_names(['surface1', 'surface2']) return self def set_surfaces_names_from_surface_points(self, surface_points): @@ -402,7 +425,7 @@ def rename_surfaces(self, to_replace: Union[str, list, dict], **kwargs): return self def reset_order_surfaces(self): - self.df['order_surfaces'] = self.df.groupby('series').cumcount() + 1 + self.df['order_surfaces'] = self.df.groupby('feature').cumcount() + 1 def modify_order_surfaces(self, new_value: int, idx: int, series_name: str = None): """ @@ -418,9 +441,9 @@ def modify_order_surfaces(self, new_value: int, idx: int, series_name: str = Non """ if series_name is None: - series_name = self.df.loc[idx, 'series'] + series_name = self.df.loc[idx, 'feature'] - group = self.df.groupby('series').get_group(series_name)['order_surfaces'] + group = self.df.groupby('feature').get_group(series_name)['order_surfaces'] assert np.isin(new_value, group), 'new_value must exist already in the order_surfaces group.' old_value = group[idx] self.df.loc[group.index, 'order_surfaces'] = group.replace([new_value, old_value], [old_value, new_value]) @@ -431,7 +454,7 @@ def modify_order_surfaces(self, new_value: int, idx: int, series_name: str = Non def sort_surfaces(self): """Sort surfaces by series and order_surfaces""" - self.df.sort_values(by=['series', 'order_surfaces'], inplace=True) + self.df.sort_values(by=['feature', 'order_surfaces'], inplace=True) self.update_id() return self.df @@ -475,8 +498,8 @@ def map_series(self, mapping_object: Union[dict, pn.DataFrame] = None): """ - # Updating surfaces['series'] categories - self.df['series'].cat.set_categories(self.series.df.index, inplace=True) + # Updating surfaces['feature'] categories + self.df['feature'].cat.set_categories(self.stack.df.index, inplace=True) # TODO Fixing this. It is overriding the formations already mapped if mapping_object is not None: # If none is passed and series exist we will take the name of the @@ -491,11 +514,11 @@ def map_series(self, mapping_object: Union[dict, pn.DataFrame] = None): s.append(k) f.append(form) - new_series_mapping = pn.DataFrame([pn.Categorical(s, self.series.df.index)], - f, columns=['series']) + new_series_mapping = pn.DataFrame([pn.Categorical(s, self.stack.df.index)], + f, columns=['feature']) elif isinstance(mapping_object, pn.Categorical): - # This condition is for the case we have surface on the index and in 'series' the category + # This condition is for the case we have surface on the index and in 'feature' the category # TODO Test this new_series_mapping = mapping_object @@ -507,10 +530,10 @@ def map_series(self, mapping_object: Union[dict, pn.DataFrame] = None): idx = self.df.index[b] # Mapping - self.df.loc[idx, 'series'] = self.df.loc[idx, 'surface'].map(new_series_mapping['series']) + self.df.loc[idx, 'feature'] = self.df.loc[idx, 'surface'].map(new_series_mapping['feature']) # Fill nans - self.df['series'].fillna(self.series.df.index.values[-1], inplace=True) + self.df['feature'].fillna(self.stack.df.index.values[-1], inplace=True) # Reorganize the pile self.reset_order_surfaces() @@ -579,8 +602,7 @@ def set_surfaces_values(self, values_array: Union[np.ndarray, list], properties_ """ # Check if there are values columns already - old_prop_names = self.df.columns[~self.df.columns.isin(['surface', 'series', 'order_surfaces', - 'id', 'isBasement', 'color'])] + old_prop_names = self.df.columns[~self.df.columns.isin(self._columns)] # Delete old self.delete_surface_values(old_prop_names) @@ -601,7 +623,7 @@ def modify_surface_values(self, idx, properties_names, values): """ properties_names = np.atleast_1d(properties_names) - assert ~np.isin(properties_names, ['surface', 'series', 'order_surfaces', 'id', 'isBasement', 'color']), \ + assert ~np.isin(properties_names, ['surface', 'feature', 'order_surfaces', 'id', 'isBasement', 'color']), \ 'only property names can be modified with this method' self.df.loc[idx, properties_names] = values @@ -701,7 +723,7 @@ def set_series_and_length_series_i(self): :class:`pn.DataFrame`: df where Structural data is stored """ - len_series = self.surfaces.series.df.shape[0] + len_series = self.surfaces.stack.df.shape[0] # Array containing the size of every series. SurfacePoints. points_count = self.surface_points.df['OrderFeature'].value_counts(sort=False) @@ -725,7 +747,7 @@ def set_length_series_o(self): """ # Array containing the size of every series. orientations. - len_series_o = np.zeros(self.surfaces.series.df.shape[0], dtype=int) + len_series_o = np.zeros(self.surfaces.stack.df.shape[0], dtype=int) ori_count = self.orientations.df['OrderFeature'].value_counts(sort=False) len_series_o[ori_count.index - 1] = ori_count.values @@ -741,7 +763,7 @@ def set_number_of_surfaces_per_series(self): :class:`pn.DataFrame`: df where Structural data is stored """ - len_sps = np.zeros(self.surfaces.series.df.shape[0], dtype=int) + len_sps = np.zeros(self.surfaces.stack.df.shape[0], dtype=int) surf_count = self.surface_points.df.groupby('OrderFeature'). \ surface.nunique() diff --git a/gempy_lite/core/kernel_data/geometric_data.py b/gempy_lite/core/kernel_data/geometric_data.py index 69c9fd3..f3be382 100644 --- a/gempy_lite/core/kernel_data/geometric_data.py +++ b/gempy_lite/core/kernel_data/geometric_data.py @@ -307,7 +307,7 @@ def add_surface_points(self, x: Union[float, np.ndarray], y: Union[float, np.nda self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) - self.map_data_from_series(self.surfaces.series, 'OrderFeature', idx=idx) + self.map_data_from_series(self.surfaces.stack, 'OrderFeature', idx=idx) self.sort_table() return self, idx @@ -348,7 +348,7 @@ def modify_surface_points(self, idx: Union[int, list, np.ndarray], **kwargs): self.df.loc[idx, ['surface']] = surface_names self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) - self.map_data_from_series(self.surfaces.series, 'OrderFeature', idx=idx) + self.map_data_from_series(self.surfaces.stack, 'OrderFeature', idx=idx) self.sort_table() except KeyError: pass @@ -615,7 +615,7 @@ def add_orientation(self, x, y, z, surface, pole_vector: Union[list, tuple, np.n self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) - self.map_data_from_series(self.surfaces.series, 'OrderFeature', idx=idx) + self.map_data_from_series(self.surfaces.stack, 'OrderFeature', idx=idx) self.sort_table() return self @@ -663,7 +663,7 @@ def modify_orientations(self, idx, **kwargs): self.df.loc[idx, ['surface']] = surface_names self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) - self.map_data_from_series(self.surfaces.series, 'OrderFeature', idx=idx) + self.map_data_from_series(self.surfaces.stack, 'OrderFeature', idx=idx) self.sort_table() except KeyError: pass diff --git a/gempy_lite/core/kernel_data/stack.py b/gempy_lite/core/kernel_data/stack.py index 44c219f..4c4c9b0 100644 --- a/gempy_lite/core/kernel_data/stack.py +++ b/gempy_lite/core/kernel_data/stack.py @@ -219,6 +219,10 @@ def reset_order_series(self): """ self.df.at[:, 'OrderFeature'] = pn.RangeIndex(1, self.df.shape[0] + 1) + @property + def number_features(self): + return self.df.shape[0] + @_setdoc_pro(reset_order_series.__doc__) def set_series_index(self, series_order: Union[list, np.ndarray], reset_order_series=True): """ @@ -492,3 +496,7 @@ def __init__( @property def faults(self): return self + + @property + def number_features(self): + return self.df.shape[0] diff --git a/test/test_input_data/test_data_classes.py b/test/test_input_data/test_data_classes.py index 0fcd51f..e33d971 100644 --- a/test/test_input_data/test_data_classes.py +++ b/test/test_input_data/test_data_classes.py @@ -3,7 +3,6 @@ import gempy_lite as gp - # Importing auxiliary libraries import numpy as np import pandas as pd @@ -44,8 +43,7 @@ def create_series(create_faults): return series -@pytest.fixture(scope='module') -def create_surfaces(create_series): +def test_surfaces(create_series): series = create_series surfaces = gempy_lite.core.kernel_data.Surfaces(series) surfaces.set_surfaces_names(['foo', 'foo2', 'foo5']) @@ -95,7 +93,7 @@ def create_surfaces(create_series): # are pandas categories. To get a overview of what this mean # check https://pandas.pydata.org/pandas-docs/stable/categorical.html. - print(surfaces.df['series']) + print(surfaces.df['feature']) print(surfaces.df['surface']) @@ -113,7 +111,7 @@ def create_surfaces(create_series): # An advantage of categories is that they are order so no we can tidy the df by series and surface - surfaces.df.sort_values(by='series', inplace=True) + surfaces.df.sort_values(by='feature', inplace=True) # If we change the basement: @@ -133,6 +131,9 @@ def create_surfaces(create_series): print(surfaces) + print(surfaces.number_surfaces_per_feature) + np.testing.assert_array_almost_equal(surfaces.number_surfaces_per_feature, + np.array([0, 2, 1, 0, 1], dtype=int)) # We can use `set_is_fault` to choose which of our series are faults: return surfaces @@ -156,11 +157,9 @@ def create_surface_points(create_surfaces, create_series): surface_points.map_data_from_surfaces(surfaces, 'series') print(surface_points) - surface_points.map_data_from_surfaces(surfaces, 'id') print(surface_points) - surface_points.map_data_from_series(create_series, 'OrderFeature') print(surface_points) @@ -236,8 +235,8 @@ def create_rescaling(create_surface_points, create_orientations, create_grid): @pytest.fixture(scope='module') def create_additional_data(create_surface_points, create_orientations, create_grid, create_faults, create_surfaces, create_rescaling): - - ad = gempy_lite.core.model_data.AdditionalData(create_surface_points, create_orientations, create_grid, create_faults, + ad = gempy_lite.core.model_data.AdditionalData(create_surface_points, create_orientations, create_grid, + create_faults, create_surfaces, create_rescaling) return ad @@ -282,4 +281,4 @@ def test_stack(save=True): stack.add_series('foo20') print(stack.df) - assert stack.df.iloc[1, 4] == True \ No newline at end of file + assert stack.df.iloc[1, 4] == True From 934e62e343c3204d841f78ee08744cbcd13a725c Mon Sep 17 00:00:00 2001 From: Leguark Date: Tue, 27 Oct 2020 13:53:56 +0100 Subject: [PATCH 05/10] [CLN] Cleaning geometric_data.py --- gempy_lite/core/kernel_data/__init__.py | 661 +++++++++--------- gempy_lite/core/kernel_data/geometric_data.py | 196 +++--- gempy_lite/core/kernel_data/stack.py | 53 +- gempy_lite/core/model.py | 105 ++- gempy_lite/core/model_data.py | 11 +- test/test_input_data/test_data_classes.py | 19 +- 6 files changed, 561 insertions(+), 484 deletions(-) diff --git a/gempy_lite/core/kernel_data/__init__.py b/gempy_lite/core/kernel_data/__init__.py index 02be360..74af1f4 100644 --- a/gempy_lite/core/kernel_data/__init__.py +++ b/gempy_lite/core/kernel_data/__init__.py @@ -8,164 +8,7 @@ import seaborn as sns from gempy_lite.utils.meta import _setdoc_pro - - -class Colors: - """ - Object that handles the color management in the model. - """ - def __init__(self, surfaces): - self.surfaces = surfaces - self.colordict = None - self._hexcolors_soft = [ - '#015482', '#9f0052', '#ffbe00', '#728f02', '#443988', - '#ff3f20', '#5DA629', '#b271d0', '#72e54a', '#583bd1', - '#d0e63d', '#b949e2', '#95ce4b', '#6d2b9f', '#60eb91', - '#d746be', '#52a22e', '#5e63d8', '#e5c339', '#371970', - '#d3dc76', '#4d478e', '#43b665', '#d14897', '#59e5b8', - '#e5421d', '#62dedb', '#df344e', '#9ce4a9', '#d94077', - '#99c573', '#842f74', '#578131', '#708de7', '#df872f', - '#5a73b1', '#ab912b', '#321f4d', '#e4bd7c', '#142932', - '#cd4f30', '#69aedd', '#892a23', '#aad6de', '#5c1a34', - '#cfddb4', '#381d29', '#5da37c', '#d8676e', '#52a2a3', - '#9b405c', '#346542', '#de91c9', '#555719', '#bbaed6', - '#945624', '#517c91', '#de8a68', '#3c4b64', '#9d8a4d', - '#825f7e', '#2c3821', '#ddadaa', '#5e3524', '#a3a68e', - '#a2706b', '#686d56' - ] # source: https://medialab.github.io/iwanthue/ - - def generate_colordict( - self, - hex_colors: Union[List[str], str] = 'palettes', - palettes: List[str] = 'default', - ): - """Generates and sets color dictionary. - - Args: - hex_colors (list[str], str): List of hex color values. In the future this could - accommodate the actual geological palettes. For example striplog has a quite - good set of palettes. - * palettes: If hexcolors='palettes' the colors will be chosen from the - palettes arg - * soft: https://medialab.github.io/iwanthue/ - palettes (list[str], optional): list with name of seaborn palettes. Defaults to 'default'. - """ - if hex_colors == 'palettes': - hex_colors = [] - if palettes == 'default': - # we predefine some 7 colors manually - hex_colors = ['#015482', '#9f0052', '#ffbe00', '#728f02', '#443988', '#ff3f20', '#5DA629'] - # then we create a list of seaborn color palette names, as the user didn't provide any - palettes = ['muted', 'pastel', 'deep', 'bright', 'dark', 'colorblind'] - for palette in palettes: # for each palette - hex_colors += sns.color_palette(palette).as_hex() # get all colors in palette and add to list - if len(hex_colors) >= len(self.surfaces.df): - break - elif hex_colors == 'soft': - hex_colors = self._hexcolors_soft - - surface_names = self.surfaces.df['surface'].values - n_surfaces = len(surface_names) - - while n_surfaces > len(hex_colors): - hex_colors.append(self._random_hexcolor()) - - self.colordict = dict( - zip(surface_names, hex_colors[:n_surfaces]) - ) - - @staticmethod - def _random_hexcolor() -> str: - """Generates a random hex color string.""" - return "#"+str(hex(np.random.randint(0, 16777215))).lstrip("0x") - - def change_colors(self, colordict: dict = None): - """Change the model colors either by providing a color dictionary or, - if not, by using a color pick widget. - - Args: - colordict (dict, optional): dict with surface names mapped to hex color codes, e.g. {'layer1':'#6b0318'} - if None: opens jupyter widget to change colors interactively. Defaults to None. - """ - try: - from gempy_lite.core.kernel_data import ipywidgets_import - from IPython.display import display - import ipywidgets as widgets - - except ImportError: - raise ImportError('You need to install IPython and for interactive color picking.') - - assert ipywidgets_import, 'ipywidgets not imported. Make sure the library is installed.' - - if colordict: - self.update_colors(colordict) - else: - - items = [ - widgets.ColorPicker(description=surface, value=color) - for surface, color in self.colordict.items() - ] - colbox = widgets.VBox(items) - print('Click to select new colors.') - display(colbox) - - def on_change(v): - self.colordict[v['owner'].description] = v['new'] # update colordict - self._set_colors() - - for cols in colbox.children: - cols.observe(on_change, 'value') - - def update_colors(self, colordict: dict = None): - """ Updates the colors in self.colordict and in surfaces_df. - - Args: - colordict (dict, optional): dict with surface names mapped to hex - color codes, e.g. {'layer1':'#6b0318'}. Defaults to None. - """ - if colordict is None: - self.generate_colordict() - else: - for surf, color in colordict.items(): # map new colors to surfaces - # assert this because user can set it manually - assert surf in list(self.surfaces.df['surface']), str(surf) + ' is not a model surface' - assert re.search(r'^#(?:[0-9a-fA-F]{3}){1,2}$', color), str(color) + ' is not a HEX color code' - self.colordict[surf] = color - - self._set_colors() - - def _add_colors(self): - """Assign a color to the last entry of surfaces df or check isnull and assign color there""" - self.generate_colordict() - - def _set_colors(self): - """sets colordict in surfaces dataframe""" - for surf, color in self.colordict.items(): - self.surfaces.df.loc[self.surfaces.df['surface'] == surf, 'color'] = color - - def set_default_colors(self, surfaces=None): - if surfaces is not None: - self.colordict[surfaces] = self.colordict[surfaces] - self._set_colors() - - def delete_colors(self, surfaces): - for surface in surfaces: - self.colordict.pop(surface, None) - self._set_colors() - - def make_faults_black(self, series_fault): - faults_list = list(self.surfaces.df[self.surfaces.df.series.isin(series_fault)]['surface']) - for fault in faults_list: - if self.colordict[fault] == '#527682': - self.set_default_colors(fault) - else: - self.colordict[fault] = '#527682' - self._set_colors() - - def reset_default_colors(self): - self.generate_colordict() - self._set_colors() - return self.surfaces +from .stack import Stack class Surfaces(object): @@ -199,7 +42,7 @@ def __init__(self, series, surface_names=None, values_array=None, properties_nam self.stack = series # Dataframe views - self._columns = ['surface', 'feature', 'order_surfaces', + self._columns = ['Surface', 'Feature', 'OrderSurface', 'isBasement', 'isFault', 'isActive', 'hasData', 'color', 'vertices', 'edges', 'sfai', 'id'] @@ -211,12 +54,12 @@ def __init__(self, series, surface_names=None, values_array=None, properties_nam # Init df df_ = pn.DataFrame(columns=self._columns) - self.df = df_.astype({'surface': str, 'feature': 'category', - 'order_surfaces': int, + self.df = df_.astype({'Surface': str, 'Feature': 'category', + 'OrderSurface': int, 'isBasement': bool, 'isFault': bool, 'isActive': bool, 'hasData': bool, 'color': bool, 'id': int, 'vertices': object, 'edges': object}) - self.df['feature'].cat.add_categories(['Default series'], inplace=True) + self.df['Feature'].cat.add_categories(['Default series'], inplace=True) # Set initial values if surface_names is not None: @@ -232,7 +75,7 @@ def __init__(self, series, surface_names=None, values_array=None, properties_nam @property def _public_attr(self): """Properties values are arbitrary given by the user. e.g. porosity""" - fixed = ['surface', 'feature', 'order_surfaces', 'isActive', 'color', 'id', *self._properites_vals] + fixed = ['Surface', 'Feature', 'OrderSurface', 'isActive', 'color', 'id', *self._properites_vals] return fixed @property @@ -241,11 +84,11 @@ def series(self): return self.stack @property - def number_surfaces(self): - return self.df['surface'].nunique() + def n_surfaces(self): + return self.df['Surface'].nunique() @property - def number_surfaces_per_feature(self): + def n_surfaces_per_feature(self): """ Set number of surfaces for each series @@ -260,7 +103,7 @@ def number_surfaces_per_feature(self): # len_sps[surf_count.index - 1] = surf_count.values # # self.df.at['values', 'number surfaces per series'] = len_sps - return self.df.groupby('feature').surface.nunique().values + return self.df.groupby('Feature').Surface.nunique().values def update_id(self, id_list: list = None): """ @@ -282,7 +125,7 @@ def update_id(self, id_list: list = None): return self def map_faults(self): - self.df['isFault'] = self.df['feature'].map(self.stack.faults.df['isFault']) + self.df['isFault'] = self.df['Feature'].map(self.stack.faults.df['isFault']) @staticmethod def background_color(value): @@ -310,11 +153,11 @@ def set_surfaces_names(self, surfaces_list: list, update_df=True): # Deleting all columns if they exist # TODO check if some of the names are in the df and not deleting them? self.df.drop(self.df.index, inplace=True) - self.df['surface'] = surfaces_list + self.df['Surface'] = surfaces_list # Changing the name of the series is the only way to mutate the series object from surfaces if update_df is True: - self.map_series() + self.map_stack() self.update_id() self.set_basement() self.reset_order_surfaces() @@ -346,7 +189,7 @@ def set_surfaces_names_from_surface_points(self, surface_points): Returns: """ - self.set_surfaces_names(surface_points.df['surface'].unique()) + self.set_surfaces_names(surface_points.df['Surface'].unique()) return self def add_surface(self, surface_list: Union[str, list], update_df=True): @@ -363,16 +206,16 @@ def add_surface(self, surface_list: Union[str, list], update_df=True): surface_list = np.atleast_1d(surface_list) # Remove from the list categories that already exist - surface_list = surface_list[~np.in1d(surface_list, self.df['surface'].values)] + surface_list = surface_list[~np.in1d(surface_list, self.df['Surface'].values)] for c in surface_list: idx = self.df.index.max() if idx is np.nan: idx = -1 - self.df.loc[idx + 1, 'surface'] = c + self.df.loc[idx + 1, 'Surface'] = c if update_df is True: - self.map_series() + self.map_stack() self.update_id() self.set_basement() self.reset_order_surfaces() @@ -395,7 +238,7 @@ def delete_surface(self, indices: Union[int, str, list, np.ndarray], update_id=T if indices.dtype == int: self.df.drop(indices, inplace=True) else: - self.df.drop(self.df.index[self.df['surface'].isin(indices)], inplace=True) + self.df.drop(self.df.index[self.df['Surface'].isin(indices)], inplace=True) if update_id is True: self.update_id() @@ -418,14 +261,14 @@ def rename_surfaces(self, to_replace: Union[str, list, dict], **kwargs): :any:`pandas.Series.replace` """ - if np.isin(to_replace, self.df['surface']).any(): + if np.isin(to_replace, self.df['Surface']).any(): print('Two surfaces cannot have the same name.') else: - self.df['surface'].replace(to_replace, inplace=True, **kwargs) + self.df['Surface'].replace(to_replace, inplace=True, **kwargs) return self def reset_order_surfaces(self): - self.df['order_surfaces'] = self.df.groupby('feature').cumcount() + 1 + self.df['OrderSurface'] = self.df.groupby('Feature').cumcount() + 1 def modify_order_surfaces(self, new_value: int, idx: int, series_name: str = None): """ @@ -441,12 +284,12 @@ def modify_order_surfaces(self, new_value: int, idx: int, series_name: str = Non """ if series_name is None: - series_name = self.df.loc[idx, 'feature'] + series_name = self.df.loc[idx, 'Feature'] - group = self.df.groupby('feature').get_group(series_name)['order_surfaces'] + group = self.df.groupby('Feature').get_group(series_name)['OrderSurface'] assert np.isin(new_value, group), 'new_value must exist already in the order_surfaces group.' old_value = group[idx] - self.df.loc[group.index, 'order_surfaces'] = group.replace([new_value, old_value], [old_value, new_value]) + self.df.loc[group.index, 'OrderSurface'] = group.replace([new_value, old_value], [old_value, new_value]) self.sort_surfaces() self.set_basement() return self @@ -454,7 +297,7 @@ def modify_order_surfaces(self, new_value: int, idx: int, series_name: str = Non def sort_surfaces(self): """Sort surfaces by series and order_surfaces""" - self.df.sort_values(by=['feature', 'order_surfaces'], inplace=True) + self.df.sort_values(by=['Feature', 'OrderSurface'], inplace=True) self.update_id() return self.df @@ -480,7 +323,7 @@ def set_basement(self): # endregion # set_series - def map_series(self, mapping_object: Union[dict, pn.DataFrame] = None): + def map_stack(self, mapping_object: Union[dict, pn.DataFrame] = None): """ Method to map to which series every surface belongs to. This step is necessary to assign differenct tectonics such as unconformities or faults. @@ -498,8 +341,8 @@ def map_series(self, mapping_object: Union[dict, pn.DataFrame] = None): """ - # Updating surfaces['feature'] categories - self.df['feature'].cat.set_categories(self.stack.df.index, inplace=True) + # Updating surfaces['Feature'] categories + self.df['Feature'].cat.set_categories(self.stack.df.index, inplace=True) # TODO Fixing this. It is overriding the formations already mapped if mapping_object is not None: # If none is passed and series exist we will take the name of the @@ -515,10 +358,10 @@ def map_series(self, mapping_object: Union[dict, pn.DataFrame] = None): f.append(form) new_series_mapping = pn.DataFrame([pn.Categorical(s, self.stack.df.index)], - f, columns=['feature']) + f, columns=['Feature']) elif isinstance(mapping_object, pn.Categorical): - # This condition is for the case we have surface on the index and in 'feature' the category + # This condition is for the case we have surface on the index and in 'Feature' the category # TODO Test this new_series_mapping = mapping_object @@ -526,14 +369,14 @@ def map_series(self, mapping_object: Union[dict, pn.DataFrame] = None): raise AttributeError(str(type(mapping_object)) + ' is not the right attribute type.') # Checking which surfaces are on the list to be mapped - b = self.df['surface'].isin(new_series_mapping.index) + b = self.df['Surface'].isin(new_series_mapping.index) idx = self.df.index[b] # Mapping - self.df.loc[idx, 'feature'] = self.df.loc[idx, 'surface'].map(new_series_mapping['feature']) + self.df.loc[idx, 'Feature'] = self.df.loc[idx, 'Surface'].map(new_series_mapping['Feature']) # Fill nans - self.df['feature'].fillna(self.stack.df.index.values[-1], inplace=True) + self.df['Feature'].fillna(self.stack.df.index.values[-1], inplace=True) # Reorganize the pile self.reset_order_surfaces() @@ -623,13 +466,171 @@ def modify_surface_values(self, idx, properties_names, values): """ properties_names = np.atleast_1d(properties_names) - assert ~np.isin(properties_names, ['surface', 'feature', 'order_surfaces', 'id', 'isBasement', 'color']), \ + assert ~np.isin(properties_names, ['Surface', 'Feature', 'OrderSurface', 'id', 'isBasement', 'color']), \ 'only property names can be modified with this method' self.df.loc[idx, properties_names] = values return self +class Colors: + """ + Object that handles the color management in the model. + """ + def __init__(self, surfaces): + self.surfaces = surfaces + self.colordict = None + self._hexcolors_soft = [ + '#015482', '#9f0052', '#ffbe00', '#728f02', '#443988', + '#ff3f20', '#5DA629', '#b271d0', '#72e54a', '#583bd1', + '#d0e63d', '#b949e2', '#95ce4b', '#6d2b9f', '#60eb91', + '#d746be', '#52a22e', '#5e63d8', '#e5c339', '#371970', + '#d3dc76', '#4d478e', '#43b665', '#d14897', '#59e5b8', + '#e5421d', '#62dedb', '#df344e', '#9ce4a9', '#d94077', + '#99c573', '#842f74', '#578131', '#708de7', '#df872f', + '#5a73b1', '#ab912b', '#321f4d', '#e4bd7c', '#142932', + '#cd4f30', '#69aedd', '#892a23', '#aad6de', '#5c1a34', + '#cfddb4', '#381d29', '#5da37c', '#d8676e', '#52a2a3', + '#9b405c', '#346542', '#de91c9', '#555719', '#bbaed6', + '#945624', '#517c91', '#de8a68', '#3c4b64', '#9d8a4d', + '#825f7e', '#2c3821', '#ddadaa', '#5e3524', '#a3a68e', + '#a2706b', '#686d56' + ] # source: https://medialab.github.io/iwanthue/ + + def generate_colordict( + self, + hex_colors: Union[List[str], str] = 'palettes', + palettes: List[str] = 'default', + ): + """Generates and sets color dictionary. + + Args: + hex_colors (list[str], str): List of hex color values. In the future this could + accommodate the actual geological palettes. For example striplog has a quite + good set of palettes. + * palettes: If hexcolors='palettes' the colors will be chosen from the + palettes arg + * soft: https://medialab.github.io/iwanthue/ + palettes (list[str], optional): list with name of seaborn palettes. Defaults to 'default'. + """ + if hex_colors == 'palettes': + hex_colors = [] + if palettes == 'default': + # we predefine some 7 colors manually + hex_colors = ['#015482', '#9f0052', '#ffbe00', '#728f02', '#443988', '#ff3f20', '#5DA629'] + # then we create a list of seaborn color palette names, as the user didn't provide any + palettes = ['muted', 'pastel', 'deep', 'bright', 'dark', 'colorblind'] + for palette in palettes: # for each palette + hex_colors += sns.color_palette(palette).as_hex() # get all colors in palette and add to list + if len(hex_colors) >= len(self.surfaces.df): + break + elif hex_colors == 'soft': + hex_colors = self._hexcolors_soft + + surface_names = self.surfaces.df['Surface'].values + n_surfaces = len(surface_names) + + while n_surfaces > len(hex_colors): + hex_colors.append(self._random_hexcolor()) + + self.colordict = dict( + zip(surface_names, hex_colors[:n_surfaces]) + ) + + @staticmethod + def _random_hexcolor() -> str: + """Generates a random hex color string.""" + return "#"+str(hex(np.random.randint(0, 16777215))).lstrip("0x") + + def change_colors(self, colordict: dict = None): + """Change the model colors either by providing a color dictionary or, + if not, by using a color pick widget. + + Args: + colordict (dict, optional): dict with surface names mapped to hex color codes, e.g. {'layer1':'#6b0318'} + if None: opens jupyter widget to change colors interactively. Defaults to None. + """ + try: + from gempy_lite.core.kernel_data import ipywidgets_import + from IPython.display import display + import ipywidgets as widgets + + except ImportError: + raise ImportError('You need to install IPython and for interactive color picking.') + + assert ipywidgets_import, 'ipywidgets not imported. Make sure the library is installed.' + + if colordict: + self.update_colors(colordict) + else: + + items = [ + widgets.ColorPicker(description=surface, value=color) + for surface, color in self.colordict.items() + ] + colbox = widgets.VBox(items) + print('Click to select new colors.') + display(colbox) + + def on_change(v): + self.colordict[v['owner'].description] = v['new'] # update colordict + self._set_colors() + + for cols in colbox.children: + cols.observe(on_change, 'value') + + def update_colors(self, colordict: dict = None): + """ Updates the colors in self.colordict and in surfaces_df. + + Args: + colordict (dict, optional): dict with surface names mapped to hex + color codes, e.g. {'layer1':'#6b0318'}. Defaults to None. + """ + if colordict is None: + self.generate_colordict() + else: + for surf, color in colordict.items(): # map new colors to surfaces + # assert this because user can set it manually + assert surf in list(self.surfaces.df['surface']), str(surf) + ' is not a model surface' + assert re.search(r'^#(?:[0-9a-fA-F]{3}){1,2}$', color), str(color) + ' is not a HEX color code' + self.colordict[surf] = color + + self._set_colors() + + def _add_colors(self): + """Assign a color to the last entry of surfaces df or check isnull and assign color there""" + self.generate_colordict() + + def _set_colors(self): + """sets colordict in surfaces dataframe""" + for surf, color in self.colordict.items(): + self.surfaces.df.loc[self.surfaces.df['Surface'] == surf, 'color'] = color + + def set_default_colors(self, surfaces=None): + if surfaces is not None: + self.colordict[surfaces] = self.colordict[surfaces] + self._set_colors() + + def delete_colors(self, surfaces): + for surface in surfaces: + self.colordict.pop(surface, None) + self._set_colors() + + def make_faults_black(self, series_fault): + faults_list = list(self.surfaces.df[self.surfaces.df.series.isin(series_fault)]['Surface']) + for fault in faults_list: + if self.colordict[fault] == '#527682': + self.set_default_colors(fault) + else: + self.colordict[fault] = '#527682' + self._set_colors() + + def reset_default_colors(self): + self.generate_colordict() + self._set_colors() + return self.surfaces + + class Structure(object): """ The structure_data class analyse the different lengths of subset in the interface and orientations categories_df @@ -670,145 +671,145 @@ def __init__(self, surface_points, orientations, surfaces: Surfaces, faults): self.df = df_.astype({'isLith': bool, 'isFault': bool, 'number faults': int, 'number surfaces': int, 'number series': int}) - - self.update_structure_from_input() - - def __repr__(self): - return self.df.T.to_string() - - def _repr_html_(self): - return self.df.T.to_html() - - def update_structure_from_input(self): - """ - Update all fields dependent on the linked Data objects. - - Returns: - bool: True - """ - self.set_length_surfaces_i() - self.set_series_and_length_series_i() - self.set_length_series_o() - self.set_number_of_surfaces_per_series() - self.set_number_of_faults() - self.set_number_of_surfaces() - self.set_is_lith_is_fault() - return True - - def set_length_surfaces_i(self): - """ - Set the length of each **surface** on `SurfacePoints` i.e. how many data points are for each surface - - Returns: - :class:`pn.DataFrame`: df where Structural data is stored - - """ - # ================== - # Extracting lengths - # ================== - # Array containing the size of every surface. SurfacePoints - lssp = self.surface_points.df.groupby('id')['OrderFeature'].count().values - lssp_nonzero = lssp[np.nonzero(lssp)] - - self.df.at['values', 'len surfaces surface_points'] = lssp_nonzero - - return self.df - - def set_series_and_length_series_i(self): - """ - Set the length of each **series** on `SurfacePoints` i.e. how many data points are for each series. Also - sets the number of series itself. - - Returns: - :class:`pn.DataFrame`: df where Structural data is stored - - """ - len_series = self.surfaces.stack.df.shape[0] - - # Array containing the size of every series. SurfacePoints. - points_count = self.surface_points.df['OrderFeature'].value_counts(sort=False) - len_series_i = np.zeros(len_series, dtype=int) - len_series_i[points_count.index - 1] = points_count.values - - if len_series_i.shape[0] == 0: - len_series_i = np.insert(len_series_i, 0, 0) - - self.df.at['values', 'len series surface_points'] = len_series_i - self.df['number series'] = len(len_series_i) - return self.df - - def set_length_series_o(self): - """ - Set the length of each **series** on `Orientations` i.e. how many orientations are for each series. - - Returns: - :class:`pn.DataFrame`: df where Structural data is stored - - """ - # Array containing the size of every series. orientations. - - len_series_o = np.zeros(self.surfaces.stack.df.shape[0], dtype=int) - ori_count = self.orientations.df['OrderFeature'].value_counts(sort=False) - len_series_o[ori_count.index - 1] = ori_count.values - - self.df.at['values', 'len series orientations'] = len_series_o - - return self.df - - def set_number_of_surfaces_per_series(self): - """ - Set number of surfaces for each series - - Returns: - :class:`pn.DataFrame`: df where Structural data is stored - - """ - len_sps = np.zeros(self.surfaces.stack.df.shape[0], dtype=int) - surf_count = self.surface_points.df.groupby('OrderFeature'). \ - surface.nunique() - - len_sps[surf_count.index - 1] = surf_count.values - - self.df.at['values', 'number surfaces per series'] = len_sps - return self.df - - def set_number_of_faults(self): - """ - Set number of faults series. This method in gempy_lite v2 is simply informative - - Returns: - :class:`pn.DataFrame`: df where Structural data is stored - - """ - # Number of faults existing in the surface_points df - self.df.at['values', 'number faults'] = self.faults.df['isFault'].sum() - return self.df - - def set_number_of_surfaces(self): - """ - Set the number of total surfaces - - Returns: - :class:`pn.DataFrame`: df where Structural data is stored - - """ - # Number of surfaces existing in the surface_points df - self.df.at['values', 'number surfaces'] = self.surface_points.df['surface'].nunique() - - return self.df - - def set_is_lith_is_fault(self): - """ - Check if there is lithologies in the data and/or df. This method in gempy_lite v2 is simply informative - - Returns: - :class:`pn.DataFrame`: df where Structural data is stored - """ - self.df['isLith'] = True if self.df.loc['values', 'number series'] >= self.df.loc['values', 'number faults'] \ - else False - self.df['isFault'] = True if self.df.loc['values', 'number faults'] > 0 else False - - return self.df + # + # self.update_structure_from_input() + # + # def __repr__(self): + # return self.df.T.to_string() + # + # def _repr_html_(self): + # return self.df.T.to_html() + # + # def update_structure_from_input(self): + # """ + # Update all fields dependent on the linked Data objects. + # + # Returns: + # bool: True + # """ + # self.set_length_surfaces_i() + # self.set_series_and_length_series_i() + # self.set_length_series_o() + # self.set_number_of_surfaces_per_series() + # self.set_number_of_faults() + # self.set_number_of_surfaces() + # self.set_is_lith_is_fault() + # return True + # + # def set_length_surfaces_i(self): + # """ + # Set the length of each **surface** on `SurfacePoints` i.e. how many data points are for each surface + # + # Returns: + # :class:`pn.DataFrame`: df where Structural data is stored + # + # """ + # # ================== + # # Extracting lengths + # # ================== + # # Array containing the size of every surface. SurfacePoints + # lssp = self.surface_points.df.groupby('id')['OrderFeature'].count().values + # lssp_nonzero = lssp[np.nonzero(lssp)] + # + # self.df.at['values', 'len surfaces surface_points'] = lssp_nonzero + # + # return self.df + # + # def set_series_and_length_series_i(self): + # """ + # Set the length of each **series** on `SurfacePoints` i.e. how many data points are for each series. Also + # sets the number of series itself. + # + # Returns: + # :class:`pn.DataFrame`: df where Structural data is stored + # + # """ + # len_series = self.surfaces.stack.df.shape[0] + # + # # Array containing the size of every series. SurfacePoints. + # points_count = self.surface_points.df['OrderFeature'].value_counts(sort=False) + # len_series_i = np.zeros(len_series, dtype=int) + # len_series_i[points_count.index - 1] = points_count.values + # + # if len_series_i.shape[0] == 0: + # len_series_i = np.insert(len_series_i, 0, 0) + # + # self.df.at['values', 'len series surface_points'] = len_series_i + # self.df['number series'] = len(len_series_i) + # return self.df + # + # def set_length_series_o(self): + # """ + # Set the length of each **series** on `Orientations` i.e. how many orientations are for each series. + # + # Returns: + # :class:`pn.DataFrame`: df where Structural data is stored + # + # """ + # # Array containing the size of every series. orientations. + # + # len_series_o = np.zeros(self.surfaces.stack.df.shape[0], dtype=int) + # ori_count = self.orientations.df['OrderFeature'].value_counts(sort=False) + # len_series_o[ori_count.index - 1] = ori_count.values + # + # self.df.at['values', 'len series orientations'] = len_series_o + # + # return self.df + # + # def set_number_of_surfaces_per_series(self): + # """ + # Set number of surfaces for each series + # + # Returns: + # :class:`pn.DataFrame`: df where Structural data is stored + # + # """ + # len_sps = np.zeros(self.surfaces.stack.df.shape[0], dtype=int) + # surf_count = self.surface_points.df.groupby('OrderFeature'). \ + # surface.nunique() + # + # len_sps[surf_count.index - 1] = surf_count.values + # + # self.df.at['values', 'number surfaces per series'] = len_sps + # return self.df + # + # def set_number_of_faults(self): + # """ + # Set number of faults series. This method in gempy_lite v2 is simply informative + # + # Returns: + # :class:`pn.DataFrame`: df where Structural data is stored + # + # """ + # # Number of faults existing in the surface_points df + # self.df.at['values', 'number faults'] = self.faults.df['isFault'].sum() + # return self.df + # + # def set_number_of_surfaces(self): + # """ + # Set the number of total surfaces + # + # Returns: + # :class:`pn.DataFrame`: df where Structural data is stored + # + # """ + # # Number of surfaces existing in the surface_points df + # self.df.at['values', 'number surfaces'] = self.surface_points.df['Surface'].nunique() + # + # return self.df + # + # def set_is_lith_is_fault(self): + # """ + # Check if there is lithologies in the data and/or df. This method in gempy_lite v2 is simply informative + # + # Returns: + # :class:`pn.DataFrame`: df where Structural data is stored + # """ + # self.df['isLith'] = True if self.df.loc['values', 'number series'] >= self.df.loc['values', 'number faults'] \ + # else False + # self.df['isFault'] = True if self.df.loc['values', 'number faults'] > 0 else False + # + # return self.df @_setdoc_pro([Structure.__doc__]) diff --git a/gempy_lite/core/kernel_data/geometric_data.py b/gempy_lite/core/kernel_data/geometric_data.py index f3be382..f270b3b 100644 --- a/gempy_lite/core/kernel_data/geometric_data.py +++ b/gempy_lite/core/kernel_data/geometric_data.py @@ -5,7 +5,8 @@ import numpy as np import pandas as pn -from gempy_lite.core.kernel_data import Surfaces +from gempy_lite.core.kernel_data import Surfaces, Stack + from gempy_lite.core.checkers import check_for_nans from gempy_lite.utils import docstring as ds from gempy_lite.utils.meta import _setdoc_pro, _setdoc @@ -44,17 +45,18 @@ def init_dependent_properties(self): method will get invoked for example when we add a new point.""" # series - self.df['series'] = 'Default series' - self.df['series'] = self.df['series'].astype('category', copy=True) - #self.df['OrderFeature'] = self.df['OrderFeature'].astype('category', copy=True) - - self.df['series'].cat.set_categories(self.surfaces.df['series'].cat.categories, inplace=True) + self.df['Feature'] = 'Default series' + self.df['Feature'] = self.df['Feature'].astype('category', copy=True) + self.df['Feature'].cat.set_categories(self.surfaces.df['Feature'].cat.categories, inplace=True) # id self.df['id'] = np.nan # order_series self.df['OrderFeature'] = 1 + + # + self.df['isActive'] = True return self @staticmethod @@ -79,19 +81,19 @@ def sort_table(self): inplace=True) return self.df - # @_setdoc_pro(Series.__doc__) + @_setdoc_pro(Stack.__doc__) def set_series_categories_from_series(self, series): """set the series categorical columns with the series index of the passed :class:`Series` Args: series (:class:`Series`): [s0] """ - self.df['series'].cat.set_categories(series.df.index, inplace=True) + self.df['Feature'].cat.set_categories(series.df.index, inplace=True) return True def update_series_category(self): """Update the series categorical columns with the series categories of the :class:`Surfaces` attribute.""" - self.df['series'].cat.set_categories(self.surfaces.df['series'].cat.categories, inplace=True) + self.df['Feature'].cat.set_categories(self.surfaces.df['Feature'].cat.categories, inplace=True) return True @@ -104,10 +106,10 @@ def set_surface_categories_from_surfaces(self, surfaces: Surfaces): """ - self.df['surface'].cat.set_categories(surfaces.df['surface'], inplace=True) + self.df['Surface'].cat.set_categories(surfaces.df['Surface'], inplace=True) return True - # @_setdoc_pro(Series.__doc__) + @_setdoc_pro(Stack.__doc__) def map_data_from_series(self, series, attribute: str, idx=None): """ Map columns from the :class:`Series` data frame to a :class:`GeometricData` data frame. @@ -125,10 +127,10 @@ def map_data_from_series(self, series, attribute: str, idx=None): idx = np.atleast_1d(idx) if attribute in ['id', 'OrderFeature']: - self.df.loc[idx, attribute] = self.df['series'].map(series.df[attribute]).astype(int) + self.df.loc[idx, attribute] = self.df['Feature'].map(series.df[attribute]).astype(int) else: - self.df.loc[idx, attribute] = self.df['series'].map(series.df[attribute]) + self.df.loc[idx, attribute] = self.df['Feature'].map(series.df[attribute]) if type(self.df['OrderFeature'].dtype) is pn.CategoricalDtype: @@ -153,35 +155,17 @@ def map_data_from_surfaces(self, surfaces, attribute: str, idx=None): if idx is None: idx = self.df.index idx = np.atleast_1d(idx) - if attribute == 'series': - if surfaces.df.loc[~surfaces.df['isBasement']]['series'].isna().sum() != 0: + if attribute == 'Feature': + if surfaces.df.loc[~surfaces.df['isBasement']]['Feature'].isna().sum() != 0: raise AttributeError('Surfaces does not have the correspondent series assigned. See' 'Surfaces.map_series_from_series.') - self.df.loc[idx, attribute] = self.df.loc[idx, 'surface'].map(surfaces.df.set_index('surface')[attribute]) + self.df.loc[idx, attribute] = self.df.loc[idx, 'Surface'].map(surfaces.df.set_index('Surface')[attribute]) elif attribute in ['id', 'OrderFeature']: - self.df.loc[idx, attribute] = (self.df.loc[idx, 'surface'].map(surfaces.df.set_index('surface')[attribute])).astype(int) + self.df.loc[idx, attribute] = (self.df.loc[idx, 'Surface'].map(surfaces.df.set_index('Surface')[attribute])).astype(int) else: - self.df.loc[idx, attribute] = self.df.loc[idx, 'surface'].map(surfaces.df.set_index('surface')[attribute]) - - # def map_data_from_faults(self, faults, idx=None): - # """ - # Method to map a df object into the data object on surfaces. Either if the surface is fault or not - # Args: - # faults (Faults): - # - # Returns: - # pandas.core.frame.DataFrame: Data frame with the raw data - # - # """ - # if idx is None: - # idx = self.df.index - # idx = np.atleast_1d(idx) - # if any(self.df['series'].isna()): - # warnings.warn('Some points do not have series/fault') - # - # self.df.loc[idx, 'isFault'] = self.df.loc[[idx], 'series'].map(faults.df['isFault']) + self.df.loc[idx, attribute] = self.df.loc[idx, 'Surface'].map(surfaces.df.set_index('Surface')[attribute]) @_setdoc_pro([Surfaces.__doc__, ds.coord, ds.surface_sp]) @@ -204,21 +188,43 @@ class SurfacePoints(GeometricData): def __init__(self, surfaces: Surfaces, coord=None, surface=None): super().__init__(surfaces) - self._columns_i_all = ['X', 'Y', 'Z', 'surface', 'series', 'X_std', 'Y_std', 'Z_std', + self._columns_i_all = ['X', 'Y', 'Z', 'Surface', 'Feature', 'X_std', 'Y_std', 'Z_std', 'OrderFeature', 'surface_number'] - self._columns_i_1 = ['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r', 'surface', 'series', 'id', + self._columns_i_1 = ['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r', 'Surface', 'Feature', 'id', 'OrderFeature', 'isFault', 'smooth'] - self._columns_rep = ['X', 'Y', 'Z', 'surface', 'series'] + self._columns_rep = ['X', 'Y', 'Z', 'Surface', 'Feature'] self._columns_i_num = ['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r'] - self._columns_rend = ['X', 'Y', 'Z', 'smooth', 'surface'] + self._columns_rend = ['X', 'Y', 'Z', 'smooth', 'Surface'] + + self._private_attr = ['X_r', 'Y_r', 'Z_r', 'Features', 'id', 'OrderFeature'] + self._public_attr = ['X', 'Y', 'Z', 'smooth', 'Surface', 'isActive'] if (np.array(sys.version_info[:2]) <= np.array([3, 6])).all(): self.df: pn.DataFrame self.set_surface_points(coord, surface) + @property + def n_sp_per_feature(self): + + """ + Set the length of each **series** on `SurfacePoints` i.e. how many data points are for each series. Also + sets the number of series itself. + + Returns: + :class:`pn.DataFrame`: df where Structural data is stored + + """ + len_series = np.zeros(self.surfaces.stack.n_features, dtype=int) + # Array containing the size of every series. SurfacePoints. + points_count = self.df['OrderFeature'].value_counts(sort=False) + len_series_i = np.zeros(len_series, dtype=int) + len_series_i[points_count.index - 1] = points_count.values + + return len_series_i + @_setdoc_pro([ds.coord, ds.surface_sp]) def set_surface_points(self, coord: np.ndarray = None, surface: list = None): """ @@ -231,14 +237,14 @@ def set_surface_points(self, coord: np.ndarray = None, surface: list = None): Returns: :class:`SurfacePoints` """ - self.df = pn.DataFrame(columns=['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r', 'surface'], dtype=float) + self.df = pn.DataFrame(columns=['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r', 'Surface'], dtype=float) if coord is not None and surface is not None: self.df[['X', 'Y', 'Z']] = pn.DataFrame(coord) - self.df['surface'] = surface + self.df['Surface'] = surface - self.df['surface'] = self.df['surface'].astype('category', copy=True) - self.df['surface'].cat.set_categories(self.surfaces.df['surface'].values, inplace=True) + self.df['Surface'] = self.df['Surface'].astype('category', copy=True) + self.df['Surface'].cat.set_categories(self.surfaces.df['Surface'].values, inplace=True) # Choose types self.init_dependent_properties() @@ -246,8 +252,8 @@ def set_surface_points(self, coord: np.ndarray = None, surface: list = None): # Add nugget columns self.df['smooth'] = 2e-6 - assert ~self.df['surface'].isna().any(), 'Some of the surface passed does not exist in the Formation' \ - 'object. %s' % self.df['surface'][self.df['surface'].isna()] + assert ~self.df['Surface'].isna().any(), 'Some of the surface passed does not exist in the Formation' \ + 'object. %s' % self.df['Surface'][self.df['Surface'].isna()] return self @@ -284,12 +290,12 @@ def add_surface_points(self, x: Union[float, np.ndarray], y: Union[float, np.nda assert coord_array.ndim == 1, 'Adding an interface only works one by one.' try: - if self.surfaces.df.groupby('isBasement').get_group(True)['surface'].isin(surface).any(): + if self.surfaces.df.groupby('isBasement').get_group(True)['Surface'].isin(surface).any(): warnings.warn('Surface Points for the basement will not be used. Maybe you are missing an extra' 'layer at the bottom of the pile.') self.df.loc[idx, ['X', 'Y', 'Z']] = coord_array.astype('float64') - self.df.loc[idx, 'surface'] = surface + self.df.loc[idx, 'Surface'] = surface # ToDO test this except ValueError as error: self.del_surface_points(idx) @@ -299,13 +305,13 @@ def add_surface_points(self, x: Union[float, np.ndarray], y: Union[float, np.nda self.df.loc[idx, ['smooth']] = 1e-6 - self.df['surface'] = self.df['surface'].astype('category', copy=True) - self.df['surface'].cat.set_categories(self.surfaces.df['surface'].values, inplace=True) + self.df['Surface'] = self.df['Surface'].astype('category', copy=True) + self.df['Surface'].cat.set_categories(self.surfaces.df['Surface'].values, inplace=True) - self.df['series'] = self.df['series'].astype('category', copy=True) - self.df['series'].cat.set_categories(self.surfaces.df['series'].cat.categories, inplace=True) + self.df['Feature'] = self.df['Feature'].astype('category', copy=True) + self.df['Feature'].cat.set_categories(self.surfaces.df['Feature'].cat.categories, inplace=True) - self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) + self.map_data_from_surfaces(self.surfaces, 'Feature', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) self.map_data_from_series(self.surfaces.stack, 'OrderFeature', idx=idx) @@ -344,9 +350,9 @@ def modify_surface_points(self, idx: Union[int, list, np.ndarray], **kwargs): """ idx = np.array(idx, ndmin=1) try: - surface_names = kwargs.pop('surface') - self.df.loc[idx, ['surface']] = surface_names - self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) + surface_names = kwargs.pop('Surface') + self.df.loc[idx, ['Surface']] = surface_names + self.map_data_from_surfaces(self.surfaces, 'Feature', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) self.map_data_from_series(self.surfaces.stack, 'OrderFeature', idx=idx) self.sort_table() @@ -354,14 +360,14 @@ def modify_surface_points(self, idx: Union[int, list, np.ndarray], **kwargs): pass # keys = list(kwargs.keys()) - # is_surface = np.isin('surface', keys).all() + # is_surface = np.isin('Surface', keys).all() # Check idx exist in the df assert np.isin(np.atleast_1d(idx), self.df.index).all(), 'Indices must exist in the' \ ' dataframe to be modified.' # Check the properties are valid - assert np.isin(list(kwargs.keys()), ['X', 'Y', 'Z', 'surface', 'smooth']).all(),\ + assert np.isin(list(kwargs.keys()), ['X', 'Y', 'Z', 'Surface', 'smooth']).all(),\ 'Properties must be one or more of the following: \'X\', \'Y\', \'Z\', ' '\'surface\'' # stack properties values values = np.array(list(kwargs.values())) @@ -447,7 +453,7 @@ def set_default_surface_points(self): Set a default point at the middle of the extent area to be able to start making the model """ if self.df.shape[0] == 0: - self.add_surface_points(0.00001, 0.00001, 0.00001, self.surfaces.df['surface'].iloc[0]) + self.add_surface_points(0.00001, 0.00001, 0.00001, self.surfaces.df['Surface'].iloc[0]) return True def update_annotations(self): @@ -485,17 +491,41 @@ class Orientations(GeometricData): def __init__(self, surfaces: Surfaces, coord=None, pole_vector=None, orientation=None, surface=None): super().__init__(surfaces) self._columns_o_all = ['X', 'Y', 'Z', 'G_x', 'G_y', 'G_z', 'dip', 'azimuth', 'polarity', - 'surface', 'series', 'id', 'OrderFeature', 'surface_number'] + 'Surface', 'Feature', 'id', 'OrderFeature', 'surface_number'] self._columns_o_1 = ['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r', 'G_x', 'G_y', 'G_z', 'dip', 'azimuth', 'polarity', - 'surface', 'series', 'id', 'OrderFeature', 'isFault'] - self._columns_o_num = ['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r', 'G_x', 'G_y', 'G_z', 'dip', 'azimuth', 'polarity'] - self._columns_rend = ['X', 'Y', 'Z', 'G_x', 'G_y', 'G_z', 'smooth', 'surface'] + 'Surface', 'Feature', 'id', 'OrderFeature', 'isFault'] + self._columns_o_num = ['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r', + 'G_x', 'G_y', 'G_z', 'dip', 'azimuth', 'polarity'] + self._columns_rend = ['X', 'Y', 'Z', 'G_x', 'G_y', 'G_z', 'smooth', 'Surface'] + + self._private_attr = ['X_r', 'Y_r', 'Z_r', 'Features', 'id', 'OrderFeature'] + self._public_attr = ['X', 'Y', 'Z', 'smooth', 'Surface', 'dip', + 'azimuth', 'polarity', 'isActive'] if (np.array(sys.version_info[:2]) <= np.array([3, 6])).all(): self.df: pn.DataFrame self.set_orientations(coord, pole_vector, orientation, surface) + @property + def n_orientations_per_feature(self): + + """ + Set the length of each **series** on `Orientations` i.e. how many orientations are for each series. + + Returns: + :class:`pn.DataFrame`: df where Structural data is stored + + """ + # Array containing the size of every series. orientations. + + len_series_o = np.zeros(self.surfaces.stack.n_features, dtype=int) + ori_count = self.df['OrderFeature'].value_counts(sort=False) + len_series_o[ori_count.index - 1] = ori_count.values + + return len_series_o + + @_setdoc_pro([ds.coord_ori, ds.surface_sp, ds.pole_vector, ds.orientations]) def set_orientations(self, coord: np.ndarray = None, pole_vector: np.ndarray = None, orientation: np.ndarray = None, surface: list = None): @@ -514,10 +544,10 @@ def set_orientations(self, coord: np.ndarray = None, pole_vector: np.ndarray = N """ self.df = pn.DataFrame(columns=['X', 'Y', 'Z', 'X_r', 'Y_r', 'Z_r', 'G_x', 'G_y', 'G_z', 'dip', - 'azimuth', 'polarity', 'surface'], dtype=float) + 'azimuth', 'polarity', 'Surface'], dtype=float) - self.df['surface'] = self.df['surface'].astype('category', copy=True) - self.df['surface'].cat.set_categories(self.surfaces.df['surface'].values, inplace=True) + self.df['Surface'] = self.df['Surface'].astype('category', copy=True) + self.df['Surface'].cat.set_categories(self.surfaces.df['Surface'].values, inplace=True) pole_vector = check_for_nans(pole_vector) orientation = check_for_nans(orientation) @@ -525,7 +555,7 @@ def set_orientations(self, coord: np.ndarray = None, pole_vector: np.ndarray = N if coord is not None and ((pole_vector is not None) or (orientation is not None)) and surface is not None: self.df[['X', 'Y', 'Z']] = pn.DataFrame(coord) - self.df['surface'] = surface + self.df['Surface'] = surface if pole_vector is not None: self.df['G_x'] = pole_vector[:, 0] self.df['G_y'] = pole_vector[:, 1] @@ -544,15 +574,15 @@ def set_orientations(self, coord: np.ndarray = None, pole_vector: np.ndarray = N raise AttributeError('At least pole_vector or orientation should have been passed to reach' 'this point. Check previous condition') - self.df['surface'] = self.df['surface'].astype('category', copy=True) - self.df['surface'].cat.set_categories(self.surfaces.df['surface'].values, inplace=True) + self.df['Surface'] = self.df['Surface'].astype('category', copy=True) + self.df['Surface'].cat.set_categories(self.surfaces.df['Surface'].values, inplace=True) self.init_dependent_properties() # Add nugget effect self.df['smooth'] = 0.01 - assert ~self.df['surface'].isna().any(), 'Some of the surface passed does not exist in the Formation' \ - 'object. %s' % self.df['surface'][self.df['surface'].isna()] + assert ~self.df['Surface'].isna().any(), 'Some of the surface passed does not exist in the Formation' \ + 'object. %s' % self.df['Surface'][self.df['Surface'].isna()] @_setdoc_pro([ds.x, ds.y, ds.z, ds.surface_sp, ds.pole_vector, ds.orientations, ds.idx_sp]) def add_orientation(self, x, y, z, surface, pole_vector: Union[list, tuple, np.ndarray] = None, @@ -590,7 +620,7 @@ def add_orientation(self, x, y, z, surface, pole_vector: Union[list, tuple, np.n if pole_vector is not None: self.df.loc[idx, ['X', 'Y', 'Z', 'G_x', 'G_y', 'G_z']] = np.array([x, y, z, *pole_vector], dtype=float) - self.df.loc[idx, 'surface'] = surface + self.df.loc[idx, 'Surface'] = surface self.calculate_orientations(idx) @@ -600,20 +630,20 @@ def add_orientation(self, x, y, z, surface, pole_vector: Union[list, tuple, np.n if orientation is not None: self.df.loc[idx, ['X', 'Y', 'Z', ]] = np.array([x, y, z], dtype=float) self.df.loc[idx, ['azimuth', 'dip', 'polarity']] = np.array(orientation, dtype=float) - self.df.loc[idx, 'surface'] = surface + self.df.loc[idx, 'Surface'] = surface self.calculate_gradient(idx) else: raise AttributeError('At least pole_vector or orientation should have been passed to reach' 'this point. Check previous condition') self.df.loc[idx, ['smooth']] = 0.01 - self.df['surface'] = self.df['surface'].astype('category', copy=True) - self.df['surface'].cat.set_categories(self.surfaces.df['surface'].values, inplace=True) + self.df['Surface'] = self.df['Surface'].astype('category', copy=True) + self.df['Surface'].cat.set_categories(self.surfaces.df['Surface'].values, inplace=True) - self.df['series'] = self.df['series'].astype('category', copy=True) - self.df['series'].cat.set_categories(self.surfaces.df['series'].cat.categories, inplace=True) + self.df['Feature'] = self.df['Feature'].astype('category', copy=True) + self.df['Feature'].cat.set_categories(self.surfaces.df['Feature'].cat.categories, inplace=True) - self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) + self.map_data_from_surfaces(self.surfaces, 'Feature', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) self.map_data_from_series(self.surfaces.stack, 'OrderFeature', idx=idx) @@ -659,9 +689,9 @@ def modify_orientations(self, idx, **kwargs): idx = np.array(idx, ndmin=1) try: - surface_names = kwargs.pop('surface') - self.df.loc[idx, ['surface']] = surface_names - self.map_data_from_surfaces(self.surfaces, 'series', idx=idx) + surface_names = kwargs.pop('Surface') + self.df.loc[idx, ['Surface']] = surface_names + self.map_data_from_surfaces(self.surfaces, 'Feature', idx=idx) self.map_data_from_surfaces(self.surfaces, 'id', idx=idx) self.map_data_from_series(self.surfaces.stack, 'OrderFeature', idx=idx) self.sort_table() @@ -676,7 +706,7 @@ def modify_orientations(self, idx, **kwargs): # Check the properties are valid assert np.isin(list(kwargs.keys()), ['X', 'Y', 'Z', 'G_x', 'G_y', 'G_z', 'dip', - 'azimuth', 'polarity', 'surface', 'smooth']).all(),\ + 'azimuth', 'polarity', 'Surface', 'smooth']).all(),\ 'Properties must be one or more of the following: \'X\', \'Y\', \'Z\', \'G_x\', \'G_y\', \'G_z\', \'dip,\''\ '\'azimuth\', \'polarity\', \'surface\'' @@ -778,7 +808,7 @@ def set_default_orientation(self): """ if self.df.shape[0] == 0: self.add_orientation(.00001, .00001, .00001, - self.surfaces.df['surface'].iloc[0], + self.surfaces.df['Surface'].iloc[0], [0, 0, 1], ) diff --git a/gempy_lite/core/kernel_data/stack.py b/gempy_lite/core/kernel_data/stack.py index 4c4c9b0..80c501a 100644 --- a/gempy_lite/core/kernel_data/stack.py +++ b/gempy_lite/core/kernel_data/stack.py @@ -1,3 +1,4 @@ +import warnings from typing import Union, Iterable import numpy as np @@ -220,7 +221,7 @@ def reset_order_series(self): self.df.at[:, 'OrderFeature'] = pn.RangeIndex(1, self.df.shape[0] + 1) @property - def number_features(self): + def n_features(self): return self.df.shape[0] @_setdoc_pro(reset_order_series.__doc__) @@ -447,12 +448,19 @@ def __init__( ): # Dataframe views + self._columns = ['OrderFeature', 'BottomRelation', 'Level', + 'Range', 'Sill', + 'DriftDegree', + 'isActive', 'isFault', 'isFinite', + 'isComputed'] self._public_attr = ['OrderFeature', 'BottomRelation', 'Level', + 'Range', 'Sill', + 'DriftDegree', 'isActive', 'isFault', 'isFinite'] self._private_attr = ['isComputed'] # Default values - self._default_values = [1, np.nan, 0, False, False, False, False] + self._default_values = [1, np.nan, 0, 5, 1, 1, False, False, False, False] if features_names is None: features_names = ['Default series'] @@ -460,8 +468,7 @@ def __init__( # Init df df_ = pn.DataFrame(np.array([self._default_values]), index=pn.CategoricalIndex(features_names, ordered=False), - columns=['OrderFeature', 'BottomRelation', 'Level', - 'isActive', 'isFault', 'isFinite', 'isComputed']) + columns=self._columns) # Setting attribute types: self.df = df_.astype({'OrderFeature': int, @@ -470,6 +477,9 @@ def __init__( 'isActive': bool, 'isFault': bool, 'isFinite': bool, + 'Range': float, + 'Sill': float, + 'DriftDegree': int, 'isComputed': bool}) #self.df['OrderFeature'] = self.df['OrderFeature'].astype(int) @@ -493,10 +503,43 @@ def __init__( # older features self._offset_faults = False + def set_default_range(self, extent=None): + """ + Set default kriging_data range + + Args: + extent (Optional[float, np.array]): extent used to compute the default range--i.e. largest diagonal. If None + extent of the linked :class:`Grid` will be used. + + Returns: + + """ + if extent is None: + extent = self.grid.regular_grid.extent + if np.sum(extent) == 0 and self.grid.values.shape[0] > 1: + extent = np.concatenate((np.min(self.grid.values, axis=0), + np.max(self.grid.values, axis=0)))[[0, 3, 1, 4, 2, 5]] + + try: + range_var = np.sqrt( + (extent[0] - extent[1]) ** 2 + + (extent[2] - extent[3]) ** 2 + + (extent[4] - extent[5]) ** 2) + except TypeError: + warnings.warn('The extent passed or if None the extent ' + 'of the grid object has some type of problem', + TypeError) + range_var = np.nan + + self.df['range'] = range_var + + return range_var + + @property def faults(self): return self @property - def number_features(self): + def n_features(self): return self.df.shape[0] diff --git a/gempy_lite/core/model.py b/gempy_lite/core/model.py index abca3dc..67db22f 100644 --- a/gempy_lite/core/model.py +++ b/gempy_lite/core/model.py @@ -211,11 +211,10 @@ def update_structure(self, update_theano=None, update_series_is_active=True, self._additional_data.update_structure() if update_series_is_active is True: - len_series_i = self._additional_data.structure_data.df.loc['values', 'len series surface_points'] - \ - self._additional_data.structure_data.df.loc['values', 'number surfaces per series'] + pass - len_series_o = self._additional_data.structure_data.df.loc['values', 'len series orientations'].astype( - 'int32') + len_series_i = self._surface_points.n_sp_per_feature - self._surfaces.n_surfaces_per_feature + len_series_o = self._orientations.n_orientations_per_feature # Remove series without data non_zero_i = len_series_i.nonzero()[0] @@ -227,7 +226,7 @@ def update_structure(self, update_theano=None, update_series_is_active=True, self._stack.df['isActive'] = bool_vec if update_surface_is_active is True: - act_series = self._surfaces.df['series'].map(self._stack.df['isActive']).astype(bool) + act_series = self._surfaces.df['Feature'].map(self._stack.df['isActive']).astype(bool) unique_surf_points = np.unique(self._surface_points.df['id']) if len(unique_surf_points) != 0: bool_surf_points = np.zeros_like(act_series, dtype=bool) @@ -486,9 +485,9 @@ def add_features(self, features_list: Union[str, list], reset_order_series=True) """ self._stack.add_series(features_list, reset_order_series) - self._surfaces.df['series'].cat.add_categories(features_list, inplace=True) - self._surface_points.df['series'].cat.add_categories(features_list, inplace=True) - self._orientations.df['series'].cat.add_categories(features_list, inplace=True) + self._surfaces.df['Feature'].cat.add_categories(features_list, inplace=True) + self._surface_points.df['Feature'].cat.add_categories(features_list, inplace=True) + self._orientations.df['Feature'].cat.add_categories(features_list, inplace=True) #self._interpolator.set_flow_control() return self._stack @@ -518,12 +517,12 @@ def delete_features(self, indices: Union[str, list], reset_order_features=True, if remove_surfaces is True: for s in indices: - self.delete_surfaces(self._surfaces.df.groupby('series').get_group(s)['surface'], + self.delete_surfaces(self._surfaces.df.groupby('Feature').get_group(s)['surface'], remove_data=remove_data) - self._surfaces.df['series'].cat.remove_categories(indices, inplace=True) - self._surface_points.df['series'].cat.remove_categories(indices, inplace=True) - self._orientations.df['series'].cat.remove_categories(indices, inplace=True) + self._surfaces.df['Feature'].cat.remove_categories(indices, inplace=True) + self._surface_points.df['Feature'].cat.remove_categories(indices, inplace=True) + self._orientations.df['Feature'].cat.remove_categories(indices, inplace=True) self.map_geometric_data_df(self._surface_points.df) self.map_geometric_data_df(self._orientations.df) @@ -558,9 +557,9 @@ def rename_features(self, new_categories: Union[dict, list]): """ self._stack.rename_series(new_categories) - self._surfaces.df['series'].cat.rename_categories(new_categories, inplace=True) - self._surface_points.df['series'].cat.rename_categories(new_categories, inplace=True) - self._orientations.df['series'].cat.rename_categories(new_categories, inplace=True) + self._surfaces.df['Feature'].cat.rename_categories(new_categories, inplace=True) + self._surface_points.df['Feature'].cat.rename_categories(new_categories, inplace=True) + self._orientations.df['Feature'].cat.rename_categories(new_categories, inplace=True) return self._stack @_setdoc(rename_features.__doc__, indent=False) @@ -584,7 +583,7 @@ def modify_order_features(self, new_value: int, idx: str): """ self._stack.modify_order_series(new_value, idx) - self._surfaces.df['series'].cat.reorder_categories(np.asarray(self._stack.df.index), + self._surfaces.df['Feature'].cat.reorder_categories(np.asarray(self._stack.df.index), ordered=False, inplace=True) self._surfaces.sort_surfaces() @@ -617,7 +616,7 @@ def reorder_features(self, new_categories: Iterable[str]): :class:`gempy_lite.core.kernel_data.stack.Stack` """ self._stack.reorder_series(new_categories) - self._surfaces.df['series'].cat.reorder_categories(np.asarray(self._stack.df.index), + self._surfaces.df['Feature'].cat.reorder_categories(np.asarray(self._stack.df.index), ordered=False, inplace=True) self._surfaces.sort_surfaces() @@ -672,7 +671,7 @@ def set_is_fault(self, feature_fault: Union[str, list] = None, toggle: bool = Fa for fault in feature_fault: if self._surfaces.df.shape[0] == 0: aux_assert = True - elif np.sum(self._surfaces.df.groupby('isBasement').get_group(False)['series'] == fault) < 2: + elif np.sum(self._surfaces.df.groupby('isBasement').get_group(False)['Feature'] == fault) < 2: aux_assert = True else: aux_assert = False @@ -734,8 +733,8 @@ def set_surfaces_object(self): @_setdoc(Surfaces.add_surface.__doc__, indent=False) def add_surfaces(self, surface_list: Union[str, list], update_df=True): self._surfaces.add_surface(surface_list, update_df) - self._surface_points.df['surface'].cat.add_categories(surface_list, inplace=True) - self._orientations.df['surface'].cat.add_categories(surface_list, inplace=True) + self._surface_points.df['Surface'].cat.add_categories(surface_list, inplace=True) + self._orientations.df['Surface'].cat.add_categories(surface_list, inplace=True) self.update_structure() return self._surfaces @@ -758,7 +757,7 @@ def delete_surfaces(self, indices: Union[str, Iterable[str]], update_id=True, re self._surfaces.delete_surface(indices, update_id) if indices.dtype == int: - surfaces_names = self._surfaces.df.loc[indices, 'surface'] + surfaces_names = self._surfaces.df.loc[indices, 'Surface'] else: surfaces_names = indices @@ -768,8 +767,8 @@ def delete_surfaces(self, indices: Union[str, Iterable[str]], update_id=True, re self._orientations.del_orientation( self._orientations.df[self._orientations.df.surface.isin(surfaces_names)].index) - self._surface_points.df['surface'].cat.remove_categories(surfaces_names, inplace=True) - self._orientations.df['surface'].cat.remove_categories(surfaces_names, inplace=True) + self._surface_points.df['Surface'].cat.remove_categories(surfaces_names, inplace=True) + self._orientations.df['Surface'].cat.remove_categories(surfaces_names, inplace=True) self.map_geometric_data_df(self._surface_points.df) self.map_geometric_data_df(self._orientations.df) self._surfaces.colors.delete_colors(surfaces_names) @@ -784,7 +783,7 @@ def delete_surfaces(self, indices: Union[str, Iterable[str]], update_id=True, re def rename_surfaces(self, to_replace: Union[dict], **kwargs): self._surfaces.rename_surfaces(to_replace, **kwargs) - self._surface_points.df['surface'].cat.rename_categories(to_replace, inplace=True) + self._surface_points.df['Surface'].cat.rename_categories(to_replace, inplace=True) self._orientations.df['surface'].cat.rename_categories(to_replace, inplace=True) return self._surfaces @@ -857,18 +856,18 @@ def map_stack_to_surfaces(self, mapping_object: Union[dict, pn.Categorical] = No series_list = list(mapping_object.keys()) self._stack.add_series(series_list) elif isinstance(mapping_object, pn.Categorical): - series_list = mapping_object['series'].values + series_list = mapping_object['Feature'].values self._stack.add_series(series_list) else: raise AttributeError(str(type(mapping_object)) + ' is not the right attribute type.') - self._surfaces.map_series(mapping_object) + self._surfaces.map_stack(mapping_object) # Here we remove the series that were not assigned to a surface if remove_unused_series is True: - self._surfaces.df['series'].cat.remove_unused_categories(inplace=True) + self._surfaces.df['Feature'].cat.remove_unused_categories(inplace=True) unused_cat = self._stack.df.index[~self._stack.df.index.isin( - self._surfaces.df['series'].cat.categories)] + self._surfaces.df['Feature'].cat.categories)] self._stack.delete_series(unused_cat) self._stack.reset_order_series() @@ -886,7 +885,7 @@ def map_stack_to_surfaces(self, mapping_object: Union[dict, pn.Categorical] = No if twofins is False: # assert if every fault has its own series for serie in list(self._faults.df[self._faults.df['isFault'] == True].index): - assert np.sum(self._surfaces.df['series'] == serie) < 2, \ + assert np.sum(self._surfaces.df['Feature'] == serie) < 2, \ 'Having more than one fault in a series is generally rather bad. Better give each ' \ 'fault its own series. If you are really sure what you are doing, you can set ' \ 'twofins to True to suppress this error.' @@ -1046,8 +1045,8 @@ def delete_surface_points(self, idx: Union[int, Iterable[int]]): def delete_surface_points_basement(self): """Delete surface points belonging to the basement layer if any""" - basement_name = self._surfaces.df['surface'][self._surfaces.df['isBasement']].values[0] - select = (self._surface_points.df['surface'] == basement_name) + basement_name = self._surfaces.df['Surface'][self._surfaces.df['isBasement']].values[0] + select = (self._surface_points.df['Surface'] == basement_name) self.delete_surface_points(self._surface_points.df.index[select]) return True @@ -1076,10 +1075,10 @@ def modify_surface_points(self, indices: Union[int, list], """ keys = list(kwargs.keys()) - is_surface = np.isin('surface', keys).all() + is_surface = np.isin('Surface', keys).all() if is_surface: - assert (~self._surfaces.df[self._surfaces.df['isBasement']]['surface'].isin( - np.atleast_1d(kwargs['surface']))).any(), \ + assert (~self._surfaces.df[self._surfaces.df['isBasement']]['Surface'].isin( + np.atleast_1d(kwargs['Surface']))).any(), \ 'Surface points cannot belong to Basement. Add a new surface.' self._surface_points.modify_surface_points(indices, **kwargs) @@ -1093,7 +1092,7 @@ def modify_surface_points(self, indices: Union[int, list], self._rescaling.set_rescaled_surface_points(indices) keys = list(kwargs.keys()) - is_surface = np.isin('surface', keys).all() + is_surface = np.isin('Surface', keys).all() if is_surface == True: self.update_structure(update_theano='matrices') @@ -1158,7 +1157,7 @@ def modify_orientations(self, idx: list, **kwargs): idx = np.array(idx, ndmin=1) keys = list(kwargs.keys()) - is_surface = np.isin('surface', keys).all() + is_surface = np.isin('Surface', keys).all() self._orientations.modify_orientations(idx, **kwargs) self._rescaling.set_rescaled_orientations(idx) @@ -1219,7 +1218,7 @@ def set_default_surface_point(self, **kwargs): """ if self._surface_points.df.shape[0] == 0: - self.add_surface_points(0.00001, 0.00001, 0.00001, self._surfaces.df['surface'].iloc[0], + self.add_surface_points(0.00001, 0.00001, 0.00001, self._surfaces.df['Surface'].iloc[0], recompute_rescale_factor=True, **kwargs) return self._surface_points @@ -1236,7 +1235,7 @@ def set_default_orientation(self, **kwargs): if self._orientations.df.shape[0] == 0: # TODO DEBUG: I am not sure that surfaces always has at least one entry. Check it self.add_orientations(.00001, .00001, .00001, - self._surfaces.df['surface'].iloc[0], + self._surfaces.df['Surface'].iloc[0], [0, 0, 1], recompute_rescale_factor=True, **kwargs) def set_default_surfaces(self): @@ -1246,8 +1245,8 @@ def set_default_surfaces(self): :class:`gempy_lite.core.data.Surfaces` """ - if len(self._surfaces.df['surface']) != 0: - self.delete_surfaces(self._surfaces.df['surface']) + if len(self._surfaces.df['Surface']) != 0: + self.delete_surfaces(self._surfaces.df['Surface']) if self._surfaces.df.shape[0] == 0: self.add_surfaces(['surface1', 'surface2']) @@ -1289,7 +1288,7 @@ def update_from_series(self, reorder_series=True, sort_geometric_data=True, """ if reorder_series is True: - self._surfaces.df['series'].cat.reorder_categories(np.asarray(self._stack.df.index), + self._surfaces.df['Feature'].cat.reorder_categories(np.asarray(self._stack.df.index), ordered=False, inplace=True) self._stack.df.index = self._stack.df.index.reorder_categories(self._stack.df.index.array, ordered=False) @@ -1299,7 +1298,7 @@ def update_from_series(self, reorder_series=True, sort_geometric_data=True, # Update surface is active from series does not work because you can have only a subset of surfaces of a # series active - self._surfaces.df['isFault'] = self._surfaces.df['series'].map(self._faults.df['isFault']) + self._surfaces.df['isFault'] = self._surfaces.df['Feature'].map(self._faults.df['isFault']) self._surfaces.set_basement() # Add categories from series @@ -1348,11 +1347,11 @@ def update_from_surfaces(self, set_categories_from_series=True, set_categories_f self._orientations.set_surface_categories_from_surfaces(self._surfaces) if map_surface_points is True: - self._surface_points.map_data_from_surfaces(self._surfaces, 'series') + self._surface_points.map_data_from_surfaces(self._surfaces, 'Feature') self._surface_points.map_data_from_surfaces(self._surfaces, 'id') if map_orientations is True: - self._orientations.map_data_from_surfaces(self._surfaces, 'series') + self._orientations.map_data_from_surfaces(self._surfaces, 'Feature') self._orientations.map_data_from_surfaces(self._surfaces, 'id') if update_structural_data is True: @@ -1441,9 +1440,9 @@ def map_geometric_data_df(self, d: pn.DataFrame): Returns: DataFrame """ - d['series'] = d['surface'].map(self._surfaces.df.set_index('surface')['series']) - d['id'] = d['surface'].map(self._surfaces.df.set_index('surface')['id']).astype(int) - d['OrderFeature'] = d['series'].map(self._stack.df['OrderFeature']).astype(int) + d['Feature'] = d['Surface'].map(self._surfaces.df.set_index('Surface')['Feature']) + d['id'] = d['Surface'].map(self._surfaces.df.set_index('Surface')['id']).astype(int) + d['OrderFeature'] = d['Feature'].map(self._stack.df['OrderFeature']).astype(int) return d def set_surface_order_from_solution(self): @@ -1461,14 +1460,14 @@ def set_surface_order_from_solution(self): self._sfai_order_0 = sfai_order sel = self._surfaces.df['isActive'] & ~self._surfaces.df['isBasement'] self._surfaces.df.loc[sel, 'sfai'] = sfai_order - self._surfaces.df.sort_values(by=['series', 'sfai'], inplace=True, ascending=False) + self._surfaces.df.sort_values(by=['Feature', 'sfai'], inplace=True, ascending=False) self._surfaces.reset_order_surfaces() self._surfaces.sort_surfaces() self._surfaces.set_basement() - self._surface_points.df['id'] = self._surface_points.df['surface'].map( - self._surfaces.df.set_index('surface')['id']).astype(int) - self._orientations.df['id'] = self._orientations.df['surface'].map( - self._surfaces.df.set_index('surface')['id']).astype(int) + self._surface_points.df['id'] = self._surface_points.df['Surface'].map( + self._surfaces.df.set_index('Surface')['id']).astype(int) + self._orientations.df['id'] = self._orientations.df['Surface'].map( + self._surfaces.df.set_index('Surface')['id']).astype(int) self._surface_points.sort_table() self._orientations.sort_table() self.update_structure() @@ -1693,7 +1692,7 @@ def get_data(self, itype='data', verbosity=0, numeric=False): elif itype == 'surfaces': raw_data = self._surfaces - elif itype == 'series': + elif itype == 'Feature': raw_data = self._stack elif itype == 'faults': raw_data = self._faults diff --git a/gempy_lite/core/model_data.py b/gempy_lite/core/model_data.py index 7f600e2..79677af 100644 --- a/gempy_lite/core/model_data.py +++ b/gempy_lite/core/model_data.py @@ -1,3 +1,4 @@ +import warnings from typing import Union import numpy as np @@ -479,7 +480,9 @@ def update_structure(self): """ Update fields dependent on input data sucha as structure and universal kriging grade """ - self.structure_data.update_structure_from_input() - if len(self.kriging_data.df.loc['values', 'drift equations']) < \ - self.structure_data.df.loc['values', 'number series']: - self.kriging_data.set_u_grade() \ No newline at end of file + warnings.warn('structured is not used anymore', DeprecationWarning) + pass + # self.structure_data.update_structure_from_input() + # if len(self.kriging_data.df.loc['values', 'drift equations']) < \ + # self.structure_data.df.loc['values', 'number series']: + # self.kriging_data.set_u_grade() \ No newline at end of file diff --git a/test/test_input_data/test_data_classes.py b/test/test_input_data/test_data_classes.py index e33d971..c930dba 100644 --- a/test/test_input_data/test_data_classes.py +++ b/test/test_input_data/test_data_classes.py @@ -43,7 +43,8 @@ def create_series(create_faults): return series -def test_surfaces(create_series): +@pytest.fixture(scope='module') +def create_surfaces(create_series): series = create_series surfaces = gempy_lite.core.kernel_data.Surfaces(series) surfaces.set_surfaces_names(['foo', 'foo2', 'foo5']) @@ -57,7 +58,7 @@ def test_surfaces(create_series): # The column surface is also a pandas.Categories. # This will be important for the Data clases (SurfacePoints and Orientations) - print(surfaces.df['surface']) + print(surfaces.df['Surface']) ### Set values @@ -93,9 +94,9 @@ def test_surfaces(create_series): # are pandas categories. To get a overview of what this mean # check https://pandas.pydata.org/pandas-docs/stable/categorical.html. - print(surfaces.df['feature']) + print(surfaces.df['Feature']) - print(surfaces.df['surface']) + print(surfaces.df['Surface']) # ### Map series to surface @@ -104,14 +105,14 @@ def test_surfaces(create_series): d = {"foo7": 'foo', "booX": ('foo2', 'foo5', 'fee')} - surfaces.map_series(d) - surfaces.map_series({"foo7": 'foo', "boo": ('foo2', 'foo5', 'fee')}) + surfaces.map_stack(d) + surfaces.map_stack({"foo7": 'foo', "boo": ('foo2', 'foo5', 'fee')}) print(surfaces) # An advantage of categories is that they are order so no we can tidy the df by series and surface - surfaces.df.sort_values(by='feature', inplace=True) + surfaces.df.sort_values(by='Feature', inplace=True) # If we change the basement: @@ -131,8 +132,8 @@ def test_surfaces(create_series): print(surfaces) - print(surfaces.number_surfaces_per_feature) - np.testing.assert_array_almost_equal(surfaces.number_surfaces_per_feature, + print(surfaces.n_surfaces_per_feature) + np.testing.assert_array_almost_equal(surfaces.n_surfaces_per_feature, np.array([0, 2, 1, 0, 1], dtype=int)) # We can use `set_is_fault` to choose which of our series are faults: return surfaces From c8454d22b122d0beed428760feaa90d15eebd2f6 Mon Sep 17 00:00:00 2001 From: Leguark Date: Tue, 27 Oct 2020 14:28:50 +0100 Subject: [PATCH 06/10] [CLN] Adapting all code to new changes --- gempy_lite/__init__.py | 2 +- gempy_lite/assets/geophysics.py | 4 +- gempy_lite/core/kernel_data/__init__.py | 4 +- gempy_lite/core/kernel_data/geometric_data.py | 4 +- gempy_lite/core/kernel_data/stack.py | 45 ++++++++++++++++++- gempy_lite/core/model.py | 19 ++++---- gempy_lite/core/model_data.py | 2 +- gempy_lite/core/predictor/__init__.py | 0 .../{ => predictor}/grid_modules/__init__.py | 0 .../grid_modules/create_topography.py | 0 .../grid_modules/diamond_square.py | 0 .../grid_modules/grid_types.py | 2 - .../grid_modules/section_utils.py | 0 .../grid_modules/topography.py | 0 gempy_lite/core/{ => predictor}/solution.py | 4 +- .../core/{ => predictor}/structured_data.py | 2 +- gempy_lite/gempy_api.py | 4 +- test/test_input_data/test_data_classes.py | 4 +- test/test_input_data/test_data_mutation.py | 2 +- test/test_input_data/test_input.py | 27 ----------- test/test_input_data/test_model.py | 6 +-- 21 files changed, 73 insertions(+), 58 deletions(-) create mode 100644 gempy_lite/core/predictor/__init__.py rename gempy_lite/core/{ => predictor}/grid_modules/__init__.py (100%) rename gempy_lite/core/{ => predictor}/grid_modules/create_topography.py (100%) rename gempy_lite/core/{ => predictor}/grid_modules/diamond_square.py (100%) rename gempy_lite/core/{ => predictor}/grid_modules/grid_types.py (99%) rename gempy_lite/core/{ => predictor}/grid_modules/section_utils.py (100%) rename gempy_lite/core/{ => predictor}/grid_modules/topography.py (100%) rename gempy_lite/core/{ => predictor}/solution.py (99%) rename gempy_lite/core/{ => predictor}/structured_data.py (99%) diff --git a/gempy_lite/__init__.py b/gempy_lite/__init__.py index 75ebc57..ab38916 100644 --- a/gempy_lite/__init__.py +++ b/gempy_lite/__init__.py @@ -30,7 +30,7 @@ from gempy_lite.core.kernel_data import Surfaces, Structure, KrigingParameters from gempy_lite.core.model_data import Options, RescaledData, AdditionalData -from gempy_lite.core.solution import Solution +from gempy_lite.core.predictor.solution import Solution from gempy_lite.addons.gempy_to_rexfile import geomodel_to_rex diff --git a/gempy_lite/assets/geophysics.py b/gempy_lite/assets/geophysics.py index a0e5945..edbe8b4 100644 --- a/gempy_lite/assets/geophysics.py +++ b/gempy_lite/assets/geophysics.py @@ -16,9 +16,7 @@ """ import numpy as np -import theano -import theano.tensor as T -from gempy_lite.core.grid_modules.grid_types import CenteredGrid +from gempy_lite.core.predictor.grid_modules.grid_types import CenteredGrid class GravityPreprocessing(CenteredGrid): diff --git a/gempy_lite/core/kernel_data/__init__.py b/gempy_lite/core/kernel_data/__init__.py index 74af1f4..9e7205a 100644 --- a/gempy_lite/core/kernel_data/__init__.py +++ b/gempy_lite/core/kernel_data/__init__.py @@ -103,7 +103,7 @@ def n_surfaces_per_feature(self): # len_sps[surf_count.index - 1] = surf_count.values # # self.df.at['values', 'number surfaces per series'] = len_sps - return self.df.groupby('Feature').Surface.nunique().values + return self.df.groupby('Feature').count()['Surface'].values def update_id(self, id_list: list = None): """ @@ -617,7 +617,7 @@ def delete_colors(self, surfaces): self._set_colors() def make_faults_black(self, series_fault): - faults_list = list(self.surfaces.df[self.surfaces.df.series.isin(series_fault)]['Surface']) + faults_list = list(self.surfaces.df[self.surfaces.df.Feature.isin(series_fault)]['Surface']) for fault in faults_list: if self.colordict[fault] == '#527682': self.set_default_colors(fault) diff --git a/gempy_lite/core/kernel_data/geometric_data.py b/gempy_lite/core/kernel_data/geometric_data.py index f270b3b..a5c71ea 100644 --- a/gempy_lite/core/kernel_data/geometric_data.py +++ b/gempy_lite/core/kernel_data/geometric_data.py @@ -217,10 +217,10 @@ def n_sp_per_feature(self): :class:`pn.DataFrame`: df where Structural data is stored """ - len_series = np.zeros(self.surfaces.stack.n_features, dtype=int) + #len_series = np.zeros(self.surfaces.stack.n_features, dtype=int) # Array containing the size of every series. SurfacePoints. points_count = self.df['OrderFeature'].value_counts(sort=False) - len_series_i = np.zeros(len_series, dtype=int) + len_series_i = np.zeros(self.surfaces.stack.n_features, dtype=int) len_series_i[points_count.index - 1] = points_count.values return len_series_i diff --git a/gempy_lite/core/kernel_data/stack.py b/gempy_lite/core/kernel_data/stack.py index 80c501a..88dfd5d 100644 --- a/gempy_lite/core/kernel_data/stack.py +++ b/gempy_lite/core/kernel_data/stack.py @@ -453,6 +453,8 @@ def __init__( 'DriftDegree', 'isActive', 'isFault', 'isFinite', 'isComputed'] + + self._kriging_view = ['Range', 'Sill', 'DriftDegree'] self._public_attr = ['OrderFeature', 'BottomRelation', 'Level', 'Range', 'Sill', 'DriftDegree', @@ -535,11 +537,52 @@ def set_default_range(self, extent=None): return range_var - @property def faults(self): return self + @property + def kriging(self): + return self.df[self._kriging_view] + @property def n_features(self): return self.df.shape[0] + + def modify_parameter(self, attribute: str, value, idx=None, **kwargs): + """Method to modify a given field + + Args: + attribute (str): Name of the field to modify + value: new value of the field. It will have to exist in the category in order for pandas to modify it. + kwargs: + * u_grade_sep (str): If drift equations values are `str`, symbol that separates the values. + + Returns: + :class:`pandas.DataFrame`: df where options data is stored + """ + + u_grade_sep = kwargs.get('u_grade_sep', ',') + assert np.isin(attribute, self.df.columns).all(), 'Valid properties are: ' +\ + np.array2string(self.df.columns) + if idx is None: + idx = slice(None) + + if attribute == 'drift equations': + value = np.asarray(value) + print(value) + + if type(value) is str: + value = np.fromstring(value[1:-1], sep=u_grade_sep, dtype=int) + try: + assert value.shape[0] is self.structure.df.loc['values', 'len series surface_points'].shape[0] + print(value, attribute) + self.df.at[idx, attribute] = value + print(self.df) + + except AssertionError: + print('u_grade length must be the same as the number of series') + + else: + self.df.loc[idx, attribute] = value + diff --git a/gempy_lite/core/model.py b/gempy_lite/core/model.py index 67db22f..3750f84 100644 --- a/gempy_lite/core/model.py +++ b/gempy_lite/core/model.py @@ -1,7 +1,6 @@ import os import shutil import sys -from abc import ABC import numpy as np import pandas as pn @@ -12,8 +11,8 @@ from gempy_lite.core.kernel_data import KrigingParameters from gempy_lite.core.kernel_data.stack import Stack, Faults, Series from gempy_lite.core.model_data import MetaData, Options, AdditionalData, RescaledData -from gempy_lite.core.solution import Solution -from gempy_lite.core.structured_data import Grid +from gempy_lite.core.predictor.solution import Solution +from gempy_lite.core.predictor.structured_data import Grid from gempy_lite.utils.meta import _setdoc, _setdoc_pro import gempy_lite.utils.docstring as ds from gempy_lite.plot.decorators import * @@ -165,6 +164,10 @@ def interpolator(self): accepted_members=['__repr__', '_repr_html_', 'theano_graph']) + @property + def kriging(self): + return self._stack.kriging + def _add_valid_idx_s(self, idx): if idx is None: idx = self._surface_points.df.index.max() @@ -691,7 +694,7 @@ def set_is_fault(self, feature_fault: Union[str, list] = None, toggle: bool = Fa else: self._stack.df.loc[feature_fault, 'BottomRelation'] = 'Fault' - self._additional_data.structure_data.set_number_of_faults() + #self._additional_data.structure_data.set_number_of_faults() #self._interpolator.set_theano_shared_relations() #self._interpolator.set_theano_shared_loop() if change_color: @@ -763,9 +766,9 @@ def delete_surfaces(self, indices: Union[str, Iterable[str]], update_id=True, re if remove_data: self._surface_points.del_surface_points( - self._surface_points.df[self._surface_points.df.surface.isin(surfaces_names)].index) + self._surface_points.df[self._surface_points.df.Surface.isin(surfaces_names)].index) self._orientations.del_orientation( - self._orientations.df[self._orientations.df.surface.isin(surfaces_names)].index) + self._orientations.df[self._orientations.df.Surface.isin(surfaces_names)].index) self._surface_points.df['Surface'].cat.remove_categories(surfaces_names, inplace=True) self._orientations.df['Surface'].cat.remove_categories(surfaces_names, inplace=True) @@ -784,7 +787,7 @@ def rename_surfaces(self, to_replace: Union[dict], **kwargs): self._surfaces.rename_surfaces(to_replace, **kwargs) self._surface_points.df['Surface'].cat.rename_categories(to_replace, inplace=True) - self._orientations.df['surface'].cat.rename_categories(to_replace, inplace=True) + self._orientations.df['Surface'].cat.rename_categories(to_replace, inplace=True) return self._surfaces @_setdoc(Surfaces.modify_order_surfaces.__doc__, indent=False) @@ -1182,7 +1185,7 @@ def modify_options(self, attribute, value): # region Kriging @_setdoc(KrigingParameters.modify_kriging_parameters.__doc__, indent=False, position='beg') def modify_kriging_parameters(self, attribute, value, **kwargs): - self._additional_data.kriging_data.modify_kriging_parameters(attribute, value, **kwargs) + self._stack.modify_parameter(attribute, value, **kwargs) #self._interpolator.set_theano_shared_kriging() if attribute == 'drift equations': # self._interpolator.set_initial_results() diff --git a/gempy_lite/core/model_data.py b/gempy_lite/core/model_data.py index 79677af..1c49055 100644 --- a/gempy_lite/core/model_data.py +++ b/gempy_lite/core/model_data.py @@ -6,7 +6,7 @@ from gempy_lite.core.kernel_data.geometric_data import SurfacePoints, Orientations, Surfaces from gempy_lite.core.kernel_data import Structure, KrigingParameters -from gempy_lite.core.structured_data import Grid +from gempy_lite.core.predictor.structured_data import Grid from gempy_lite.utils import docstring as ds from gempy_lite.utils.meta import _setdoc_pro diff --git a/gempy_lite/core/predictor/__init__.py b/gempy_lite/core/predictor/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/gempy_lite/core/grid_modules/__init__.py b/gempy_lite/core/predictor/grid_modules/__init__.py similarity index 100% rename from gempy_lite/core/grid_modules/__init__.py rename to gempy_lite/core/predictor/grid_modules/__init__.py diff --git a/gempy_lite/core/grid_modules/create_topography.py b/gempy_lite/core/predictor/grid_modules/create_topography.py similarity index 100% rename from gempy_lite/core/grid_modules/create_topography.py rename to gempy_lite/core/predictor/grid_modules/create_topography.py diff --git a/gempy_lite/core/grid_modules/diamond_square.py b/gempy_lite/core/predictor/grid_modules/diamond_square.py similarity index 100% rename from gempy_lite/core/grid_modules/diamond_square.py rename to gempy_lite/core/predictor/grid_modules/diamond_square.py diff --git a/gempy_lite/core/grid_modules/grid_types.py b/gempy_lite/core/predictor/grid_modules/grid_types.py similarity index 99% rename from gempy_lite/core/grid_modules/grid_types.py rename to gempy_lite/core/predictor/grid_modules/grid_types.py index 01075ef..d4846d6 100644 --- a/gempy_lite/core/grid_modules/grid_types.py +++ b/gempy_lite/core/predictor/grid_modules/grid_types.py @@ -1,6 +1,4 @@ -from gempy_lite.core.grid_modules.create_topography import LoadDEMArtificial import numpy as np -import matplotlib.pyplot as plt from scipy.constants import G from scipy import interpolate from gempy_lite.utils.meta import _setdoc_pro diff --git a/gempy_lite/core/grid_modules/section_utils.py b/gempy_lite/core/predictor/grid_modules/section_utils.py similarity index 100% rename from gempy_lite/core/grid_modules/section_utils.py rename to gempy_lite/core/predictor/grid_modules/section_utils.py diff --git a/gempy_lite/core/grid_modules/topography.py b/gempy_lite/core/predictor/grid_modules/topography.py similarity index 100% rename from gempy_lite/core/grid_modules/topography.py rename to gempy_lite/core/predictor/grid_modules/topography.py diff --git a/gempy_lite/core/solution.py b/gempy_lite/core/predictor/solution.py similarity index 99% rename from gempy_lite/core/solution.py rename to gempy_lite/core/predictor/solution.py index 728b0a0..2675978 100644 --- a/gempy_lite/core/solution.py +++ b/gempy_lite/core/predictor/solution.py @@ -3,10 +3,10 @@ import warnings # from skimage import measure from gempy_lite.utils.input_manipulation import find_interfaces_from_block_bottoms -from gempy_lite.core.structured_data import Grid +from gempy_lite.core.predictor.structured_data import Grid from gempy_lite.core.kernel_data import Surfaces from gempy_lite.core.kernel_data.stack import Series -from gempy_lite.utils.meta import _setdoc, _setdoc_pro +from gempy_lite.utils.meta import _setdoc_pro import gempy_lite.utils.docstring as ds diff --git a/gempy_lite/core/structured_data.py b/gempy_lite/core/predictor/structured_data.py similarity index 99% rename from gempy_lite/core/structured_data.py rename to gempy_lite/core/predictor/structured_data.py index 9ee3311..ecf3e8b 100644 --- a/gempy_lite/core/structured_data.py +++ b/gempy_lite/core/predictor/structured_data.py @@ -8,7 +8,7 @@ """ from typing import Union -from gempy_lite.core.grid_modules import grid_types, topography +from gempy_lite.core.predictor.grid_modules import grid_types, topography from gempy_lite.utils.meta import _setdoc_pro, _setdoc import gempy_lite.utils.docstring as ds import numpy as np diff --git a/gempy_lite/gempy_api.py b/gempy_lite/gempy_api.py index e65705b..5f4e39d 100644 --- a/gempy_lite/gempy_api.py +++ b/gempy_lite/gempy_api.py @@ -15,8 +15,8 @@ from typing import Union import warnings from gempy_lite.core.model import Project -from gempy_lite.core.solution import Solution -from gempy_lite.utils.meta import _setdoc, _setdoc_pro +from gempy_lite.core.predictor.solution import Solution +from gempy_lite.utils.meta import _setdoc_pro # region get diff --git a/test/test_input_data/test_data_classes.py b/test/test_input_data/test_data_classes.py index c930dba..81cf658 100644 --- a/test/test_input_data/test_data_classes.py +++ b/test/test_input_data/test_data_classes.py @@ -155,7 +155,7 @@ def create_surface_points(create_surfaces, create_series): print(surface_points) - surface_points.map_data_from_surfaces(surfaces, 'series') + surface_points.map_data_from_surfaces(surfaces, 'Feature') print(surface_points) surface_points.map_data_from_surfaces(surfaces, 'id') @@ -195,7 +195,7 @@ def create_orientations(create_surfaces, create_series): print(orientations) # ### Mapping data from the other df - orientations.map_data_from_surfaces(surfaces, 'series') + orientations.map_data_from_surfaces(surfaces, 'Feature') print(orientations) orientations.map_data_from_surfaces(surfaces, 'id') diff --git a/test/test_input_data/test_data_mutation.py b/test/test_input_data/test_data_mutation.py index 90b45ba..638d96c 100644 --- a/test/test_input_data/test_data_mutation.py +++ b/test/test_input_data/test_data_mutation.py @@ -33,7 +33,7 @@ def test_rename_surface(): mm = gp.ImplicitCoKriging() mm.add_surfaces(['surface1', 'foo1', 'foo2', 'foo3']) mm.rename_surfaces({'foo1': 'changed'}) - assert mm._surfaces.df.loc[1, 'surface'] == 'changed' + assert mm._surfaces.df.loc[1, 'Surface'] == 'changed' def test_modify_order_surfaces(): diff --git a/test/test_input_data/test_input.py b/test/test_input_data/test_input.py index 933de9f..7bf115d 100644 --- a/test/test_input_data/test_input.py +++ b/test/test_input_data/test_input.py @@ -4,30 +4,3 @@ def test_all_running(model_horizontal_two_layers): print(model_horizontal_two_layers.surfaces) - - -@pytest.mark.skip -def test_combine_stack_surfaces(model_horizontal_two_layers): - surf = model_horizontal_two_layers.surfaces.df - stack = model_horizontal_two_layers.stack.df - surfaces = xr.DataArray(surf, - dims=['surface', 'surface_attributes'],\ - name='surfaces', - ) - #stack = xr.DataArray(stack, dims=['feature', 'feature_attributes'], name='series') - - m = xr.merge([surfaces, stack]) - print(surfaces) - print(stack) - print(m) - - -def test_combine_stack_surfaces_df(model_horizontal_two_layers): - df1 = model_horizontal_two_layers.surfaces.df - df2 = model_horizontal_two_layers.stack.df - df3 = df1.join(df2, on='series', lsuffix='foo') - df4 = df3.set_index(['series', 'surface']) - print(df3) - - x = xr.DataArray(df4) - print(x) diff --git a/test/test_input_data/test_model.py b/test/test_input_data/test_model.py index 995ff4c..c08ca03 100644 --- a/test/test_input_data/test_model.py +++ b/test/test_input_data/test_model.py @@ -120,6 +120,6 @@ def test_define_sequential_pile(map_sequential_pile): def test_kriging_mutation(map_sequential_pile): geo_model = map_sequential_pile - - geo_model.modify_kriging_parameters('range', 1) - geo_model.modify_kriging_parameters('drift equations', [0, 3]) \ No newline at end of file + geo_model.modify_kriging_parameters('Range', 1) + geo_model.modify_kriging_parameters('DriftDegree', [0, 1]) + print(geo_model.kriging) \ No newline at end of file From 40c36e8f42d3952e922cf0a6b5a25dd3db0b1fbe Mon Sep 17 00:00:00 2001 From: Leguark Date: Tue, 27 Oct 2020 18:17:28 +0100 Subject: [PATCH 07/10] [ENH] Prototyping Xsolution --- gempy_lite/api_modules/setters.py | 2 +- .../core/predictor/grid_modules/grid_types.py | 30 ++-- gempy_lite/core/predictor/solution.py | 92 ++++++++++- gempy_lite/core/predictor/structured_data.py | 5 - test/test_grids/__init__.py | 0 test/test_grids/test_diamond_square.py | 148 ++++++++++++++++++ test/test_grids/test_grid.py | 62 ++++++++ test/test_grids/test_topography.py | 137 ++++++++++++++++ test/test_grids/test_xsol.py | 63 ++++++++ 9 files changed, 516 insertions(+), 23 deletions(-) create mode 100644 test/test_grids/__init__.py create mode 100644 test/test_grids/test_diamond_square.py create mode 100644 test/test_grids/test_grid.py create mode 100644 test/test_grids/test_topography.py create mode 100644 test/test_grids/test_xsol.py diff --git a/gempy_lite/api_modules/setters.py b/gempy_lite/api_modules/setters.py index 8e4c7a0..7ae86e4 100644 --- a/gempy_lite/api_modules/setters.py +++ b/gempy_lite/api_modules/setters.py @@ -163,7 +163,7 @@ def set_orientation_from_surface_points(geo_model, indices_array): if np.ndim(indices_array) == 1: indices = indices_array form = geo_model._surface_points.df['surface'].loc[indices].unique() - assert form.shape[0] is 1, 'The interface points must belong to the same surface' + assert form.shape[0] == 1, 'The interface points must belong to the same surface' form = form[0] ori_parameters = geo_model._orientations.create_orientation_from_surface_points( diff --git a/gempy_lite/core/predictor/grid_modules/grid_types.py b/gempy_lite/core/predictor/grid_modules/grid_types.py index d4846d6..bb90cb2 100644 --- a/gempy_lite/core/predictor/grid_modules/grid_types.py +++ b/gempy_lite/core/predictor/grid_modules/grid_types.py @@ -31,12 +31,26 @@ def __init__(self, extent=None, resolution=None, **kwargs): self.values = np.zeros((0, 3)) self.values_r = np.zeros((0, 3)) self.mask_topo = np.zeros((0, 3), dtype=bool) + self.x = None + self.y = None + self.z = None + if extent is not None and resolution is not None: self.set_regular_grid(extent, resolution) self.dx, self.dy, self.dz = self.get_dx_dy_dz() - @staticmethod - def create_regular_grid_3d(extent, resolution): + def set_coord(self, extent, resolution): + dx = (extent[1] - extent[0]) / resolution[0] + dy = (extent[3] - extent[2]) / resolution[1] + dz = (extent[5] - extent[4]) / resolution[2] + + self.x = np.linspace(extent[0] + dx / 2, extent[1] - dx / 2, resolution[0], dtype="float64") + self.y = np.linspace(extent[2] + dy / 2, extent[3] - dy / 2, resolution[1], dtype="float64") + self.z = np.linspace(extent[4] + dz / 2, extent[5] - dz / 2, resolution[2], dtype="float64") + + return self.x, self.y, self.z + + def create_regular_grid_3d(self, extent, resolution): """ Method to create a 3D regular grid where is interpolated @@ -49,16 +63,8 @@ def create_regular_grid_3d(extent, resolution): """ - dx = (extent[1] - extent[0]) / resolution[0] - dy = (extent[3] - extent[2]) / resolution[1] - dz = (extent[5] - extent[4]) / resolution[2] - - g = np.meshgrid( - np.linspace(extent[0] + dx / 2, extent[1] - dx / 2, resolution[0], dtype="float64"), - np.linspace(extent[2] + dy / 2, extent[3] - dy / 2, resolution[1], dtype="float64"), - np.linspace(extent[4] + dz / 2, extent[5] - dz / 2, resolution[2], dtype="float64"), indexing="ij" - ) - + coords = self.set_coord(extent, resolution) + g = np.meshgrid(*coords, indexing="ij") values = np.vstack(tuple(map(np.ravel, g))).T.astype("float64") return values diff --git a/gempy_lite/core/predictor/solution.py b/gempy_lite/core/predictor/solution.py index 2675978..f7511ff 100644 --- a/gempy_lite/core/predictor/solution.py +++ b/gempy_lite/core/predictor/solution.py @@ -1,14 +1,97 @@ import numpy as np -from typing import Union +from typing import Union, Iterable import warnings # from skimage import measure from gempy_lite.utils.input_manipulation import find_interfaces_from_block_bottoms from gempy_lite.core.predictor.structured_data import Grid from gempy_lite.core.kernel_data import Surfaces -from gempy_lite.core.kernel_data.stack import Series +from gempy_lite.core.kernel_data.stack import Series, Stack from gempy_lite.utils.meta import _setdoc_pro import gempy_lite.utils.docstring as ds +import xarray as xr + + +@_setdoc_pro([Grid.__doc__, Surfaces.__doc__, Series.__doc__, ds.weights_vector, ds.sfai, ds.bai, ds.mai, ds.vai, + ds.lith_block, ds.sfm, ds.bm, ds.mm, ds.vm, ds.vertices, ds.edges, ds.geological_map]) +class XSolution(object): + """This class stores the output of the interpolation and the necessary objects + to visualize and manipulate this data. + + Depending on the activated grid (see :class:`Grid`) a different number of + properties are returned returned: + + Args: + grid (Grid): [s0] + surfaces (Surfaces): [s1] + series (Series): [s2] + + Attributes: + grid (Grid) + surfaces (Surfaces) + series (Series) + weights_vector (numpy.array): [s3] + scalar_field_at_surface_points (numpy.array): [s4] + block_at_surface_points (numpy.array): [s5] + mask_at_surface_points (numpy.array): [s6] + values_at_surface_points (numpy.array): [s7] + lith_block (numpy.array): [s8] + scalar_field_matrix (numpy.array): [s9] + block_matrix (numpy.array): [s10] + mask_matrix (numpy.array): [s11] + mask_matrix_pad (numpy.array): mask matrix padded 2 block in order to guarantee that the layers intersect each + other after marching cubes + values_matrix (numpy.array): [s12] + vertices (list[numpy.array]): [s13] + edges (list[numpy.array]): [s14] + geological_map (numpy.array): [s15] + + """ + + def __init__(self, grid: Grid, + surfaces: Surfaces = None, + stack: Stack = None, + ): + # self.additional_data = additional_data + self.grid = grid + # self.surface_points = surface_points + self.stack = stack + self.surfaces = surfaces # Used to store ver/sim there + + # Define xarrays + self.weights_vector = None + self.at_surface_points = None + self.s_regular_grid = None + self.meshes = None + + def get_grid_args(self, grid_type: str) -> tuple: + return self.grid.get_grid_args(grid_type) + + def set_values(self, values: Iterable): + self.set_values_to_regular_grid(values) + + def set_values_to_regular_grid(self, values: Iterable, l0=None, l1=None): + if l0 is None or l1 is None: + l0, l1 = self.get_grid_args('regular') + + coords = dict() + + if self.stack is not None: + coords['Features'] = self.stack.df.groupby('isActive').get_group(True).index + + coords['X'] = self.grid.regular_grid.x + coords['Y'] = self.grid.regular_grid.y + coords['Z'] = self.grid.regular_grid.z + property_matrix = xr.DataArray( + data=values[0][:, l0:l1].reshape(-1, *self.grid.regular_grid.resolution), + dims=['Features', 'X', 'Y', 'Z'], + coords=coords + ) + + self.s_regular_grid = xr.Dataset({ + 'property_matrix': property_matrix + }) + @_setdoc_pro([Grid.__doc__, Surfaces.__doc__, Series.__doc__, ds.weights_vector, ds.sfai, ds.bai, ds.mai, ds.vai, ds.lith_block, ds.sfm, ds.bm, ds.mm, ds.vm, ds.vertices, ds.edges, ds.geological_map]) @@ -211,7 +294,6 @@ def compute_marching_cubes_regular_grid(self, level: float, scalar_field, return [vertices, simplices, normals, values] - def padding_mask_matrix(self, mask_topography=True, shift=2): """Pad as many elements as in shift to the masking arrays. This is done to guarantee intersection of layers if masked marching cubes are done @@ -243,7 +325,7 @@ def padding_mask_matrix(self, mask_topography=True, shift=2): def compute_all_surfaces(self, **kwargs): """Compute all surfaces of the model given the geological features rules. - Args: +0 Args: **kwargs: :any:`skimage.measure.marching_cubes` args (see below) Returns: @@ -276,7 +358,7 @@ def compute_all_surfaces(self, **kwargs): sfas = self.scalar_field_at_surface_points[e] # Drop sfas = sfas[np.nonzero(sfas)] - mask_array = self.mask_matrix_pad[e-1 if series_type[e-1] == 'Onlap' else e] + mask_array = self.mask_matrix_pad[e - 1 if series_type[e - 1] == 'Onlap' else e] for level in sfas: try: v, s, norm, val = self.compute_marching_cubes_regular_grid( diff --git a/gempy_lite/core/predictor/structured_data.py b/gempy_lite/core/predictor/structured_data.py index ecf3e8b..fc89799 100644 --- a/gempy_lite/core/predictor/structured_data.py +++ b/gempy_lite/core/predictor/structured_data.py @@ -60,16 +60,11 @@ def __init__(self, **kwargs): # Init optional grids self.custom_grid = None - self.custom_grid_grid_active = False self.topography = None - self.topography_grid_active = False - self.sections_grid_active = False self.centered_grid = None - self.centered_grid_active = False # Init basic grid empty self.regular_grid = self.create_regular_grid(set_active=False, **kwargs) - self.regular_grid_active = False # Init optional sections self.sections = grid_types.Sections(regular_grid=self.regular_grid) diff --git a/test/test_grids/__init__.py b/test/test_grids/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/test_grids/test_diamond_square.py b/test/test_grids/test_diamond_square.py new file mode 100644 index 0000000..24f73ec --- /dev/null +++ b/test/test_grids/test_diamond_square.py @@ -0,0 +1,148 @@ +import gempy_lite.core.predictor.grid_modules.diamond_square +import pytest # to add fixtures and to test error raises +import numpy as np # as another testing environment + + +def test_class_nocrash(): + """Simply check if class can be instantiated""" + gempy_lite.core.predictor.grid_modules.diamond_square.DiaomondSquare(size=(5, 5)) + + +def test_grid_generation(): + """Test grid generation and extension for non-suitable grid sizes""" + ds = gempy_lite.core.predictor.grid_modules.diamond_square.DiaomondSquare(size=(5, 5)) + assert ds.grid.shape == (5, 5) + ds = gempy_lite.core.predictor.grid_modules.diamond_square.DiaomondSquare(size=(8, 10)) + assert ds.grid.shape == (9, 17) + + +def test_diamond_selection(): + """Test selection of diamond positions""" + ds = gempy_lite.core.predictor.grid_modules.diamond_square.DiaomondSquare(size=(5, 5)) + z = ds.get_selection_diamond(1) + assert np.all(z == np.array([[2, 0, 0, 0, 2], + [0, 0, 0, 0, 0], + [0, 0, 1, 0, 0], + [0, 0, 0, 0, 0], + [2, 0, 0, 0, 2]])) + z = ds.get_selection_diamond(0) + assert np.all(z == np.array([[2, 0, 2, 0, 2], + [0, 1, 0, 1, 0], + [2, 0, 2, 0, 2], + [0, 1, 0, 1, 0], + [2, 0, 2, 0, 2]])) + + +def test_square_selection(): + """Test selection of diamond positions""" + ds = gempy_lite.core.predictor.grid_modules.diamond_square.DiaomondSquare(size=(5, 5)) + z = ds.get_selection_square(0) + assert np.all(z == np.array([[0, 0, 2, 0, 2, 0, 0], + [0, 2, 1, 2, 1, 2, 0], + [2, 1, 2, 1, 2, 1, 2], + [0, 2, 1, 2, 1, 2, 0], + [2, 1, 2, 1, 2, 1, 2], + [0, 2, 1, 2, 1, 2, 0], + [0, 0, 2, 0, 2, 0, 0]])) + z = ds.get_selection_square(1) + assert np.all(z == np.array([[0, 0, 0, 0, 2, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 2, 0, 1, 0, 2, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [2, 0, 1, 0, 2, 0, 1, 0, 2], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 2, 0, 1, 0, 2, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 2, 0, 0, 0, 0]])) + + +def test_random_initialization(): + """Test random initialization of corner points""" + ds = gempy_lite.core.predictor.grid_modules.diamond_square.DiaomondSquare(size=(5, 6), seed=52062) + ds.random_initialization() + m_pow_max = min(ds.n, ds.m) + step_size = int(2 ** m_pow_max) + np.testing.assert_array_almost_equal(ds.grid[::step_size, ::step_size], + np.array([[0.35127005, 0.55476571, 0.93745213], + [0.66668382, 0.85215985, 0.53222795]])) + + +def test_random_initialization_level(): + """Test random initialization on lower level""" + ds = gempy_lite.core.predictor.grid_modules.diamond_square.DiaomondSquare(size=(33, 33), seed=52062) + level = 3 + ds.random_initialization(level=level) + step_size = int(2 ** level) + np.testing.assert_array_almost_equal(ds.grid[::step_size, ::step_size], + np.array([[0.35127005, 0.55476571, 0.93745213, 0.66668382, 0.85215985], + [0.53222795, 0.55800027, 0.20974513, 0.74837501, 0.64394326], + [0.0359961, 0.22723278, 0.56347804, 0.13438884, 0.32613594], + [0.20868763, 0.03116471, 0.1498014, 0.20755495, 0.86021482], + [0.64707457, 0.44744272, 0.36504945, 0.52473407, 0.27948164]])) + +def test_reset_grid(): + ds = gempy_lite.core.predictor.grid_modules.diamond_square.DiaomondSquare(size=(5, 6), seed=52062) + ds.random_initialization() + ds.reset_grid() + np.testing.assert_array_almost_equal(ds.grid, + np.array([[0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 0.], + [0., 0., 0., 0., 0., 0., 0., 0., 0.]])) + + +def test_random_func(): + """Test random function implementation""" + ds = gempy_lite.core.predictor.grid_modules.diamond_square.DiaomondSquare(size=(33, 33), seed=52062) + np.testing.assert_array_almost_equal(ds.random_func(2, 2), + np.array([-0.14872995, 0.05476571])) + # testing for correct default implementation + ds.r_type = 'default' + np.testing.assert_array_almost_equal(ds.random_func(2, 2), + np.array([0.43745213, 0.16668382])) + # testing long-range correlation + ds.r_type = 'long_range' + np.testing.assert_array_almost_equal(ds.random_func(2, 2), + np.array([0.04401998, 0.00402849])) + # testing level-scale correlation + ds.r_type = 'level_scale' + np.testing.assert_array_almost_equal(ds.random_func(2, 2), + np.array([0.18600009, 0.06991504])) + # testing deterministic implementation (no random value) + ds.r_type = 'deterministic' + assert ds.random_func(2, 2) == 0.0 + + +def test_random_func_raises_error(): + """Test if random function raises NonImplementedError correctly""" + ds = gempy_lite.core.predictor.grid_modules.diamond_square.DiaomondSquare(size=(33, 33), seed=52062) + ds.r_type = 'fail' + + with pytest.raises(NotImplementedError): + ds.random_func(2, 2) + + +def test_interpolate(): + """Test interpolation step itself""" + ds = gempy_lite.core.predictor.grid_modules.diamond_square.DiaomondSquare(size=(9, 9), seed=52062) + ds.interpolate() + np.testing.assert_array_almost_equal(ds.grid, + np.array([[0., 0.2951411, 0.21781267, 0.29361906, 0.01037812, + -0.0376406, -0.59889259, 0.01136296, 0.], + [-0.13102895, -0.07079394, 0.40240191, -0.24139454, -0.64535709, + -0.25358984, -0.20811689, -0.38977623, -0.02280871], + [-0.32311967, -0.08246826, 0.03236034, -0.72313104, -0.6863271, + -0.09742037, 0.16154592, -0.41643384, -0.23968483], + [-0.53344647, -0.09313507, -0.66247738, -0.42849468, -0.06519284, + -0.50628043, -0.31159035, 0.53516982, 0.07387422], + [0.23421434, 0.32817758, -0.45156142, -0.24627659, -0.2974599, + 0.16071127, 0.36261452, 0.62070397, 0.60516641], + [-0.20172896, -0.05668301, -0.26331217, -0.22196496, 0.42029741, + 0.35078669, 0.77129922, 0.38999358, 0.95701668], + [-0.41296032, -0.04377428, -0.23235603, 0.60954786, 0.72643437, + 0.37788456, 0.62211967, 0.16198846, 0.61709021], + [0.00192109, -0.28399285, 0.28596529, 0.54081866, 1.00235637, + 0.25454729, 0.1248549, 0.85789169, 0.23511424], + [0., -0.54507578, -0.33592062, 0.62216544, 0.77575097, + 0.5338132, 0.22007596, 0.02926128, 0.]])) diff --git a/test/test_grids/test_grid.py b/test/test_grids/test_grid.py new file mode 100644 index 0000000..97109f0 --- /dev/null +++ b/test/test_grids/test_grid.py @@ -0,0 +1,62 @@ +# Importing GemPy +import gempy_lite as gp + + +# Importing auxiliary libraries +import numpy as np + + +class TestGrid: + def test_set_regular_grid(self): + # Test creating an empty list + grid = gp.Grid() + + # Test set regular grid by hand + grid.create_regular_grid([0, 2000, 0, 2000, -2000, 0], [50, 50, 50]) + + def test_grid_init(self): + # Or we can init one of the default grids since the beginning by passing + # the correspondant attributes + grid = gp.Grid(extent=[0, 2000, 0, 2000, -2000, 0], + resolution=[50, 50, 50]) + + def test_section_grid(self): + geo_data = gp.create_data('section_grid', [0, 1000, 0, 1000, 0, 1000], resolution=[10, 10, 10]) + geo_data.set_topography() + section_dict = {'section1': ([0, 0], [1000, 1000], [100, 80]), + 'section2': ([800, 0], [800, 1000], [150, 100]), + 'section3': ([50, 200], [100, 500], [200, 150])} + + geo_data.set_section_grid(section_dict) + + print(geo_data._grid.sections) + np.testing.assert_almost_equal(geo_data._grid.sections.df.loc['section3', 'dist'], 304.138127, + decimal=4) + + def test_set_section_twice(self): + geo_data = gp.create_data(extent=[0, 1000, 0, 1000, 0, 1000], resolution=[10, 10, 10]) + section_dict = {'section1': ([0, 0], [1000, 1000], [100, 80]), + 'section2': ([800, 0], [800, 1000], [150, 100]), + 'section3': ([50, 200], [100, 500], [200, 150])} + + geo_data.set_section_grid(section_dict) + geo_data.set_section_grid(section_dict) + print(geo_data._grid.sections) + + def test_custom_grid(self): + # create custom grid + grid = gp.Grid() + cg = np.array([[1, 2, 3], + [4, 5, 6], + [7, 8, 9]]) + grid.create_custom_grid(cg) + # make sure the custom grid is active + assert grid.active_grids[1] + # make sure the custom grid is equal to the provided values + np.testing.assert_array_almost_equal(cg, grid.custom_grid.values) + # make sure we have the correct number of values in our grid + l0, l1 = grid.get_grid_args('custom') + assert l0 == 0 + assert l1 == 3 + + diff --git a/test/test_grids/test_topography.py b/test/test_grids/test_topography.py new file mode 100644 index 0000000..9e1ead4 --- /dev/null +++ b/test/test_grids/test_topography.py @@ -0,0 +1,137 @@ +import sys, os +sys.path.append("../..") + +os.environ["THEANO_FLAGS"] = "mode=FAST_RUN,device=cpu" +import warnings + +try: + import faulthandler + faulthandler.enable() +except Exception as e: # pragma: no cover + warnings.warn('Unable to enable faulthandler:\n%s' % str(e)) + +import gempy_lite as gp +import matplotlib.pyplot as plt +from gempy_lite.core.predictor.grid_modules.topography import Topography +import pytest + +import numpy as np +data_path = os.path.dirname(__file__)+'/../../input_data' + + +@pytest.fixture(scope='module') +def artificial_grid(one_fault_model_no_interp): + geo_model = one_fault_model_no_interp + topo = Topography(geo_model._grid.regular_grid) + topo.load_random_hills() + print(topo.values, topo.values_2d) + return topo + + +def test_real_grid_ales(): + resolution = [30, 20, 50] + extent = np.array([729550.0, 751500.0, 1913500.0, 1923650.0, -50, 800.0]) + path_interf = data_path + "/2018_interf.csv" + path_orient = data_path + "/2018_orient_clust_n_init5_0.csv" + path_dem = data_path + "/_cropped_DEM_coarse.tif" + + geo_model = gp.create_model('Alesmodel') + gp.init_data(geo_model, extent=extent, resolution=resolution, + path_i=path_interf, + path_o=path_orient) + + geo_model.set_topography(source='gdal', filepath=path_dem) + + p2d = gp.plot_2d(geo_model, section_names=['topography'], show_topography=True, + kwargs_topography={'hillshade': False, 'fill_contour': False}) + plt.show() + + p2d = gp.plot_2d(geo_model, section_names=['topography'], show_topography=True, + kwargs_topography={'hillshade': False, 'fill_contour': True}) + plt.show() + + p2d = gp.plot_2d(geo_model, section_names=['topography'], show_topography=True, + kwargs_topography={'hillshade': True, 'fill_contour': True}) + plt.show() + + if False: + gp.map_stack_to_surfaces(geo_model, {'fault_left': ('fault_left'), + 'fault_right': ('fault_right'), + 'fault_lr': ('fault_lr'), + 'Trias_Series': ('TRIAS', 'LIAS'), + 'Carbon_Series': ('CARBO'), + 'Basement_Series': ('basement')}, remove_unused_series=True) + + geo_model.set_is_fault(['fault_right', 'fault_left', 'fault_lr'], change_color=True) + gp.set_interpolator(geo_model, + output=['geology'], compile_theano=True, + theano_optimizer='fast_run', dtype='float64', + verbose=[]) + + gp.compute_model(geo_model, compute_mesh=True) + + geo_model._grid.regular_grid.set_topography_mask(geo_model._grid.topography) + + gpv = gp.plot.plot_3d(geo_model, + plotter_type='basic', off_screen=True, + show_topography=True, + show_scalar=False, + show_lith=True, + show_surfaces=False, + kwargs_plot_structured_grid={'opacity': 1, + 'show_edges': False}, + ve=10, + image=True, + kwargs_plot_topography={'scalars': 'topography'}) + # + # gpv.p.set_scale(zscale=10) + # + # img = gpv.p.show(screenshot=True) + # plt.imshow(img[1]) + # plt.show() + + +def test_plot_2d_topography(one_fault_model_no_interp, artificial_grid): + geo_model = one_fault_model_no_interp + #geo_model._grid.topography = artificial_grid + geo_model.set_topography() + p2d = gp.plot_2d(geo_model, section_names=['topography'], show_topography=True, + kwargs_topography={'hillshade': False}) + plt.show() + + p2d = gp.plot_2d(geo_model, section_names=['topography'], show_topography=True, + kwargs_topography={'hillshade': True, 'fill_contour': False}) + plt.show() + + p2d = gp.plot_2d(geo_model, section_names=['topography'], show_topography=True, + kwargs_topography={'hillshade': True}) + plt.show() + + +def test_plot_3d_structure_topo2(unconformity_model_topo, artificial_grid): + + geo_model = unconformity_model_topo + with pytest.raises(AssertionError): + geo_model._grid.regular_grid.set_topography_mask(artificial_grid) + + # geo_model._grid.regular_grid.set_topography_mask(geo_model._grid.topography) + + p2d = gp.plot_2d(geo_model, section_names=['topography'], show_topography=True, + show_lith=False, + kwargs_topography={'hillshade': True}) + plt.show() + + gpv = gp.plot.plot_3d(unconformity_model_topo, + plotter_type='basic', off_screen=True, + show_topography=True, + show_scalar=False, + show_lith=True, + show_surfaces=True, + kwargs_plot_structured_grid={'opacity': .5, + 'show_edges': True}, + image=True, + kwargs_plot_topography={'scalars': 'topography'}) + + # img = gpv.p.show(screenshot=True) + # plt.imshow(img[1]) + # plt.show() \ No newline at end of file diff --git a/test/test_grids/test_xsol.py b/test/test_grids/test_xsol.py new file mode 100644 index 0000000..9e17a52 --- /dev/null +++ b/test/test_grids/test_xsol.py @@ -0,0 +1,63 @@ +import pytest +import gempy_lite as gp +import numpy as np + +from gempy_lite.core.kernel_data import Stack +from gempy_lite.core.predictor.solution import XSolution + + +@pytest.fixture(scope='module') +def regular_grid(): + # Or we can init one of the default grids since the beginning by passing + # the correspondant attributes + grid = gp.Grid(extent=[0, 2000, 0, 2000, -2000, 0], + resolution=[50, 50, 50]) + grid.set_active('regular') + return grid + +@pytest.fixture(scope='module') +def stack_eg(): + + series = Stack() + series.set_series_index(['foo', 'foo2', 'foo5', 'foo7']) + series.add_series('foo3') + series.delete_series('foo2') + series.rename_series({'foo': 'boo'}) + series.reorder_series(['foo3', 'boo', 'foo7', 'foo5']) + + series.set_is_fault(['boo']) + + fr = np.zeros((4, 4)) + fr[2, 2] = True + series.set_fault_relation(fr) + + series.add_series('foo20') + + # Mock + series.df['isActive'] = True + return series + + +@pytest.fixture(scope='module') +def sol_values(regular_grid): + rg_s = regular_grid.values.shape[0] + n_input = 100 + len_x = rg_s + n_input + + n_features = 5 + n_properties = 2 + # Generate random solution + values = list() + values_matrix = np.random.random_integers(0, 10, (n_features, len_x)) + block_matrix = np.random.random_integers( + 0, 10, (n_properties, n_features, len_x) + ) + values.append(values_matrix) + values.append(block_matrix) + return values + + +def test_xsol(sol_values, regular_grid, stack_eg): + sol = XSolution(regular_grid, stack=stack_eg) + sol.set_values(sol_values) + print(sol.s_regular_grid) \ No newline at end of file From d9ee3e8a13f7287c34dbb1ca98f03354a8dc1c03 Mon Sep 17 00:00:00 2001 From: Leguark Date: Wed, 28 Oct 2020 08:59:20 +0100 Subject: [PATCH 08/10] [ENH] First prototype of regular grid solution runnig --- gempy_lite/core/kernel_data/__init__.py | 9 +++++++-- gempy_lite/core/predictor/solution.py | 24 +++++++++++++++++++----- test/test_grids/test_xsol.py | 18 +++++++++++++----- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/gempy_lite/core/kernel_data/__init__.py b/gempy_lite/core/kernel_data/__init__.py index 9e7205a..aba8590 100644 --- a/gempy_lite/core/kernel_data/__init__.py +++ b/gempy_lite/core/kernel_data/__init__.py @@ -46,7 +46,7 @@ def __init__(self, series, surface_names=None, values_array=None, properties_nam 'isBasement', 'isFault', 'isActive', 'hasData', 'color', 'vertices', 'edges', 'sfai', 'id'] - self._properites_vals = [] + #self.properties_val = [] self._private_attr = [ 'vertices', 'edges', 'sfai', 'isBasement', 'hasData', 'isFault'] @@ -75,9 +75,14 @@ def __init__(self, series, surface_names=None, values_array=None, properties_nam @property def _public_attr(self): """Properties values are arbitrary given by the user. e.g. porosity""" - fixed = ['Surface', 'Feature', 'OrderSurface', 'isActive', 'color', 'id', *self._properites_vals] + fixed = ['Surface', 'Feature', 'OrderSurface', 'isActive', 'color', 'id', *self.properties_val] return fixed + @property + def properties_val(self): + all_col = self.df.columns + return all_col.drop(self._columns) + @property def series(self): warnings.warn('Series will be deprecated use stack intead', DeprecationWarning) diff --git a/gempy_lite/core/predictor/solution.py b/gempy_lite/core/predictor/solution.py index f7511ff..4881fdb 100644 --- a/gempy_lite/core/predictor/solution.py +++ b/gempy_lite/core/predictor/solution.py @@ -79,18 +79,32 @@ def set_values_to_regular_grid(self, values: Iterable, l0=None, l1=None): if self.stack is not None: coords['Features'] = self.stack.df.groupby('isActive').get_group(True).index + if self.surfaces is not None: + coords['Properties'] = self.surfaces.properties_val + coords['X'] = self.grid.regular_grid.x coords['Y'] = self.grid.regular_grid.y coords['Z'] = self.grid.regular_grid.z + property_matrix = xr.DataArray( data=values[0][:, l0:l1].reshape(-1, *self.grid.regular_grid.resolution), - dims=['Features', 'X', 'Y', 'Z'], - coords=coords + dims=['Properties', 'X', 'Y', 'Z'], + ) + + i, j, _ = values[1].shape + + block_matrix = xr.DataArray( + data=values[1][:, :, l0:l1].reshape(i, j, *self.grid.regular_grid.resolution), + dims=['Features', 'Properties', 'X', 'Y', 'Z'], ) - self.s_regular_grid = xr.Dataset({ - 'property_matrix': property_matrix - }) + self.s_regular_grid = xr.Dataset( + { + 'property_matrix': property_matrix, + 'block_matrix': block_matrix + }, + coords=coords + ) @_setdoc_pro([Grid.__doc__, Surfaces.__doc__, Series.__doc__, ds.weights_vector, ds.sfai, ds.bai, ds.mai, ds.vai, diff --git a/test/test_grids/test_xsol.py b/test/test_grids/test_xsol.py index 9e17a52..7095120 100644 --- a/test/test_grids/test_xsol.py +++ b/test/test_grids/test_xsol.py @@ -2,7 +2,7 @@ import gempy_lite as gp import numpy as np -from gempy_lite.core.kernel_data import Stack +from gempy_lite.core.kernel_data import Stack, Surfaces from gempy_lite.core.predictor.solution import XSolution @@ -38,6 +38,14 @@ def stack_eg(): return series +@pytest.fixture(scope='module') +def surface_eg(stack_eg): + surfaces = Surfaces(stack_eg) + surfaces.set_surfaces_names(['foo', 'foo2', 'foo5', 'fee']) + surfaces.add_surfaces_values([[2, 2, 2, 6], [2, 2, 1, 8]], ['val_foo', 'val2_foo']) + return surfaces + + @pytest.fixture(scope='module') def sol_values(regular_grid): rg_s = regular_grid.values.shape[0] @@ -48,16 +56,16 @@ def sol_values(regular_grid): n_properties = 2 # Generate random solution values = list() - values_matrix = np.random.random_integers(0, 10, (n_features, len_x)) + values_matrix = np.random.random_integers(0, 10, (n_properties, len_x)) block_matrix = np.random.random_integers( - 0, 10, (n_properties, n_features, len_x) + 0, 10, (n_features, n_properties, len_x) ) values.append(values_matrix) values.append(block_matrix) return values -def test_xsol(sol_values, regular_grid, stack_eg): - sol = XSolution(regular_grid, stack=stack_eg) +def test_xsol(sol_values, regular_grid, stack_eg, surface_eg): + sol = XSolution(regular_grid, stack=stack_eg, surfaces=surface_eg) sol.set_values(sol_values) print(sol.s_regular_grid) \ No newline at end of file From c087ff4e1b2577650a24bcdf6e3a6a311c04edc5 Mon Sep 17 00:00:00 2001 From: Leguark Date: Wed, 28 Oct 2020 09:40:38 +0100 Subject: [PATCH 09/10] [ENH] Added cartesian values to custom grid --- gempy_lite/core/predictor/solution.py | 44 +++++++++++++++++++++++++-- test/test_grids/test_xsol.py | 28 +++++++++++++---- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/gempy_lite/core/predictor/solution.py b/gempy_lite/core/predictor/solution.py index 4881fdb..a373a62 100644 --- a/gempy_lite/core/predictor/solution.py +++ b/gempy_lite/core/predictor/solution.py @@ -69,6 +69,46 @@ def get_grid_args(self, grid_type: str) -> tuple: def set_values(self, values: Iterable): self.set_values_to_regular_grid(values) + self.set_custom_grid(values) + + def set_custom_grid(self, values: Iterable, l0=None, l1=None): + if l0 is None or l1 is None: + l0, l1 = self.get_grid_args('custom') + coords = dict() + + if self.stack is not None: + coords['Features'] = self.stack.df.groupby('isActive').get_group(True).index + + if self.surfaces is not None: + coords['Properties'] = self.surfaces.properties_val + + cartesian_matrix = xr.DataArray( + data=self.grid.custom_grid.values, + dims=['Point', 'XYZ'], + coords= {'XYZ': ['X', 'Y', 'Z'], + 'Point': [1, 2, 3, 4]} + ) + + property_matrix = xr.DataArray( + data=values[0][:, l0:l1], + dims=['Properties', 'Point'], + ) + + scalar_field_matrix = xr.DataArray( + data=values[4][:, l0:l1], + dims=['Features', 'Point'], + ) + + self.s_custom_grid = xr.Dataset( + { + 'property_matrix': property_matrix, + 'scalar_field_matrix': scalar_field_matrix, + 'cartesian_matrix': cartesian_matrix + }, + coords = coords + ) + + self.custom = np.array([values[0][:, l0: l1], values[4][:, l0: l1].astype(float)]) def set_values_to_regular_grid(self, values: Iterable, l0=None, l1=None): if l0 is None or l1 is None: @@ -296,8 +336,8 @@ def compute_marching_cubes_regular_grid(self, level: float, scalar_field, """ raise NotImplementedError() - rg = self.grid.regular_grid - spacing = self.grid.regular_grid.get_dx_dy_dz(rescale=rescale) + rg = self.grid.a_grid + spacing = self.grid.a_grid.get_dx_dy_dz(rescale=rescale) vertices, simplices, normals, values = measure.marching_cubes( scalar_field.reshape(rg.resolution), level, spacing=spacing, mask=mask_array, **kwargs) diff --git a/test/test_grids/test_xsol.py b/test/test_grids/test_xsol.py index 7095120..fe580f1 100644 --- a/test/test_grids/test_xsol.py +++ b/test/test_grids/test_xsol.py @@ -7,12 +7,16 @@ @pytest.fixture(scope='module') -def regular_grid(): +def a_grid(): # Or we can init one of the default grids since the beginning by passing # the correspondant attributes grid = gp.Grid(extent=[0, 2000, 0, 2000, -2000, 0], resolution=[50, 50, 50]) grid.set_active('regular') + + grid.create_custom_grid(np.arange(12).reshape(-1, 3)) + grid.set_active('custom') + return grid @pytest.fixture(scope='module') @@ -47,8 +51,8 @@ def surface_eg(stack_eg): @pytest.fixture(scope='module') -def sol_values(regular_grid): - rg_s = regular_grid.values.shape[0] +def sol_values(a_grid): + rg_s = a_grid.values.shape[0] n_input = 100 len_x = rg_s + n_input @@ -60,12 +64,24 @@ def sol_values(regular_grid): block_matrix = np.random.random_integers( 0, 10, (n_features, n_properties, len_x) ) + + fault_block = None + weights = None + scalar_field = np.random.random_integers(20, 30, (n_features, len_x)) + unknows = None + mask_matrix = None + fault_mask = None + values.append(values_matrix) values.append(block_matrix) + for i in [fault_block, weights, scalar_field, unknows, mask_matrix, fault_mask]: + values.append(i) return values -def test_xsol(sol_values, regular_grid, stack_eg, surface_eg): - sol = XSolution(regular_grid, stack=stack_eg, surfaces=surface_eg) +def test_xsol(sol_values, a_grid, stack_eg, surface_eg): + sol = XSolution(a_grid, stack=stack_eg, surfaces=surface_eg) sol.set_values(sol_values) - print(sol.s_regular_grid) \ No newline at end of file + print(sol.s_regular_grid) + + print(sol.s_custom_grid) From 7ce70065c15a4f6ec60948c411279fa566c66521 Mon Sep 17 00:00:00 2001 From: Leguark Date: Fri, 30 Oct 2020 11:44:22 +0100 Subject: [PATCH 10/10] [ENH] XSolution functionality --- gempy_lite/core/predictor/solution.py | 389 +++++++++++++++++++++----- test/test_grids/test_xsol.py | 41 ++- 2 files changed, 357 insertions(+), 73 deletions(-) diff --git a/gempy_lite/core/predictor/solution.py b/gempy_lite/core/predictor/solution.py index a373a62..3e64f99 100644 --- a/gempy_lite/core/predictor/solution.py +++ b/gempy_lite/core/predictor/solution.py @@ -12,9 +12,110 @@ import xarray as xr -@_setdoc_pro([Grid.__doc__, Surfaces.__doc__, Series.__doc__, ds.weights_vector, ds.sfai, ds.bai, ds.mai, ds.vai, - ds.lith_block, ds.sfm, ds.bm, ds.mm, ds.vm, ds.vertices, ds.edges, ds.geological_map]) -class XSolution(object): +class XSolutionCompat: + """This class is for GemPy Lite to enable compatibility with a bunch + of old functionality.""" + + # Input data results + @property + def scalar_field_at_surface_points(self): + return self.s_at_surface_points['scalar_field_v3'].values + + @property + def block_at_surface_points(self): + return self.s_at_surface_points['block_v3'].values + + @property + def mask_at_surface_points(self): + return self.s_at_surface_points['mask_v3'].values + + @property + def values_at_surface_points(self): + return self.s_at_surface_points['values_v3'].values + + @property + def lith_block(self): + return self.s_regular_grid['property_matrix'].loc['id'].values.reshape(1, -1) + + @property + def scalar_field_matrix(self): + shape = self.s_regular_grid['scalar_field_matrix'].shape + return self.s_regular_grid['scalar_field_matrix'].values.reshape(shape[0], + -1) + + @property + def block_matrix(self): + shape = self.s_regular_grid['block_matrix'].shape + return self.s_regular_grid['block_matrix'].values.reshape(shape[0], + shape[1], + -1) + + @property + def mask_matrix(self): + shape = self.s_regular_grid['mask_matrix'].shape + return self.s_regular_grid['mask_matrix'].values.reshape(shape[0], -1) + + # This is should be private + # @property + # def mask_matrix_pad(self): + # return + + @property + def values_matrix(self): + prop = self.s_regular_grid['property_matrix'].Properties.values + sel = prop != 'id' + values_other_than_id = prop[sel] + array = self.s_regular_grid['property_matrix'].loc[ + values_other_than_id].values + return array.reshape(len(values_other_than_id), -1) + + @property + def gradient(self): + raise NotImplementedError + + @property + def vertices(self): + return + + @property + def edges(self): + return + + @property + def geological_map(self): + shape = self.s_topography['scalar_field_matrix'].shape + + p = self.s_topography['property_matrix'].values.reshape(shape[0], -1) + s = self.s_topography['scalar_field_matrix'].values.reshape(shape[0], -1) + return np.array([p, s]) + + @property + def sections(self): + return NotImplementedError + # shape = self.s_sections['scalar_field_matrix'].shape + # + # p = self.s_topography['property_matrix'].values.reshape(shape[0], -1) + # s = self.s_topography['scalar_field_matrix'].values.reshape(shape[0], -1) + # return np.array([p, s]) + + @property + def custom(self): + return + + @property + def fw_gravity(self): + return + + @property + def fw_magnetics(self): + return + +@_setdoc_pro( + [Grid.__doc__, Surfaces.__doc__, Series.__doc__, ds.weights_vector, ds.sfai, + ds.bai, ds.mai, ds.vai, + ds.lith_block, ds.sfm, ds.bm, ds.mm, ds.vm, ds.vertices, ds.edges, + ds.geological_map]) +class XSolution(object, XSolutionCompat): """This class stores the output of the interpolation and the necessary objects to visualize and manipulate this data. @@ -53,6 +154,7 @@ def __init__(self, grid: Grid, stack: Stack = None, ): # self.additional_data = additional_data + self.grid = grid # self.surface_points = surface_points self.stack = stack @@ -61,91 +163,248 @@ def __init__(self, grid: Grid, # Define xarrays self.weights_vector = None self.at_surface_points = None - self.s_regular_grid = None + self.s_regular_grid = xr.Dataset() + self.s_custom_grid = xr.Dataset() + self.s_topography = xr.Dataset() + self.s_at_surface_points = xr.Dataset() + self.s_sections = dict() self.meshes = None - def get_grid_args(self, grid_type: str) -> tuple: - return self.grid.get_grid_args(grid_type) + def set_values(self, + values: list, + active_features=None, + surf_properties=None, + attach_xyz=True): + """ At this stage we should split values into the different grids - def set_values(self, values: Iterable): - self.set_values_to_regular_grid(values) - self.set_custom_grid(values) + Args: + values: + + Returns: - def set_custom_grid(self, values: Iterable, l0=None, l1=None): - if l0 is None or l1 is None: - l0, l1 = self.get_grid_args('custom') - coords = dict() + """ - if self.stack is not None: - coords['Features'] = self.stack.df.groupby('isActive').get_group(True).index + # Get an array with all the indices for each grid + l = self.grid.length - if self.surfaces is not None: - coords['Properties'] = self.surfaces.properties_val + coords_base, xyz = self.prepare_common_args(active_features, attach_xyz, + surf_properties) + self.weights_vector = values[3] - cartesian_matrix = xr.DataArray( - data=self.grid.custom_grid.values, - dims=['Point', 'XYZ'], - coords= {'XYZ': ['X', 'Y', 'Z'], - 'Point': [1, 2, 3, 4]} - ) + if self.grid.active_grids[0]: + self.set_values_to_regular_grid(values, l[0], l[1], coords_base.copy()) + if self.grid.active_grids[1]: + self.set_values_to_custom_grid(values, l[1], l[2], coords_base.copy(), + xyz=xyz) + if self.grid.active_grids[2]: + self.set_values_to_topography(values, l[2], l[3], coords_base.copy()) + if self.grid.active_grids[3]: + self.set_values_to_sections(values, l[3], l[4], coords_base.copy()) + if self.grid.active_grids[4]: + self.set_values_to_centered() + + # TODO: Add xyz from surface points + self.set_values_to_surface_points(values, l[-1], coords_base, xyz=None) + + def prepare_common_args(self, active_features, attach_xyz, surf_properties): + if active_features is None and self.stack is not None: + active_features = self.stack.df.groupby('isActive').get_group(True).index + if surf_properties is None and self.surfaces is not None: + surf_properties = self.surfaces.properties_val + coords_base = dict() + if active_features is not None: + coords_base['Features'] = active_features + if surf_properties is not None: + coords_base['Properties'] = surf_properties + if attach_xyz and self.grid.custom_grid is not None: + xyz = self.grid.custom_grid.values + else: + xyz = None + return coords_base, xyz - property_matrix = xr.DataArray( - data=values[0][:, l0:l1], - dims=['Properties', 'Point'], - ) + def set_values_to_centered(self): + return + + def set_values_to_surface_points(self, values, l0, coords_base, xyz=None): + coords = coords_base + l1 = values[0].shape[-1] + arrays = self.create_unstruct_xarray(values, l0, l1, xyz) - scalar_field_matrix = xr.DataArray( - data=values[4][:, l0:l1], - dims=['Features', 'Point'], + self.s_at_surface_points = xr.Dataset( + data_vars=arrays, + coords=coords ) + return self.s_at_surface_points + + @staticmethod + def create_struc_xarrays(values, l0, l1, res: Union[list, np.ndarray]): + arrays = dict() + + n_dim = len(res) + xyz = ['X', 'Y', 'Z'][:n_dim] + if values[0] is not None: + # This encompass lith_block and values matrix + property_matrix = xr.DataArray( + data=values[0][:, l0:l1].reshape(-1, *res), + dims=['Properties', *xyz], + ) + arrays['property_matrix'] = property_matrix + + if values[1] is not None: + # This is the block matrix + i, j, _ = values[1].shape + block_matrix = xr.DataArray( + data=values[1][:, :, l0:l1].reshape(i, j, *res), + dims=['Features', 'Properties', *xyz], + ) + arrays['block_matrix'] = block_matrix + + # Fault block? + + if values[4] is not None: + # Scalar field matrix + scalar_matrix = xr.DataArray( + data=values[4][:, l0:l1].reshape(-1, *res), + dims=['Features', *xyz], + ) + arrays['scalar_field_matrix'] = scalar_matrix + + if values[6] is not None: + # Mask matrix + mask_matrix = xr.DataArray( + data=values[6][:, l0:l1].reshape(-1, *res), + dims=['Features', *xyz], + ) + arrays['mask_matrix'] = mask_matrix + + if values[7] is not None: + # Fault mask matrix + fault_mask = xr.DataArray( + data=values[7][:, l0:l1].reshape(-1, *res), + dims=['Features', *xyz], + ) + arrays['fault_mask'] = fault_mask + + return arrays + + @staticmethod + def create_unstruct_xarray(values, l0, l1, xyz): + arrays = dict() + if xyz is not None: + cartesian_matrix = xr.DataArray( + data=xyz, + dims=['Point', 'XYZ'], + coords={'XYZ': ['X', 'Y', 'Z']} + ) + arrays['cartesian_matrix'] = cartesian_matrix + + if values[0] is not None: + # Values and lith block + property_v3 = xr.DataArray( + data=values[0][:, l0:l1], + dims=['Properties', 'Point'], + ) + + arrays['property_v3'] = property_v3 + + if values[1] is not None: + # block + block_v3 = xr.DataArray( + data=values[1][:, :, l0:l1], + dims=['Features', 'Properties', 'Point'], + ) + + arrays['block_v3'] = block_v3 + + if values[4] is not None: + # Scalar field + scalar_field_v3 = xr.DataArray( + data=values[4][:, l0:l1], + dims=['Features', 'Point'], + ) + arrays['scalar_field_v3'] = scalar_field_v3 + + if values[6] is not None: + # Scalar field + mask_v3 = xr.DataArray( + data=values[6][:, l0:l1], + dims=['Features', 'Point'], + ) + arrays['mask_v3'] = mask_v3 + + return arrays + + def set_values_to_custom_grid(self, values: list, l0, l1, + coords_base: dict, xyz=None): + + coords = coords_base + arrays = self.create_unstruct_xarray(values, l0, l1, xyz) self.s_custom_grid = xr.Dataset( - { - 'property_matrix': property_matrix, - 'scalar_field_matrix': scalar_field_matrix, - 'cartesian_matrix': cartesian_matrix - }, - coords = coords + data_vars=arrays, + coords=coords ) + return self.s_custom_grid - self.custom = np.array([values[0][:, l0: l1], values[4][:, l0: l1].astype(float)]) - - def set_values_to_regular_grid(self, values: Iterable, l0=None, l1=None): - if l0 is None or l1 is None: - l0, l1 = self.get_grid_args('regular') + def set_values_to_regular_grid(self, values: list, l0, l1, + coords_base: dict): - coords = dict() + coords = self.add_cartesian_coords(coords_base) - if self.stack is not None: - coords['Features'] = self.stack.df.groupby('isActive').get_group(True).index + arrays = self.create_struc_xarrays(values, l0, l1, + self.grid.regular_grid.resolution) - if self.surfaces is not None: - coords['Properties'] = self.surfaces.properties_val + self.s_regular_grid = xr.Dataset( + data_vars=arrays, + coords=coords + ) + def add_cartesian_coords(self, coords_base): + coords = coords_base coords['X'] = self.grid.regular_grid.x coords['Y'] = self.grid.regular_grid.y coords['Z'] = self.grid.regular_grid.z - - property_matrix = xr.DataArray( - data=values[0][:, l0:l1].reshape(-1, *self.grid.regular_grid.resolution), - dims=['Properties', 'X', 'Y', 'Z'], - ) - - i, j, _ = values[1].shape - - block_matrix = xr.DataArray( - data=values[1][:, :, l0:l1].reshape(i, j, *self.grid.regular_grid.resolution), - dims=['Features', 'Properties', 'X', 'Y', 'Z'], - ) - - self.s_regular_grid = xr.Dataset( - { - 'property_matrix': property_matrix, - 'block_matrix': block_matrix - }, + return coords + + def set_values_to_topography(self, + values: list, + l0, l1, + coords_base): + coords = coords_base + coords['X'] = self.grid.topography.x + coords['Y'] = self.grid.topography.y + resolution = self.grid.topography.resolution + arrays = self.create_struc_xarrays(values, l0, l1, resolution) + + self.s_topography = xr.Dataset( + data_vars=arrays, coords=coords ) - + return self.s_topography + + def set_values_to_sections(self, + values: list, + l0, l1, + coords_base): + coords = coords_base + sections = self.grid.sections + + for e, axis_coord in enumerate(sections.generate_axis_coord()): + resolution = sections.resolution[e] + l0_s = sections.length[e] + l1_s = sections.length[e + 1] + name, xy = axis_coord + coords['X'] = xy[:, 0] + coords['Y'] = xy[:, 1] + + arrays = self.create_struc_xarrays(values, l0 + l0_s, l0 + l1_s, + resolution) + + self.s_sections[name] = xr.Dataset( + data_vars=arrays, + coords=coords + ) + return self.s_sections @_setdoc_pro([Grid.__doc__, Surfaces.__doc__, Series.__doc__, ds.weights_vector, ds.sfai, ds.bai, ds.mai, ds.vai, ds.lith_block, ds.sfm, ds.bm, ds.mm, ds.vm, ds.vertices, ds.edges, ds.geological_map]) diff --git a/test/test_grids/test_xsol.py b/test/test_grids/test_xsol.py index fe580f1..3be837f 100644 --- a/test/test_grids/test_xsol.py +++ b/test/test_grids/test_xsol.py @@ -1,7 +1,6 @@ import pytest -import gempy_lite as gp import numpy as np - +import gempy_lite as gp from gempy_lite.core.kernel_data import Stack, Surfaces from gempy_lite.core.predictor.solution import XSolution @@ -17,11 +16,19 @@ def a_grid(): grid.create_custom_grid(np.arange(12).reshape(-1, 3)) grid.set_active('custom') + grid.create_topography() + grid.set_active('topography') + + section_dict = {'section_SW-NE': ([250, 250], [1750, 1750], [100, 100]), + 'section_NW-SE': ([250, 1750], [1750, 250], [100, 100])} + grid.create_section_grid(section_dict) + grid.set_active('sections') + return grid + @pytest.fixture(scope='module') def stack_eg(): - series = Stack() series.set_series_index(['foo', 'foo2', 'foo5', 'foo7']) series.add_series('foo3') @@ -46,7 +53,8 @@ def stack_eg(): def surface_eg(stack_eg): surfaces = Surfaces(stack_eg) surfaces.set_surfaces_names(['foo', 'foo2', 'foo5', 'fee']) - surfaces.add_surfaces_values([[2, 2, 2, 6], [2, 2, 1, 8]], ['val_foo', 'val2_foo']) + surfaces.add_surfaces_values([[2, 2, 2, 6], [2, 2, 1, 8]], + ['val_foo', 'val2_foo']) return surfaces @@ -57,7 +65,7 @@ def sol_values(a_grid): len_x = rg_s + n_input n_features = 5 - n_properties = 2 + n_properties = 3 # Generate random solution values = list() values_matrix = np.random.random_integers(0, 10, (n_properties, len_x)) @@ -65,7 +73,7 @@ def sol_values(a_grid): 0, 10, (n_features, n_properties, len_x) ) - fault_block = None + fault_block = np.random.random_integers(40, 50, (n_features, len_x)) weights = None scalar_field = np.random.random_integers(20, 30, (n_features, len_x)) unknows = None @@ -82,6 +90,23 @@ def sol_values(a_grid): def test_xsol(sol_values, a_grid, stack_eg, surface_eg): sol = XSolution(a_grid, stack=stack_eg, surfaces=surface_eg) sol.set_values(sol_values) - print(sol.s_regular_grid) + print('\n regular', sol.s_regular_grid) + print('\n custom', sol.s_custom_grid) + print('\n topo', sol.s_topography) + print('\n sect', sol.s_sections['section_SW-NE']) + print('\n sect', sol.s_sections['section_NW-SE']) + + sol.set_values(sol_values, attach_xyz=False) + print('\n custom2', sol.s_custom_grid) + + +def test_property(sol_values, a_grid, stack_eg, surface_eg): + sol = XSolution(a_grid, stack=stack_eg, surfaces=surface_eg) + sol.set_values(sol_values) + print('scalar', sol.scalar_field_matrix) + print('lith', sol.lith_block) + print('values', sol.values_matrix) + + print('scalar_asp', sol.scalar_field_at_surface_points) + - print(sol.s_custom_grid)