Source code for ogstools.propertylib.mesh_dependent

# Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org)
#            Distributed under a Modified BSD License.
#            See accompanying file LICENSE.txt or
#            http://www.opengeosys.org/project/license
#

"Functions related to stress analysis which can be only applied to a mesh."

from typing import Union

import numpy as np
import pyvista as pv
from pint.facets.plain import PlainQuantity

from .property import Property
from .tensor_math import _split_quantity, eigenvalues, mean, octahedral_shear
from .unit_registry import u_reg

ValType = Union[PlainQuantity, np.ndarray]


[docs] def depth(mesh: pv.UnstructuredGrid, use_coords: bool = False) -> np.ndarray: """Return the depth values of the mesh. For 2D, the last axis of the plane wherein the mesh is lying is used as the vertical axis (i.e. y if the mesh is in the xy-plane, z if it is in the xz-plane), for 3D, the z-axes is used. If `use_coords` is True, returns the negative coordinate value of the vertical axis. Otherwise, the vertical distance to the top facing edges / surfaces are returned. """ if mesh.volume > 0: # prevents inner edge (from holes) to be detected as a top boundary edges = mesh.extract_surface().connectivity("point_seed", point_ids=[0]) vertical_dim = 2 if use_coords: return -mesh.points[:, vertical_dim] point_upwards = edges.cell_normals[..., vertical_dim] > 0 top_cells = point_upwards else: mean_normal = np.abs( np.mean(mesh.extract_surface().cell_normals, axis=0) ) vertical_dim = np.delete([0, 1, 2], int(np.argmax(mean_normal)))[-1] if use_coords: return -mesh.points[:, vertical_dim] # prevents inner edge (from holes) to be detected as a top boundary edges = mesh.extract_feature_edges().connectivity( "point_seed", point_ids=[0] ) edge_horizontal_extent = [ np.diff(np.reshape(cell.bounds, (-1, 2))).ravel()[0] for cell in edges.cell ] edge_centers = edges.cell_centers().points adj_cells = [mesh.find_closest_cell(point) for point in edge_centers] adj_centers = np.vstack( [ mesh.extract_cells(adj_cell).cell_centers().points for adj_cell in adj_cells ] ) are_above = [ edge_center[vertical_dim] > adj_center[vertical_dim] for edge_center, adj_center in zip(edge_centers, adj_centers) ] are_non_vertical = np.asarray(edge_horizontal_extent) > 1e-12 top_cells = are_above & are_non_vertical top = edges.extract_cells(top_cells) eucl_distance_projected_top_points = np.sum( np.abs( np.delete(mesh.points[:, None] - top.points, vertical_dim, axis=-1) ), axis=-1, ) matching_top = np.argmin(eucl_distance_projected_top_points, axis=-1) return np.abs( mesh.points[..., vertical_dim] - top.points[matching_top, vertical_dim] )
[docs] def p_fluid(mesh: pv.UnstructuredGrid) -> PlainQuantity: """Return the fluid pressure in the mesh. If "depth" is given in the mesh's point _data, it is used return a hypothetical water column defined as: .. math:: p_{fl} = 1000 \\frac{kg}{m^3} 9.81 \\frac{m}{s^2} h where `h` is the depth below surface. Otherwise, If "pressure" is given in the mesh, return the "pressure" data of the mesh. If that is also not the case, the hypothetical water column from above is returned with the depth being calculated via :py:func:`ogstools.propertylib.mesh_dependent.depth`. """ Qty = u_reg.Quantity if "depth" in mesh.point_data: return ( Qty(1000, "kg/m^3") * Qty(9.81, "m/s^2") * Qty(mesh["depth"], "m") ) if "pressure" in mesh.point_data: return Qty(mesh["pressure"], "Pa") return Qty(1000, "kg/m^3") * Qty(9.81, "m/s^2") * Qty(depth(mesh), "m")
[docs] def fluid_pressure_criterion( mesh: pv.UnstructuredGrid, mesh_property: Property ) -> PlainQuantity: """Return the fluid pressure criterion. Defined as the difference between fluid pressure and minimal principal stress (compression positive). .. math:: F_{p} = p_{fl} - \\sigma_{min} Fluid pressure is evaluated via :py:func:`ogstools.propertylib.mesh_dependent.p_fluid`. """ Qty = u_reg.Quantity sigma = mesh[mesh_property.data_name] sig_min = _split_quantity(eigenvalues(-sigma))[0][..., 0] return p_fluid(mesh) - Qty(sig_min, mesh_property.data_unit)
[docs] def dilatancy_critescu( mesh: pv.UnstructuredGrid, mesh_property: Property, a: float = -0.01697, b: float = 0.8996, effective: bool = False, ) -> PlainQuantity: """Return the dilatancy criterion defined as: .. math:: F_{dil} = \\frac{\\tau_{oct}}{\\sigma_0} - a \\left( \\frac{\\sigma_m}{\\sigma_0} \\right)^2 - b \\frac{\\sigma_m}{\\sigma_0} for total stresses and defined as: .. math:: F'_{dil} = \\frac{\\tau_{oct}}{\\sigma_0} - a \\left( \\frac{\\sigma'_m}{\\sigma_0} \\right)^2 - b \\frac{\\sigma'_m}{\\sigma_0} for effective stresses (uses :func:`~ogstools.propertylib.mesh_dependent.p_fluid`). <https://www.sciencedirect.com/science/article/pii/S0360544222000512?via%3Dihub> """ Qty = u_reg.Quantity sigma = -Qty(mesh[mesh_property.data_name], mesh_property.data_unit) sigma_0 = Qty(1, "MPa") sigma_m = mean(sigma) if effective: sigma_m -= p_fluid(mesh) tau_oct = octahedral_shear(sigma) return ( tau_oct / sigma_0 - a * (sigma_m / sigma_0) ** 2 - b * sigma_m / sigma_0 )
[docs] def dilatancy_alkan( mesh: pv.UnstructuredGrid, mesh_property: Property, b: float = 0.04, effective: bool = False, ) -> ValType: """Return the dilatancy criterion defined as: .. math:: F_{dil} = \\tau_{oct} - \\tau_{max} \\cdot b \\frac{\\sigma'_m}{\\sigma_0 + b \\cdot \\sigma'_m} for total stresses and defined as: .. math:: F_{dil} = \\tau_{oct} - \\tau_{max} \\cdot b \\frac{\\sigma'_m}{\\sigma_0 + b \\cdot \\sigma'_m} for effective stresses (uses :func:`~ogstools.propertylib.mesh_dependent.p_fluid`). <https://www.sciencedirect.com/science/article/pii/S1365160906000979> """ Qty = u_reg.Quantity sigma = -Qty(mesh[mesh_property.data_name], mesh_property.data_unit) tau_max = Qty(33, "MPa") sigma_m = mean(sigma) if effective: sigma_m -= p_fluid(mesh) tau = octahedral_shear(sigma) return tau - tau_max * (b * sigma_m / (Qty(1, "MPa") + b * sigma_m))